如何分片 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),但是编写中间辅助函数会给任务带来额外的复杂性。
很抱歉,如果这个问题已在别处得到解答,我找不到类似的内容。
有什么方法可以将 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),但是编写中间辅助函数会给任务带来额外的复杂性。