如何分片 BigQuery table?

How do I shard a BigQuery table?

很抱歉,如果这个问题已在别处得到解答,我找不到类似的内容。

有什么方法可以将 table 拆分为多个分片而无需使用多个查询?这里有两个例子:

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

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

的数据

我肯定遗漏了一些东西,必须有更有效的方法来完成上述操作。

一个要求:

让我们假设下面简化了 case/scenario

1 我们有一个 "big" table:
表全部

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 我们需要拆分数据以分离 "smaller" tables 按字段 "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

表A3

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;

优点:速度与激情!
缺点:我们需要对整个 table 进行尽可能多的 table 扫描(全部成本),因为我们拥有 "a" 的不同值(在这种特殊情况下只有三个,但在现实生活中可能是假设最多 N=1K 个不同的值)。

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

Our Target Goal

We want to minimize cost as much as possible 
ideally down to fixed price of  * 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 – 透视 table 以便字段 "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"}

注意:以上数据的大小与原始table的大小相同(w/o a 列)
它仍然比原始数据大,因为现在的数据是详细的 json 格式,而不是本机数据类型 + 列名。
这可以通过消除空格、不需要的引号、normalizing/minimizing 原始列名称在名称中只有一个字符等来优化。
我认为随着 N 的增加,这种差异可以忽略不计! (虽然没有机会对此进行评估)

步骤 3 – 将结果数据透视保存到 table 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 步的成本: * TableAllSizeInTB
根据第 2 步中的评论假设:Size(TableAllPivot) = 2 * Size(TableAll)

第 4 步 – 生成分片,每个分片仅查询一列
要保留 schema/data-types – 可以提前创建相应的分片表

数据提取 :
//对于表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 步的成本: * TableAllPivot

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

总结:
建议进场固定价格= * 3 * SizeInTB(TableAll)
对比
初始线性价格 = * N * SizeInTB(TableAll)

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

中列数的硬性限制

C 一些辅助代码和参考资料

1 生成透视查询(结果用于上面部分的第 3 步)
对于初始 table 中大于 10-20 的字段数很有用,当手动输入查询很无聊时,您可以使用下面的 script/query

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 如果您想探索和深入了解此选项 - 另请参阅下面对相关和可能有用的代码的引用



2018年更新

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

请参阅以下内容post 以从集群中获益:


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

假设您的 table 有 8 个数字(将每个数字视为一个分区):12345678。要将其分成 8 个 table,您正在查看 运行 8乘以 table 大小 8 的查询(成本:8*8=64)。

如果你先把这个table分成2份会怎么样:1234, 5678。成本是8*2(2次全扫描),但我们现在有2个table。如果我们要把这一半table分出来,现在只需要扫描一半2次(2*4*2)。然后我们剩下 4 tables:12,34,56,78。划分它们的成本是 4*2*2... 所以总成本是 8*2+2*4*2+4*2*2=48。通过减半,我们将 table in 8 从 64 划分为 48 的成本。

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

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