无法从Spanner删除大量行

时间:2019-09-26 00:08:36

标签: google-cloud-spanner

我有3个节点的Spanner实例,以及一个包含约40亿行的表。 DDL看起来像这样:

CREATE TABLE predictions (
    name STRING(MAX),
    ...,
    model_version INT64,
) PRIMARY KEY (name, model_version)

我想设置一个作业,以使用Python Spanner客户端定期从该表中删除一些旧行。我要运行的查询是:

DELETE FROM predictions WHERE model_version <> ? 

根据文档,听起来我需要将其作为Partitioned DML语句来执行。我正在按以下方式使用Python Spanner客户端,但由于表中的行数过多,因此出现超时(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错误。 / p>

无论如何,我不太确定下一步该怎么做,或者我是否不清楚是否有明显的东西可以让我在此巨大的Spanner表上及时运行删除查询。 model_version列的基数非常低(整个表中通常有2-3个唯一的model_version值),因此我不确定这是否会包含在任何建议中。但是,如果有人可以提供一些意见或建议,那就太好了:)预先感谢

3 个答案:

答案 0 :(得分:2)

设置timeout_secs无效的原因是,不幸的是,该参数不是事务的超时。它是交易的retry timeout,因此它用于设置截止日期,在该截止日期后交易将不再重试。

我们将更新run_in_transaction的文档以对此进行更好的解释。

答案 1 :(得分:1)

第一个建议是尝试使用gcloud。

https://cloud.google.com/spanner/docs/modify-gcloud#modifying_data_using_dml

另一个建议是也传递名称范围,以限制扫描的行数。例如,您可以在WHERE子句中添加类似STARTS_WITH(name,'a')的内容,以确保每个事务都涉及少量的行,但是首先,您需要了解名称列值的域。

最后的建议是尽量避免使用'<>',因为评估起来通常非常昂贵。

答案 2 :(得分:1)

根本原因是客户端库中Streaming RPC调用的总超时设置太低,对于Streaming API(例如,分区DML调用使用的ExecuteStreamingSQL)设置为120s。

这已在客户端库源代码中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();