StarRocks engineers describe a shared analytics table with 400 tenants where, at launch, the largest tenant held under 5% of the rows. Nine months later one tenant had outgrown all the others, and the symptoms were the ones every multi-tenant OLAP team eventually meets: query latency degraded unevenly, compaction pressure concentrated on a handful of tablets, and p99 stopped being predictable. Nothing was broken — the DISTRIBUTED BY HASH(tenant_id) BUCKETS 64 written on day one had simply outlived the data it was designed for. StarRocks 4.1 is the release that attacks that specific failure mode, by making the physical layout something the system revises at runtime instead of something you freeze at CREATE TABLE.
- Range distribution (FE config
enable_range_distribution, defaultfalse) buckets data by sort-key range instead of a fixed hash, so StarRocks can reshape tablets after the table exists — no schema change, no SQL change, no re-ingestion. - Tablets split automatically once they exceed
tablet_reshard_target_size(default 10 GB), and the tablet ceiling rises to 100 GB in 4.1. Merge ships too, but gated behindtablet_reshard_enable_tablet_merge=false. - It is a trade, not a free win. At 32 concurrent threads Range delivered 1.86× the QPS (7.05 vs 3.79) and cut p99 from 36.6 s to 11.5 s — but a single-user warm-cache query was ~2× slower (3.2 s vs 1.5 s) because Colocate Join is off the table.
- Bulk-loading a Primary Key table is ~1.7× slower under Range (1,075 s vs 624 s), which matters because there is no in-place migration: existing Hash tables never auto-convert.
- Beyond distribution, 4.1 adds native SQL DELETE on Iceberg, incremental MVs on Iceberg (7–30× faster refreshes), recursive CTEs, and Skew Join v2.
- Upgrade traps: skip the 4.1.0 container image, know that you can only downgrade to 4.0.6 or later, and test
query_queue_v2and theFULL OUTER JOIN ... USINGsemantics change before production.
What Actually Changed in StarRocks 4.1
StarRocks 4.1.0 shipped in April 2026, with 4.1.1 following on 29 May 2026. The headline is not a new engine or a new table model — it is the removal of a decision. For a decade, distributed OLAP systems have asked operators to predict, at table-creation time, how data would be distributed years later. StarRocks 4.1 stops asking.
The change is scoped to shared-data mode, where storage lives in object storage and compute nodes are stateless. That separation is what makes resharding cheap enough to automate: splitting a tablet is a metadata and file-level operation, not a cluster-wide data shuffle across local disks.
Everything else in the release — larger tablets, Fast Schema Evolution v2, cache observability — exists to make adaptive layout safe to run. If you read 4.1 as a list of features you will miss the design; it is one idea with a support structure around it.
How Range Distribution and Adaptive Tablets Work
Hash Buckets vs Sort-Key Ranges
Hash distribution assigns a row to a bucket by hashing the distribution key modulo a fixed bucket count. It is excellent at one thing: guaranteeing that rows with the same key land in the same place, which is what makes Colocate Join possible. It is bad at one thing: the bucket count is a promise you made before you had evidence.
Range distribution instead treats the key columns as an ordering hint. Each tablet owns a contiguous range of the sort key, and the boundaries of those ranges are runtime state that the FE can revise. The documentation puts it plainly: "the data will be sequenced according to the data range of the key columns, and each tablet contains the data from a certain range."
The Grow → Split → Merge Lifecycle
The system deliberately does nothing while a tablet grows. That patience is the point: it lets StarRocks distinguish a Black Friday spike from a tenant that has genuinely changed shape. In StarRocks' own validation run, a single tablet absorbed 64.8 million rows (1.5 GB) before the adaptive response triggered.
When a tablet crosses tablet_reshard_target_size, the FE enqueues a SPLIT_TABLET job. In that same validation, the 1.5 GB tablet was split along the real sort-key distribution into three tablets of 228 MB, 656 MB, and 656 MB — uneven by size because it split on where the data actually was, not on an arithmetic midpoint. The job was reported FINISHED within the same second the threshold was crossed.
Merge is the inverse: adjacent ranges recombine when a tenant shrinks or rows are deleted, preventing unbounded fragmentation. Merge shipped in 4.1 but is disabled by default, which has a real operational consequence covered below.
Why 100 GB Tablets Matter
From v4.1 the maximum tablet size rises to 100 GB. This inverts a decade of StarRocks and Doris tuning advice, where the standard move was to over-bucket early so no single tablet became hot.
Large tablets act as a shock absorber. They tolerate short-term imbalance, avoid premature fragmentation, and reduce total tablet count — which directly lowers FE metadata pressure and scheduling overhead. Fewer, bigger tablets plus automatic splitting is a strictly better trade than many, small, permanently wrong ones.
Tablet merge ships behind tablet_reshard_enable_tablet_merge (default false), with default-on validation deferred to a later release. On a table with heavy deletes or churning tenants, splits accumulate and nothing reclaims them automatically. Track tablet counts per partition over time, and plan to run ALTER TABLE ... MERGE TABLETS manually during maintenance windows until the flag flips.
Enabling Range Distribution
Step 1 — Turn On the FE Config
Range distribution is an FE-level configuration, not a session variable, and it is off by default for backward compatibility. Set it dynamically to test, then persist it in fe.conf so it survives a restart.
-- Dynamic, cluster-wide, effective immediately
ADMIN SET FRONTEND CONFIG ("enable_range_distribution" = "true");
-- Confirm it took
ADMIN SHOW FRONTEND CONFIG LIKE 'enable_range_distribution';
-- Split threshold (bytes). 10 GB is the default.
ADMIN SET FRONTEND CONFIG ("tablet_reshard_target_size" = "10737418240");
-- Opt in to automatic merge once you have validated split behaviour
ADMIN SET FRONTEND CONFIG ("tablet_reshard_enable_tablet_merge" = "true");Persist the same keys in fe.conf on every FE node:
# fe.conf
enable_range_distribution = true
tablet_reshard_target_size = 10737418240
tablet_reshard_enable_tablet_merge = false
tablet_reshard_max_parallel_tablets = 10240Step 2 — Create the Table Without a Bucket Count
With the config on, you stop writing a DISTRIBUTED BY clause with a hard-coded bucket count. The primary key becomes the organizing hint, and the system owns the physical shape from there.
-- Before 4.1: the bucket count is a permanent guess
CREATE TABLE tenant_events (
tenant_id BIGINT NOT NULL,
event_id BIGINT NOT NULL,
event_ts DATETIME NOT NULL,
payload JSON NULL
)
PRIMARY KEY (tenant_id, event_id)
PARTITION BY date_trunc('month', event_ts)
DISTRIBUTED BY HASH (tenant_id) BUCKETS 64;
-- In 4.1 with Range distribution: no bucket count to get wrong
CREATE TABLE tenant_events (
tenant_id BIGINT NOT NULL,
event_id BIGINT NOT NULL,
event_ts DATETIME NOT NULL,
payload JSON NULL
)
PRIMARY KEY (tenant_id, event_id)
PARTITION BY date_trunc('month', event_ts);Step 3 — Drive Resharding Manually When You Need To
Automation handles the steady state, but 4.1 exposes explicit DDL for migrations, incident response, and post-backfill cleanup. Both statements accept a partition scope, an explicit tablet list, or a per-statement target size.
-- Split everything that is over the target size
ALTER TABLE tenant_events SPLIT TABLETS;
-- Scope to a hot partition after a bulk backfill
ALTER TABLE tenant_events SPLIT TABLETS PARTITION (p202608);
-- Split named tablets you found while chasing a hotspot
ALTER TABLE tenant_events SPLIT TABLETS (9588955, 9588956, 9588957);
-- Reclaim fragmentation with a 2 GB target
ALTER TABLE tenant_events MERGE TABLETS
PROPERTIES ("tablet_reshard_target_size" = "2147483648");
-- Merge two specific adjacent pairs
ALTER TABLE tenant_events MERGE TABLETS (9588955, 9588956)(9588958, 9588959);A tablet is eligible to split when it exceeds the target size and the number of tablets already resharding is under tablet_reshard_max_parallel_tablets (default 10,240). Merge applies the mirror condition: adjacent tablets whose combined size falls below the target.
The Benchmark Numbers, and What They Cost You
StarRocks published a like-for-like comparison on two identical AWS shared-data clusters — 1 FE on m6i.xlarge, 3 CNs on m6i.4xlarge, 500 GB gp3 per CN — against a 200 GB dataset containing a 1-billion-row event log partitioned monthly and a 215-million-row Primary Key table. The results are unusually honest, because they show where Range loses.
| Workload | Hash distribution | Range distribution | Verdict |
|---|---|---|---|
| Single user, warm cache (COUNT DISTINCT) | 1.5 s | 3.2 s | Hash wins ~2× |
| 8 concurrent threads | 3.60 QPS | 3.53 QPS | Tie (<2%) |
| 32 concurrent threads | 3.79 QPS | 7.05 QPS | Range wins 1.86× |
| 32 threads, p99 latency | 36.6 s | 11.5 s | Range wins 3.2× |
| Mixed query + ingest, QPS | 2.86 | 5.31 | Range wins 1.86× |
| Mixed load, query p99 | 9.7 s | 4.8 s | Range wins 2× |
| Bulk load, 1B-row duplicate-key table | 625 s | 591 s | Range slightly ahead |
| Bulk load, 215M-row Primary Key table | 624 s | 1,075 s | Hash wins 1.7× |
Read the Inflection Point, Not the Averages
The interesting detail is what the Hash cluster was doing at 32 threads: sitting over 90% CPU idle while p99 sat at 36.6 seconds. That is not a compute shortage, it is contention — threads queuing on the same hot buckets while most of the cluster does nothing. Range spreads shuffle work across independent tablets, so the same hardware actually gets used.
The single-user regression has the same root cause in reverse. Range gives up Colocate Join, so aggregations that were local become shuffles. If your workload is a handful of analysts running sequential queries, Range distribution will make your dashboards slower and buy you nothing.
Enable Range distribution when concurrency is high and the key distribution is unpredictable — customer-facing multi-tenant analytics, embedded dashboards, per-account APIs. Stay on Hash when concurrency is low, the distribution key is naturally even, or you depend on Colocate Join for large fact-to-fact joins. If you cannot name the tenant that will be 40% of your data in a year, that uncertainty is the argument for Range.
There Is No In-Place Migration Path
This is the operational fact the announcement posts underplay. Existing Hash-distributed tables do not auto-upgrade when you flip enable_range_distribution. Adopting Range means creating new tables and moving data into them — and the benchmark says a Primary Key backfill runs about 1.7× slower under Range because of post-commit compaction overhead.
Budget for that. A 215-million-row PK table that reloads in 10 minutes today should be planned as closer to 18. On a real estate of dozens of tables, this is a migration project with a cutover plan, not a config flag you flip on a Friday.
The Rest of 4.1 Worth Your Attention
Iceberg Gets Native DELETE
StarRocks 4.1 can run distributed SQL DELETE directly against Iceberg tables, producing standard V2 position delete files with atomic snapshot commits. For teams that have been shelling out to a Spark job every time GDPR erasure or a bad CDC batch needed correcting, that removes an entire piece of infrastructure from the loop.
-- Correct a bad CDC batch without spinning up Spark
DELETE FROM iceberg_catalog.analytics.orders
WHERE order_date = '2026-07-14'
AND source_system = 'legacy_pos';Alongside it: incremental materialized views on Iceberg shift from partition-level recomputation to version-range delta processing, which StarRocks measured at 7–30× faster on subsequent refreshes against a 100 GB dataset. Refresh cost now tracks how much changed, not how big the table is. Iceberg v3 VARIANT support also lands, using offset-based binary field lookup so nested field access skips repeated JSON parsing.
Table maintenance improved too: 4.1 adds a rewrite_manifests procedure and extends expire_snapshots and remove_orphan_files with finer-grained arguments, plus TRUNCATE support for Hive and Iceberg tables.
Query Engine and Observability
- Recursive CTEs — hierarchical rollups and graph traversal in plain SQL, no more application-side loops.
- Skew Join v2 — statistics-driven detection with histogram support and NULL-skew awareness, replacing manual hints.
- Fast Schema Evolution v2 — second-level DDL in shared-data mode, decoupled from tablet metadata rewrites.
- Cache observability — per-query cache hit ratio in the audit log, cluster-wide cache metrics in Prometheus BE metrics, and I/O counters in the SQL profile.
- Inverted index (beta) on shared-data, with a builtin parser and more analyzers planned.
- Window functions accept ARRAY types, and
COUNT DISTINCTnow works over framed windows.
Upgrade Traps to Test Before Production
StarRocks flagged a container image issue in v4.1.0 where BE processes fail reliably in containerised environments — Kubernetes and Docker users should go straight to 4.1.1. Separately, because of the tablet distribution changes, once you are on 4.1 you can only downgrade to v4.0.6 or later. If your cluster is on 4.0.3, upgrade to 4.0.6+ first so you keep a rollback path.
Four behaviour changes deserve a staging replay before you promote:
query_queue_v2is enabled by default in 4.1.0. Admission control now behaves differently under load. Replay a peak-hour workload and watch for queries queuing that previously ran immediately.FULL OUTER JOIN ... USINGfollows SQL-standard semantics. The USING column now appears once in the output instead of twice. Any downstream consumer doing positional column access orSELECT *into a fixed schema can break silently.- ETL mode is on by default for batch operations, changing the execution profile of large INSERT and load jobs.
- 4.1.1 disabled query rewrite over INCREMENTAL/AUTO materialized views, and now rejects FORCE and partition refresh on them. If you built incremental MVs on 4.1.0 expecting transparent rewrite, verify your queries still hit them.
Also note that SQL transactions are now gated behind the enable_sql_transaction session variable, and lag/lead accept column references rather than constants only — the latter is additive, but it changes what the planner will accept from generated SQL.
What to Watch After You Enable Range Distribution
-- Partition-level shape: bucket count, size, rows, compaction score, balance
SELECT PARTITION_NAME,
BUCKETS,
DATA_SIZE,
ROW_COUNT,
MAX_CS,
TABLET_BALANCED
FROM information_schema.partitions_meta
WHERE DB_NAME = 'analytics_db'
AND TABLE_NAME = 'tenant_events'
ORDER BY ROW_COUNT DESC;
-- Tablet-level detail for a partition you suspect is skewed
SHOW TABLET FROM analytics_db.tenant_events PARTITION (p202608);The signal to track is not average tablet size — it is the ratio between your largest tablet and your median, which SHOW TABLET gives you directly via DataSize and RowCount. If that ratio climbs steadily while BUCKETS stays flat, splits are not firing: either tablet_reshard_target_size is set too high, or the tablet has not yet crossed it. If BUCKETS climbs monotonically and never falls, that is merge being disabled, and it is your cue to schedule a manual MERGE TABLETS pass. Watch MAX_CS alongside it — a rising compaction score on one partition is the earliest signal that resharding is not keeping up with ingestion.
- Treat Range distribution as a workload decision, not an upgrade step. Enable it for high-concurrency multi-tenant tables with unpredictable key distribution; keep Hash where you rely on Colocate Join or run low-concurrency sequential queries.
- Plan a migration, not a flag flip. Existing Hash tables never auto-convert, and Primary Key backfills run roughly 1.7× slower under Range — size the cutover window accordingly.
- Skip 4.1.0 if you run containers and confirm you are on 4.0.6 or later before upgrading, so you keep a working downgrade path.
- Enable merge deliberately. With
tablet_reshard_enable_tablet_mergeoff, fragmentation only accumulates — monitor tablet counts and runALTER TABLE ... MERGE TABLETSuntil the default changes. - Replay peak load in staging against
query_queue_v2and theFULL OUTER JOIN ... USINGchange before promoting 4.1 to production. - Use the new Iceberg DELETE to retire Spark jobs that exist purely for data correction, and switch eligible Iceberg MVs to incremental refresh for 7–30× faster refresh cycles.
Working with JusDB on StarRocks 4.1
JusDB runs StarRocks in production for teams that need real-time analytics without staffing a dedicated OLAP team. Our DBAs handle version upgrades and rollback planning, distribution and partition design, compaction and tablet health, Kafka and CDC ingestion pipelines, and 24/7 incident response — so a release like 4.1 becomes a scheduled change instead of a weekend.
If you are weighing Range distribution, we will benchmark it against your actual query mix and concurrency rather than a synthetic dataset, then build the migration and cutover plan around what the numbers say.
StarRocks Consulting → | StarRocks Performance Tuning | Talk to a DBA
Related reading:
- StarRocks vs ClickHouse: Architecture, Table Models & When to Choose Each (2026)
- StarRocks Monitoring & Alerting: The Complete Production Guide
- Database Sharding Patterns: Hash, Range, and Directory-Based Approaches
- Data Lakehouse Architecture: Delta Lake, Apache Iceberg, and Apache Hudi
Sources: StarRocks 4.1: Built for Production, Designed to Simplify, When Your Table Design Outlives Its Assumptions, and the StarRocks 4.1 release notes.