Skip to main content
 首页 » 编程设计

python之在 BigQuery 中运行异步查询并没有明显加快

2025年05月04日29grandyang

我在 App Engine 上使用 Google 的 python API 客户端库在 Big Query 中运行大量查询以生成实时分析。每次调用大约需要两秒钟,有五个查询,这太长了,所以我研究了加快速度的方法并想到了 running queries asynchronously将是一个坚实的改进。当时的想法是,我可以一次插入五个查询,Google 会施展魔法同时运行它们,然后使用 jobs.getQueryResults(jobId) 获取每个作业的结果。我决定通过对两个异步查询的执行进行计时并将其与 running queries synchronously 进行比较来通过概念验证来检验该理论。 .结果:

  • 同步:3.07 秒(每个查询 1.34 秒和 1.29 秒)
  • 异步:2.39 秒(每次插入需要 0.52 秒和 0.44 秒,另外 getQueryResults() 需要 1.09 秒)

仅相差 0.68 秒。因此,虽然异步查询更快,但它们并没有实现 Google 并行魔术减少总执行时间的目标。所以第一个问题:对并行魔法的期望是否正确?即使不是,我特别感兴趣的是 Google 的声明

An asynchronous query returns a response immediately, generally before the query completes.

插入查询大约半秒不符合我对“立即”的定义!我想 Jordan或 Big Query 团队中的其他人将是唯一可以回答这个问题的人,但我欢迎任何答案!

编辑注释:

  1. 根据 Mikhail Berlyant 的建议,我从 jobs response 中收集了 creationTime、startTime 和 endTime并发现:

    • creationTime 到 startTime:462ms、387ms(查询 1 和 2 的计时)
    • startTime 到 endTime:744 毫秒,1005 毫秒

虽然我不确定这是否会增加故事的内容,因为它是发出 insert() 和我想知道的调用完成之间的时间。

  1. 来自 BQ's Jobs documentation ,我关于并行魔法的第一个问题的答案是肯定的:

You can run multiple jobs concurrently in BigQuery

代码:

就其值(value)而言,我在本地和生产 App Engine 上都对其进行了测试。 Local 慢了大约 2-3 倍,但复制了结果。在我的研究中,我还发现了 partitioned tables ,我希望我以前知道(这很可能最终成为我的解决方案)但这个问题是独立存在的。这是我的代码。我省略了实际的 SQL,因为它们在这种情况下无关紧要:

    def test_sync(self, request): 
    t0 = time.time() 
 
    request = bigquery.jobs() 
    data = { 'query': (sql) } 
    response = request.query(projectId=project_id, body=data).execute() 
    t1 = time.time() 
 
    data = { 'query': (sql) } 
    response = request.query(projectId=project_id, body=data).execute() 
    t2 = time.time() 
 
    print("0-1: " + str(t1 - t0)) 
    print("1-2: " + str(t2 - t1)) 
    print("elapsed: " + str(t2 - t0)) 
 
def test_async(self, request): 
    job_ids = {} 
 
    t0 = time.time() 
    job_id = async_query(sql) 
    job_ids['a'] = job_id 
    print("job_id: " + job_id) 
    t1 = time.time() 
 
    job_id = async_query(sql) 
    job_ids['b'] = job_id 
    print("job_id: " + job_id) 
    t2 = time.time() 
 
    for key, value in job_ids.iteritems(): 
 
        response = bigquery.jobs().getQueryResults( 
            jobId=value, 
            projectId=project_id).execute() 
 
    t3 = time.time() 
    print("0-1: " + str(t1 - t0)) 
    print("1-2: " + str(t2 - t1)) 
    print("2-3: " + str(t3 - t2)) 
    print("elapsed: " + str(t3 - t0)) 
 
def async_query(sql): 
    job_data = { 
        'jobReference': { 
            'projectId': project_id 
        }, 
        'configuration': { 
            'query': { 
                'query': sql, 
                'priority': 'INTERACTIVE' 
            } 
        } 
    } 
 
response = bigquery.jobs().insert( 
    projectId=project_id, 
    body=job_data).execute() 
job_id = response['jobReference']['jobId'] 
 
return job_id 

请您参考如下方法:

并行运行查询是否会加速结果的答案当然是“视情况而定”。

当您使用异步作业 API 时,每个查询都会有大约半秒的内置延迟。这是因为 API 不是为短期查询而设计的;如果您的查询在一两秒内运行,则不需要异步处理。

半秒延迟在未来可能会下降,但有许多固定成本不会变得更好。例如,您向 google 发送了两个 HTTP 请求,而不是一个。这些需要多长时间取决于您从何处发送请求以及您使用的网络的特征。如果您在美国,往返时间可能只有几毫秒,但如果您在巴西,则可能需要 100 毫秒。

此外,当您执行 jobs.query() 时,接收请求的 BigQuery API 服务器与启动查询的服务器相同。它可以在查询完成后立即返回结果。但是当您使用异步 api 时,您的 getQueryResults() 请求将转到不同的服务器。该服务器必须轮询作业状态或找到运行请求以获取状态的服务器。这需要时间。

因此,如果您并行运行一堆查询,每个查询需要 1-2 秒,但您为每个查询增加半秒,再加上初始请求需要半秒,您不太可能看到很多加速。另一方面,如果您的查询每次花费 5 或 10 秒,则固定开销占总时间的百分比会更小。

我的猜测是,如果您并行运行更多的查询,您会看到更多的加速。另一种选择是使用 API 的同步版本,但在客户端使用多个线程并行发送多个请求。

还有一个警告,那就是查询大小。除非您购买额外的容量,否则默认情况下,BigQuery 将为您提供 2000 个“插槽”来处理您的所有查询。槽是可以并行完成的工作单元。您可以使用这 2000 个槽来运行一个巨大的查询,或者 20 个较小的查询,每个查询一次使用 100 个槽。如果您运行使 2000 个槽饱和的并行查询,您将遇到减速。

也就是说,2000 个插槽很多。粗略估计,2000 个插槽每秒可以处理数百 GB 的数据。因此,除非您通过 BigQuery 插入这种量,否则添加并行查询不太可能减慢您的速度。