具有使用哈希的基于时间戳的历史记录的 Cloud Spanner

Cloud Spanner with timestamp based history using hash

我想保留特定 table 上的数据库写入的完整历史记录。这个table的主要用途是读取最新的数据,但是所有插入和更新的完整可审计性也是业务需求。 official Spanner document here calls out on schema anti-patterns, and one of them 是关于单调递增的数据用作主键。涉及到改变主键顺序可以分散负载,还建议使用散列、shard with modulo、UUID等

This Google Cloud blog post 提到使用 ShardId 而不是时间戳哈希是首选。

Note, however, that using a simple hash will make queries by timestamp range extremely slow, since retrieving a range of timestamps will require a full table scan to cover all the hashes. Instead, we recommend generating a ShardId from the timestamp.

示例 table 设置已提供,查询使用 TimestampShardId

TimestampShardId = CRC32(Timestamp) % 100
CREATE TABLE Events (
     TimestampShardId INT64 NOT NULL
     Timestamp TIMESTAMP NOT NULL,
     event_info...
) PRIMARY KEY (TimestampShardId, Timestamp DESC)

Select * from Events
WHERE
   TimestampShardId BETWEEN 0 AND 99
   AND Timestamp > @lower_bound
   AND Timestamp < @upper_bound;

我无法理解 TimestampShardId 如何使扫描比简单散列法更快。这两种方法似乎都需要扫描整个 table - 谁能告诉我为什么 ShardId 是首选?例如,为了获得完整的历史记录,使用时间戳哈希作为主键的历史记录 table 是否会导致问题?带有 UUID 和时间戳的主键呢?

这个想法是,Cloud Spanner 可以通过对 TimestampShardId 的每个值执行分布式联合,然后按顺序读取该分片的键,从而避免对事件进行全面排序。

将此视为与进行完整排序相比合并 N 个排序列表的复杂性。如果 N 较小,则合并会相对有效。另一方面,当 N 接近列表中的项目数时,性能会下降到完整排序的性能。

通过使用不同的 TimestampShardId 基数,您可以在写入可伸缩性和查询性能之间进行权衡——更多的分片允许更多的写入并发,但代价是在查询期间的合并步骤中要处理更多的数据。我们建议使用不同数量的分片对您的特定工作负载进行性能测试,以查看 space 中的哪一点最适合您。