无法从 Spanner 中删除大量行
Unable to delete large number of rows from Spanner
我有 3 个节点的 Spanner 实例,以及一个包含大约 40 亿行的 table。 DDL 如下所示:
CREATE TABLE predictions (
name STRING(MAX),
...,
model_version INT64,
) PRIMARY KEY (name, model_version)
我想设置一个作业,使用 Python Spanner 客户端定期从此 table 中删除一些旧行。我想要 运行 的查询是:
DELETE FROM predictions WHERE model_version <> ?
根据文档,听起来我需要将其作为 Partitioned DML 语句执行。我正在按如下方式使用 Python Spanner 客户端,但由于 table.
中的大量行,我遇到了超时(504 Deadline Exceeded
错误)
# this always throws a "504 Deadline Exceeded" error
database.execute_partitioned_dml(
"DELETE FROM predictions WHERE model_version <> @version",
params={"model_version": 104},
param_types={"model_version": Type(code=INT64)},
)
我的第一直觉是想看看是否可以增加某种超时,但我在 the source 中没有看到任何超时参数:/
我确实注意到 Spanner 库中有一个 run_in_transaction
方法包含超时参数,因此我决定偏离分区 DML 方法以查看使用此方法是否有效。这是我 运行:
def delete_old_rows(transaction, model_version):
delete_dml = "DELETE FROM predictions WHERE model_version <> {}".format(model_version),
dml_statements = [
delete_dml,
]
status, row_counts = transaction.batch_update(dml_statements)
database.run_in_transaction(delete_old_rows,
model_version=104,
timeout_secs=3600,
)
奇怪的是 timeout_secs
参数似乎被忽略了,因为尽管超时一小时,我仍然在执行上述代码的一两分钟内收到 504 Deadline Exceeded
错误.
无论如何,我不太确定接下来要尝试什么,或者我是否遗漏了一些明显的东西,这些东西可以让我在这个巨大的 Spanner 上及时 运行 删除查询 table。 model_version
列的基数很低(整个 table 中通常有 2-3 个唯一的 model_version
值),所以我不确定这是否会影响任何建议。但是,如果有人可以提供一些意见或建议,那就太好了 :) 提前致谢
第一个建议是改用 gcloud。
https://cloud.google.com/spanner/docs/modify-gcloud#modifying_data_using_dml
另一个建议是也传递名称的范围,以限制扫描的行数。例如,您可以向 WHERE 子句添加类似 STARTS_WITH(name, 'a') 的内容,以确保每个事务都涉及少量行,但首先,您需要了解名称列值。
最后的建议是尽量避免使用“<>”,因为它的评估成本通常很高。
设置 timeout_secs 没有帮助的原因是因为该参数很遗憾不是事务超时。它是交易的 retry timeout,因此它用于设置交易将停止重试的截止日期。
我们将更新 run_in_transaction 的文档以更好地解释这一点。
根本原因是客户端库中流式 RPC 调用的总超时设置太低,流式 API 设置为 120 秒(例如分区 DML 调用使用的 ExecuteStreamingSQL。)
这已经 fixed 在客户端库源代码中,将它们更改为 60 分钟的超时(这是最大值),并将成为下一个客户端库版本的一部分。
作为解决方法,在 Java 中,您可以在连接数据库时将超时配置为 SpannerOptions 的一部分。 (我不知道如何在 Python 中设置自定义超时,抱歉)
final RetrySettings retrySettings =
RetrySettings.newBuilder()
.setInitialRpcTimeout(Duration.ofMinutes(60L))
.setMaxRpcTimeout(Duration.ofMinutes(60L))
.setMaxAttempts(1)
.setTotalTimeout(Duration.ofMinutes(60L))
.build();
SpannerOptions.Builder builder =
SpannerOptions.newBuilder()
.setProjectId("[PROJECT]"));
builder
.getSpannerStubSettingsBuilder()
.applyToAllUnaryMethods(
new ApiFunction<UnaryCallSettings.Builder<?, ?>, Void>() {
@Override
public Void apply(Builder<?, ?> input) {
input.setRetrySettings(retrySettings);
return null;
}
});
builder
.getSpannerStubSettingsBuilder()
.executeStreamingSqlSettings()
.setRetrySettings(retrySettings);
builder
.getSpannerStubSettingsBuilder()
.streamingReadSettings()
.setRetrySettings(retrySettings);
Spanner spanner = builder.build().getService();
我有 3 个节点的 Spanner 实例,以及一个包含大约 40 亿行的 table。 DDL 如下所示:
CREATE TABLE predictions (
name STRING(MAX),
...,
model_version INT64,
) PRIMARY KEY (name, model_version)
我想设置一个作业,使用 Python Spanner 客户端定期从此 table 中删除一些旧行。我想要 运行 的查询是:
DELETE FROM predictions WHERE model_version <> ?
根据文档,听起来我需要将其作为 Partitioned DML 语句执行。我正在按如下方式使用 Python Spanner 客户端,但由于 table.
中的大量行,我遇到了超时(504 Deadline Exceeded
错误)
# this always throws a "504 Deadline Exceeded" error
database.execute_partitioned_dml(
"DELETE FROM predictions WHERE model_version <> @version",
params={"model_version": 104},
param_types={"model_version": Type(code=INT64)},
)
我的第一直觉是想看看是否可以增加某种超时,但我在 the source 中没有看到任何超时参数:/
我确实注意到 Spanner 库中有一个 run_in_transaction
方法包含超时参数,因此我决定偏离分区 DML 方法以查看使用此方法是否有效。这是我 运行:
def delete_old_rows(transaction, model_version):
delete_dml = "DELETE FROM predictions WHERE model_version <> {}".format(model_version),
dml_statements = [
delete_dml,
]
status, row_counts = transaction.batch_update(dml_statements)
database.run_in_transaction(delete_old_rows,
model_version=104,
timeout_secs=3600,
)
奇怪的是 timeout_secs
参数似乎被忽略了,因为尽管超时一小时,我仍然在执行上述代码的一两分钟内收到 504 Deadline Exceeded
错误.
无论如何,我不太确定接下来要尝试什么,或者我是否遗漏了一些明显的东西,这些东西可以让我在这个巨大的 Spanner 上及时 运行 删除查询 table。 model_version
列的基数很低(整个 table 中通常有 2-3 个唯一的 model_version
值),所以我不确定这是否会影响任何建议。但是,如果有人可以提供一些意见或建议,那就太好了 :) 提前致谢
第一个建议是改用 gcloud。
https://cloud.google.com/spanner/docs/modify-gcloud#modifying_data_using_dml
另一个建议是也传递名称的范围,以限制扫描的行数。例如,您可以向 WHERE 子句添加类似 STARTS_WITH(name, 'a') 的内容,以确保每个事务都涉及少量行,但首先,您需要了解名称列值。
最后的建议是尽量避免使用“<>”,因为它的评估成本通常很高。
设置 timeout_secs 没有帮助的原因是因为该参数很遗憾不是事务超时。它是交易的 retry timeout,因此它用于设置交易将停止重试的截止日期。
我们将更新 run_in_transaction 的文档以更好地解释这一点。
根本原因是客户端库中流式 RPC 调用的总超时设置太低,流式 API 设置为 120 秒(例如分区 DML 调用使用的 ExecuteStreamingSQL。)
这已经 fixed 在客户端库源代码中,将它们更改为 60 分钟的超时(这是最大值),并将成为下一个客户端库版本的一部分。
作为解决方法,在 Java 中,您可以在连接数据库时将超时配置为 SpannerOptions 的一部分。 (我不知道如何在 Python 中设置自定义超时,抱歉)
final RetrySettings retrySettings =
RetrySettings.newBuilder()
.setInitialRpcTimeout(Duration.ofMinutes(60L))
.setMaxRpcTimeout(Duration.ofMinutes(60L))
.setMaxAttempts(1)
.setTotalTimeout(Duration.ofMinutes(60L))
.build();
SpannerOptions.Builder builder =
SpannerOptions.newBuilder()
.setProjectId("[PROJECT]"));
builder
.getSpannerStubSettingsBuilder()
.applyToAllUnaryMethods(
new ApiFunction<UnaryCallSettings.Builder<?, ?>, Void>() {
@Override
public Void apply(Builder<?, ?> input) {
input.setRetrySettings(retrySettings);
return null;
}
});
builder
.getSpannerStubSettingsBuilder()
.executeStreamingSqlSettings()
.setRetrySettings(retrySettings);
builder
.getSpannerStubSettingsBuilder()
.streamingReadSettings()
.setRetrySettings(retrySettings);
Spanner spanner = builder.build().getService();