CockroachDB 多区域指南:表本地性、延迟与容灾取舍
CockroachDB 可以让 SQL 数据库跨区域运行,但多区域数据库不是魔法。它可以提升韧性,并在数据靠近用户时降低延迟;同时也可能带来写入延迟、事务冲突、运维复杂度和数据驻留设计问题。
这篇文章关注 CockroachDB 多区域设计中的实际选择:应该添加哪些区域、如何选择表本地性、什么时候用 regional by row、什么时候用 global table,以及生产前应该验证什么。
参考文档:
- CockroachDB multi-region overview
- Choosing a multi-region configuration
- Table localities
- Multi-region survival goals
最重要的设计问题
改 SQL 之前,先确认应用最需要什么。
| 目标 | 设计方向 | 取舍 |
|---|---|---|
| 降低区域用户读取延迟 | 把数据放到主要读取它的区域附近 | 跨区域写入仍可能更慢 |
| 数据驻留 | 使用区域放置和明确的数据归属 | Schema 和路由必须严格 |
| 全球参考数据 | 对读多写少的小表使用 global table | 写入通常需要更谨慎 |
| 区域故障容忍 | 选择合适的 survival goal 和拓扑 | 更多副本和协调可能增加延迟 |
| 多区域活跃用户 | 流量路由到近端区域,并建模数据归属 | 冲突写入仍需应用处理 |
好的多区域设计从访问模式出发,而不是只从地图出发。
核心概念
CockroachDB 多区域抽象围绕这些概念:
- Cluster region:节点启动时通过 locality 设置的区域。
- Database region:加入到数据库的区域,其中一个是 primary region。
- Primary region:数据库默认使用的区域,除非表本地性另有设置。
- Table locality:CockroachDB 如何放置和优化表数据。
- Survival goal:数据库设计上可承受的故障范围。
- Secondary region:部分配置中用于故障切换相关的区域。
这些设置会影响性能和可用性,不只是部署标签。
基础多区域配置
多区域集群从节点 locality 开始。每个节点应知道自己的 region 和 zone。
cockroach start \
--locality=region=us-east-1,zone=us-east-1a \
--certs-dir=certs \
--join=node1:26257,node2:26257,node3:26257
然后配置数据库区域。
ALTER DATABASE global_app SET PRIMARY REGION "us-east-1";
ALTER DATABASE global_app ADD REGION "eu-west-1";
ALTER DATABASE global_app ADD REGION "ap-southeast-1";
SHOW REGIONS FROM DATABASE global_app;
这不会自动让所有查询在所有地方都变快。它只是建立区域基础。用户能感受到的延迟,更多取决于表本地性和应用路由。
选择表本地性
表本地性决定数据如何放置和优化。
| Locality | 适合场景 | 说明 |
|---|---|---|
| Regional by table | 整张表主要属于一个区域 | 适合区域专属表 |
| Regional by row | 不同行属于不同区域 | 适合用户、租户或账户数据 |
| Global | 小型读多写少参考数据 | 读体验好,写入要更谨慎 |
Regional By Table
当整张表主要属于一个区域时,使用 regional by table。
CREATE TABLE eu_tax_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
country STRING NOT NULL,
rule JSONB NOT NULL
) LOCALITY REGIONAL BY TABLE IN "eu-west-1";
它适合区域配置、法律规则、本地目录,或与某个地理区域绑定的运营数据。
Regional By Row
当每一行都有自己的 home region 时,使用 regional by row。用户或租户数据很常见。
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING NOT NULL,
home_region crdb_internal_region NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
) LOCALITY REGIONAL BY ROW;
应用必须正确设置并维护 home region。不要把区域字段当成普通附属字段,它是数据模型的一部分。
Global Tables
global table 适合各地都要读、但很少写的小表。
CREATE TABLE countries (
code STRING PRIMARY KEY,
name STRING NOT NULL
) LOCALITY GLOBAL;
适合国家代码、很少变动的功能开关、公共配置和参考数据。不要把高频交易写入表默认设为 global,除非已经测试过延迟和冲突行为。
有意设计流量路由
数据库放置只是设计的一半。应用流量通常也应路由到最近的健康区域。
需要检查:
- 每个用户请求由哪个应用区域处理。
- 连接池是否连接到本地数据库节点。
- 读取流量是否靠近用户数据 home region。
- 写入是否发送到拥有该行或租户的区域。
- 故障切换后,流量是否进入可以安全服务的区域。
如果欧洲用户访问欧洲应用服务器,但该服务器写入 home 在美国的行,数据库无法完全隐藏跨区域延迟。
处理事务冲突
多区域不会消除写入归属设计。如果两个区域频繁更新同一行,仍可能出现重试和竞争。
降低冲突风险的方法:
- 为每个租户、账户或文档指定清晰写入归属。
- 避免热点全局计数器。
- 使用幂等命令和可安全重试的事务逻辑。
- 缩短事务时间。
- 将高写入记录拆成更小的归属单元。
- 把读多共享数据放进参考表。
Serializable 隔离很有价值,但应用仍然需要明确的重试策略。
数据驻留
如果有数据驻留要求,要明确哪些数据必须留在哪些区域:
- 用户资料。
- 支付或账单数据。
- 日志和审计记录。
- 备份。
- 派生分析数据。
- 搜索索引和缓存。
正确放置主表还不够。副本、导出、日志、备份和下游系统也必须遵守同一策略。
Survival Goals
Survival goal 定义数据库设计上要承受什么故障。更高韧性通常需要更谨慎的拓扑,也可能影响延迟。
需要问:
- 需要承受 zone 故障,还是完整 region 故障?
- 有多少 region 和 zone 可用?
- 哪些表在故障时仍必须可写?
- 当前 table locality 是否和相关特性兼容?
- 是否用真实应用流量做过故障切换测试?
在没有测试之前,不要在架构文档里承诺固定 RTO 或 RPO。
生产检查清单
上线前确认:
- 节点启动时 region 和 zone 配置一致。
- 数据库 primary region 和 secondary region 是有意设计的。
- 每张表按访问模式选择 locality。
- regional by row 表有可靠的区域赋值。
- 应用路由匹配数据归属。
- 事务重试逻辑已经实现。
- 备份和下游系统遵守驻留规则。
- Schema 变更从合适区域执行。
- 监控覆盖延迟、重试、leaseholder 变化和 unavailable range。
- 故障演练有文档并且会重复执行。
常见错误
把所有表都设成 Global
Global table 有用,但不是默认选项。它适合小型读多写少数据。高写入业务表必须谨慎测试。
忽略应用层
如果应用流量没有按区域路由,数据库本地性无法完全解决延迟。
缺少重试逻辑
分布式 SQL 系统需要应用处理可重试事务错误。重试应明确,并且操作要幂等。
把数据驻留当成单表设置
数据驻留包括备份、日志、分析、搜索和导出。要审计完整数据流。
不做故障测试
多区域设计应在节点、zone 和 region 故障场景下测试。不要假设架构图等于生产行为。
总结
CockroachDB 多区域设计的核心是取舍:让数据靠近用户、满足驻留要求、承受故障,并保持写入路径可理解。先从访问模式和数据归属开始,再选择表本地性、配置区域、实现重试并测试故障切换。
目标不是“所有地方都 active-active”,而是在正常流量和区域故障时都有可预期的行为。
本站提供浏览器本地工具,免注册即可试用 →