You need to enable JavaScript to run this app.
文档中心
ByteHouse云数仓版

ByteHouse云数仓版

复制全文
下载 pdf
数据建模
ByteHouse 分区与分桶最佳实践
复制全文
下载 pdf
ByteHouse 分区与分桶最佳实践

本文将为您详细介绍在 ByteHouse 中进行分区与分桶设计的原则、方法与最佳实践。

背景信息

随着表数据规模持续增长,系统通常会同时面对三类压力:

  • 查询性能压力: 扫描数据量过大,I/O 成本高,难以快速定位有效数据范围。
  • 数据管理压力: 历史数据删除、归档与 TTL 清理成本上升,运维操作变重。
  • 写入与计算压力: 单节点写入吞吐受限,后台合并、去重与并行计算能力容易成为瓶颈。

在这样的背景下,ByteHouse 中常见的建模原则是:能分区尽量先分区,分桶作为性能优化手段按需引入。
对于典型分析型业务,这一实践尤其适用于以下场景:

  • 面向明细查询与聚合分析的一体化表设计。
  • 需要按时间或业务维度高效筛选数据的分析场景。
  • 需要按周期清理、归档、重导历史数据的运维场景。
  • 数据规模快速增长,希望提前建立可扩展的数据组织方式。

场景与适用范围

典型适用场景

分区更适合解决的问题

  • 时间范围查询频繁: 查询条件中经常包含 create_timeevent_timerequest_time 等时间字段。
  • 需要定期删除或归档数据: 例如按天、按月删除历史分区,或按分区执行覆盖写入。
  • 需要基于生命周期做自动清理: 通过与分区粒度对齐的 TTL 实现高效清理。
  • 数据具备天然隔离维度: 如平台、业务线等字段,且这些字段能和时间一起合理控制单分区规模。
  • 单表规模持续增长: 当表数据达到亿级甚至更高时,合理的分区策略能明显降低管理与扫描成本。

分桶更适合解决的问题

  • 单分区内数据量仍然很大: 即便已经按天或按月分区,单分区内仍达到亿级规模。
  • 高吞吐写入场景: 特别是 UNIQUE 表或需要全表级去重时,希望降低写入与后台处理压力。
  • 需要更强并行能力: 希望将数据更均匀地分散到多个桶中,提升写入吞吐与查询并发处理能力。
  • 查询模式与分桶键高度一致: 常见的 JOINGROUP BYIN、等值过滤与分桶键一致时,可进一步获得查询优化收益。

不适用或需谨慎场景

不建议优先引入分区的场景

  • 数据量较小: 例如规模仍处于千万级以下,分区收益可能有限。
  • 查询几乎不带过滤条件: 无法有效利用分区裁剪。
  • 没有合适的分区字段: 仅为了“看起来更规范”而强行分区,往往得不偿失。
  • 候选分区字段基数过高: 这通常是“分区键选择不合理”的问题,需要重新设计,而不是简单继续细分。

不建议急于引入分桶的场景

  • 单分区数据量仍在可控范围内: 例如按小时/按天分区后,单分区规模已经足够小。
  • 查询模式不稳定: 暂时无法确认高频 join key、group key 或等值筛选字段。
  • 字段分布明显倾斜: 如果候选分桶键本身分布不均匀,分桶后仍可能出现热点。

已知约束与实践边界

  • 分区与分桶都不是越多越好,核心是让数据组织方式与查询模式、生命周期管理方式保持一致
  • 组合分区键虽然能进一步缩小分区规模,但也可能造成分区数量膨胀。
  • 分桶可以提升并行能力,但不能替代合理的分区设计。

方案架构

ByteHouse 中分区与分桶的职责可以概括为“一个负责组织与裁剪,一个负责分布与并行”。在实际链路中,常见的逻辑关系如下:

  • 数据写入链路: 业务数据进入 ByteHouse 后,先按 PARTITION BY 规则进入对应分区,再按 CLUSTER BY 规则分散到多个桶。
  • 查询执行链路: 查询先利用分区键做范围裁剪,尽量减少需要扫描的分区,再在分区内部依赖桶分布提升并行处理能力。
  • 生命周期链路: 历史数据删除、TTL 清理、分区级维护等操作主要依赖分区。

前提条件

在开始设计分区与分桶方案前,建议先确认以下前提:

  • 已经明确目标表的核心查询模式,特别是高频筛选字段、JOIN 条件与聚合维度。
  • 已经掌握表的数据规模特征,包括日增量、历史总量、单分区预期规模与保留周期。
  • 已经确认该表的运维诉求,例如是否需要按天/月删除数据、是否要做 TTL、是否存在重导需求。
  • 如果目标表使用 UNIQUE KEY,还需要提前确认是否存在全表唯一仅分区内唯一的约束。
  • 已具备 ByteHouse 建表、表结构调整和查询验证所需权限。

使用限制

分区与分桶设计本身存在一些天然限制,实践中需要提前接受这些约束:

  • 分区设计不合理会直接影响长期维护成本。 一旦表长期运行后再调整分区键,改造成本通常较高。
  • 组合分区键不是通用解法。 如果组合字段基数高、维度多,容易导致总分区数快速膨胀。
  • 分桶键选择错误会导致收益有限。 如果字段倾斜严重、与查询模式无关,即便增加桶数量,也未必能从根本上解决问题。
  • TTL 设计要与分区粒度对齐。 否则很难发挥分区级清理的效率优势。

注意事项

分区设计注意事项

  • 优先选择时间字段作为分区键。 如果绝大多数查询都会带时间过滤条件,这通常是最稳妥的起点。
  • 尽量选择不会频繁更新的字段。 被选作分区键的字段若经常变更,会带来额外复杂度。
  • 控制单分区规模与总分区数。 经验上应避免单分区过大,也要避免总分区数失控。
  • 业务隔离字段要谨慎使用。 如果平台、租户、事件类型等字段分布不均匀,直接拿来分区容易造成数据倾斜。

分桶设计注意事项

  • 优先选择高基数字段。user_idorder_iddevice_idtrace_id 等。
  • 尽量与查询模式对齐。 如果分桶键也是常用的 join key 或 group key,更容易获得收益。
  • 字段数量不宜过多。 通常建议分桶键字段数不超过 3 个。
  • 分桶是增强项,不是默认项。 只有在单分区压力、写入吞吐、全表去重或并行处理能力确实存在瓶颈时,再考虑引入。

关于分区裁剪的理解

分区裁剪能否生效,关键取决于查询条件是否能与表定义中的 PARTITION BY 表达式建立有效映射。简单理解:

  • 当查询条件能够直接命中分区键,或命中分区键表达式中的内层字段/函数时,更容易触发分区裁剪。
  • 当查询条件写法与分区表达式偏差较大时,即便语义接近,也可能无法获得理想的裁剪效果。

操作流程

方式一:用于快速判断是否需要分区或分桶

  1. 确认主要目标。
  • 如果核心目标是查询裁剪、数据清理、TTL、归档和按周期运维,优先考虑分区
  • 如果核心目标是提升单分区内并行处理能力、写入吞吐或全表去重性能,再评估是否需要分桶
  1. 评估数据规模。
  • 先估算单表规模、单分区规模、保留时长和未来增长趋势。
  • 再判断按小时、按天、按月等粒度划分后,单分区是否处于可控范围。
  1. 检查查询模式。
  • 分区键应尽量出现在高频过滤条件中。
  • 分桶键应尽量与高频 JOINGROUP BYIN、等值过滤字段一致。
  1. 评估运维需求。
  • 如果需要高效删除某一天、某个月的数据,分区粒度应与运维粒度保持一致。
  • 如果需要 TTL,建议让 TTL 与分区表达式保持对齐。
  1. 最终决策。
  • 仅分区: 适合大多数常规分析型表。
  • 分区 + 分桶: 适合已经分区但单分区压力仍高的高吞吐或高并行场景。
  • 不分桶: 如果按合理粒度分区后,单分区规模已经在可控范围内,就没有必要额外增加复杂度。

方式二:按设计主题逐步落地

步骤一:设计分区键

优先从以下三类字段中选择:

  • 时间字段: 最适合承接查询过滤、TTL 与按周期运维。
  • 业务隔离字段: 适合存在明确业务隔离边界,且分布相对均衡的场景。
  • 业务隔离字段 + 时间字段: 适合仅靠时间字段仍无法控制单分区规模时的组合设计,但要特别关注分区爆炸风险。

步骤二:设计分区粒度

常见粒度包括小时、天、月、年,选择时重点看两个问题:

  • 单分区数据量是否可控。
  • 总分区数是否会随着保留周期快速膨胀。

在超高流量场景下,按小时分区可以有效降低单分区压力;而在归档、月度运维场景下,按月分区往往更符合操作习惯。

步骤三:判断是否需要分桶

以下两类场景通常值得进一步考虑分桶:

  1. 单分区内数据量仍然非常大。 即使已经合理分区,单分区内仍达到亿级规模,导致查询、写入、Merge 或去重压力较高。
  2. 全表级唯一约束或高吞吐 UNIQUE 表。 需要缩小写入参与去重的数据范围,并提升整体吞吐能力。

步骤四:选择分桶键与桶数

  • 优先选择高基数、分布均匀、不易产生热点的字段。
  • 尽量选择与典型查询模式一致的字段,例如 user_idorder_id
  • 桶数设置应结合目标数据规模、节点能力与并行需求综合评估,不追求越大越好。

典型判断参考

什么情况下建议使用分区

表业务类型

分区?

常见分区表达式

事实表
事件表

按天分区: PARTITION BY toYYYYMMDD(create_time)
按月分区: PARTITION BY toYYYYMM(create_time)
按年分区: PARTITION BY toYear(create_time)

大型表

有时

选择按照时间字段或者业务特征字段进行分区
比如按照platform字段+创建时间进行分区: PARTITION BY (platform, toYYYYMM(create_time))

小维表/查询

数据量比较小(百万行以下): 推荐不进行分区

分区与分桶差异对比

方面

分区

分桶

设计目标

粗粒度管理数据的生命周期(运维&TTL)

细粒度的控制并行度以及每个分区内的数据分布

执行计划

分区是元数据信息,查询时能通过实际的明文筛选条件直接跳过特定的分区数据而不去真正扫描数据

只能通过等值类筛选条件来跳过特定的分桶内的数据

运维层面

删除分区(DROP PARTITION), 分离分区(DETACH PARTITION) 都是元数据操作,高效而且方便。

业务中极少执行分桶删除操作,数据表数据量通常持续上涨。为防止单个分桶存储数据量过大,可随数据规模成倍扩容分桶数量,支持通过 ALTER TABLE 语句动态调整表分桶数。详细可以参考:Bucket Table 动态扩容最佳实践

单表推荐个数

每个表1-10000个(小时,天,月,年,租户等)。

分桶个数依据数据量决定,尽量控制单个分桶内的数据量在千万级别最佳,同时考虑到负载均衡的问题,简单的做法是从这些数字中选择一个作为桶个数4,8,16,32,64,128,这样能保证是worker节点个数的整倍数,降低负载倾斜的可能性

数据出现倾斜时

通过修改分区键以获得更均匀合理的数据分割范围,极端场景(分桶键本身比较离散,数据分布得很均匀)也可以去掉分区完全依靠分桶来处理数据分布问题

合理选择更离散的复合的字段组合来作为分桶键

尽量避免

过多的分区意味着过多的part文件,在发生查询时可能导致更重的IO操作反而拉低查询性能,另外后台的Merge操作也需要消耗更多的资源

单桶存储记录数不宜超过 8000 万,将单桶记录规模维持在 4000 万区间可保障最佳读写性能。单桶数据体量过高将显著损耗读写效率,针对该场景,可结合数据增长情况按倍数扩容表分桶数量,均衡各桶数据负载

分区粒度参考

分区粒度

应用场景

优劣

常用的表达式

按天

大多数报表和日志表等

简单,能较好的控制分区的个数,方便和明确的分区运维管理
但是在按小时查询时不能仅依靠分区就做到精准筛选数据块

toYYYYMMDD(create_time)
toStartOfDay(create_time)
toDate(create_time)

按小时

表写入量巨大的场景,比如每小时可能写入超过4亿行

将一天分成24个分区,能在按小时查询时做到精确筛选数据块
但是可能会造成表的分区总数过多,需要合理利用TTL控制表的总分区数

toStartOfHour(create_time)

按月

按天分区的单分区数据量较少(<100万)

更粗粒度的管理数据,对时间细粒度的查询上没有大的优化效果,主要方便管理运维历史数据

toYYYYMM(create_time)
toStartOfMonth(create_time)

按年

主要是归档或者按年分区后但分区数据量也只在千万级的表

很大限度的控制了表的总分区个数,分区本身对查询性能没有特别大帮助,需要依赖索引或者排序键协助改进查询性能

toYear(create_time)
toStartOfYear(create_time)

验证

完成设计后,建议至少从以下三个维度验证方案是否合理:

  • 查询验证: 通过 EXPLAIN 命令检查核心查询的执行计划,确认查询是否能够有效利用分区裁剪(例如,检查 Parts/Scanned Partitions 指标),确保扫描的分区范围符合预期。
  • 写入验证: 观察写入吞吐、后台合并与去重压力是否处于可接受范围。
  • 运维验证: 模拟 TTL、按分区删除、按分区重导等操作,确认是否符合预期。

如果验证结果显示:

  • 查询仍然扫描过大范围,优先回看分区键和分区粒度。
  • 单分区压力过大,优先评估是否需要引入分桶。
  • 分区总数增长过快,则需要重新评估组合分区键与保留周期的关系。

实践案例

案例一:事件日志表

背景:

  • event_log 每天写入量约 2000 万行。
  • 高频查询条件是 event_time,同时存在按天聚合分析需求。
  • 仅保留最近 180 天数据。

结论:

  • 选择 event_time 作为分区键,并按天分区。
  • 使用与分区粒度对齐的 TTL 管理 180 天保留周期。
  • 不额外引入分桶,因为单分区规模处于可控范围。
CREATE TABLE event_log
(
    event_id        String,
    user_id         UInt64,
    device_id       String,
    event_name      String,
    event_time      DateTime,
    platform        LowCardinality(String),
    properties      String,
    ingest_time     DateTime DEFAULT now()
)
ENGINE = CnchMergeTree
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (event_time, user_id, event_id)
TTL toYYYYMMDD(event_time) + INTERVAL 180 DAY;

案例二:历史订单归档表

背景:

  • order_history 每月平均约 3 亿行。
  • 需要按订单创建时间进行月度运维。
  • 极少数情况下需要基于 order_id 修改记录。
  • 偶尔需要使用 order_id 与其他维表做联合分析。

设计结论:

  • 使用 create_time 按月分区,满足月度删除与重导诉求。
  • 使用 order_id 作为唯一键。
  • 在分区基础上引入分桶,分桶键选择 order_id,以降低单分区内写入与并行处理压力。
CREATE TABLE order_history
(
    order_id            String,
    shop_id             UInt64,
    region              LowCardinality(String),
    order_amount        Decimal(18, 2),
    pay_order_amount    Decimal(18, 2),
    refund_amount       Decimal(18, 2),
    create_time         DateTime64(3) DEFAULT now(),
    update_time         DateTime64(3) DEFAULT now()
)
ENGINE = CnchMergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY (region, shop_id, order_id)
UNIQUE KEY(order_id)
CLUSTER BY EXPRESSION cityHash64V2(order_id) % 8 INTO 8 BUCKETS
TTL toYYYYMM(create_time) + INTERVAL 48 MONTH;

案例三:超高流量日志表

背景:

  • 访问日志表每天平均写入约 70 亿行。
  • 查询常按 request_time 过滤特定小时数据。
  • 仅保留最近 7 天数据。

设计结论:

  • 选择 request_time 作为分区键,并按小时分区。
  • 使用与分区粒度对齐的 TTL 管理 7 天保留周期。
  • 不进一步分桶,因为按小时分区后,单分区规模已处于可控区间。
CREATE TABLE access_log
(
    request_id       String,
    service_name     LowCardinality(String),
    api_path         String,
    client_ip        IPv4,
    status_code      UInt16,
    latency_ms       UInt32,
    request_time     DateTime,
    ingest_time      DateTime DEFAULT now()
)
ENGINE = CnchMergeTree
PARTITION BY toStartOfHour(request_time)
ORDER BY (request_time, service_name, api_path, request_id)
TTL toStartOfHour(request_time) + INTERVAL 7 DAY;

常见问题

Q:分区和分桶应该先做哪个?
A: 通常先设计分区。因为分区直接决定查询裁剪、数据生命周期管理和运维方式;只有当分区设计完成后,单分区内仍存在明显性能瓶颈,才再评估是否需要分桶。
Q:时间字段是不是总是最好的分区键?
A: 在大多数分析型场景下,时间字段通常是最稳妥的选择,但前提是查询确实会大量使用该字段过滤,且字段本身不会频繁更新。如果业务隔离维度更关键,也可以考虑与时间字段组合设计。
Q:引入分桶后,是不是一定能解决数据倾斜?
A: 不一定。分桶能提升并行处理能力,但如果分桶键本身分布不均匀,倾斜仍然可能存在。分桶前应优先判断字段基数与分布是否足够均衡。

最近更新时间:2026.06.23 12:58:48
这个页面对您有帮助吗?
有用
有用
无用
无用