How Region Partitioning Works in GreptimeDB for Horizontal Scaling
GreptimeDB achieves horizontal scaling by dividing logical tables into independent regions distributed across datanodes, using pluggable partition rules that deterministically map rows to region numbers based on multi-dimensional expressions.
GreptimeDB stores each logical table as a collection of regions—autonomous storage units that can reside on different datanodes. The mapping from row data to specific regions is governed by a partition rule defined at table creation and stored in the table's metadata. This architecture allows the database to distribute write and read workloads across a cluster while supporting online rebalancing.
Core Architecture: Regions and Partition Rules
The Region Abstraction
In greptimeteam/greptimedb, a region represents an independent storage unit containing a subset of a table's data. Regions can be placed on different datanodes, enabling data to spread across the cluster. Each region is identified by a RegionNumber, and the collection of regions forms the complete table.
The PartitionRule Trait
The core abstraction for mapping data to regions is the PartitionRule trait defined in src/partition/src/partition.rs. This trait abstracts the conversion from a row's partition-key values to its target RegionNumber. The default implementation follows the multi-dimensional partition rule design described in the RFC "multi-dimension partition rule":
// src/partition/src/partition.rs#L29-L40
pub trait PartitionRule: Send + Sync {
fn partition(
&self,
values: &[Value],
) -> Result<PartitionResult, PartitionError>;
fn find_regions(
&self,
exprs: &[Expr],
) -> Result<Vec<RegionNumber>, PartitionError>;
}
Multi-Dimensional Partition Rules
The concrete implementation MultiDimPartitionRule in src/partition/src/multi_dim.rs evaluates user-defined expressions to determine region placement.
Rule Evaluation Logic
MultiDimPartitionRule holds the allowed partition columns, a list of expressions (exprs), and the target region numbers (regions). For each incoming row, the rule evaluates expressions in order; the first matching expression yields its assigned region. If no expressions match, the row routes to the special default region (0):
// src/partition/src/multi_dim.rs#L102-L119
fn partition(&self, values: &[Value]) -> Result<PartitionRuleResult> {
for (idx, expr) in self.exprs.iter().enumerate() {
if expr.eval(values)? {
return Ok(PartitionRuleResult {
region: self.regions[idx],
});
}
}
// Default region when no expressions match
Ok(PartitionRuleResult { region: 0 })
}
Expression Matching
Partition expressions support multi-dimensional predicates combining multiple columns (e.g., host = 'us-west' AND time < '2025-01-01'). The expressions are evaluated sequentially, making the order of definition significant for routing logic.
Region Routing and Metadata Management
The PartitionRuleManager in src/partition/src/manager.rs serves as the central coordinator for retrieving and caching partition metadata from the meta-server.
Key APIs
The manager exposes two critical methods for cluster operations:
-
find_table_partition_rule– Returns the partition rule and a map of region-to-rule-version. The frontend uses this to attach version stamps to write requests, ensuring consistency during reconfiguration (lines 78-99). -
batch_find_region_routes– ObtainsRegionRoutestructures for query planning, allowing the distributed planner to target only relevant regions (lines 108-126).
Write Path: Row Splitting and Distribution
RowSplitter Implementation
When clients insert data, the RowSplitter in src/partition/src/splitter.rs groups rows by their target regions before network transmission. The splitter:
- Extracts partition column values from each row
- Consults the
PartitionRuleto determine theRegionNumber - Aggregates rows into a
HashMap<RegionNumber, Rows>
// src/partition/src/splitter.rs#L34-L50
pub fn split(&self, rows: Rows) -> Result<HashMap<RegionNumber, Rows>> {
let mut batches: HashMap<RegionNumber, Vec<Row>> = HashMap::new();
for row in rows.rows {
let values = self.extract_partition_values(&row)?;
let region = self.rule.partition(&values)?;
batches.entry(region).or_default().push(row);
}
// Convert to final format...
}
This batching minimizes cross-datanode traffic by grouping all rows for region 0 together, all rows for region 1 together, and so on.
Horizontal Scaling Through Region Migration and Repartition
GreptimeDB supports two primary mechanisms for horizontal scaling without downtime: region migration and repartition.
Region Migration
Region migration moves an existing region to a different datanode without changing its partition boundaries. The procedure is coordinated by MetaSrv and exercised in the integration test tests-integration/tests/region_migration.rs. During migration:
MetaSrvallocates the target datanode- The region data transfers to the new peer
- The
RegionRoutetable updates atomically - Subsequent queries route to the new location
Repartitioning
When data grows unevenly, administrators can repartition by splitting or merging regions. This workflow:
- Creates new regions with updated boundaries
- Rewrites table manifests with the new partition rule
- Updates the meta-server's route table
- Increments the partition-rule version
The detailed procedure is documented in docs/rfcs/2025-06-20-repartition.md and validated in tests-integration/tests/repartition.rs. Write requests carrying outdated version stamps are rejected, ensuring new data only flows to correctly placed regions.
// Conceptual repartition workflow
let sql = r#"
ALTER TABLE demo
PARTITION BY (host, time) (
host = 'us-west' AND time < '2025-06-01' => REGION 0,
host = 'us-west' AND time >= '2025-06-01' => REGION 2, -- new split
host = 'us-east' => REGION 1
);
"#;
Read Path Optimization
The query planner leverages PartitionRuleManager to build region-aware execution plans. When filter predicates match partition columns, the planner pushes down these filters to the partition rule, scanning only regions that could contain matching data. This optimization is detailed in the distributed planner RFC docs/rfcs/2023-05-09-distributed-planner.md.
Summary
- Regions are independent storage units distributed across datanodes, forming the horizontal scaling unit in GreptimeDB.
- PartitionRule trait abstracts the mapping logic, with
MultiDimPartitionRuleproviding multi-dimensional expression evaluation. - RowSplitter groups incoming writes by region before network transmission, optimizing the write path.
- PartitionRuleManager caches metadata and provides routing information for both write and read operations.
- Region migration moves regions between nodes; repartition splits or merges regions to rebalance data.
- Version stamps on partition rules ensure consistency during topology changes, rejecting stale writes.
Frequently Asked Questions
How does GreptimeDB decide which region stores a specific row?
GreptimeDB evaluates the table's partition rule—specifically the MultiDimPartitionRule implementation—which checks the row's partition column values against a list of expressions in src/partition/src/multi_dim.rs. The first matching expression determines the RegionNumber; if none match, the row routes to default region 0.
What happens when a region becomes too large or receives too much traffic?
Administrators can trigger repartition to split the region into smaller ranges or region migration to move it to a less loaded datanode. Both operations are coordinated by MetaSrv, update the route table atomically, and increment the partition-rule version to ensure write consistency.
Can partition rules be modified after table creation?
Yes. GreptimeDB supports ALTER TABLE statements to modify partition expressions, implemented through the repartition procedure defined in docs/rfcs/2025-06-20-repartition.md. The system creates new regions, migrates data, and updates metadata without requiring table downtime.
How does the query optimizer use region partitioning?
The distributed planner queries PartitionRuleManager to obtain RegionRoute information. When SQL filters align with partition columns, the planner pushes predicates down to the partition rule evaluation, scanning only regions that could contain matching rows rather than the full table.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →