如何对 BigQuery 表进行分片?

Posted

技术标签:

【中文标题】如何对 BigQuery 表进行分片?【英文标题】:How do I shard a BigQuery table? 【发布时间】:2016-01-22 20:07:59 【问题描述】:

抱歉,如果这已在其他地方得到回答,我找不到类似的东西。

有什么方法可以将一个表拆分为多个分片,而不必使用多个查询?这里有两个例子:

1) 加载带有时间戳数据(unix 时间戳)的表。我想每天将数据保存到一张表中。天真的方法是:a)加载数据; b) 运行查询以获取每天的所有数据并将其附加到适当的分片中。这种方法将导致查询涉及 N x [整个表的大小] 数据,其中 N 是表中的天数。再加上一个查询来查找最小和最大时间戳,这样我就可以确定我需要创建的分片范围。

2) 使用字段中的数据将表拆分为分片。例如,一个包含 10 亿行的表,其中包含具有 1,000 个不同值的字段 X。如果我想将表拆分为 1000 个不同的表,每个 X 的值一个表,那么天真的方法是运行 SELECT * FROM table WHERE X=[value],然后将结果插入分片 table_value。但是,这将导致 1000 个查询,每个查询都涉及整个表的数据!

当然我错过了一些东西,必须有更有效的方法来完成上述工作。

【问题讨论】:

请澄清一下,为什么不将数据加载或流式传输到 dily 表开始,所以以后不需要拆分?或者这是为了分割历史数据? 在上面的问题(1)中,数据在一个csv表中。 “加载”功能中是否有一个选项可以按天将其加载到单独的表格中?如果是这样,我很乐意使用它。否则,您隐含地假设数据是每天导入的,但事实并非如此。给定的 csv 文件可能包含多天的数据。我必须将 csv 文件拆分为多个文件(每天一个),然后加载它。也就是说,在使用 BQ 之前,我必须使用不同的数据库。 在情况 (2) 中,数据是从查询开始生成的。所以我们有表 A 和表 B 并生成表 C 为:SELECT col1, col2, col3 FROM A JOIN B ON xyz_condition。数据需要根据 col2 的值进行分片。我可以使用连接查询对数据进行分片吗?然后很高兴这样做 我找到了一种方法来减少上述繁重,但仍然不理想:(a) 运行一个查询,按要分片的字段对表的数据进行排序(在 case 1 或 col2 中的时间戳在情况 2) 中; (b) 运行另一个查询,找到每个分片的最小和最大 id(我可能必须在此处添加各种“id”,尽管这在时间戳中是隐含的)(即,如果我们按天进行分片,则为最小时间戳;或最小(col2)位置的位置); (c) 以查询 (b) 的最小值/最大值定义的间隔列出查询 (a) 创建的表的结果。如果这可行,则需要对表进行 3 次扫描以进行分片。 2018:我更新了下面的答案,以展示如何免费(或仅使用一次扫描)对表进行分区。 ***.com/a/34959361/132438 【参考方案1】:

A 要求

让我们假设下面的简化案例/场景

1 我们有一个“大”表: 全部表格

Row a   b   c
1   1   11  12
2   1   13  14
3   1   15  16
4   1   17  18
5   2   21  22
6   2   23  24
7   2   25  26
8   2   27  28
9   3   31  32
10  3   33  34
11  3   35  36
12  3   37  38

2 我们需要将数据拆分为由字段“a”分区的“较小”表 表A1

Row b   c
1   11  12
2   13  14
3   15  16
4   17  18

表A2

Row b   c
1   21  22
2   23  24
3   25  26
4   27  28

TableA3

Row b   c
1   31  32
2   33  34
3   35  36
4   37  38

3 解决问题 最直接的方法是发出三个单独的语句,分别将输出写入 TableA1、TableA2、TableA3

SELECT b, c FROM TableAll WHERE a = 1;
SELECT b, c FROM TableAll WHERE a = 2;
SELECT b, c FROM TableAll WHERE a = 3;

优点:速度与激情! 缺点:我们需要对整个表(全部成本)进行尽可能多的表扫描,与我们拥有的“a”的不同值一样多(在这种特殊情况下只有三个,但在现实生活中,可以说最多 N=1K 不同的值) .

所以最终成本是 $5 * N * SizeInTB(TableAll)

我们的目标

We want to minimize cost as much as possible 
ideally down to fixed price of $5 * SizeInTB(TableAll)  

B 可能的解决方案(想法和简单的实现)

逻辑步骤 1 - 将数据转换为如下所示(将列转换为 JSON)

Row a   json
1   1   "b":"11", "c":"12"
2   1   "b":"13", "c":"14"
3   1   "b":"15", "c":"16"
4   1   "b":"17", "c":"18"
5   2   "b":"21", "c":"22"
6   2   "b":"23", "c":"24"
7   2   "b":"25", "c":"26"
8   2   "b":"27", "c":"28"
9   3   "b":"31", "c":"32"
10  3   "b":"33", "c":"34"
11  3   "b":"35", "c":"36"
12  3   "b":"37", "c":"38"

逻辑步骤 2 - 数据透视表,使字段“a”的值成为字段名称(以 a 为前缀以确保我们符合列名约定)

Row a1                    a2                    a3
1   "b":"11", "c":"12"  null                  null
2   "b":"13", "c":"14"  null                  null
3   "b":"15", "c":"16"  null                  null
4   "b":"17", "c":"18"  null                  null
5   null                  "b":"21", "c":"22"  null
6   null                  "b":"23", "c":"24"  null
7   null                  "b":"25", "c":"26"  null
8   null                  "b":"27", "c":"28"  null
9   null                  null                  "b":"31", "c":"32"
10  null                  null                  "b":"33", "c":"34"
11  null                  null                  "b":"35", "c":"36"
12  null                  null                  "b":"37", "c":"38"

注意:上述数据的大小与原始表的大小相同(不含a列) 它仍然比原始数据大,因为现在数据是详细的 json 格式,而不是原生数据类型 + 列名。 这可以通过消除空格、不需要的引号、规范化/最小化原始列名以使其名称中只有一个字符等来优化。 我认为随着 N 的增加,这种差异变得可以忽略不计! (虽然还没有机会评估这个)

第 3 步 - 将结果数据透视表保存到 TableAllPivot 中 实现示例:

SELECT 
  IF(a=1, json, NULL) as a1,
  IF(a=2, json, NULL) as a2,
  IF(a=3, json, NULL) as a3 
FROM (
  SELECT a, CONCAT("\"b\":\"",STRING(b), "\","," \"c\":\"", STRING(c), "\"") AS json 
  FROM TableAll
)

第 3 步的费用:$5 * TableAllSizeInTB 基于步骤 2 中的 cmets 假设:Size(TableAllPivot) = 2 * Size(TableAll)

第 4 步 - 生成分片,每个分片仅查询一列 为了保留模式/数据类型——可以提前创建相应的分片表

数据提取 : //对于表A1:

SELECT 
  JSON_EXTRACT_SCALAR(a1, '$.b') AS b, 
  JSON_EXTRACT_SCALAR(a1, '$.c') AS c 
FROM TableAllPivot
WHERE NOT a1 IS NULL

//对于表A2:

SELECT 
  JSON_EXTRACT_SCALAR(a2, '$.b') AS b, 
  JSON_EXTRACT_SCALAR(a2, '$.c') AS c 
FROM TableAllPivot
WHERE NOT a2 IS NULL

//对于A3表:

SELECT 
  JSON_EXTRACT_SCALAR(a3, '$.b') AS b, 
  JSON_EXTRACT_SCALAR(a3, '$.c') AS c 
FROM TableAllPivot
WHERE NOT a3 IS NULL

第 4 步的成本:$5 * TableAllPivot

总成本:Step 3 Cost + Step 4 Cost =$5 * SizeInTB(TableAll) + $5 * SizeInTB(TableAllPivot) ~ $5 * 3 * SizeInTB(TableAll)

总结: 建议方法固定价格 = $5 * 3 * SizeInTB(TableAll) 对比 初始线性价格 = $5 * N * SizeInTB(TableAll)

请注意:在我的简化示例中,$5 * 3 * SizeInTB(TableAll) 公式中的3 不是由分片数量定义的,而是主要反映将数据转换为 json 的价格的估计常数。分片的数量在这里无关紧要。相同的公式适用于 100 个分片和 1K 个分片,依此类推。此解决方案的唯一限制是 10K 分片,因为这是一个表中列数的硬性限制

C 一些帮助代码和参考

1 生成透视查询(结果用于上述第 3 步) 当手动输入查询很无聊时,对于初始表中大于 10-20 的字段数可能很有用,因此您可以使用下面的脚本/查询

SELECT 'SELECT ' + 
   GROUP_CONCAT_UNQUOTED(
      'IF(a=' + STRING(a) + ', json, NULL) as a' + STRING(a) 
   ) 
   + ' FROM (
 SELECT a, 
 CONCAT("\\\"b\\\":\\\"\",STRING(b),"\\\","," \\\"c\\\":\\\"\", STRING(c),"\\\"") AS json
 FROM TableAll
       )'
FROM (
  SELECT a FROM TableAll GROUP BY a 
)

2 如果您想探索和深入了解此选项 - 另请参阅下面对相关和可能有用的代码的参考

Pivot Repeated fields in BigQueryHow to scale Pivoting in BigQuery?How to extract all the keys in a JSON object with BigQuery

【讨论】:

嗨 Mikhail,感谢您的详细回答,但它会产生一些问题。首先,我认为您的成本估算中的“3”不是一个固定数字,而是需要的分片数量。所以成本是 O(N * k),其中 N 是表大小,k 是分片。整个目标是使这个 O(N)。第二:我上面概述的解决方案(基本上按 col a 排序表;添加“行号”列;然后简单地列出表从 min 到 max row per column value)is O(N ),前提是我可以将“列表”命令的值导出到另一个表中。我可以吗? list [startIndex,maxResults]) -->分片 on #1 - 我认为它接近 3*O(n),我试图在我的回答中提供简要的细节;在#2 - 这里的潜在问题是有问题的:保存“排序”结果时是否保证物理顺序。如果是 - 应该工作。无论如何 - 使用 tabledata.list API 在这里看起来很有创意(而且它是免费的);试着让我们知道。我个人 - 我还没有看到你计划中的步骤 (c) 将如何实施 - 我可能会稍后再试。同时 - 我认为你应该尝试实施你的选择 - 如果问题 - 打开新问题,以便有人会提供帮助 这里是步骤 (c)。它创建一个内部表,其 row_num 按时间戳有效排序;外部循环(最小/最大选择)基本上找到每个分片的 startIndex。但是 -- 我仍然找不到将 tabledata:list 的结果输出到destinationTable 的方法: SELECT day, min(row_num), max(row_num) FROM ( -- 将下面保存为中间表;这是我们的表list SELECT col1, col2, timestamp DATE(SEC_TO_TIMESTAMP(timestamp)) as day, row_number() over () as row_num FROM (SELECT col1, col2, timestamp, FROM table ORDER BY timestamp) ) GROUP BY day 我的建议是在您的 cmets 中打开具有特定详细信息的新问题。还有一次关于成本 - 我认为您可能认为我的快速示例中的 3 个分片定义了成本估算中的第 3 位。不——它们彼此无关。相同的 3*O(n) 将是 100 个分片和 1K 个分片的成本,依此类推。此解决方案的唯一限制是 10K 分片,因为这是一个表中列数的硬性限制。我希望这有帮助。我期待着新问题的详细信息如上述评论:o) 2018:我更新了下面的答案,以展示如何免费(或仅使用一次扫描)对表进行分区。 ***.com/a/34959361/132438【参考方案2】:

2018 年更新

不要创建多个表,而是创建一个分区表。 免费分区:创建分区表(按日期),导入其中。 分区一次查询(一次扫描):CREATE TABLE ... AS SELECT * FROM old-table

请参阅以下帖子以从集群中受益:

https://medium.com/google-cloud/bigquery-optimized-cluster-your-tables-65e2f684594b

我真的很喜欢米哈伊尔的回答,但让我给你一个不同的答案:分而治之:

假设您的表有 8 个数字(将每个数字视为一个分区):12345678。要将其分割为 8 个表,您正在考虑在表大小为 8 上运行 8 次查询(成本:8*8= 64)。

如果你先把这张表分成 2 份:1234、5678。成本是 8*2(2 次全扫描),但我们现在有 2 张表。如果我们想对这半张表进行分区,现在我们只需要扫描一半(2*4*2)。然后我们剩下 4 张桌子:12、34、56、78。划分它们的成本是 4*2*2... 所以总成本是 8*2+2*4*2+4*2*2=48。通过减半,我们将一个表从 64 分区到 48 分区的成本为 8。

从数学上讲,我们从 O(n**2) 到 O(n(log n)) - 这总是一件好事。

在成本方面,Mikhail 的答案更好,因为它从 O(n**2) 到 O(n),但编写中间辅助函数会给任务带来额外的复杂性。

【讨论】:

以上是关于如何对 BigQuery 表进行分片?的主要内容,如果未能解决你的问题,请参考以下文章

Bigquery 分片与 Bigquery 分区

是否有元数据表来检查 BigQuery 中的表是否已分区?

在 BigQuery 中将数据插入/创建分片/通配符表

sql [BigQuery - Facebook产品目录]查询para obtenerelcatálogodeproductos de Kichink。 #facebook #bigqu

BigQuery - 在插入表时调用查询

在 Dataflow 中从 BigQuery 写入云存储时如何设置文件大小而不是分片数