问题描述
对于 YugabyteDB 中的写绑定工作负载,是否需要进行任何性能调整?我们认为通过简单地向 YugabyteDB 集群添加额外的节点,无需进一步调整,我们会看到写入的一些显着增加,但事实并非如此。可以在下面找到架构。
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
update_id | character varying(255) | | not null | | extended | |
node_id | character varying(255) | | not null | | extended | |
data | character varying | | not null | | extended | |
created_at | timestamp without time zone | | | timezone('utc'::text,Now()) | plain | |
Indexes:
"test_pkey" PRIMARY KEY,lsm (update_id HASH)
"test_crat" lsm (created_at DESC)
此表包含分布在所有 RF=3 的 tserver 上的平板电脑。 Created_at 是一个不断变化的时间戳。此时它没有超过两天的数据,所有新插入都在获取新的时间戳。
解决方法
在上面调用的模式的情况下,这里的 test_crat 索引限制为 1 个平板电脑,因为它是范围分片的。由于 created_at 只有最近的值,它们最终会进入 1 个分片/平板电脑,即使是平板电脑拆分,这意味着所有插入都将进入 1 个分片。正如此 Google Spanner 文档(其分片、复制和事务架构 YugabyteDB 所基于的)中所述,这是可扩展性的反模式。如该文档中所述:
如果您需要一个全局(跨节点)时间戳排序表,并且您需要支持比单个节点更高的写入速率,请使用应用程序级分片。对表进行分片意味着将其划分为 N 个大致相等的部分,称为分片。这通常通过在原始主键前面加上一个额外的 ShardId 列来完成,该列包含 [0,N) 之间的整数值。给定写入的 ShardId 通常是随机选择的,或者通过散列基本密钥的一部分来选择。散列通常是首选,因为它可用于确保给定类型的所有记录进入同一个分片,从而提高检索性能。无论哪种方式,目标都是确保随着时间的推移,写入均匀分布在所有分片上。这种方法有时意味着读取需要扫描所有分片以重建写入的原始总排序。
这意味着:要获得最近的更改,您必须查询每个分片。假设您有 32 个分片:
select * from raw3 where shard_id = 0 and created_at > now() - INTERVAL 'xxx';
..
select * from raw3 where shard_id = 31 and created_at > now() - INTERVAL 'xxx';
在插入时,每一行都可以为您的 shard_id 列提供一个从 0..31 开始的随机值。您的索引将从:
(created_at DESC)
到
(shard_id HASH,created_at DESC)
您可以使用的另一种可能不那么直观但可能更有效的方法是为您想要的每个 shard_id 使用部分索引。
这是一个使用 4 个分片的简单示例:
create index partial_0 ON raw3(created_at DESC) where (extract(epoch from timezone('utc',created_at)) * 1000)::bigint % 4=0;
上面的部分索引仅包括 created_at
时间戳的以毫秒为单位的纪元模数为 0 的行。您对其他 3 个分片重复:
create index partial_1 ON raw3(created_at DESC) where (extract(epoch from timezone('utc',created_at)) * 1000)::bigint % 4 = 1;
create index partial_2 ON raw3(created_at DESC) where (extract(epoch from timezone('utc',created_at)) * 1000)::bigint % 4 = 2;
create index partial_3 ON raw3(created_at DESC) where (extract(epoch from timezone('utc',created_at)) * 1000)::bigint % 4 = 3;
然后当您查询 PostgreSQL 时,它足够聪明,可以选择正确的索引:
yugabyte=# explain analyze select * from raw3 where (extract(epoch from timezone('utc',created_at)) * 1000)::bigint % 4 = 3 AND created_at < now();
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Index Scan using partial_3 on raw3 (cost=0.00..5.10 rows=10 width=16) (actual time=1.429..1.429 rows=0 loops=1)
Index Cond: (created_at < now())
Planning Time: 0.210 ms
Execution Time: 1.502 ms
(4 rows)
无需在基表或索引中添加新的 shard_id
列。如果您想重新分片,可以使用不同的分片重新创建新的部分索引并删除旧索引。
有关 YugabyteDB 中 DocDB 分片层的更多信息,请参见 here。如果您对我们评估的不同分片策略以及为什么我们决定将一致哈希分片作为默认分片策略感兴趣,请查看我们的联合创始人兼首席技术官 Karthik Ranganathan 撰写的this blog。