修复 BigQuery 中的“超出资源”并使其运行更快
Posted
技术标签:
【中文标题】修复 BigQuery 中的“超出资源”并使其运行更快【英文标题】:Fixing "Resources exceeded" in BigQuery and making it run faster 【发布时间】:2016-01-14 02:10:38 【问题描述】:GDELT 的 Kalev Leetaru 遇到了这个问题 - 分析一整月时,以下查询将在 BigQuery 中运行,但如果超过一整年则不会运行。
SELECT Source, Target, count, RATIO_TO_REPORT(count) OVER() Weight
FROM (
SELECT a.name Source, b.name Target, COUNT(*) AS COUNT
FROM (FLATTEN(
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999, name)) a
JOIN EACH (
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999 ) b
ON a.GKGRECORDID=b.GKGRECORDID
WHERE a.name<b.name
AND a.name != '0.000000#0.000000'
AND b.name != '0.000000#0.000000'
GROUP EACH BY 1, 2
ORDER BY 3 DESC )
WHERE count > 50
LIMIT 500000
“查询执行期间资源超出。”
我们如何解决这个问题?
【问题讨论】:
【参考方案1】:首先是关于成本优化的说明:BigQuery 按扫描列收费,此查询将超过 72GB。 GDELT gkg 表将其整个故事存储在一个表中 - 我们可以通过创建年度表而不是单个表来优化成本。
现在,我们如何修复这个查询,让它运行一整年? “查询执行期间超出的资源”通常来自不可扩展的函数。例如:
RATIO_TO_REPORT(COUNT) OVER()
无法缩放:OVER() 函数在整个结果集上运行,允许我们计算总数以及每行贡献的总数 - 但要运行,我们需要整个结果设置为适合一个 VM。好消息是 OVER() 能够在对数据进行分区时进行扩展,例如通过 OVER(PARTITION BY month) - 然后我们只需要每个分区都适合 VM。对于此查询,为简单起见,我们将删除此结果列。
ORDER BY 无法缩放:要对结果进行排序,我们还需要将所有结果都放在一个 VM 上。这就是为什么“--allow-large-results”不允许运行 ORDER BY 步骤的原因,因为每个 VM 将并行处理和输出结果。
在这个查询中,我们有一个简单的方法来处理 ORDER BY 的可伸缩性 - 我们将把后面的过滤器“WHERE COUNT > 50”更早地移到流程中。我们将移动它并将其更改为 HAVING,而不是对所有结果进行排序并过滤 COUNT>50 的结果,因此它在 ORDER BY 之前运行:
SELECT Source, Target, count
FROM (
SELECT a.name Source, b.name Target, COUNT(*) AS COUNT
FROM (FLATTEN(
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999,name)) a
JOIN EACH (
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999 ) b
ON a.GKGRECORDID=b.GKGRECORDID
WHERE a.name<b.name
AND a.name != '0.000000#0.000000'
AND b.name != '0.000000#0.000000'
GROUP EACH BY 1, 2
HAVING count>50
ORDER BY 3 DESC )
LIMIT 500000
现在查询运行了一整年的数据!
让我们看看解释统计:
我们可以看到 1.88 亿行的表被读取了两次:第一个子查询产生了 15 亿行(给定“FLATTEN”),第二个过滤掉了 2015 年以外的行(注意这个表开始存储数据2015 年初)。
第 3 阶段很有趣:连接两个子查询产生了 30 亿行!通过 FILTER 和 AGGREGATE 步骤,这些数量减少到了 5 亿:
我们可以做得更好吗?
是的!让我们将 2 WHERE a.name != '....'
移动到更早的“HAVING”:
SELECT Source, Target, count
FROM (
SELECT a.name Source, b.name Target, COUNT(*) AS COUNT
FROM (FLATTEN(
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999
HAVING name != '0.000000#0.000000',name)) a
JOIN EACH (
SELECT
GKGRECORDID, CONCAT( STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)), '#', STRING(ROUND(FLOAT(IFNULL(REGEXP_EXTRACT(SPLIT(V2Locations,';'),r'^[2-5]#.*?#.*?#.*?#.*?#.*?#(.*?)#'), '0')), 3)) ) AS name
FROM [gdelt-bq:gdeltv2.gkg]
WHERE DATE>20150100000000 and DATE<20151299999999
HAVING name != '0.000000#0.000000') b
ON a.GKGRECORDID=b.GKGRECORDID
WHERE a.name<b.name
GROUP EACH BY 1, 2
HAVING count>50
ORDER BY 3 DESC )
LIMIT 500000
这运行得更快!
让我们看看解释统计:
看到了吗?通过将过滤移动到加入前的一个步骤,阶段 3 只需通过 10 亿行,而不是 30 亿行。速度更快(即使对于 BigQuery,您也可以自行检查,它能够在短时间内遍历 JOIN 生成的超过 30 亿行)。
这个查询是为了什么?
在这里查看漂亮的结果:http://blog.gdeltproject.org/a-city-level-network-diagram-of-2015-in-one-line-of-sql/
【讨论】:
子查询内部的ORDER BY
是否保证外部/最终 SELECT 的相应顺序?以上是关于修复 BigQuery 中的“超出资源”并使其运行更快的主要内容,如果未能解决你的问题,请参考以下文章