Minn Fong
05/10/2026, 3:52 PMRocky
05/10/2026, 3:52 PM__row) that stores all values for a row. When you query by Primary Key (using = or IN), StarRocks takes a "Short Circuit Scan" path. This avoids the overhead of columnar reconstruction and retrieves the row directly, much like an OLTP database or KV store.
• Latency: While KVRocks (using RocksDB) will generally have lower p99 latency (low single-digit ms), StarRocks point queries are highly competitive (often sub-10ms) and can handle very high concurrency.
2. Cost Analysis: StarRocks vs. KVRocks
For a 1-2 TB dataset, the "competitiveness" in cost depends on your hardware density and analytical needs:
* Storage Overhead (StarRocks is higher):
* In hybrid mode, StarRocks stores data twice: once in columnar format (for analytics) and once in the __row column (for point lookups).
* Row-based storage typically has a lower compression ratio than columnar storage.
* Result: You should expect StarRocks to consume significantly more disk space (often 2x or more) than KVRocks for the same raw data.
* Memory Overhead:
* StarRocks' Primary Key model requires a "Primary Key Index." To manage 1-2 TB of data without exhausting RAM, you should use the Persistent Index (storing the index on SSD rather than memory). This makes the memory footprint very manageable and comparable to KVRocks' caching layer.
* Compute Efficiency:
_ KVRocks is a lightweight Redis-compatible wrapper. If your _only* requirement is a simple GET key, KVRocks will run on smaller instances.
* StarRocks is an MPP database. The overhead of the FE (Frontend) and BE (Backend) nodes is higher. However, if you already have a StarRocks cluster for other analytics, adding a hybrid table is effectively "free" in terms of operational overhead.
3. When StarRocks is More Cost-Effective
StarRocks becomes the clear winner on TCO if your use case involves more than just a simple lookup:
• Enrichment + Analytics: If you ever need to join this metadata with your event streams for a "Daily Active Users by DS-Segment" report, StarRocks allows you to do this in-situ. With KVRocks, you'd have to export that 1-2 TB to an OLAP engine, doubling your storage and pipeline costs.
• Complex Lookups: If you ever need to look up metadata by something other than the primary key (e.g., "find all users with metadata_version = 'v2'"), StarRocks' secondary indexes and columnar scanning will be vastly cheaper and faster than scanning a KV store.
• Standardization: Using the MySQL protocol (SQL) instead of a Redis-specific API often reduces development and maintenance costs.
Summary Comparison Table
Feature | KVRocks (RocksDB-based) | StarRocks (Hybrid Storage)
Point Lookup Latency | Ultra-low (1-5ms) | Low (Short-circuit, ~10ms)
Storage Efficiency | High (Single copy, compressed) | Medium (Dual copy: Row + Column)
Memory Cost | Low (Block cache) | Medium (Persistent Index on SSD)
Analytical Power | None (requires export) | Full SQL / MPP / Joins
Protocol | Redis | MySQL / SQL
Rocky’s Recommendation:
If your 1-2 TB of metadata is purely for high-speed lookups in an application hot-path and will never be used for batch analytics or complex filtering, KVRocks is likely cheaper on raw disk/compute.Rocky
05/10/2026, 3:52 PMMinn Fong
05/10/2026, 3:54 PMRocky
05/10/2026, 3:55 PM__row column). For 1-2 TB of raw data, expect StarRocks to consume 2x to 3x that amount on EBS because row-based binary data does not compress as well as columnar data.
* Persistent Index: Since you have 1-2 TB of data, you must enable enable_persistent_index = true. This stores the PK index on EBS. The formula for its storage is: (KeySize + 8 bytes) _ RowCount _ 0.5.
* Total EBS Cost: You are paying for the Columnar data + Row data + Persistent Index + Replicas (usually 3).
* KVRocks (RocksDB-based):
* Single Storage: KVRocks (RocksDB) stores data in an LSM-tree. It is highly space-efficient. You would likely only need ~1.2x to 1.5x the raw data size on disk, even with compaction overhead.
* Total EBS Cost: Data + Replicas.
Verdict on Storage: KVRocks will always use 50-60% less disk space than StarRocks Hybrid Storage for the same dataset.
2. Compute & Infrastructure Overhead
This is where StarRocks usually wins on Total Cost of Ownership (TCO) for existing users.
* StarRocks (Existing Cluster):
* Incremental CPU/RAM: Since you are already adding the data to a PK table, the only extra resource you consume by enabling storgage_type = 'column_with_row' is the small amount of CPU needed to encode/decode the __row column during writes and point lookups.
* Zero New Instances: You don't need to spin up new EC2 nodes. You simply utilize the "slack" in your existing cluster.
* KVRocks (New Cluster):
* Base Cost: You must provision a new set of EC2 instances for KVRocks. Even if they are small (e.g., r6g.large), you are paying for the base OS, networking, and compute overhead of a separate system.
* Operational Cost: You now have to manage, monitor, and backup two different database systems.
Verdict on Compute: Adding to StarRocks is almost always cheaper in terms of monthly AWS bills if your existing nodes have the disk/IOPS headroom.
3. EBS Performance (GP3 vs. IO2)
Point lookups are I/O intensive.
* StarRocks: The "Short Circuit Scan" bypasses the columnar engine and reads the __row column directly from EBS. For a 1-2 TB table, GP3 is usually sufficient if you provision enough IOPS (StarRocks point lookups usually require ~1-2ms of disk wait time).
* KVRocks: Also relies on disk I/O. Since latency is not your primary concern, you can use GP3 for both.
Comparison Summary for Your Specific Setup
Feature | StarRocks (Hybrid) | KVRocks
Storage Cost | Higher (stores data twice) | Lower (LSM-tree efficiency)
New EC2 Costs | $0 (uses existing cluster) | $X/mo (new cluster overhead)
Operational Effort | Low (SQL-only, 1 system) | Medium (New protocol, 2 systems)
Maintenance | Auto-scaling with cluster | Manual sharding/balancing
Data Silos | None (Metadata is joinable) | High (Data is trapped in KV)
Rocky’s Recommendation
Since you already have the cluster and already plan to use a PK table:
1. Start with StarRocks Hybrid Storage. The cost of the extra EBS volume (the __row column) is almost certainly lower than the cost of a dedicated KVRocks cluster + the engineering time to build a second data pipeline.Rocky
05/10/2026, 3:55 PMMinn Fong
05/10/2026, 6:00 PMRocky
05/10/2026, 6:01 PM__row column (for your hybrid storage).
The Hybrid Trap: StarRocks documentation notes that the "Short Circuit Scan" (the high-speed point lookup you are likely using) can be blocked by the Apply Phase of a write operation to ensure data consistency.
* At 60s: You are likely experiencing a significant "stall" or latency spike for point lookups once a minute while those 1.2 million rows are being processed.
* At 15-30s: You break that 1.2M rows into smaller 300k–600k chunks. The "stall" becomes much shorter and less noticeable, leading to smoother p99 latencies for your data science metadata lookups.
2. Preventing Resource Exhaustion (CPU & IO)
Your current "machine idle" state for 50+ seconds followed by a massive burst is inefficient.
* IO Jitter: Flushing 1.2M rows (especially with the extra __row column overhead) creates a massive write burst to EBS. If you hit the IOPS/Throughput limit of your GP3 volumes during that burst, the Apply Phase will slow down even further, extending the "stall" period.
* CPU Spikes: The compression and encoding of a 1-2 TB table's metadata during a 1.2M row burst can spike CPU to 100%, potentially starving concurrent query requests.
3. Faster Space Reclamation (Compaction)
StarRocks uses an LSM-tree-like structure. Data isn't truly deleted until Compaction runs.
* If you flush once a minute, you create one large "rowset."
* If you flush every 15-30 seconds, you create smaller rowsets.
StarRocks' background compaction threads can often work more efficiently on these smaller, more frequent files, reclaiming disk space from the "metadata updates" more continuously rather than waiting for a massive merge operation.
The "Sweet Spot" Recommendation
I do not recommend going below 5-10 seconds. If you flush too frequently (e.g., every 1 second), you will hit the "Too many versions" error, where the background compaction cannot keep up with the rate of new file creation.
Rocky's Rationale-based Plan:
1. Try 30 seconds first: This should immediately cut your resource spikes in half and reduce the duration of any "write-block-query" stalls.
2. Monitor "Compaction Score": Use SHOW PROC '/statistic'; or check Grafana for the "Tablet Max Version Count." As long as this stays well below 100-200, you are safe.
3. Check p99 Latency: You should see your point-lookup latency become much more "flat" rather than having a spike at the top of every minute.
Summary: For Hybrid storage point-lookup use cases, consistency is better than throughput. Moving to a 20-30 second interval will trade a tiny bit of total throughput for a much more responsive and stable system.
References
• table_design/hybrid_table.md
• knowledge/usage/best-practice.mdMinn Fong
05/10/2026, 6:02 PMRocky
05/10/2026, 6:02 PMtablet_max_versions limit (default 1000).
* Rationale: 15–30 seconds is the "Goldilocks zone." It is frequent enough to smooth out the load but infrequent enough that the compaction engine can easily keep the "version count" low.
4. Impact on "Idle" Time
You mentioned the machine is idle between flushes. While this seems fine, it's actually expensive "white space."
_ By spreading the load to 15-30s, you effectively lower the _peak* resource requirement of your cluster.
* This might eventually allow you to downsize your instance types (e.g., moving from a large to a medium or using fewer nodes) because you no longer need to provision for that massive 60-second burst capacity.
Rocky’s Recommendation:
* Target Interval: 20-30 seconds. This is the best balance for a 1-2 TB PK table.
* Why not 10s? Unless you need sub-10s data freshness, the risk of "Too Many Versions" during peak spikes isn't worth the marginal gain.
* Monitor: Keep an eye on the "Compaction Score" in your StarRocks metrics. If it stays below 50, your 30s interval is perfect. If it creeps up, your batches might be too small or too frequent.
Summary: The rationale isn't about throughput (total rows/sec will be the same); it's about reducing peak resource pressure and ensuring that your Primary Key Index and Compaction operations are "streaming" rather than "bursting."
References
• best_practices/primarykey_table.md
• knowledge/usage/load/shared_data_batch_ingestion.mdMinn Fong
05/10/2026, 6:04 PMRocky
05/10/2026, 6:05 PMtoo many versions error because compaction cannot keep up with a 9TB dataset's metadata.
* Apply Efficiency: In a PK table, the cost of "applying" 100k rows is not 10x the cost of 10k rows—it's much less because of batch optimizations. 30 seconds allows you to accumulate a "heavy" enough batch (approx. 600k rows across the cluster) to make the I/O against the 9TB index worthwhile.
3. Implementation Recommendations
If you are using Stream Load (from your application workers), add these headers to your requests:
http
enable_merge_commit: true
merge_commit_interval_ms: 20000 -- 20 seconds
merge_commit_async: false -- Keeps it synchronous for reliability
Configuration Rationale for 9TB / 24 Partitions:
* Partition Size: Your partitions are ~375GB each ($9TB / 24$). This is slightly above the "100GB rule of thumb," but perfectly fine for a 1-day TTL. Because you have only 24 partitions, the FE's metadata memory pressure is low, but the BE's Persistent Index on EBS is doing the heavy lifting.
* Persistent Index: Ensure enable_persistent_index = true is set. With 9TB, an in-memory index would likely crash your BEs.
* Merge Commit Parallelism: If you find that the "Apply" phase is taking too long (look at PublishVersion time in your metrics), you can increase the parallelism:
* Header: merge_commit_parallel: 8 (or similar, depending on your BE count).
Summary of Rocky’s Advice
1. Don't sync: Let each worker/node run its own 30s ticker with a random start delay.
2. Enable Merge Commit: Use the server-side feature to let StarRocks handle the batching internally.
3. Target 20-30s: This is the "Goldilocks zone" for 9TB tables—long enough to keep the compaction engine happy, short enough to provide fresh data for your queries.
References
• sql-reference/sql-statements/loading_unloading/STREAM_LOAD.md
• loading/StreamLoad.md
• best_practices/primarykey_table.md
• best_practices/partitioning.md
ReferencesRocky
05/10/2026, 6:05 PMMinn Fong
05/10/2026, 6:10 PMRocky
05/10/2026, 6:11 PMstart_delay = rand(0, 15) seconds). This ensures that your 20 workers are hitting the cluster at different times throughout the 15-second window.
* StarRocks Side: Enable Merge Commit with a much smaller interval, like 3 or 5 seconds.
Why this works:
1. Data Freshness: Data becomes visible every ~15-20 seconds (15s app batch + 3s server merge). This is a 3x improvement over your current 60s lag.
2. The "Apply" Efficiency: Even with staggered app flushes, some requests will overlap. A 3-5s Merge Commit window on the server will "catch" those overlapping loads and merge them into a single transaction. Instead of 20 "Apply" cycles (one for each worker), the cluster might only do 4 or 5.
3. 9TB Index Health: For a table this size, the Persistent Index is likely your bottleneck. Reducing the number of "Apply" cycles is the single best way to reduce I/O wait and keep queries fast.
2. Recommended Configuration
When you send your Stream Loads from the application, use these headers:
http
enable_merge_commit: true
merge_commit_interval_ms: 3000 -- Only 3 seconds of server-side lag
merge_commit_parallel: 4 -- Helps speed up the merge for large tables
3. Rationale for the Change
Metric | Current (60s Synchronized) | Recommended (15s Staggered + 3s Merge)
Data Freshness | 60 seconds | ~18 seconds
Peak CPU/IO | Massive spike (Top of minute) | Smooth, continuous load
Index Contention | High (20 workers hitting at once) | Low (Merged & staggered hits)
Compaction Health | High pressure once a minute | Low, steady background work
4. Monitor the "Version Count"
Since you are increasing the frequency of writes to a 9TB table, you must ensure you don't create too many small files (versions) for the compaction engine to handle.
* Run SHOW PROC '/statistic'; and look at the TabletMaxVersionCount.
* If this number stays under 200, you are in perfect health.
* If it creeps toward 500+, increase your application ticker (e.g., from 15s to 20s) or increase the update_compaction_num_threads_per_disk in be.conf.
Rocky’s Summary:
Don't fear the merge commit. By using a small 3-5s window and staggering your app nodes, you get the best of both worlds: much fresher data and a cluster that isn't "choking" on a massive once-a-minute update.
References
• best_practices/primarykey_table.md
• loading/StreamLoad.md
• administration/management/BE_configuration.md
• sql-reference/sql-statements/loading_unloading/STREAM_LOAD.md
References
• best_practices/primarykey_table.mdRocky
05/10/2026, 6:11 PMMinn Fong
05/10/2026, 6:12 PMRocky
05/10/2026, 6:12 PMmerge_commit_interval_ms from 10 seconds to 3 seconds is a trade-off. While it significantly improves data freshness, it does increase the "tax" on your system resources. For a 9TB table, here is how that 7-second difference will manifest:
1. Impact on Apply Latency (The "I/O Tax")
In a Primary Key table, the "Apply" phase is the most expensive part of a write. It involves looking up keys in your 9TB Persistent Index and writing Delete Vectors.
* At 10 seconds: You are grouping more data into a single transaction. The fixed overhead of "opening" the transaction, updating the index metadata, and committing the version is spread across a larger batch of rows.
* At 3 seconds: You are performing the Apply phase 3.3x more frequently. Even though each batch is smaller, the cumulative I/O against the EBS/Disk for the Persistent Index will increase.
_ Rocky's Take: Since you have 1.2M rows/minute (~20k rows/sec), a 3s interval gives you ~60,000 rows per transaction. This is still a very "healthy" batch size for StarRocks. You shouldn't see a massive spike in _individual* apply latency, but your total BE CPU usage for "Update" tasks will likely rise by 10-15%.
2. Impact on Compaction Score (The "Version Tax")
The Compaction Score is directly tied to the number of Rowsets (versions) created.
* Version Creation: 3 seconds vs 10 seconds means you will generate ~20 versions per minute instead of ~6 versions per minute.
* Compaction Pressure: Your compaction score will likely increase. In a 9TB table, compaction is a heavy background process. If your BEs are already under high I/O wait (check iostat), increasing the version count by 3x could cause the compaction score to "creep" upward during peak hours.
_ _Rocky's Take:* If your current compaction score is consistently below 20-30, moving to 3s is perfectly safe. If your score is already sitting at 50-70, moving to 3s might push you into the "Ingest Slowdown" zone (Score > 100).
3. Will it "Hurt" a 9TB Table?
The sheer size (9TB) makes the Persistent Index the most sensitive component.
* Index Cache: If your l0_max_mem_usage (the index cache) is too small, more frequent 3s applies will force more random-read I/O from the disk to fetch the index pages.
* Recommendation: If you move to 3s, you should monitor the metric update_rowset_commit_apply_duration_us in Grafana. If the duration starts to climb, it means the disk cannot keep up with the frequent index lookups.
Rocky’s Final Verdict:
Yes, you can move to 3 seconds, but do it in steps.
1. Try 5 seconds first. It’s a great middle ground. It gives you "near-real-time" feel (5s merge + app latency) without the aggressive versioning of 3s.
2. Check your Compaction Score: Run SHOW PROC '/statistic'; during your peak traffic. If TabletMaxVersionCount stays under 150, you are safe to drop further to 3s.
3. Use `merge_commit_parallel`: Since your table is 9TB, ensure you set merge_commit_parallel to at least 4 or 8 (depending on your BE count). This allows StarRocks to use multiple threads to "Apply" the merged data, which prevents the 3s window from becoming a bottleneck.
Summary: The 3s interval is technically fine for your row volume, but for a 9TB table, the disk I/O for the index is the limiting factor. If your disks are SSD/NVMe, go for it. If they are standard EBS (gp3 with low IOPS), stick to 5-10s.
References
• loading/StreamLoad.md
• best_practices/primarykey_table.md
• administration/management/compaction.md
• sql-reference/sql-statements/loading_unloading/STREAM_LOAD.md
ReferencesMinn Fong
05/10/2026, 6:36 PMselect * from table where primary_key in ('a', 'b', 'c', ....)
PK in could possibly search up to 10k primary keys at onceRocky
05/10/2026, 6:36 PMSELECT * is an expensive operation because the engine must fetch every column from separate disk locations and "stitch" them back together (materialization).
StarRocks' Hybrid storage solves this by creating a hidden __row column that stores the entire row in a contiguous binary format. When your query hits the primary key exactly (using = or IN), StarRocks uses Short-Circuit Execution:
* OLTP-like path: It skips the entire MPP plan generation, complex expression evaluation, and columnar scan.
* Direct Lookup: It goes straight to the Primary Key Index (which is likely in memory or cached on NVMe) and fetches the pre-materialized row from the __row column.
* Criteria: Your query qualifies for this shortcut if the WHERE clause includes the full primary key and uses = or IN.
2. Comparing Performance: StarRocks vs. PostgreSQL
Feature | PostgreSQL (OLTP) | StarRocks (Hybrid PK Table)
Point Lookup Latency | Winner: ~0.5ms - 2ms. Highly optimized for single-row B-Tree lookups. | Strong: ~5ms - 15ms. Optimized for high concurrency, but has slightly more RPC overhead.
10k Keys IN Clause | Slower: Postgres typically processes IN lists sequentially or via Bitmap Heap Scans. Large IN lists can stress the planner. | Winner: StarRocks can parallelize the lookup of 10k keys across multiple BE nodes and tablets simultaneously.
Concurrency | High: Excellent, but limited by the single-node CPU/Memory of your master instance. | Scale-Out: You can increase QPS linearly by adding more BE nodes.
Data Freshness | Instant: Visible immediately after commit. | Near-Instant: Visible after the "Apply" phase (usually < 1s lag with high-volume writes).
3. Impact of the 10,000 Key IN Clause
For a query like WHERE primary_key IN (10000_keys), StarRocks is uniquely efficient:
* Tablet Pruning: StarRocks uses the hash of each key in your list to pinpoint exactly which "Tablets" (shards) contain the data. It only talks to the BE nodes holding those shards.
* Batched Point Lookups: Instead of 10,000 separate random I/O requests, StarRocks batches the lookups per segment. Since your table is 9TB, your data is likely spread across many segments; the "Short-Circuit" logic allows it to pull these rows with minimal CPU overhead.
* Limit: The default limit for items in an IN clause is usually governed by the FE parameter expr_children_limit (default is 10,000). You are right at the edge, so ensure you don't exceed this without adjusting the config.
4. Critical Trade-offs (The "Fine Print")
While StarRocks can match OLTP query performance for this use case, there are physical costs:
* Storage Overhead: Hybrid storage essentially stores your data twice (once in columns for analytics, once in rows for point lookups). For a 9TB table, this could push your disk usage to 15TB+.
* Write Impact: Writing the __row column increases CPU usage during ingestion. In your 1.2M rows/min environment, this will add measurable load to your BE nodes.
* Row Size Limit: The total size of a single row in the hybrid store (all columns combined) cannot exceed 1MB.
Rocky’s Recommendation
If your primary goal is to serve an API or Dashboard that pulls specific records by ID from a 9TB dataset:
1. Enable Hybrid Store: Create your table with STORE_TYPE = 'column_with_row'.
2. Enable Short-Circuit: Set the session variable enable_short_circuit = true.
3. Use Prepared Statements: If you are using a Java/Go driver, use prepared statements to reduce FE parsing overhead.Rocky
05/10/2026, 6:36 PMEXPLAIN on your query. If you see Short Circuit Scan: true, you are getting OLTP-level performance.
Verdict: For 10,000 keys at once, StarRocks will likely outperform PostgreSQL because it can distribute the work across your entire cluster, whereas Postgres is bound by the resources of a single machine.
References
• table_design/hybrid_table.md
• table_design/table_types/primary_key_table.md
• sql-reference/System_variable.md
• best_practices/primarykey_table.md
References
• knowledge/usage/best-practice.md
• best_practices/primarykey_table.md
• table_design/table_types/primary_key_table.mdMinn Fong
05/10/2026, 6:46 PMRocky
05/10/2026, 6:46 PMsql
-- Move data older than 30 days
INSERT INTO iceberg_catalog.db.historical_table
SELECT * FROM internal_db.pk_table
WHERE event_date < CURRENT_DATE - INTERVAL 30 DAY;
4. Cleanup: Drop the expired partitions from your internal PK table to free up local disk space.
5. Create the Unified View:
sql
CREATE VIEW unified_table_v AS
SELECT * FROM internal_db.pk_table
UNION ALL
SELECT * FROM iceberg_catalog.db.historical_table;
3. Comparison: Tiering vs. Just increasing PK TTL
Feature | PK Table (Local) | Iceberg Table (S3)
Storage Cost | High (Local SSD/EBS) | Very Low (S3)
PK Point Lookup | Ultra Fast (Short-circuit, ~5-15ms) | Slow (Full scan or min/max pruning, >500ms)
Upserts | Supported (Real-time) | Not natively (requires full rewrite or MoR)
Index Overhead | Uses Persistent Index (RAM/Disk) | Zero StarRocks index overhead
Crucial Performance Note: Your query WHERE primary_key IN ('a', 'b', 'c') will lose its "Short-Circuit" optimization when hitting the Iceberg tier. StarRocks cannot use its Primary Key Index on Iceberg data. For the Iceberg portion, StarRocks must rely on file-level min/max statistics to find those 10k keys, which will be significantly slower than the local PK lookup.
4. Is there a better "Native" way?
If you want to keep the performance and simplicity of a Primary Key table but use S3 for old data, you have two cleaner options:
Option A: Storage Tiering (Shared-Nothing)
If you stay on a Shared-Nothing cluster, you can use Storage Cool-down.
* How: You set a storage_cooldown_time or storage_cooldown_ttl on the partition.
* Result: StarRocks automatically moves the data files to S3 after X days, but the table remains a "StarRocks Table."
* Benefit: You don't need a View. You don't need to manually move data. The PK index still works (though fetching the actual data from S3 will be slower than local disk).
Option B: Shared-Data Cluster (Cloud-Native PK)
If you move to a StarRocks Shared-Data architecture, the "Hybrid" behavior is built-in.
* How: Set persistent_index_type = 'CLOUD_NATIVE'.
* Result: All data is stored on S3. Local disks are used only as a cache.
* Benefit: You get the low cost of S3 for 9TB+ of data, but you keep the full Primary Key engine features (real-time upserts and fast lookups) for the entire dataset, not just the hot partitions.
Rocky’s Recommendation
If you must have the data in Iceberg format for other tools (like Spark or Trino) to read, use the View + Iceberg approach.
If you are only using StarRocks and just want to save money on 9TB of storage, use Storage Tiering (Cool-down). It is much easier to manage because it keeps your SQL queries simple (no UNION ALL views) and preserves the Primary Key engine's logic for all your data.
References
• table_design/table_types/primary_key_table.md
• data_source/catalog/iceberg/iceberg_catalog.mdRocky
05/10/2026, 6:46 PMMinn Fong
05/10/2026, 6:50 PMRocky
05/10/2026, 6:51 PMstorage_cooldown_ttl property only supports moving data between local disk types (SSD $\rightarrow$ HDD). It does not natively move local data blocks to S3 while keeping them as part of the same internal StarRocks table.
To achieve your "Hybrid" goal (1 day local, rest on S3), you have two primary paths:
──────────
2. Path A: The "Shared-data" Cluster (Recommended)
This is exactly the architecture you described. In this mode, S3 is the source of truth, and your local disks act as a transparent, high-speed cache.
* How it works: You deploy the cluster in shared_data mode. You create a table and set a Data Cache TTL.
* The "Cooldown" equivalent: Data is written to S3 and the local cache simultaneously. After the "hot" period (e.g., 1 day) expires, the local cache is evicted. If you query that 1-day-old data, the BE (or CN) node fetches it from S3—behaving exactly like a tiered architecture.
* Table Property:
sql
PROPERTIES (
"datacache.enable" = "true",
"datacache.partition_duration" = "1 DAY" -- Data older than 1 day isn't prioritized in cache
);
──────────
3. Path B: The "Lakehouse Tiering" (Shared-nothing)
If you must stay on a Shared-nothing cluster, you can use the approach you mentioned:
1. Hot Tier: Use a standard Primary Key table (Shared-nothing).
2. Cold Tier: Manually (or via script) export expired partitions to S3 in Iceberg format.
3. Unified Query: Use a VIEW with a UNION ALL to query both.
* Pros: S3 data is accessible by other tools (Spark/Presto).
* Cons: You lose the "Primary Key" performance (point lookups) on the S3 tier.
──────────
4. Required Configurations for S3
To enable StarRocks to talk to S3 (for either Shared-data mode or querying Iceberg), you must configure your fe.conf and be.conf (or cn.conf).
FE Configuration (fe.conf)
You need to provide the "Master" credentials so the FE can manage metadata on S3.
properties
# For Shared-data mode specifically:
run_mode = shared_data
aws_s3_path = my-bucket/starrocks-data
aws_s3_region = us-east-1
# Credentials (can also use Instance Profile/IAM Role)
aws_s3_access_key = YOUR_ACCESS_KEY
aws_s3_secret_key = YOUR_SECRET_KEY
aws_s3_endpoint = <https://s3.us-east-1.amazonaws.com>
BE/CN Configuration (be.conf or cn.conf)
The backend nodes need these to perform the actual data reads/writes.
properties
# Authentication (if not using IAM roles)
aws_s3_access_key = YOUR_ACCESS_KEY
aws_s3_secret_key = YOUR_SECRET_KEY
aws_s3_endpoint = <https://s3.us-east-1.amazonaws.com>
aws_s3_region = us-east-1
# If using Shared-data mode, specify the cache path
storage_root_path = /home/disk1/sr_cache;/home/disk2/sr_cache
5. Summary of Configurations
If you choose to use Storage Volumes (available in v3.1+), you can define the S3 access in SQL rather than config files:
sql
CREATE STORAGE VOLUME s3_archived_data
TYPE = S3
LOCATIONS = ("<s3://my-bucket/cold-data/>")
PROPERTIES (
"aws.s3.region" = "us-east-1",
"aws.s3.access_key" = "xxxxx",
"aws.s3.secret_key" = "xxxxx"
);
-- Set as default for the cluster
SET s3_archived_data AS DEFAULT STORAGE VOLUME;Rocky
05/10/2026, 6:51 PMMinn Fong
05/10/2026, 6:54 PMRocky
05/10/2026, 6:54 PMSELECT * (via INSERT INTO SELECT) is actually the most efficient, high-performance way to accomplish this.
Here is the technical breakdown of why the backup approach won't work and why the "manual" approach is actually the standard.
1. The "Backup and Restore" Limitation
* Architectural Incompatibility: StarRocks BACKUP and RESTORE functionality is strictly for Shared-Nothing (SN) clusters. Shared-Data clusters (Cloud-Native mode) do not support the RESTORE command at all because their metadata management and data format on S3 are fundamentally different from how SN snapshots are structured.
* One-Way Street: You cannot change the run_mode of a cluster once it is deployed. A cluster is either Shared-Nothing or Shared-Data from day one. There is no native "migration" tool to convert an SN backup into an SD internal table.
*2. Is INSERT INTO ... SELECT * really costly?*
While it looks like a "manual" SQL query, in StarRocks it is a distributed MPP (Massively Parallel Processing) job.
* Parallelism: When you run INSERT INTO iceberg_table SELECT * FROM pk_table WHERE ..., StarRocks splits the work across all your BE nodes. Each BE node reads its local primary key data and writes Parquet files directly to S3 in the Iceberg format.
* Efficiency: This is often faster than a backup/restore cycle because it skips the intermediate "snapshot" step. It transforms the data into the target format (Iceberg) in a single pass.
* Resource Control: You can control the impact on your cluster by using Workload Groups to limit the CPU/Memory dedicated to these "offload" jobs so they don't interfere with your high-priority primary key lookups.
3. The "Hybrid" Architecture Reality
If you want to use a Shared-Nothing cluster for hot data and a Shared-Data cluster for cold data, the process would look like this:
1. Cluster A (SN): Holds your 9TB Primary Key table.
2. The Offload: Once a day, run an INSERT INTO iceberg_catalog.db.cold_table SELECT * FROM pk_table WHERE partition_date = 'expired_date'.
_ _Note: This write happens directly to S3 via the Iceberg Catalog.*
3. Cluster B (SD): This cluster doesn't need to "restore" anything. You simply create an Iceberg Catalog on Cluster B pointing to the same S3 location. Cluster B can now query the cold data instantly.
4. A More Efficient Native Alternative: Storage Tiering
If your goal is purely to save money on 9TB of storage and you don't specifically need the data in Iceberg format for other tools (like Spark), you should look at Storage Cooldown in your existing Shared-Nothing cluster:
* SSD to HDD: You can configure partitions to move from expensive NVMe/SSD to cheap HDD automatically using storage_cooldown_ttl.
* Limitation: StarRocks SN does not currently support cooling down directly to S3 within the same table.
Summary Comparison
Method | Feasibility | Performance | Complexity
Backup/Restore | Impossible | N/A | High (Manual snapshot/restore)
Export/Load | Possible | Medium (2-step process) | High (Requires intermediate files)
INSERT INTO SELECT | Best Practice | High (Distributed/Parallel) | Low (Single SQL statement)
Rocky’s Tip: Don't fear the SELECT *. For a 100GB partition offload, a well-tuned StarRocks cluster can often finish the INSERT INTO in minutes. If you are worried about the impact, simply run the offload during your lowest traffic window.
References
• administration/management/Backup_and_restore.md
• deployment/shared_data/feature-support-shared-data.md
• data_source/catalog/iceberg/iceberg_catalog.mdRocky
05/10/2026, 6:54 PMMinn Fong
05/10/2026, 7:56 PMRocky
05/10/2026, 7:57 PMmerge_commit_interval_ms)
This is the time the server waits to gather concurrent small Stream Load requests into a single batch. Even if you send data instantly, the request may wait for the full interval unless the merge_commit_chunk_size limit is reached first.
B. Transaction Write Latency (starrocks_fe_txn_write_latency_ms)
This phase covers the time from the transaction Prepare to the Commit. It includes:
* The actual time spent writing data to the BE nodes.
* Waiting for the "Plan" to be generated by the FE.
* The FE processing the commit request.
C. Transaction Apply/Publish Latency (starrocks_fe_txn_publish_latency_ms)
This is likely what you are referring to as "Apply Latency." It is the time from commit to finish (making the data visible). It is further broken down into:
* Schedule Latency: Time spent in the FE's publish queue.
* Execute Latency: Time to actually notify BEs to move data from the write buffer to the visible data set.
* Acknowledge (Ack) Latency: Time to receive confirmation from all involved replicas.
──────────
2. The Formula for BE Merge Commit Requests
StarRocks tracks the BE-side perceived latency with a specific formula in its metrics:
merge_commit_request = merge_commit_pending + merge_commit_wait_plan + merge_commit_append_pipe + merge_commit_wait_finish
* merge_commit_wait_finish: This is the big one. It is the time the BE spent waiting for the FE to finish both the Commit and the Publish (Apply) phases.
3. Key Factor: enable_sync_publish
If you are using Primary Key tables, your "Apply Latency" behavior changes based on this FE configuration:
* true (Default): The load success is only returned to the user after the data is applied and visible. Latency = Batching + Write + Apply.
* `false`: The load returns success as soon as the transaction is committed, but before it is applied. Latency = Batching + Write. (The "Apply" happens in the background).
Summary
While Merge Commit Interval sets the baseline floor for latency, the Transaction Apply/Publish phase is only one of the subsequent steps. If you are seeing high latency, you should check starrocks_fe_txn_publish_schedule_latency_ms to see if your FE is struggling to schedule the tasks, or merge_commit_pending to see if your BE thread pools are exhausted.
References
• administration/management/monitoring/metrics.md
• loading/StreamLoad.md
• administration/management/FE_configuration.md
References
• administration/management/monitoring/metrics.md
• administration/management/FE_configuration.md
• knowledge/usage/load/shared_data_batch_ingestion.mdRocky
05/10/2026, 7:57 PMMinn Fong
05/10/2026, 8:00 PMRocky
05/10/2026, 8:00 PMmerge_commit_interval_ms)
In synchronous Merge Commit mode, the FE/BE will hold a request open until one of two conditions is met:
1. Size-based: The accumulated data reaches merge_commit_chunk_size.
2. Time-based: The merge_commit_interval_ms timer expires.
If your total latency is staying flat, it likely means your data volume per batch is small enough that it never hits the size limit. Consequently, every load is waiting for the full duration of the merge_commit_interval_ms before the "Commit" even begins.
* Example: If merge_commit_interval_ms is set to 5 seconds:
* Before: 5s (Wait) + 1.0s (Apply) = 6.0s Total
* After: 5s (Wait) + 0.5s (Apply) = 5.5s Total
* To a user, 6.0s vs 5.5s feels "pretty much the same," especially if network jitter is involved.
2. The Bottleneck shifted to "Write Latency" or "Pending"
If the apply phase is no longer the issue, the "missing" time is likely spent in:
* merge_commit_wait_plan: The time the BE waits for the FE to generate the execution plan for the merged batch.
* merge_commit_append_pipe: The time spent actually moving data from the BEs into the internal memory structures.
* RPC Overhead: If you have many small requests, the coordination overhead between the "Coordinator BE" and the "Follower BEs" can exceed the time saved on the Apply phase.
3. What this indicates for Tuning
This state indicates that your cluster is "under-loaded" relative to your Merge Commit settings. You have two options depending on your goal:
* If you want lower latency:
Decrease merge_commit_interval_ms. If you change it from 5000ms to 2000ms, you will see a massive drop in total latency, because you are currently just "sitting on the clock" waiting for the window to close.
* If you want higher throughput:
Increase your client-side concurrency or the amount of data per Stream Load request. Since your Apply phase is now faster (halved), the system can actually handle much larger batches within that same time window without increasing total latency.
How to verify:
Check the following BE metrics on your coordinator nodes:
1. starrocks_be_merge_commit_wait_finish_ms: This tracks the time from when the merge window closes to when the response is sent. Since your apply latency is halved, this metric should have decreased.
2. starrocks_be_merge_commit_request_latency_ms: If this matches your merge_commit_interval_ms setting, then you are definitely being gated by the timer.
Rocky's Conclusion: Your optimization of the Apply phase was successful, but you are currently "over-buffered." To see the benefit in your end-to-end metrics, you now need to reduce the Merge Commit Interval.
References
• loading/StreamLoad.md
• loading/Flink-connector-starrocks.md
• administration/management/monitoring/metrics.mdMinn Fong
05/10/2026, 8:03 PMRocky
05/10/2026, 8:03 PMmerge_commit_interval_ms (10s): Optimizes for Throughput. You get massive batches, high efficiency, and the cluster handles more data with less stress. But, every single client is "punished" with a mandatory 10s wait.
* Low merge_commit_interval_ms (1s): Optimizes for Latency. Clients get responses in ~1-2s, but you create 10x more transactions and 10x more small data versions, which can eventually slow down the cluster (and queries) via "Too many versions" or heavy Compaction.
3. Why "Bigger Batch" didn't lower your latency
In most database operations, a "bigger batch" makes things faster because it reduces the per-row overhead. But in Merge Commit, the "Wait Time" is a fixed wall that doesn't care about batch size.
* If you send 1MB in 10s, you wait 10s.
* If you send 100MB in 10s, you wait 10s.
The only way the "bigger batch" would lower your latency is if your data volume was so high that you hit the merge_commit_chunk_size before the 10s timer expired. If that happened, the window would close early, and latency would drop.
What this tells you
Your observation indicates that your incoming data rate is not high enough to fill the "Chunk Size" within 10 seconds.
Recommendations:
1. If you need lower latency: Reduce merge_commit_interval_ms to 2s or 5s. You will see the Total Latency drop immediately because you are removing that "Wait Wall."
2. If you want the best of both worlds: Switch to Asynchronous Mode (merge_commit_async=true). In this mode, the client gets a "Success" as soon as the BE receives the data. The BE then handles the 10s wait and the Apply phase in the background. Your client-side latency will drop to milliseconds, while the cluster still gets the efficiency of the 10s batch.
References
• loading/StreamLoad.md
• loading/Flink-connector-starrocks.md
• administration/management/monitoring/metrics.md
• sql-reference/sql-statements/loading_unloading/STREAM_LOAD.md
References
• loading/Flink-connector-starrocks.md
• loading/StreamLoad.md
• administration/management/monitoring/metrics.mdMinn Fong
05/10/2026, 8:07 PMRocky
05/10/2026, 8:07 PMtablet_update_max_compaction_score: This is your most critical "early warning" metric. If this climbs above 100, your BEs are falling behind on merging updates. If it hits 1000, your loads will fail with "Too many versions."
* update_compaction_duration_us: If this spikes, it means the BE is struggling with the disk I/O or CPU required to merge upserts.
2. The "Backlog" Indicators (Apply Queue)
Even if the Apply Latency is low, the number of things waiting to be applied can grow.
* update_apply_queue_count: This tracks the tasks waiting in the Primary Key apply thread pool. If this is consistently > 0, your BEs are saturated.
* starrocks_fe_txn_publish_schedule_latency_ms: This tracks how long a committed transaction sits in the FE's queue before it even tells the BEs to start applying. If this rises, your FE Leader is the bottleneck.
3. The "Merge Commit" Efficiency
Since you are using Merge Commit, you want to ensure your batches are "fat" enough to be efficient.
* merge_commit_request_bytes: Monitor the throughput (MB/s).
* merge_commit_pending_total: If this is high, it means the BE-side threads that handle incoming Merge Commit streams are exhausted.
* merge_commit_wait_finish: Since you halved the Apply latency, this metric (on the BE side) should have decreased significantly.
4. Memory Pressure
Upserting into Primary Key tables requires memory for the Primary Key Index.
* update_mem_bytes: This tracks the total memory used by PK indexes and the apply process. If this gets close to the BE's mem_limit, the system will trigger emergency compactions or fail loads.
Summary: Why Halving Apply Latency Matters Now
Even though your Total Latency is currently stuck at 10 seconds (because of your merge_commit_interval_ms), your cluster is now "waiting faster."
* Before: Your system might have been able to handle 10 big batches per minute.
* Now: Because you halved the apply work, your system can potentially handle 20 batches per minute without the Apply Queue building up.
Rocky's Advice: If you plan to scale throughput further, watch the Compaction Score. Once that starts rising, you may need to increase merge_commit_interval_ms (to make even bigger, more efficient batches) or add more BE nodes to provide more compaction horsepower.
References
• administration/management/monitoring/metrics.md
• administration/management/monitoring/Monitor_and_Alert.md
• knowledge/admin_and_operation/shared_data_compaction.md
• loading/StreamLoad.md
References
• administration/management/monitoring/metrics.md
• administration/management/monitoring/Monitor_and_Alert.mdRocky
05/10/2026, 8:07 PMMinn Fong
05/10/2026, 8:13 PMRocky
05/10/2026, 8:13 PMupdate_apply_queue_count while the other seven are idle, you have a load imbalance or a node-specific performance bottleneck. In a Primary Key (PK) table, the "Apply" phase is where the BE node looks up the PK index, updates the Delete Vector, and makes data visible.
Here is a step-by-step guide to diagnosing why one node is struggling:
1. Check for Data Skew (Distribution Skew)
The most common cause is that a single node is physically hosting more data or more "active" tablets than others.
* Check Data Size per Node:
Run this to see if the problematic BE has significantly more data than others.
sql
SELECT be_id,
sum(data_size) / 1024 / 1024 / 1024 AS data_gb,
count(*) AS tablet_count
FROM information_schema.be_tablets
GROUP BY be_id;
* Check for "Hot" Tablets:
If the data size is balanced, you might have "hot" tablets (tablets receiving a disproportionate number of updates). Check if the problematic BE hosts tablets with a high number of versions/rowsets:
sql
SELECT tablet_id, table_id, num_version, num_rowset, data_size
FROM information_schema.be_tablets
WHERE be_id = <problematic_be_id>
ORDER BY num_version DESC LIMIT 10;
_If one tablet has a much higher num_version than others, your Primary Key distribution (the DISTRIBUTED BY column) might not be granular enough._
2. Check Node-Level Resource Contention
If the data is balanced, the node itself might be slower at processing the same amount of work.
* CPU & Disk I/O: Use top or Grafana to check if this specific node has higher iowait or CPU usage than the others.
* Memory Pressure (PK Index): The Primary Key index is stored in memory. If this node is hitting its memory limit, it may be frequently flushing the index to disk or struggling with memory management.
* Check memory: http://<be_ip>:<be_http_port>/mem_tracker?type=update
* Look for the update category. If it’s significantly higher than other nodes, the index for your PK tables is consuming a lot of RAM here.
3. Compare Thread Pool Configs
Check if the "Apply" worker count is restricted on this node.
* Metric: transaction_apply_worker_count (in be.conf).
* By default, this is equal to the number of CPU cores. If this node has fewer cores or a custom config, it will queue tasks faster.
* Check the current value via:
bash
curl http://<be_ip>:<be_http_port>/api/show_config?var=transaction_apply_worker_count
4. Check Compaction Scores
In PK tables, the Apply phase performance is tied to how many rowsets (versions) a tablet has. If compaction is lagging on one node, the Apply phase becomes exponentially slower because it has to check more files.
* Metric to watch: starrocks_be_tablet_update_max_compaction_score on that specific BE.
* If this score is > 100 on that node, the Apply queue is likely spiky because the BE is struggling to "apply" updates to tablets that have too many un-compacted versions.
Summary of what this indicates:
1. If Data Size is skewed: You need to rebalance or change your BUCKETS count / Distribution Key.
2. If Compaction Score is high on that node only: Check if that node has a failing/slow disk or if it has more write-heavy tablets than the others.
3. If the Apply Queue spikes only during specific loads: Your Primary Key values in that specific load batch might be concentrated in the range handled by that BE (e.g., if you use a timestamp as a distribution key, which is usually a bad idea).
Rocky's Quick Tip: If you find "hot tablets" on that node, you can manually trigger a balance or move a tablet to another node using ADMIN REPAIR TABLET (though usually, increasing the bucket count and re-creating the table with a better distribution key is the permanent fix).
ReferencesRocky
05/10/2026, 8:13 PMMinn Fong
05/10/2026, 8:21 PMRocky
05/10/2026, 8:22 PMnum_version is high and why only one node is spiking despite a low compaction score:
1. The "Apply" vs. "Compaction" Distinction
You are seeing a low Compaction Score because StarRocks is successfully merging data in the background. However, the Apply Phase happens before compaction and is the most resource-intensive part of a Primary Key (PK) upsert.
* Apply Phase: For every transaction, the BE must load the PK index into memory, look up every incoming key, and generate a "Delete Vector" to mark old rows as deleted. This is CPU and RAM heavy.
* Compaction Score: This only tracks the backlog of Rowsets waiting to be merged. If your disk I/O is fast enough, the score stays low (< 100), but that doesn't mean the CPU isn't struggling to handle the "Apply" work for the incoming stream.
2. Why "Tablet 0-15" are Hot (The Distribution Key)
In StarRocks, data is distributed into buckets (tablets) based on hash(distribution_key) % num_buckets.
If your "recent" data is hitting only the first 16 tablets out of 160, it means your Distribution Key is not uniform.
* Common Trap: Using a low-cardinality column (like status, category_id, or region_id) as the distribution key. If you have a few categories that receive 90% of the updates, those tablets will become "hot."
* The Sequential Clue: If "Tablets 0-15" are the hot ones, check if you are using a key where the hash values are naturally clustered, or if you are accidentally using a very small set of distinct keys for your updates.
3. Why only one BE is spiking
With 160 tablets and 8 BEs, each BE should ideally host 20 tablets.
If the "first 16" tablets are the hot ones, there is a high probability (due to StarRocks' tablet placement logic) that those 16 tablets are concentrated on a subset of BEs. If one BE happens to hold 8 of those hot tablets while other BEs hold only 1 or 2, that BE will have a spiky Apply Queue because it is doing 4-8x more metadata work than its neighbors.
How to Diagnose & Fix
Step 1: Confirm the Skew
Run this to see which BEs are hosting your hot tablets:
sql
-- Find the BEs hosting the tablets with the highest version counts
SELECT be_id, count(*) as hot_tablet_count
FROM information_schema.be_tablets
WHERE table_name = 'your_table_name'
AND num_version > 450
GROUP BY be_id;
If one BE shows a much higher hot_tablet_count than others, you've found your bottleneck.
Step 2: Check your Distribution Key
Look at your table DDL:
* If your DISTRIBUTED BY HASH(...) uses a column that doesn't change much (like a type column), change it to a high-cardinality column like user_id, order_id, or the Primary Key itself.
* Never use a timestamp or a column that grows sequentially as a distribution key, as this can lead to "tail-end" hotness in some scenarios.
Step 3: Increase Bucket Count (If needed)
If your 16 hot tablets are each 100GB+, they are too large. While 160 tablets for 8 BEs (20 per BE) is generally a good ratio, if the workload is heavily concentrated, you might need more buckets to spread the "Apply" load across more CPU cores.
Summary:
Your one BE is spiky because it is the "unlucky" host of the tablets receiving the most updates. The Apply Queue is the indicator of CPU/Index pressure, while the Compaction Score is the indicator of Disk/Merging pressure. You are currently CPU-limited on a single node due to data skew.
References
• table_design/table_types/primary_key_table.md
• best_practices/primarykey_table.md
• administration/management/monitoring/metrics.mdRocky
05/10/2026, 8:22 PMMinn Fong
05/10/2026, 8:26 PMRocky
05/10/2026, 8:27 PMauction_id has high cardinality, the string structure is likely interacting poorly with the hash modulo ($160$). When you see exactly 10% of your tablets (16 out of 160) being hot, it usually means the data follows a pattern that consistently maps to the same sub-range of the hash space.
Why your auction_id is problematic
Your ID format: 1-<4 digit bidder ID>-<unix nano + 4 digit adserver id>-<1/2/3/4>
1. Prefix Entropy: The string starts with 1- (constant) and then a 4-digit bidder ID. If a few bidders dominate your traffic, the first 6–7 characters of your "high cardinality" string are essentially constant.
2. Power-of-Two Factors: Your bucket count is $160$. Note that $160$ is a multiple of $16, 32, \text{ and } 8$. If your ID generation or the MurmurHash of these specific strings has any bit-pattern regularity, it can "clump" when performing % 160.
3. Temporal Locality: Since updates touch "recent" data, the unix nano part for all those rows is very similar (e.g., they all start with 171...). This further reduces the effective entropy the hash function has to work with in a short time window.
──────────
Recommendation 1: Use a Composite Distribution Key
Instead of just auction_id, use a combination of columns to "stir" the hash.
* Proposed: DISTRIBUTED BY HASH(bidder_id, auction_id) or DISTRIBUTED BY HASH(auction_id, some_other_id)
* Why: This increases the entropy used by the hash function. Even if the prefixes of auction_id are similar, adding the bidder_id (especially if it's an INT) changes the hash input significantly.
_ _Note: In a Primary Key table, all distribution columns must be part of your Primary Key.*
Recommendation 2: Use a Prime Number for Buckets
Avoid "round" numbers like 160, which share factors with many computer-generated ID patterns.
* Proposed: Change your bucket count to a prime number like 163 or 167.
* Why: Prime numbers are the best defense against periodic patterns in data aligning with the bucket modulo. This ensures that even if there is a slight bias in the hash, it doesn't "stack up" on specific tablets.
Recommendation 3: Fix the Tablet Placement Skew
You mentioned one node has 33 hot tablets while others have 16. This is the smoking gun for why that node is spiking. For 160 tablets on 8 nodes, every node must have exactly 20 tablets.
* If one node has 33, your cluster is physically unbalanced. This happens if nodes were added/removed or if the balancer is stuck.
* Check balance status: SHOW PROC '/cluster_balance/running_queries'; (or similar depending on version).
* Check tablet distribution:
sql
SELECT be_id, count(*) FROM information_schema.be_tablets GROUP BY be_id;
* Action: If it’s not balanced, run ADMIN REPAIR TABLET or check your fe.conf for tablet_sched_checker_interval_seconds. You may need to trigger a rebalance to move those 13 extra tablets off that node.
Recommendation 4: Experimental (StarRocks 4.1+)
If you are on the cutting edge (v4.1+), you can enable Range-based Distribution.
* Setting: SET GLOBAL enable_range_distribution = true;
* Benefit: Instead of using a fixed Hash, StarRocks will automatically split "hot" ranges of your Primary Key into new tablets and move them to different nodes dynamically. This "surgical correction" is designed exactly for the multi-tenant/bidder skew you're experiencing.
Summary Strategy
1. Immediate: Check the cluster balance. If one BE has 33 tablets and others have 20, the physical imbalance is your #1 bottleneck.
2. Next DDL: When you recreate the table (or use ALTER TABLE in 3.2+), change buckets to 167 and use DISTRIBUTED BY HASH(bidder_id, auction_id). This will virtually guarantee a uniform spread.Rocky
05/10/2026, 8:27 PMMinn Fong
05/10/2026, 8:32 PMRocky
05/10/2026, 8:32 PMupdate_apply_queue_count means your BEs are constantly working through a backlog of metadata tasks to make recently loaded data visible to queries.
1. The Drawbacks of a Persistent Queue
If you are okay with sacrificing data freshness, a non-zero queue isn't necessarily a "failure," but it does have specific side effects:
* Visibility Lag: This is the primary drawback. Data that the loader says is "Success" is not actually queryable until it clears this queue. With a constant backlog, your "End-to-End Latency" (from event time to query visibility) will be at least 30s + queue_time.
* Constant CPU Pressure: The "Apply" phase is CPU-intensive (hashing and index lookups). A persistent queue means the BE's transaction_apply thread pool is running at 100% utilization. This can steal CPU cycles from query execution if not managed via Resource Groups.
* Memory Pinned Indices: To apply an update, the PK index for that tablet must be loaded into memory. A constant backlog keeps these indices "pinned" in the BE's metadata cache, potentially increasing memory pressure on nodes with those 33 "hot" tablets.
* Risk of "Queue Bloat": If the ingestion rate slightly exceeds the apply rate, the queue won't just stay "non-zero"; it will grow indefinitely. This eventually leads to very high "Publish Version" latencies, which can cause the FE to fail new load jobs with THRIFT_RPC_ERROR or transaction timeouts.
2. Why it happened when you staggered updates
Previously, with "top of the minute" updates, the BEs likely cleared the queue quickly and then sat idle. By switching to a 30s ticker, you have doubled the number of transactions per hour.
* Metadata Overhead: Every transaction, no matter how small, has a fixed overhead in the Apply phase.
* Lock Contention: More frequent updates mean more frequent locking of the Rowset indexes.
3. Recommendations if you prioritize Throughput
Since you are okay with sacrificing freshness, you can tune the system to favor higher throughput and "bulk" apply operations:
A. Increase the Apply Concurrency
If the node has spare CPU, you can allow more parallel "Apply" tasks.
bash
# Check current worker count (0 means it equals CPU cores)
curl http://<be_ip>:<be_http_port>/api/show_config?var=transaction_apply_worker_count
# Increase it (e.g., to 1.5x or 2x core count if CPU allows)
curl -X POST "http://<be_ip>:<be_http_port>/api/update_config?transaction_apply_worker_count=32"
B. Address the "33 Tablets" Node Skew
The node with 33 hot tablets is the bottleneck for your entire pipeline. Even if you are okay with lag, that node might eventually crash or time out.
* Use ADMIN REPAIR TABLET or manual scheduling to move some of those 33 tablets to the "idle" nodes.
_ StarRocks tries to balance by count, but it doesn't always know which tablets are _hot*. You may need to manually intervene to balance the "hot" tablets specifically.
C. Use "Merge Commit" (StarRocks 3.2.x+)
If you use the Flink connector or Stream Load, enable Merge Commit. This feature allows StarRocks to take multiple small stream load commits and "merge" them into a single transaction on the FE/BE.
* This significantly reduces the number of "Apply" tasks and index lookups, which will likely drop your update_apply_queue_count to zero without changing your application's 30s ticker.
Summary: A non-zero queue is fine as long as it is stable and not growing. If it stays at a constant number (e.g., ~10-20), it just means you have a few seconds of visibility lag. If it grows over time, you must increase the batch size of your updates or fix the tablet distribution skew.
ReferencesRocky
05/10/2026, 8:32 PMMinn Fong
05/11/2026, 1:50 AMRocky
05/11/2026, 1:50 AMnum_versions are skewed and why increasing your bucket count is the right move.
1. Why "Balanced Sizes" still have "Hot Versions"
Tablet size (3.4GB) represents the total historical data stored in that bucket. num_versions represents the intensity of recent updates that haven't been compacted yet.
* The "Recent Data" Trap: In ad-tech (auctions), updates usually happen to "hot" records created in the last few minutes/hours.
* Hash Interaction: You are using 128 buckets ($2^7$). Computer-generated IDs like your auction_id often have internal structures (bit flags, counters, or timestamps) that align with powers of 2.
* The Result: Even if your total ID space is diverse, the IDs being updated right now might share a common hash suffix. If 50% of your current traffic maps to just 16 of your 128 buckets, those 16 buckets will have a massive num_versions backlog while others stay quiet.
2. Increasing Buckets to ~1GB (The Sweet Spot)
Yes, you should increase your bucket count. Here is how it improves ingestion performance:
* Granular Compaction: In StarRocks, compaction is a per-tablet task. A 3.4GB tablet is a "heavy" unit of work. Compacting a 1GB tablet is much faster and requires less memory/IO overhead.
* Parallel "Apply" Phase: For Primary Key tables, the Apply phase (looking up keys and marking deletes) is limited by the number of tablets. More tablets mean more CPU cores can work on the update stream simultaneously.
* Reduced Write Amplification: When you update a 3.4GB tablet, you are potentially triggering a large merge operation. If you split that into three 1GB tablets, an update stream targeting only one of those ranges won't force the other two to undergo unnecessary compaction cycles.
Recommendation: For your 8-node cluster, move to 383 or 401 buckets. This gives you ~48-50 tablets per BE and targets the ~1GB size.
3. Prime Numbers vs. Powers of 2
Use a Prime Number.
* The Math: Hash bucketing uses hash(key) % bucket_count. If bucket_count is a power of 2 (like 128), then the result only depends on the lowest bits of the hash. If your auction_id has any regularity in its bit pattern (e.g., the -1/2/3/4 suffix or the unix nano granularity), it will "clump" in a power-of-2 bucket scheme.
* The Prime Advantage: Using a prime number (like 383) forces the hash distribution to use all bits of the hash value, which is the best defense against skewed update patterns in structured strings.
Summary Checklist
1. Change Buckets to 383 or 401: This will drop your tablet size to ~1.1GB and increase ingestion parallelism.
2. Verify the Distribution Key: Ensure bidder_id and auction_id are both part of the distribution key if possible (this requires them to be part of the Primary Key).
3. Monitor `update_apply_queue_count`: After increasing buckets, you should see this queue drain faster because the "Apply" work is spread across 3x as many tablets.
Pro-tip: Since you are on a Primary Key table, you can't just change the bucket count on the fly. You will need to create a new table with the higher bucket count and migrate the data (e.g., INSERT INTO new_table SELECT * FROM old_table). Alternatively, if you use Dynamic Partitioning, you can change the bucket count for future partitions.
References
• best_practices/bucketing.md
• knowledge/usage/best-practice.md
• best_practices/partitioning.md
• best_practices/query_tuning/schema_tuning.mdRocky
05/11/2026, 1:50 AMMinn Fong
05/11/2026, 1:56 AMRocky
05/11/2026, 1:56 AMnum_versions from stacking up.
3. Better Data Distribution: As discussed, a prime number like 383 will break any temporal/ID patterns in your auction_id much better than 128 ($2^7$).
4. Cons: The Cost of "Too Many" Tablets
There is no "free lunch," but the "cost" is mostly metadata:
* FE Memory: Every tablet has metadata (schema, version, location) stored in the FE's JVM heap. If you have thousands of partitions, 400 buckets per partition could lead to millions of tablets, which would require increasing the FE's -Xmx (memory).
_ _Rule of thumb:* Keep total tablets per BE under 100k-200k. With 400 buckets, you'd need >2,000 partitions before hitting this limit.
* Tablet Report Overhead: BEs periodically report tablet status to the FE. More tablets = slightly more internal RPC traffic.
* Small File Problem: If your partitions are very small (e.g., <100MB), 400 buckets would create tiny files which hurts query performance. But since your data is 2-4GB per bucket (meaning 250GB-500GB per partition), you are nowhere near this risk.
Recommendation
1. Update your Bucket Count: Move from 128 to 383 (Prime).
2. Check Apply Concurrency: Ensure your BE config transaction_apply_worker_count is set to 0 (which defaults to the number of vCPUs).
3. Use Dynamic Partitioning: If you use partitioning, you can apply this change to new partitions today:
sql
ALTER TABLE your_table SET ("dynamic_partition.buckets" = "383");
Note: This only affects future partitions created by the dynamic scheduler.
Conclusion: You are currently under-utilizing your 32-core nodes because your data is "too chunky." Splitting into ~1GB tablets will unlock the parallelism you need to drain that update_apply_queue and clear the num_versions backlog.
References
• knowledge/trouble_shooting/fe_deadlock_problems.mdRocky
05/11/2026, 1:56 AMMinn Fong
05/11/2026, 1:59 AMRocky
05/11/2026, 2:00 AMApply phase (looking up the index and marking deletes) must process the metadata for the entire tablet. Applying a 600MB tablet is significantly faster and consumes less memory-pinned index space than a 3.5GB tablet.
* Parallelism is King: You have 32 vCPUs per node. With only 16 tablets per node (your current 128 buckets), 50% of your CPU is effectively "locked out" of helping with the Apply phase for a specific partition. Moving to 383 buckets gives you ~48 tablets per node, ensuring every core has a "unit of work" to pick up. This is exactly how you "mimic horizontal scaling."
2. FE Memory & Tablet Count
You mentioned your FE has 0 memory issues. Here is the math to keep it that way:
* The Limit: A standard FE with 16GB of JVM Heap can comfortably manage up to 1 million tablets.
* Your Scale: With 383 buckets, even if you keep 1,000 partitions (e.g., 3 years of daily partitions), you would only have ~383,000 tablets.
* Conclusion: You have plenty of "metadata headroom." You are nowhere near the point where tablet count becomes a liability for the FE.
3. The Real Cons of "Too Many" Buckets
If you went even further (e.g., 2000 buckets), you would encounter these drawbacks:
* Plan Generation Time: The FE takes slightly longer to "prune" buckets and generate a query plan if there are thousands of buckets to check. At 383, this is negligible (milliseconds).
* I/O Overhead during Scans: If a query needs to scan a whole partition, it has to open 383 files instead of 128. However, because you have 8 nodes and 256 cores total, the work is distributed so widely that this overhead is usually offset by the increased parallel processing power.
4. Pro-Tip: "Merge Commit" for 30s Tickers
Since you switched to a 30s ticker, you are likely hitting the BEs with many small transactions. Even with more buckets, each transaction carries a "Commit" overhead.
If you are on StarRocks 3.2 or later, ensure you have Merge Commit enabled. This feature allows the FE to group several of your 30s stream loads into a single internal transaction.
* The Benefit: It drastically reduces the number of "Apply" tasks. Instead of 10 small updates hitting the queue, the BE sees 1 larger update, which is much more efficient for the PK index.
* Config: If using the Flink connector, set sink.enable-2pc=true. If using Stream Load, look for the merge_commit header.
Final Recommendation
Go ahead with 383 buckets.
* It solves your CPU under-utilization (the 16 vs 32 core gap).
* It reduces the Apply latency by making the "work units" smaller.
* It clears the Version Backlog by allowing compaction to run more frequently on smaller files.
* The 600MB "Low Traffic" tablets will still perform excellently for queries and will be even faster to maintain.
References
• best_practices/primarykey_table.md
• table_design/table_types/primary_key_table.mdMinn Fong
05/11/2026, 12:32 PMRocky
05/11/2026, 12:33 PMstarrocks_be_update_apply_queue_count does not represent individual rows or "objects" from your update stream. Instead, it represents the number of tablet-level Apply tasks waiting in the BE's thread pool.
Here is the breakdown of what is happening when you see that spike of 700–1,000:
1. What a "Task" in this Queue represents
In a Primary Key table, every transaction (like your 30s ticker flush) goes through a "Write" phase and then an "Apply" phase.
* The Task Unit: A single "task" in this queue is one (Tablet + Version) pair.
* The Logic: If a single Stream Load transaction modifies 128 tablets on a specific BE, that BE will suddenly receive 128 Apply tasks into its queue for that one transaction.
* The Work: Each task involves loading that tablet's PK index, looking up the keys for the incoming rows, marking old rows as deleted (Delete Vector), and committing the metadata.
2. Why you see a spike of ~1,000
If you are flushing 240k updates and see a queue spike of 1,000, it usually means:
* Parallelism: You likely have multiple concurrent transactions or multiple partitions being hit at once.
* Replication: Each replica of a tablet must perform its own "Apply." If you have 3 replicas and 128 buckets, a single global transaction generates $128 \times 3 = 384$ apply tasks across the cluster.
* The Math: If you have 2–3 concurrent loads (e.g., Flink/Kafka jobs or a 30s ticker overlaps slightly with the previous one), you will easily see the queue jump to 700–1,000. It simply means 1,000 tablets are waiting for their turn to have their PK indexes updated.
3. Is the spike a problem?
The count itself isn't the problem—*latency* is.
* If the queue count is 1,000 but it drains to 0 within a few seconds, your system is healthy.
* If the queue stays high, it means your "Apply" threads (controlled by transaction_apply_worker_count) cannot keep up with the rate of incoming version updates. This is what leads to the num_versions stacking up.
4. How increasing buckets (to 383) affects this
When you increase your bucket count:
* The Queue Count will likely go UP: Since one transaction will now hit 383 tablets instead of 128, a single flush will put 3x more tasks into the queue.
* BUT the Duration will go DOWN: Each task is now "smaller" (1GB instead of 3.5GB). The BE can finish each task much faster because the PK index lookup and the DelVector generation are performed on a smaller dataset.
* Result: Even if the queue looks "busier," the tablets are updated faster, and your num_versions will stay lower because the BE can churn through the tasks more efficiently.
Summary
* Not 1k rows: You are flushing 240k rows, but they are bundled into tablet-level tasks.
* 1k = 1,000 Tablets: There are 1,000 tablet-version updates waiting for their Primary Key index merge.
* Why it's high: It's a combination of your bucket count, the number of replicas, and the number of concurrent transactions/partitions.
Recommendation: Don't be alarmed if this number increases when you move to 383 buckets. As long as the queue drains quickly after each flush, your ingestion performance is improving is the metric that matters most.
References
• table_design/table_types/primary_key_table.mdMinn Fong
05/11/2026, 12:35 PMRocky
05/11/2026, 12:36 PMMinn Fong
05/11/2026, 12:48 PMRocky
05/11/2026, 12:48 PMnum_version with a low compaction_score is a specific "danger signal" that your compaction is healthy but simply not frequent enough to keep up with your 30s ingest rate.
1. Why the metrics diverge (The Tier Logic)
In StarRocks Primary Key tables, rowsets are grouped into "tiers" based on their size (e.g., 10MB tier, 100MB tier, 1GB tier).
* num_version: This is the total sum of every rowset (delta) currently attached to the tablet.
* compaction_score: This is the count of rowsets in the single most crowded tier.
The Scenario: If you have 700 versions, they might be distributed like this:
* Tier A (Tiny): 80 rowsets
* Tier B (Small): 90 rowsets
* Tier C (Medium): 60 rowsets
* ... and so on.
In this case, your Compaction Score is 90, but your num_version is 700. The score looks "green" (under 100), but the tablet is actually nearing the system's hard limit.
2. The Real Danger: tablet_max_versions
The most important thing to know is that StarRocks stops ingestion based on num_version, not the score.
* Default Limit: tablet_max_versions = 1000.
* Your Status: At 700 and climbing, you are at 70% of the way to an ingestion failure, even though your score (<100) suggests things are "fine."
3. Why is it climbing? (The 30s vs 120s Gap)
The reason your versions are stacking up is likely due to the default compaction throttle:
* Parameter: update_compaction_per_tablet_min_interval_seconds
* Default: 120 (2 minutes).
* Your Ingest: Every 30 seconds.
Because you load 4 times for every 1 time the tablet is allowed to compact, you are creating a "version debt" of 3 rowsets every 2 minutes. Over an hour, this adds up to hundreds of versions.
4. How to fix it
Since you have already increased your buckets to 383 (giving you more parallelism), you should now tune the BE to allow compaction to work faster:
1. Lower the Compaction Interval:
Tell the BEs they don't need to wait 2 minutes between compaction runs. Set this to 60s or even 30s to match your ticker.
sql
-- Run on BE or via information_schema.be_configs
UPDATE information_schema.be_configs
SET value = "60"
WHERE name = "update_compaction_per_tablet_min_interval_seconds";
2. Increase Compaction Threads:
Since you have 32 cores per node, ensure you aren't bottlenecked on threads.
sql
UPDATE information_schema.be_configs
SET value = "8" -- 25% of your 32 cores
WHERE name = "compact_threads";
3. Check `max_cumulative_compaction_num_singleton_deltas`:
If this is set to the default (often 1000), a single compaction task tries to do too much at once. Setting it to 100 makes each task finish much faster, which helps "drain" the num_version count more frequently.
Summary for your PR:
"While the Compaction Score remains under 100 due to size-tiered grouping, the num_version is climbing towards the 1,000-version hard limit. To prevent ingestion stoppage, we recommend lowering update_compaction_per_tablet_min_interval_seconds to allow the system to merge rowsets at a frequency that matches our 30s ingest ticker."
References
• administration/management/BE_configuration.md
• knowledge/admin_and_operation/shared_data_compaction.md
• administration/management/compaction.md
• administration/management/FE_configuration.mdMinn Fong
05/11/2026, 12:51 PMRocky
05/11/2026, 12:52 PMnum_versions is still climbing despite a low compaction_score, you are likely encountering a parallelism bottleneck and a tiering mismatch.
Because you increased your bucket count to 383, each BE node now has roughly 128 tablets to manage. With only 12 compact_threads, each node can only compact ~10% of its tablets at any given moment. Your 30s flush is creating new versions for all tablets faster than the threads can cycle through them.
Here are the specific steps to fix the climbing version count:
1. Increase Per-Disk Parallelism
In StarRocks, the compact_threads is the global pool, but for Primary Key tables, there is a specific throttle per disk.
* Parameter: update_compaction_num_threads_per_disk
* Default: 1
* Action: If you are using high-performance NVMe storage, change this to 4 or 8. Even with 12 global threads, if this is set to 1, a BE with a single data path will only perform one PK compaction at a time.
sql
UPDATE information_schema.be_configs
SET value = "4"
WHERE name = "update_compaction_num_threads_per_disk";
2. Force Faster "Shallow" Merges
The "Size-Tiered" policy (default for PK) waits until a "tier" is full before merging. If your flushes are small, versions pile up in the "tiny" tier without triggering a merge because the "score" (density of that tier) stays below the threshold.
* Parameter: max_cumulative_compaction_num_singleton_deltas
* Recommendation: Set this to 100.
* Why: The default is often 1000. If a compaction task tries to merge 500 rowsets at once, it stays "busy" for a long time, holding up one of your 12 threads. Reducing this to 100 makes each task finish in milliseconds, allowing the 12 threads to "cycle" through your 383 tablets much faster.
3. Lower the "Minimum" Trigger
If the score is low, the system might not feel "urgent" enough to compact.
* Parameter: min_cumulative_compaction_num_singleton_deltas
* Action: Set this to 2 or 3.
* Why: This forces the BE to start a merge task even if only a few new versions have arrived. Since your ticker is 30s, you want to merge rowsets almost as soon as they appear.
4. Adjust the Size-Tiered Level Count
If you have 700 versions but a low score, your rowsets are likely spread across too many tiers.
* Parameter: size_tiered_level_num
* Default: 7
* Action: Try reducing this to 5.
* Effect: Reducing the number of levels forces the system to merge rowsets into larger files sooner, which reduces the total num_versions count.
5. Increase the Hard Limit (Safety Net)
While you fix the root cause, prevent your ingestion from failing by giving yourself more breathing room:
* Parameter: tablet_max_versions
* Action: Increase from 1000 to 2000.
sql
UPDATE information_schema.be_configs SET value = "2000" WHERE name = "tablet_max_versions";
Summary of why this is happening:
By increasing to 383 buckets, you made the "Apply" phase faster (as seen in your P99 drop), but you also tripled the management overhead for the compaction scheduler. The "climbing versions" is simply the scheduler failing to visit each of the 383 tablets often enough. Increasing update_compaction_num_threads_per_disk is the most likely "silver bullet" for your current state.
References
• best_practices/primarykey_table.md
• knowledge/admin_and_operation/shared_data_compaction.mdMinn Fong
05/11/2026, 12:54 PMRocky
05/11/2026, 12:55 PMCRC32_HASH(key) % bucket_count.
* The Power-of-Two Risk: If you use exactly 256, and your primary keys have any internal patterns (e.g., IDs ending in even numbers, or specific bit-flags), you risk "Resonance Skew." The hash function might unintentionally cluster data into specific buckets, making some tablets 2x larger than others.
* The Prime Benefit: A prime number like 257 or 263 mathematically breaks these patterns, ensuring a much more uniform distribution of rows across your 8 nodes. This keeps your disk usage and CPU load perfectly balanced.
3. Solving the "Compaction Cycle" Problem
The reason your versions were climbing at 383 buckets was "Scheduler Starvation."
The BE compaction scheduler has to visit every tablet to decide if it needs work. With 128 tablets per node (383 total / 3 replicas), the scheduler takes longer to complete a full "loop" of the tablets.
By dropping to 32 tablets per node (96 total replicas per node), the scheduler visits each tablet 25% more frequently. This allows it to trigger merges sooner, preventing the version count from stacking up.
Recommended Action Plan:
1. Set Buckets to 257: (A prime number that gives you ~32 tablets per node).
2. Verify Tablet Size: Ensure your tablets are still roughly 1GB–5GB. Based on your previous description (shrinking from 3.5GB @ 128 buckets), at 256 buckets your tablets should be around 1.7GB, which is the "Goldilocks zone" for StarRocks performance.
3. Keep the configuration tweaks:
* update_compaction_num_threads_per_disk = 4 (Crucial for utilizing your SSDs).
* update_compaction_per_tablet_min_interval_seconds = 10 (Keep it aggressive).
* max_cumulative_compaction_num_singleton_deltas = 100 (Keeps compaction tasks fast and light).
Summary for your PR:
"We are adjusting the bucket count to 257 (a prime number) to achieve a 1:1 ratio between tablets and vCPUs (32 per node). This maintains the low P99 transaction latency achieved previously while reducing the metadata and scheduling overhead that caused version stacking. The prime bucket count further protects against data skew in our Primary Key distribution."
References
• table_design/data_distribution/Data_distribution.md
• best_practices/bucketing.md
• best_practices/primarykey_table.md
• administration/management/compaction.md
References
• knowledge/usage/best-practice.md
• best_practices/bucketing.md
• table_design/data_distribution/Data_distribution.mdRocky
05/11/2026, 12:55 PMMinn Fong
05/11/2026, 12:59 PMRocky
05/11/2026, 12:59 PMstarrocks_be_max_tablet_rowset_num
While the compaction_score metric (which you mentioned is low) measures the density of files within a single size-tier, starrocks_be_max_tablet_rowset_num measures the absolute total count of rowsets. This is the metric that will trigger the ingestion failure when it hits the tablet_max_versions limit (default 1000).
──────────
1. Grafana / PromQL Tracker
To track how close your backends are to the limit, use this PromQL query in Grafana:
Max Versions across the cluster:
promql
max(starrocks_be_max_tablet_rowset_num)
Trend per BE node (to see if one node is worse than others):
promql
starrocks_be_max_tablet_rowset_num{job="starrocks"}
Recommended Alert Rule:
Set an alert for when this value exceeds 700 or 800. This gives you a buffer to adjust your compaction settings (as discussed in the previous steps) before the system hits the 1,000-version hard wall and stops ingestion.
──────────
2. Identifying the Specific "Problem" Tablets
If the Grafana metric shows a high number, you can find the exact tablets and tables that are causing the climb using a SQL query against the `information_schema`:
sql
SELECT
BE_ID,
TABLE_ID,
TABLET_ID,
NUM_ROWSET, -- This is the 'num_version' count
DATA_SIZE
FROM information_schema.be_tablets
WHERE NUM_ROWSET > 700
ORDER BY NUM_ROWSET DESC
LIMIT 20;
3. Why the Metrics Diverge
* Compaction Score: In Size-Tiered compaction, if you have 100 small files and 100 medium files, the score might only be 100 (representing the most crowded tier).
* `max_tablet_rowset_num`: In the same scenario, this metric will show 200.
If your flushes are small and frequent (every 30s), you are likely creating many tiny tiers that don't "trigger" a high compaction score but collectively push you toward the 1,000-version limit.
Summary of fixes (from previous context):
Since you've already lowered the interval and increased threads, the most effective way to "drain" this metric is to:
1. Increase update_compaction_num_threads_per_disk to 4+.
2. Decrease max_cumulative_compaction_num_singleton_deltas to 100 (this makes each compaction cycle finish faster).
3. Set tablet_max_versions to 2000 as a temporary safety net if you are already at 700+.
References
• administration/management/monitoring/metrics.md
• administration/management/BE_configuration.md
• reference/Information_Schema/be_tablets.md
References
• administration/management/monitoring/metrics.md
• administration/management/FE_configuration.md
• administration/management/monitoring/Monitor_and_Alert.md
• administration/management/monitoring/metrics-materialized_view.mdMinn Fong
05/11/2026, 2:20 PMRocky
05/11/2026, 2:21 PMtablet_max_versions limit (default 1000).
However, you should treat 700–800 as your "Red Zone" for two reasons:
1. Read Latency: Primary Key tables perform a "merge" or "apply" during reads. If a tablet has 900 versions, every single query touching that tablet must check 900 different rowsets to find the latest version of a row. This will significantly spike your query latency before you ever hit the 1000-ingestion-limit.
2. Sudden Spikes: If you have a burst of ingestion or a momentary slowdown in compaction, you can jump from 800 to 1000 very quickly.
Pro-tip: If you find yourself consistently hovering at 800+, increase your tablet_max_versions to 2000 via the BE config to give yourself a safety buffer while you tune the compaction threads.
──────────
How to separate by BE nodes in Grafana
To see which specific Backend (BE) is struggling, you should use the instance label in your PromQL. This label usually contains the IP or hostname of the BE.
Use this query in your Grafana panel:
promql
max(starrocks_be_max_tablet_rowset_num) by (instance)
What this tells you:
* Uniform climb: If all lines on the graph are climbing together, your bucket count is likely too high (as we discussed), or your global update_compaction_num_threads_per_disk is too low.
* One node spiking: If only one BE is high, you likely have data skew. One tablet on that node is receiving much more data than others, and its single-threaded compaction can't keep up. (This is another reason to use a prime number for buckets to ensure even distribution).
Adding a "Limit Line"
In Grafana, go to the Thresholds section of the panel and add a hard line at 1000. This makes it visually obvious when you are approaching the "ingestion wall."
Summary of what to watch:
1. Metric: starrocks_be_max_tablet_rowset_num
2. Safe Zone: < 500
3. Warning Zone: 500 – 800 (Time to tune threads)
4. Critical Zone: > 900 (Risk of ingestion failure)
References
• administration/management/monitoring/metrics.md
• administration/management/BE_configuration.md
• administration/management/monitoring/Monitor_and_Alert.md
• faq/Deploy_faq.md
References
• administration/management/BE_configuration.md
• faq/Deploy_faq.md
• administration/management/monitoring/metrics.mdMinn Fong
05/11/2026, 2:22 PMSELECT be_id, tablet_id, num_version
FROM information_schema.be_tablets
WHERE PARTITION_ID = 7208681
order by num_version desc;
Shows 280+ num_version. Why are these values diverging. Which should I useRocky
05/11/2026, 2:23 PMNUM_VERSION vs. NUM_ROWSET
In information_schema.be_tablets, there are two similar-sounding columns that mean very different things:
* NUM_VERSION (The one you queried): This is the total count of versions currently tracked in the tablet's metadata. In many table types (especially Primary Key), this counts every individual delta, update, and delete version that has been committed.
* NUM_ROWSET (The one the metric tracks): This is the count of physical data files (rowsets) currently on disk for that tablet.
Why they diverge: Compaction!
If you have performed 280 loads, your NUM_VERSION will be 280. If StarRocks compaction has already merged those into 70 larger files, your NUM_ROWSET (and the metric starrocks_be_max_tablet_rowset_num) will show 70.
2. Which one triggers the "1,000" Error?
The error too many tablet versions (>1000) is specifically triggered by the count of versions (the NUM_VERSION column).
* The Hard Limit: Even if compaction has merged your files down to 70 rowsets, if the metadata is still holding onto 1,000 version entries, the BE will reject new writes to prevent metadata bloat and performance degradation.
* Primary Key Tables: In PK tables, the version count is tied to the "Apply" and "Compaction" cycles. If versions are "applied" but not yet fully "compacted" out of the metadata, NUM_VERSION stays high.
3. Why is your Grafana metric so much lower?
There are two likely reasons for the "70" vs "280" gap:
1. Metric Definition: The metric starrocks_be_max_tablet_rowset_num tracks the maximum physical rowset count on a BE. Since compaction is working (reducing 280 versions to 70 files), the metric reflects the "healthy" physical state, but it doesn't show the "danger" of the metadata version count.
2. Label Filtering: Your PromQL uses component_service="starrocks-core-services". It is possible this label is filtering for a specific group of nodes or a "service-level" metric that is aggregated or sampled differently than the raw BE metrics.
Recommendation: Which should you use?
To prevent the "too many versions" crash, you should track both, but the SQL query is the "Ground Truth."
For your Grafana Dashboard:
Try switching to this metric if available, or remove the strict component_service filter to ensure you are seeing all BEs:
promql
# This tracks the physical files (Compaction health)
max(starrocks_be_max_tablet_rowset_num) by (instance)
# If your version supports it, this is the metadata version count (The 1000 limit)
max(starrocks_be_tablet_version_count) by (instance)
To verify the divergence via SQL:
Run this to see if NUM_ROWSET matches your Grafana "70" while NUM_VERSION shows "280":
sql
SELECT
be_id,
tablet_id,
num_version, -- The metadata count (The 1000 limit)
num_rowset -- The physical file count (What Grafana usually shows)
FROM information_schema.be_tablets
WHERE partition_id = 7208681
ORDER BY num_version DESC;
Summary:
* If num_version approaches 1,000, your ingestion will fail.
* If num_rowset (the metric) is much lower, it means your compaction is healthy (merging files) but your metadata is trailing behind.
* The fix: Increase your compaction threads and frequency (as we discussed) to help the BE "clean up" the metadata versions faster. You can also safely increase the BE config tablet_max_versions to 2000 to give yourself more breathing room.
References
• sql-reference/information_schema/be_tablets.md
• administration/management/BE_configuration.mdRocky
05/11/2026, 2:23 PMMinn Fong
05/11/2026, 2:26 PMRocky
05/11/2026, 2:27 PMversion 1.
* Primary Key "Apply" logic: In PK tables, there is an extra step called "Apply" where updates are mapped to the primary key index. If you have a high ingestion rate, the "Version" increments with every batch, even if the files are physically being merged in the background.
2. Can I just increase the limit to 2k and ignore it?
Yes, you can increase it, but no, you shouldn't ignore it.
You can change this at runtime without a restart:
bash
curl -XPOST http://<BE_IP>:<BE_HTTP_PORT>/api/update_config?tablet_max_versions=2000
Why you shouldn't "set and forget":
* Metadata Memory Bloat: Every version is tracked in the BE's memory. If you have 100,000 tablets and each has 2,000 versions, the BE's memory usage for metadata (tablet headers) will balloon, potentially causing OOM (Out of Memory) crashes or slow BE startups.
* The "Version Search" Overhead: When a query starts, it doesn't just open files. It first asks the BE: "Which rowsets belong to the current visible version?" The BE has to scan the version list. If that list is huge (e.g., 5,000+), the overhead of just planning the read increases, which adds to query latency before a single byte of data is read.
* Safety Buffer: The 1,000 limit is a "canary in the coal mine." It tells you that your ingestion frequency is faster than your compaction can "finalize." If you move it to 2,000 and don't fix the root cause (ingestion frequency), you will just hit the 2,000 wall later.
3. Does query performance only care about num_rowset?
Mostly, but not entirely.
* The 90% Factor: You are correct that the physical I/O (reading files) is the biggest bottleneck, and that is controlled by num_rowset.
* The 10% Factor: The metadata management (finding the right version, checking delete bitmaps in PK tables) is controlled by the version count.
Recommendation
1. Increase to 2,000 for breathing room: This is a perfectly fine "emergency" move to keep your pipelines running while you tune the system.
2. Focus on Ingestion Batch Size: The gap between 280 versions and 70 rowsets suggests you are doing many small loads. If you double your batch size (e.g., load every 60s instead of 30s), you will cut your version generation rate by 50% immediately.
3. Check `tablet_max_pending_versions`: If this is a Primary Key table, also ensure tablet_max_pending_versions (default 1000) isn't what's actually blocking you. This counts versions that are committed but haven't finished the "Apply" process.
Summary: Increase it to 2000 to stop the errors today, but use that time to increase your load batch size or add more compaction threads to get the num_version back down under 500.
References
• administration/management/BE_configuration.mdMinn Fong
05/11/2026, 2:30 PMRocky
05/11/2026, 2:31 PMnum_rowset), the metadata still tracks those 280 transaction points so that older queries can still see a consistent snapshot.
2. Is 256 Buckets the Problem?
For 8 nodes, 256 buckets means 32 tablets per BE node (for a single partition).
* If your table is partitioned (e.g., by day), and you are loading into multiple partitions, that 256 is multiplied by the number of active partitions.
* If you have 10 active partitions, you have 2,560 tablets per BE.
* The BE has to run compaction and metadata cleanup for every single tablet independently.
If your 50k rows are spread across 256 buckets, each tablet is only receiving ~195 rows every 10 seconds. This is "micro-batching" at the tablet level. The overhead of managing the metadata for 195 rows is almost the same as for 100,000 rows.
3. Why the values diverge (num_version vs num_rowset)
In a Primary Key table, the divergence is even more pronounced:
1. Ingestion: A new version is created.
2. Apply: The new data is mapped to the PK index (metadata update).
3. Compaction: Physical files are merged (num_rowset drops).
4. Vacuum/GC: Older metadata versions are only "cleaned up" (decreasing num_version) once they are no longer needed for queries and exceed the tablet_max_versions or a time-based threshold.
Recommendations:
Your throughput is fine, but your "metadata pressure" is high. Here is how to fix it:
1. Increase Merge Commit Interval: If your business requirement allows, increase the interval from 10s to 30s or 60s. This will instantly reduce your version generation rate by 3x–6x without changing your code.
2. Reduce Bucket Count: 256 buckets for 8 nodes is likely too many unless this table is hundreds of Terabytes. Aim for tablet sizes between 10GB and 50GB. Fewer buckets = fewer tablets = less metadata to track.
3. Tune Compaction Threads: Since your rowsets are merging but versions are staying high, give the BE more power to "finalize" these versions:
* Set update_compaction_num_threads_per_disk = 2 or 4 in be.conf.
4. Increase the Limit: As discussed, you can safely set tablet_max_versions = 2000. In your case, this isn't "ignoring a problem" but rather "adjusting for a high-frequency metadata workload."
Verdict: Your throughput isn't "too high," but your distribution is too thin. By spreading 50k rows across 256 buckets every 10 seconds, you are making the database do 256 tiny metadata updates instead of a few large ones.
References
• loading/Flink-connector-starrocks.md
• best_practices/primarykey_table.md
• knowledge/admin_and_operation/shared_data_compaction.md
• administration/management/BE_configuration.md
References
• best_practices/primarykey_table.mdMinn Fong
05/11/2026, 2:33 PMRocky
05/11/2026, 2:33 PMenable_tablet_internal_parallel)
In older MPP systems, 1 core could only scan 1 tablet at a time. If you had 32 cores and 16 tablets, 16 cores would sit idle. This is no longer the case in StarRocks.
* Internal Parallelism: Since v2.3, the variable enable_tablet_internal_parallel is TRUE by default. This allows StarRocks to split a single tablet into multiple "segments" and scan them using multiple threads (controlled by pipeline_dop).
* The Result: You can have 128 buckets (16 per node) and still saturate all 32 vCPUs on every node because each of those 16 tablets will be scanned by multiple threads simultaneously.
3. Your Specific Scenario: High-Frequency Metadata
Your throughput (50k rows) isn't the problem; it's the distribution of that throughput.
* Current (256 Buckets): Every 10 seconds, 50k rows are split into 256 tiny pieces (~195 rows each). You are creating 256 new metadata versions cluster-wide every 10 seconds.
* Proposed (64-128 Buckets): You create half as many metadata objects. Each tablet gets more data per load, which makes compaction much more efficient.
Recommendation
1. Reduce Buckets: For your 8-node cluster, try 64 or 128 buckets. This gives you 8-16 tablets per node. This is a "sweet spot" that balances metadata health with plenty of work for the pipeline engine.
2. Rely on `pipeline_dop`: StarRocks' Pipeline Engine (enabled by default) will automatically handle the parallel execution across your vCPUs. You do not need a 1:1 ratio of tablets to cores.
3. Increase the Limit: As we discussed, go ahead and set tablet_max_versions = 2000. This gives you the overhead needed for your 10s commit interval.
4. Check Tablet Size: Run SHOW TABLETS FROM your_table and look at the DataSize column. If your tablets are currently < 500MB, you definitely have too many buckets. Aim to get them closer to 5-10GB.
Summary: You won't have parallelism issues by reducing to 128 buckets because internal tablet parallelism and the Pipeline Engine will ensure all 256 vCPUs remain busy. You will, however, significantly reduce the metadata pressure that is currently causing your "too many versions" problem.
References
• table_design/data_distribution/Data_distribution.md
• sql-reference/System_variable.md
• best_practices/bucketing.md
• faq/Deploy_faq.mdlimit.
References
• table_design/data_distribution/Data_distribution.md
• sql-reference/System_variable.mdRocky
05/11/2026, 2:33 PMMinn Fong
05/11/2026, 2:39 PMRocky
05/11/2026, 2:40 PMenable_tablet_internal_parallel.
1. The "Read Snapshot" is Immutable
In StarRocks, when a query starts, it first "takes a snapshot" of the tablet's metadata (the version).
* The Lock: The BE (Backend) briefly acquires a Read-Shared Lock on the tablet to fetch the list of rowsets for the current version. This lock is non-exclusive, meaning hundreds of cores can hold it simultaneously without blocking each other.
* The Data: Once the thread has the list of rowsets, the files themselves (segments) are immutable. Multiple threads can read the same segment file or different segment files at the exact same time with zero locking overhead.
2. Morsel-Based Parallelism
When enable_tablet_internal_parallel is on, StarRocks doesn't just "let multiple threads into the tablet." It uses a Morsel-Driven Scheduler:
* The tablet is logically split into "Morsels" (ranges of rows or segments).
* Each thread (core) is assigned a unique Morsel.
* No Contention: Because Thread A is reading rows 0-10,000 and Thread B is reading rows 10,001-20,000, they are not competing for the same memory buffers or data structures.
3. Primary Key Tables (The Delete Bitmap)
If you are using Primary Key tables, you might think the "Delete Bitmap" (which tracks which rows were updated) would cause contention.
* However, even the Delete Bitmaps are versioned.
* A query at version 280 will use the Delete Bitmap that was "frozen" at version 280. If an ingestion job is currently writing a new bitmap for version 281, the reader doesn't even see it. There is no read-write contention.
4. What about the "1000 Version" Limit then?
If lock contention isn't the issue, why the limit? The bottleneck for versions is Metadata Management, not CPU Locking:
1. Tablet Header Size: The BE stores the metadata for every version in memory. If a tablet has 2,000 versions, the "Tablet Header" (the map of which rowsets belong to which version) becomes a very large object.
2. Version Traversal: When a query starts, the BE has to scan that list of 2,000 versions to determine the "shortest path" of files to read. This is a CPU-intensive metadata operation that happens before the scan even starts.
3. Reporting Overhead: The BE periodically reports the status of all tablets to the FE (Frontend). If you have 256 buckets and each has 2,000 versions, the "Report" packet sent over the network becomes massive, which can cause FE heartbeats to time out or high FE memory usage.
Summary: Parallelism vs. Versions
* Parallelism (Cores): You can safely have 32 threads scanning a single tablet. They use shared, immutable snapshots. There is no lock contention.
* Versions (Metadata): This is the part that doesn't scale infinitely. The more versions you have, the slower the planning and management of that tablet becomes.
My recommendation stands: You should feel confident reducing your bucket count to 64 or 128. Your 256 vCPUs will still be fully utilized via internal parallelism, but your BE and FE will be much "healthier" because they are managing 50% fewer metadata objects.
References
• table_design/data_distribution/Data_distribution.md
• sql-reference/System_variable.md
• knowledge/usage/best-practice.md
• administration/management/BE_configuration.md
ReferencesRocky
05/11/2026, 2:40 PMMinn Fong
05/11/2026, 3:05 PMreq.SetBasicAuth(c.username, c.password)
req.Header.Set("Expect", "100-continue")
req.Header.Set("format", "JSON")
req.Header.Set("label", label)
req.Header.Set("strip_outer_array", "true")
req.Header.Set("ignore_json_size", "true")
req.Header.Set("enable_merge_commit", "true")
req.Header.Set("merge_commit_async", "true")
req.Header.Set("merge_commit_interval_ms", "10000")
req.Header.Set("merge_commit_parallel", "8")
if options.partialUpdate {
req.Header.Set("partial_update", "true")
req.Header.Set("columns", strings.Join(options.columns, ","))
}Rocky
05/11/2026, 3:06 PMcolumns header are being overwritten (likely with NULL or default values)—is most often caused by one of the following three reasons in StarRocks:
1. The "Homogeneous" Requirement for Merge Commit
Since you are using enable_merge_commit: true, you must adhere to the Homogeneous Request rule. StarRocks merges multiple Stream Load requests into a single transaction within your 10s window.
* The Rule: All requests merged into the same window must have identical headers, including the columns header.
* The Problem: If your Go code dynamically changes options.columns for each batch (e.g., Batch A updates col1 and Batch B updates col2), the Merge Commit coordinator will likely use the column list from one of the requests for the entire merged transaction.
* The Result: For the data from Batch B, col2 is now "missing" relative to Batch A's header, and col1 is missing from the data. StarRocks then fills these missing fields with NULL (or default values), effectively performing a full upsert and overwriting your data.
Fix: If you are using Merge Commit, ensure the columns header contains the union of all columns you might update, or disable Merge Commit for batches with different schemas.
2. Case Sensitivity in the columns Header
The column names provided in the columns header are case-sensitive when matched against the table schema.
* If your table has a column named User_Score but your Go code sends user_score, StarRocks may fail to map the partial update correctly.
* When a column in a partial update is not recognized, StarRocks may fallback to a full upsert behavior for the entire row or set the unrecognized column to NULL.
Fix: Double-check that strings.Join(options.columns, ",") exactly matches the casing in SHOW CREATE TABLE.
3. Missing Primary Key in columns
For a partial update to work, the Primary Key columns must be included in the columns header so StarRocks can identify which row to update.
* If the Primary Key is missing from the columns list, StarRocks cannot perform a partial update and will either fail or treat the record as a new insertion (filling other columns with defaults).
4. merge_condition and Merge Commit Compatibility
In some versions of StarRocks (notably early v3.4.x), there was a known issue where merge_condition (used for handling out-of-order data) was not fully supported alongside Merge Commit in partial update scenarios (#65278). While you aren't explicitly setting merge_condition in your snippet, if your table relies on it, this could cause partial updates to fail and fallback to full overwrites.
Summary Checklist
1. Table Model: Confirm the table is using the Primary Key engine. Partial updates are not supported on Duplicate or Unique key models.
2. Schema Consistency: Are you sending different sets of columns in different HTTP requests while Merge Commit is enabled? If yes, you must include all possible columns in the columns header for every request.
3. Explicit Mode: Try explicitly setting the partial update mode to ensure it's not defaulting unexpectedly:
go
req.Header.Set("partial_update_mode", "row")
4. Test without Merge Commit: To isolate the issue, set enable_merge_commit to false for one test run. If the partial update works correctly then, the issue is definitively the "Homogeneous" requirement mentioned in point #1.
References
• loading/Flink-connector-starrocks.md
• loading/Load_to_Primary_Key_tables.md
• sql-reference/sql-statements/loading_unloading/STREAM_LOAD.md
• release_notes/release-3.4.md
ReferencesMinn Fong
05/11/2026, 4:50 PMRocky
05/11/2026, 4:50 PMADMIN commands and diagnostic statements are essential for maintaining cluster health, troubleshooting performance issues, and recovering from failures.
Here are the most critical commands you should know, grouped by their function.
1. Tablet & Replica Maintenance (The "Healers")
These are used when you have inconsistent data, offline BEs, or "bad" replicas.
* ADMIN SHOW REPLICA STATUS FROM table_name [WHERE STATUS = '...']
* Use case: Check if any tablets are MISSING, DEAD, or have a VERSION_ERROR. This is your first step when a query fails with "tablet missing."
* ADMIN REPAIR TABLE table_name [PARTITION (p1)]
* Use case: If a table has problematic replicas, this tells the Tablet Scheduler to prioritize fixing this specific table. It moves it to the front of the repair queue.
* ADMIN CHECK TABLET (id1, id2) PROPERTIES("type" = "consistency")
* Use case: Triggers a checksum comparison across all replicas of a tablet to ensure they contain the exact same data.
* ADMIN SET REPLICA STATUS ... PROPERTIES("status" = "bad")
* Use case: If you know a specific disk or BE has corrupted data but StarRocks hasn't flagged it yet, you can manually mark a replica as "bad" to force the system to clone a healthy one from another node.
2. Global Health Checks (The "X-Rays")
The SHOW PROC command is the "Swiss Army Knife" for StarRocks admins. It treats the system like a file directory.
* SHOW PROC '/backends'
* Check: Alive, Decommissioned, and TabletNum. If one BE has significantly more tablets than others, your cluster is unbalanced.
* SHOW PROC '/statistic'
* Check: Summary of unhealthy tablets cluster-wide. Look for UnhealthyTabletNum and InconsistentTabletNum.
* SHOW PROC '/compactions' (Available in recent versions)
* Use case: Monitor the current progress and speed of background data merging (compaction).
* SHOW DATA
* Use case: Get a quick summary of disk space usage by database and table.
3. Tuning & Configuration (The "Knobs")
You can adjust many system behaviors without restarting the services.
* ADMIN SHOW FRONTEND CONFIG
* Lists all FE parameters. Look for the IsMutable column; if it's TRUE, you can change it live.
* ADMIN SET FRONTEND CONFIG ("key" = "value")
* Example: ADMIN SET FRONTEND CONFIG ("disable_balance" = "true"); (Useful to stop data movement during a heavy maintenance window).
* BE Configuration (via HTTP):
* BE configs aren't set via SQL. You must use a curl command to the BE's web port:
* curl -X POST <http://be_ip:be_http_port/api/update_config?key=value>
4. Query & Load Troubleshooting
* SHOW PROC '/current_queries'
* Use case: Identify which query is currently consuming all the CPU or Memory.
* KILL QUERY connection_id
* Use case: Stop a "runaway" query that is hurting cluster performance.
* SHOW LOAD / SHOW ROUTINE LOAD
* Use case: Check why an ingestion job is "PAUSED" or "FAILED." The ErrorMsg column usually contains the direct cause.
* CANCEL LOAD FROM db_name WHERE LABEL = "your_label"
* Use case: Stop a stuck Stream Load or Broker Load.
5. Advanced Debugging (Expert Only)
* ADMIN EXECUTE ON FRONTEND '...'
* Allows running Groovy scripts directly inside the FE process. This is used by StarRocks Support to inspect internal Java objects or change hidden logger levels (e.g., setting a specific package to DEBUG mode).
Pro-Tip: The "Version Limit" Fix
Since you were hitting the too many versions limit earlier, this specific check is your best friend:
sql
-- Check which partitions have the highest "Compaction Score" (version count)
SELECT * FROM information_schema.partitions_meta ORDER BY Max_CS DESC LIMIT 10;Rocky
05/11/2026, 4:50 PMMax_CS (Max Compaction Score) is consistently over 100-200, it means your ingestion is faster than your BEs can compact. This is the signal to reduce your bucket count or increase the merge_commit_interval.
References
• sql-reference/sql-statements/cluster-management/tablet_replica/ADMIN_REPAIR.md
• administration/management/resource_management/Replica.md
• administration/management/FE_configuration.md
• sql-reference/sql-statements/cluster-management/config_vars/ADMIN_SET_CONFIG.md
• sql-reference/sql-statements/cluster-management/tablet_replica/ADMIN_CHECK_TABLET.mdMinn Fong
05/11/2026, 4:52 PMRocky
05/11/2026, 4:52 PMstarrocks_fe_tablet_unhealthy_num
* Metric: starrocks_fe_tablet_missing_num
* PromQL Alert:
promql
starrocks_fe_tablet_unhealthy_num > 0 or starrocks_fe_tablet_missing_num > 0
Why: In an RF=1 cluster, "Unhealthy" or "Missing" means there is no other copy of the data. Ingestion will fail with tablet not found or version not matched errors.
2. The "Ingestion Killer" (Version Count)
As we discussed earlier, hitting the 1,000-version limit stops ingestion. You should alert well before you hit that ceiling.
* Metric: starrocks_be_max_tablet_rowset_num
* PromQL Alert:
promql
starrocks_be_max_tablet_rowset_num > 700
Why: This gives you a buffer (300 versions) to either slow down your ingestion or investigate why compaction is falling behind. If this hits 1,000, your Stream Load will return [E1008] Too many versions.
3. Disk & Physical Health
If a disk on a BE becomes "Bad" or reaches 95% capacity, StarRocks will mark tablets on that disk as unhealthy.
* Metric: starrocks_be_disks_state (1 = Healthy, 0 = Bad)
* Metric: starrocks_be_disks_data_used_capacity / starrocks_be_disks_total_capacity
* PromQL Alert:
promql
starrocks_be_disks_state == 0
# OR
(starrocks_be_disks_data_used_capacity / starrocks_be_disks_total_capacity) > 0.90
Why: With RF=1, you cannot "lose" a disk without losing availability. You need to know the second a disk enters a read-only or error state.
4. Compaction Failure
If compaction is failing, versions will pile up until ingestion stops.
* Metric: increase(starrocks_be_engine_requests_total{status="failed",type="cumulative_compaction"}[5m])
* PromQL Alert:
promql
increase(starrocks_be_engine_requests_total{status="failed",type="cumulative_compaction"}[5m]) > 5
Why: This is the "leading indicator" that your version count is about to skyrocket.
5. Ingestion Success Rate
Monitor the actual success of your load jobs.
* Metric: starrocks_fe_job_load_error_rate (or derived from starrocks_fe_load_error_total)
* PromQL Alert:
promql
rate(starrocks_fe_load_error_total[5m]) > 0
Why: This is the "symptom" alert. Even if tablets look healthy, this will fire if the Go code you shared earlier starts getting 500 errors from the BEs.
Summary Checklist for RF=1
Metric Name | Threshold | Severity
starrocks_fe_tablet_unhealthy_num | > 0 | CRITICAL
starrocks_fe_tablet_missing_num | > 0 | CRITICAL
starrocks_be_max_tablet_rowset_num | > 700 | WARNING
starrocks_be_disks_state | == 0 | CRITICAL
starrocks_be_segment_file_not_found_total | increase > 0 | CRITICAL
Recommendation: Since you are using RF=1, I highly recommend using the StarRocks official Grafana Dashboard (available on GitHub or the StarRocks website). It already has a "Cluster Health" section that visualizes these exact metrics.
References
• administration/management/monitoring/Monitor_and_Alert.md
• administration/management/monitoring/alert.md
• administration/management/monitoring/metrics.mdRocky
05/11/2026, 4:52 PMMinn Fong
05/11/2026, 4:54 PMRocky
05/11/2026, 4:55 PMstarrocks_be_max_tablet_rowset_num
* Alert: > 700
* Description: This is the most critical metric for you. When this reaches 1,000, ingestion stops immediately with a "too many versions" error. For RF=1, there is no other replica to fall back on.
* Metric: starrocks_be_update_apply_queue_count
* Alert: > 10 (or a low threshold depending on your hardware)
* Description: This tracks tasks waiting in the PK "Apply" thread pool. If this queue is consistently high, the BE is backpressured and ingestion will time out or slow down significantly.
* Metric: starrocks_fe_max_tablet_compaction_score
* Alert: > 100
* Description: Reported by the FE, this tells you the highest compaction pressure in the cluster. If it stays above 100, the system starts delaying commits (slowing down ingestion).
2. Tablet Health Metrics (Frontend Leader Only)
FE metrics for tablet health are typically exported only by the Leader FE node. If you are scraping a Follower FE, these metrics may be 0 or missing.
* Metric: starrocks_fe_tablet_num{type="unhealthy"} or starrocks_fe_tablet_unhealthy
_ _Note:* The exact name can vary by version, but look for starrocks_fe_tablet_checker_unhealthy_tablet_num in recent versions (v3.x+).
* Metric: starrocks_fe_scheduled_pending_tablet_num
* Alert: > 0
* Description: This tracks tablets that the FE has identified as needing repair/clone but are still in the queue. Since you have RF=1, any number here is an emergency.
3. Physical Health (The Proxy for Unhealthy Tablets)
With RF=1, any BE or disk failure is a tablet failure. These are often easier to alert on:
* Metric: starrocks_be_disks_state (0 = Bad, 1 = Healthy)
* Metric: up{group="be"} (Standard Prometheus exporter metric for BE liveness)
Recommended Alerting Strategy for PK RF=1
Since "unhealthy tablet" metrics can sometimes be delayed by the FE's check interval (default 20s), use these three alerts for immediate notice:
1. Version Limit Alert:
starrocks_be_max_tablet_rowset_num > 800 (Ingestion will stop at 1000).
2. BE Inaccessibility Alert:
count(up{group="be"} == 0) > 0 (In RF=1, any BE down means data offline).
3. Apply Latency/Queue Alert:
starrocks_be_update_apply_queue_count > 50 (Ingestion is effectively stuck).
How to find the exact names in your cluster:
Run this on your terminal to see every metric currently exported by your BE:
bash
curl -s http://<BE_IP>:8040/metrics | grep "update_apply"
curl -s http://<BE_IP>:8040/metrics | grep "rowset_num"
And for the FE (ensure you query the Leader):
bash
curl -s http://<FE_IP>:8030/metrics | grep "tablet"
If you don't know who is leader:
mysql> SHOW FRONTEND; # Look for the 'IsLeader' column
```
## References
- [administration/management/monitoring/Monitor_and_Alert.md](https://docs.starrocks.io/docs/administration/management/monitoring/Monitor_and_Alert)
- [administration/management/monitoring/alert.md](https://docs.starrocks.io/docs/administration/management/monitoring/alert)
- [administration/management/monitoring/metrics.md](https://docs.starrocks.io/docs/administration/management/monitoring/metrics)Minn Fong
05/11/2026, 5:34 PMRocky
05/11/2026, 5:35 PMsql
-- In Stream Load header
partial_update: true
partial_update_mode: column
For "Large" Events (Impressions/Clicks with 330+ columns)
Use Row Mode.
* Why: When you are updating >30% of the columns, Row Mode is generally faster because the overhead of "reading the row" is offset by the fact that you are writing a almost-complete new row anyway.
* How:
sql
-- In Stream Load header
partial_update: true
partial_update_mode: row
3. Improving "Row Mode" with Hybrid Storage
Since you have a wide table (333 columns) and frequent updates, you should consider Hybrid Row-Column Storage (available since v3.2.3).
By setting STORE_TYPE to column_with_row during table creation, StarRocks stores data in both formats. When a Row Mode partial update occurs, it reads the existing data from the row store (super fast point lookup) instead of scanning 333 separate column files.
sql
CREATE TABLE events (
event_id BIGINT,
... 332 other columns ...
) PRIMARY KEY (event_id)
DISTRIBUTED BY HASH(event_id)
PROPERTIES (
"store_type" = "column_with_row" -- Optimizes Row Mode Partial Updates
);
Summary Comparison for your 333-column Table
Strategy | Payload | Mode | Performance Impact
All events as Row Mode | Mixed | Row | Slowest. Every tiny update triggers a massive 333-column read.
Mixed Strategy | Impression | Row | Fast. Efficient for bulk metadata updates.
Mixed Strategy | Conversions | Column | Fastest. Only touches the 1-2 columns needed.
Final Tip on Order
Even if a "small" event (e.g., a conversion) arrives first, StarRocks will:
1. Create a new row.
2. Fill the conversion column with the provided value.
3. Fill the other 332 columns with Default Values (e.g., NULL).
4. When the "Impression" arrives later, it will fill in those 330+ NULLs.
This is perfectly safe and is the standard way to handle out-of-order event streams in StarRocks.
References
• knowledge/usage/best-practice.md
• table_design/hybrid_table.md
• sql-reference/sql-statements/table_bucket_part_index/UPDATE.md
• loading/Load_to_Primary_Key_tables.md
• release_notes/release-3.3.md