<@U0A71G31CDV> I have a use case where I need to e...
# questions-and-troubleshooting
m
@Rocky I have a use case where I need to enrich events with metadata that was generated by data science. Currently using Redis, exploring other options as cardinality of fields getting too big. Thinking of using KVRocks to separate key value lookup from hot path. Given latency is not a huge concern, would using Starrocks hybrid row column storage for data science metadata point lookup be competitive in terms of cost compared to KVRocks? The data is in the order of about 1-2TB
r
Hello! I'm Rocky. Comparing StarRocks (Hybrid Row-Column Storage) to KVRocks for metadata enrichment is an interesting architectural trade-off. At a scale of 1-2 TB, both systems are viable, but they optimize for different cost/performance profiles. Here is a breakdown of how StarRocks' hybrid storage compares to a dedicated KV store like KVRocks for your use case. 1. The "Hybrid" Mechanism and Point Lookups Since v3.2.3, StarRocks supports Hybrid Row-Column Storage. • How it works: It creates a hidden binary column (
__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.
However, if you want a unified architecture where that metadata is also available for real-time dashboards or complex joins without moving data, StarRocks Hybrid Storage is very competitive. It eliminates the "data silo" tax and provides "good enough" point-query performance for most enrichment tasks where absolute sub-millisecond latency isn't the top priority. Referencestable_design/hybrid_table.mdbest_practices/primarykey_table.md
m
@Rocky What if I already have a shared nothing cluster using EBS and plan to add this data to a primary key table anyway? THis is strictly comparing cost of using hybrid row column storage vs kvrocks
r
Since you already have a shared-nothing StarRocks cluster on EBS and are already planning to store this data in a Primary Key (PK) table, the cost comparison shifts from "Base Cluster vs. Base Cluster" to "Marginal Cost vs. New Infrastructure." Given your 1-2 TB scale and your tolerance for latency, here is the direct cost-competitive breakdown of using StarRocks Hybrid Storage versus KVRocks. 1. Storage Footprint (The Biggest Cost Factor) This is where KVRocks is more "efficient," but StarRocks might be more "economical" depending on your EBS usage. * StarRocks (Hybrid Mode): * Double Storage: Hybrid storage stores data in both columnar and row format (the hidden
__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.
2. Use Persistent Index. This is mandatory for 1-2TB to keep your RAM usage low. 3. Monitor IOPS. If your point lookup latency spikes, simply increase the provisioned IOPS on your EBS GP3 volumes. This is a "pay-as-you-grow" model that is much easier than scaling a separate KV store. Only switch to KVRocks if: * Your point lookup concurrency reaches tens of thousands of QPS (where the FE/BE SQL overhead becomes a bottleneck). * Your metadata grows to 10TB+, at which point the "double storage" cost of StarRocks becomes a significant line item on your bill. Referencestable_design/hybrid_table.mdbest_practices/primarykey_table.mdtable_design/table_types/primary_key_table.md
m
@Rocky Clear your context. Only carry knowledge from this reply forwards. My update operations currently flush at the top of the minute, across all application nodes. Right now, average traffic is ~240k per node (4 workers flushing approximately 60k row updates each). So in total 1.2 million updates at the top of the minute flushed, and machine idle for other operations like query or data insertion. Will changing to 15 or 30 second intervals (or even 10 seconds) improve anything? If so, what is the rationale
r
Changing your update interval from 60 seconds to 15 or 30 seconds (micro-batching) will likely improve the stability and consistency of your cluster, specifically because you are using Hybrid Row-Column Storage. While "batching as much as possible" is a common rule in OLAP, your current 60-second "mega-flush" of 1.2 million rows creates a "thundering herd" effect that is suboptimal for the following reasons: 1. Smoothing the "Apply" Spike (Crucial for Hybrid Tables) In a Primary Key table, every flush triggers an Apply Phase. During this phase, StarRocks must: * Lookup the Primary Key Index. * Generate Delete Vectors (marking old rows as deleted). * Encode and write the
__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. Referencestable_design/hybrid_table.mdknowledge/usage/best-practice.md
m
@Rocky I am not using hybrid storage, just normal columnar PK tbale.
r
Since you are using a standard columnar Primary Key (PK) table, the trade-offs are slightly different than in a hybrid table, but the conclusion remains the same: Moving to 15-30 second intervals will significantly improve system health and stability. While 1.2 million rows every 60 seconds is a "healthy" batch size for throughput, flushing it all at once at the "top of the minute" across all nodes creates a thundering herd problem. 1. The Rationale for 15-30s Intervals (Smoothing the "Apply" Spike) In a Primary Key table, the "Write" is only half the work. The heavy lifting happens during the Apply Phase (Commit). For every flush, StarRocks must: 1. Index Lookup: Find the location of existing keys in the 1-2 TB of data. 2. Delete Vector Generation: Mark the old locations as deleted. 3. Metadata Persistence: Write these changes to the Primary Key Index and Delete Vectors. Why smoothing helps: * CPU/IO Spikes: 1.2M updates at once creates a massive burst in CPU and EBS I/O. By splitting this into 300k–600k chunks every 15-30 seconds, you turn these "spikes" into "plateaus." This leaves more headroom for concurrent queries to execute without competing for the same I/O and CPU cycles. * Transaction Lock Contention: Although StarRocks is highly concurrent, a single massive transaction holding the "Apply" lock for a long time can occasionally cause query jitter. Smaller, more frequent transactions finish faster, allowing the system to cycle through queries more smoothly. 2. Compaction Efficiency StarRocks manages data using an LSM-tree-like structure. * At 60s: You create one very large "Rowset." While this is efficient for storage, it creates a massive "to-do" list for the background compaction threads once a minute. * At 15-30s: You create smaller Rowsets. This allows the background compaction to work continuously. Compaction is like a "garbage collector"; it is much healthier for the system to have a steady stream of small tasks than a massive "stop-the-world" style cleanup task. 3. Avoiding the "Too Many Versions" Trap You asked about 10 seconds. This is where you should be cautious. * If you flush every 10 seconds, you are creating 6 rowsets per minute per tablet. * If your background compaction cannot keep up, you will eventually hit the
tablet_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." Referencesbest_practices/primarykey_table.mdknowledge/usage/load/shared_data_batch_ingestion.md
m
@Rocky Should I keep them synchronized or just give each one a ticker of 30 seconds? What merge commit interval would you recommend? my pk Table is actually ~9TB with 24 hourly partitions (1 day TTL data)
r
For a 9TB Primary Key (PK) table with high-volume updates (1.2M rows/minute), your ingestion strategy is critical. At this scale, the "Apply Phase" (where StarRocks looks up keys and generates delete vectors) is the most resource-intensive part of the lifecycle. Here is the breakdown of why you should stagger your flushes and use specific Merge Commit settings. 1. Synchronized vs. Staggered Tickers Verdict: Use staggered tickers. * The Problem with Synchronization: If all application nodes flush at the "top of the minute," you create a massive spike in CPU and I/O. For a 9TB table, the Primary Key index is likely stored on EBS (Persistent Index). A synchronized flush forces all BE nodes to perform heavy random-read I/O against the index at the exact same time, leading to "I/O storms" and potential "Apply stalls" where queries are momentarily blocked. * The Benefit of Staggering: If your 4 workers on each node have independent tickers (or even randomized start offsets), the load becomes a "stream" rather than a "burst." This allows the BE nodes to process smaller "Apply" batches continuously, which keeps your p99 query latency much flatter. 2. Recommended Merge Commit Interval Since you are updating a massive 9TB dataset, I recommend a Merge Commit Interval of 20 to 30 seconds. StarRocks (v3.4+) supports server-side Merge Commit for Stream Load. This feature merges multiple concurrent load requests into a single transaction, which is a massive efficiency gain for PK tables. Why 20-30 seconds? * Reduced Versioning: A 9TB table with hourly partitions and 1.2M rows/minute generates many "versions" (data files). If you flush too frequently (e.g., <10s), you will hit the
too 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:
Copy code
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. Referencessql-reference/sql-statements/loading_unloading/STREAM_LOAD.mdloading/StreamLoad.mdbest_practices/primarykey_table.mdbest_practices/partitioning.md References
m
@Rocky 20s merge commit interval would really hurt data freshness though.
r
Your concern about data freshness is valid. If you move to a 20-second Merge Commit interval on top of a 20-second application ticker, your data visibility could lag by up to 40 seconds. However, for a 9TB Primary Key table, the "Apply Phase" (index lookup and delete vector generation) is extremely expensive. Every single Stream Load triggers this phase. If you have 20 workers flushing independently, you are forcing the BE nodes to perform 20 separate "Apply" cycles, which creates massive I/O contention on your Persistent Index (stored on EBS/Disk). Here is the strategy to improve freshness and system health without hitting the 20s "freshness tax." 1. The Strategy: Micro-Batching + Short Merge Window Instead of one massive 60s flush, use a 10–15 second ticker with a short Merge Commit window. * App Side: Change your flush ticker to 15 seconds. * Staggering: Do not synchronize the nodes. Simply let each node start its ticker at a random offset (e.g.,
start_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:
Copy code
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. Referencesbest_practices/primarykey_table.mdloading/StreamLoad.mdadministration/management/BE_configuration.mdsql-reference/sql-statements/loading_unloading/STREAM_LOAD.md Referencesbest_practices/primarykey_table.md
m
@Rocky Will reducing merge commit interval from 10 seconds to 3 seconds impact apply latency or compaction score a lot?
r
Reducing the
merge_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. Referencesloading/StreamLoad.mdbest_practices/primarykey_table.mdadministration/management/compaction.mdsql-reference/sql-statements/loading_unloading/STREAM_LOAD.md References
m
@Rocky How does hybrid row column storage compare to mature OLTP like postgres in the context of strictly querying by the primary key? So a query could look like
Copy code
select * from table where primary_key in ('a', 'b', 'c', ....)
PK in could possibly search up to 10k primary keys at once
r
For your specific scenario—querying up to 10,000 primary keys at once using `SELECT *`—StarRocks' Hybrid Row-Column Storage is designed to compete directly with OLTP databases. Here is how it compares to a mature OLTP like PostgreSQL for this specific "multi-point lookup" workload. 1. The "Short-Circuit" Advantage In a traditional OLAP database,
SELECT *
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.
4. Monitor "Short Circuit Scan": Run
EXPLAIN
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. Referencestable_design/hybrid_table.mdtable_design/table_types/primary_key_table.mdsql-reference/System_variable.mdbest_practices/primarykey_table.md Referencesknowledge/usage/best-practice.mdbest_practices/primarykey_table.mdtable_design/table_types/primary_key_table.md
m
@Rocky Is a hybrid shared data and shared nothing architecture possible? I was thinking for expired partitions of my primary key table (shared nothing). I was store the expired data on S3, layer Iceberg on top, and then connect it to a shared data cluster. This would functionally be quite similar to my PK table with more TTL days right? How would the process be? Is it feasible?
r
This "hybrid" architecture—where you offload historical partitions to S3/Iceberg while keeping hot data in a local Primary Key (PK) table—is a common pattern in StarRocks, often called a Lakehouse Tiering Strategy. While it functionally mimics a "long TTL," there are significant differences in how the two tiers perform, especially for your Primary Key point-lookups. 1. Is it possible? Yes. You can achieve this by creating a Unified View that combines your internal PK table and an external Iceberg table. 2. The Process: Step-by-Step To move "expired" data from your 9TB PK table to S3/Iceberg: 1. Create an Iceberg Catalog: Point StarRocks to your S3 bucket (using Glue or HMS). 2. Create the Iceberg Table: Use the same schema as your PK table. 3. Data Migration (The "Offload"):
Copy code
sql
    -- 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:
Copy code
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. Referencestable_design/table_types/primary_key_table.mddata_source/catalog/iceberg/iceberg_catalog.md
m
@Rocky How does storage cooldown work? Is it a native shared nothing tiered storage architecture? Where data 1 day old and beyond for instance are moved to s3, and the shared nothing cluster queries it like how a shared data BE node would if the query involves data in those days? What configurations do I need to adjust? I know I need to configure an s3 bucket and access credentials like read and write.
r
In StarRocks, "Storage Cooldown" traditionally refers to the automatic migration of data from high-performance storage (SSD) to lower-cost storage (HDD) within a Shared-nothing cluster. However, your vision of a hybrid architecture where hot data lives on local disks and cold data lives on S3 is the core philosophy of the StarRocks Shared-data architecture. Here is how the "Cooldown" concept maps to your requirements and the configurations you need: 1. Does "Storage Cooldown" work with S3 in Shared-nothing? In a standard Shared-nothing cluster (where BE nodes own local data), the
storage_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:
Copy code
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.
Copy code
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.
Copy code
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:
Copy code
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's Final Advice: If your primary motivation is to save on 9TB of storage costs while keeping the data "queryable" within StarRocks, Shared-data mode is the more robust, native solution. It handles the "cooldown" (cache eviction) automatically and treats S3 as a first-class citizen. Referencesadministration/management/storage_management/storage_tiering.mddeployment/shared_data/s3.mdsql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.mdtable_design/table_types/primary_key_table.md Referencesadministration/management/FE_configuration.mdsql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
m
@Rocky Isn't the manual way of adding data to shared data + iceberg with select * very costly? Can I not use backup files? Create a backup file right before expiration and restore in shared data.
r
The short answer is no, you cannot use backup files to restore data into a Shared-Data (SD) cluster, and
SELECT *
(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. Referencesadministration/management/Backup_and_restore.mddeployment/shared_data/feature-support-shared-data.mddata_source/catalog/iceberg/iceberg_catalog.md
m
@Rocky Shouldn't FE merge commit load latency be a simple combination of the sum of merge commit interval and transaction apply latency?
r
The short answer is no, it is not a simple "A + B" sum. While the Merge Commit Interval and Transaction Apply Latency (Publish Phase) are major components, the true end-to-end latency for a Merge Commit load involves several other synchronous stages. Conceptualizing it as just those two metrics misses critical "wait" and "execution" phases that happen between the BE (data worker) and the FE (metadata leader). 1. The Real Components of Merge Commit Latency For a synchronous Merge Commit request, the total latency is calculated as follows: $$ \text{Total Latency} = \text{Merge Window Wait} + \text{Transaction Write Latency} + \text{Transaction Publish Latency} + \text{RPC/Pending Overhead} $$ A. Merge Commit Interval (
merge_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. Referencesadministration/management/monitoring/metrics.mdloading/StreamLoad.mdadministration/management/FE_configuration.md Referencesadministration/management/monitoring/metrics.mdadministration/management/FE_configuration.mdknowledge/usage/load/shared_data_batch_ingestion.md
m
@Rocky My transaction apply latency was halved but my merge commit load latency pretty much stayed the same. What does this indicate.
r
If your Transaction Apply Latency (the time to make data visible after a commit) was cut in half, but your total Merge Commit Load Latency remained unchanged, it indicates that the Apply phase was not your bottleneck. Instead, your total latency is being dominated by the Merge Window Wait Time. Here is the technical breakdown of what this observation reveals about your system: 1. You are hitting the "Time Floor" (
merge_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. Referencesloading/StreamLoad.mdloading/Flink-connector-starrocks.mdadministration/management/monitoring/metrics.md
m
@Rocky But doesn't the 10s merge commit interval mean more data is being collected into the window before a transaction? So bigger transaction batch
r
You are absolutely right—a 10s interval does indeed allow the system to collect a much larger transaction batch, which significantly improves throughput and system efficiency (fewer versions, less compaction pressure). However, your observation that "Total Latency stayed the same while Apply Latency halved" is a classic symptom of being Window-Limited rather than Execution-Limited. Here is why your "bigger batch" didn't result in a faster "load time" for your client: 1. The "Wait Wall" (Synchronous Mode) In Synchronous Merge Commit, StarRocks follows a strict timeline for each batch: * T=0s: The first request of the batch arrives. The timer starts. * T=0s to 10s: The FE waits. It collects every concurrent request that arrives during this window. * T=10s: The window closes. The FE merges the data and starts the Commit and Apply phases. * T=11s: Data is applied. The FE sends a "Success" response back to all clients in that batch. If you halved your Apply Latency (e.g., from 2 seconds down to 1 second), your total end-to-end latency only moved from 12s to 11s. To a monitoring dashboard or a human observer, a 12s load and an 11s load look "pretty much the same," even though the backend work was 50% faster. 2. Throughput vs. Latency There is a fundamental tradeoff here: * High
merge_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. Referencesloading/StreamLoad.mdloading/Flink-connector-starrocks.mdadministration/management/monitoring/metrics.mdsql-reference/sql-statements/loading_unloading/STREAM_LOAD.md Referencesloading/Flink-connector-starrocks.mdloading/StreamLoad.mdadministration/management/monitoring/metrics.md
m
@Rocky But if my cluster goes under more upsert stress, key indicator is the transaction apply latency right? So halving it now means optimizing it for more throughput later on. What are key grafana metrics I should observe as I increase update/upsert throughput
r
Yes, you are exactly right. Transaction Apply Latency is the "speed limit" of your metadata and index engine. By halving it, you haven't just lowered a number; you have effectively doubled your throughput headroom. In StarRocks, the "Merge Commit" mechanism ensures your cluster stays healthy by batching many small requests, but the actual bottleneck for scaling is often the FE/BE's ability to "apply" those batches (updating Primary Key indexes, checking for deletes, and creating new rowsets). As you increase your upsert throughput, keep an eye on these specific Grafana metrics to identify which "wall" you hit next: 1. The "Health" Indicators (Compaction) As throughput increases, the biggest risk is that the cluster can't merge the incoming data versions fast enough. *
tablet_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. Referencesadministration/management/monitoring/metrics.mdadministration/management/monitoring/Monitor_and_Alert.mdknowledge/admin_and_operation/shared_data_compaction.mdloading/StreamLoad.md Referencesadministration/management/monitoring/metrics.mdadministration/management/monitoring/Monitor_and_Alert.md
m
@Rocky Only one of my BE nodes have constantly spiky update apply queue count. How do I diagnose this. I have 8 nodes in total
r
If one BE node is showing spiky
update_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.
Copy code
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:
Copy code
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:
Copy code
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). References
m
@Rocky I have average num_version of ~100 but this is skewed. I think this is because updates mainly touch hot recent data. The first 16 tablets have num version of 500-550 while the subsequent 140-150 tablets have num version of 300-350. However, compaction score is consistently below 100. Explain
r
This is a textbook case of Distribution Skew (specifically "Bucket Skew"). You have identified the "What" (16 hot tablets), and the metrics explain the "Why." Here is the breakdown of why your
num_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:
Copy code
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. Referencestable_design/table_types/primary_key_table.mdbest_practices/primarykey_table.mdadministration/management/monitoring/metrics.md
m
@Rocky Admittedly I do have 4/8 nodes with hot tablets. 3 of them has 16 tablets with > 450 num versions and 1 of them has 33. I used the request hash auction_id as distribution key. It generally follows pattern of 1-<4 digit bidder ID>-<unix nano + 4 digit adserver id>-<1/2/3/4>. What else would you recommend as my distribution key?
r
The skew you are seeing—where the "first 16 tablets" are significantly hotter than the others—is a classic sign of Hash Bias or Bucket-to-Node Alignment Skew. In your case, even though
auction_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:
Copy code
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.
m
@Rocky This update apply queue count backlog only happened when I switched from synchronized top of the minute updates to staggered 30 second ticker based update flushes across my stream load applications. It did improve transaction apply latency but the update queue count is now always non zero. What is the drawback? What if I am ok with sacrificing data freshness
r
By switching to staggered 30-second flushes, you have moved from a "bursty" workload to a "continuous" workload. In StarRocks Primary Key (PK) tables, the Apply Phase is the final step of a transaction where the BE updates the Primary Key index and generates the Delete Vector. A non-zero
update_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.
Copy code
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. References
m
@Rocky I checked my tablets for most recent partition. All of them are really well balanced despite using auction id which I mentioned was heavily tied to the unix nano timestamp but we include bidder and adserver id in it. 128 tablets all around 3.3-3.6GB in size (I used 128 buckets). Why do I still have hot tablets with high num_versions? Should I increase buckets to perhaps double or triple so that I hit the sweet spot of ~1GB data per tablet? Would this improve ingestion performance and if so, how? Should I use a prime number for bucket count to help distribution too? Or use powers of 2
r
Your situation is a classic example of Update Hotness (Temporal Skew) vs. Storage Balance. Even if your tablets are perfectly balanced in size (~3.4GB), they can be drastically unbalanced in workload. Here is why your
num_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. Referencesbest_practices/bucketing.mdknowledge/usage/best-practice.mdbest_practices/partitioning.mdbest_practices/query_tuning/schema_tuning.md
m
@Rocky By increasing bucket and hence tablet count per partition, I effectively reduce tablet lock contention and hence drive system performance? What is the cons to too many buckets/tablets? My data per partition ranges from 2GB-4GB per bucket now since traffic varies throughout the day, with minimal traffic in the early EST mornings. What is the ideal bucket count? I currently have 32 vCPU machines and 8 nodes, I want to try to mimic the effect of horizontal scaling since vertical scaling is not as effective,.
r
Increasing the bucket count to reach that ~1GB per tablet "sweet spot" is the single most effective way to improve ingestion performance on a Primary Key (PK) table. Since you have 32 vCPUs per node but only 16 tablets per node (128 total / 8 nodes), you are currently bottlenecked by "tablet-level parallelism." 1. How increasing buckets "Mimics" Horizontal Scaling In StarRocks, several critical ingestion tasks are parallelized at the tablet level: * The Apply Phase: This is where the BE looks up the Primary Key index and generates the Delete Vector. Each tablet is processed by one thread at a time. * Compaction: Compaction is triggered per-tablet. * Index Memory: PK indexes are loaded/unloaded per tablet. The Math of your Current Bottleneck: With 16 tablets per node and 32 vCPUs, any single-partition ingestion job can only ever utilize 50% of your CPU (16 threads) for the heavy "Apply" logic on that node. The other 16 cores sit idle for that specific task. By increasing your bucket count to 256 or 383, you ensure there are enough "units of work" (tablets) for all 32 cores to process simultaneously. 2. The Ideal Bucket Count for your Setup For 8 nodes with 32 vCPUs each, I recommend targeting ~40-60 tablets per node. * Recommended Bucket Count: 383 or 401 (Both are Prime). * Result: * Tablets per Node: ~48-50. * Tablet Size: Your 3.5GB tablets will drop to ~1.1GB. * CPU Utilization: This allows a single partition's ingestion to fully saturate your 32 vCPUs with room for background compaction. 3. Pros: Why this helps 1. Reduces Lock Contention: StarRocks uses a Rowset Lock per tablet during the "Apply" and "Commit" phases. With 3x more tablets, the probability of two concurrent loads (or a load and a query) hitting the same lock drops by 66%. 2. Faster Compaction: It is much easier (and faster) for the system to merge three 1GB files than one massive 3.5GB file. This prevents the
num_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:
Copy code
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. Referencesknowledge/trouble_shooting/fe_deadlock_problems.md
m
@Rocky If my FE has 0 memory issues currently, then too many tablets isn't a concern? Wouldn't 383 buckets cause the low traffic partitions like in the early mornings where each tablet only has rouhgly 2GB of data now to drop to ~600MB of data per tablet? That is below the sweet spot
r
To address your concern directly: 600MB per tablet is not a "danger zone." In fact, for a Primary Key (PK) table under high update pressure, 600MB is often a better operating point than 3.5GB. The "1GB to 10GB" recommendation you see in the documentation is a general guideline for Query Scan Efficiency. However, for Ingestion and Update performance in PK tables, the "sweet spot" is governed by a different set of trade-offs. 1. Why 600MB is perfectly fine (and likely better) * The "Small File" threshold is much lower: In StarRocks, performance usually only starts to degrade significantly when tablets drop below 100MB. At 600MB, you still have enough data to take full advantage of columnar storage compression and large-block sequential reads. * Faster "Apply" Cycles: For a PK table, the
Apply
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. Referencesbest_practices/primarykey_table.mdtable_design/table_types/primary_key_table.md
m
@Rocky What does starrocks_be_update_apply_queue_count represent? Is it the updates pending apply? So if I flush 240k updates and there's sometimes a spike of 700-1k update apply queue count, it's 1k of those objects pending?
r
The metric
starrocks_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. Referencestable_design/table_types/primary_key_table.md
m
@Rocky My p99 transaction publish latency also dropped from 10s to ~2.25s. How can I explain in a PR why this is good? In terms of cluster health/ingestion performance
r
The drop in P99 transaction publish latency from 10s to 2.25s is a major win for both system stability and data freshness. Here is a structured way to explain why this is a critical health indicator in your PR: ────────── Summary: Optimization of Primary Key Ingestion via Increased Tablet Parallelism 1. Unlocked CPU Parallelism (Mimicking Horizontal Scaling) Previously, with only 16 tablets per node (128 total) but 32 vCPUs available, our ingestion was bottlenecked at the tablet-level. In StarRocks Primary Key tables, the "Apply" phase is a single-threaded task per tablet. * The Improvement: By increasing the bucket count to 383, we now have ~48 tablets per node. This allows the system to utilize all 32 vCPUs for parallel PK index lookups and DelVector generation, effectively "scaling out" our fixed hardware. 2. Drastic Reduction in "Apply" Duration Transaction Publish Latency is dominated by the Apply Phase (the time it takes to load the PK index and mark old rows as deleted). * The Math: By shrinking tablets from ~3.5GB down to ~1GB, we have reduced the size of the PK index that must be searched/updated for every 30s flush. * Result: A 75% reduction in P99 latency (10s → 2.25s) means the BEs spend significantly less time with PK indexes pinned in memory, reducing global memory pressure. 3. Mitigation of Version Stacking & "Too Many Versions" Risks When publish latency is high (10s), rowsets accumulate faster than the system can commit them. This leads to high "Compaction Scores." * Health Impact: Faster commits allow the background compaction threads to pick up new rowsets sooner. This prevents the "Too Many Versions" error that causes ingestion to fail during traffic spikes. * Consistency: A tight P99 latency ensures that even during peak traffic (240k updates/flush), the system maintains a steady state rather than spiraling into a backlog. 4. Improved Data Freshness (Visibility) In StarRocks, data is only visible to queries after the transaction is published. * User Impact: We have reduced the "visibility gap" by nearly 8 seconds. Users querying the dashboard will now see real-time updates in ~2 seconds rather than ~10, providing a much truer "real-time" experience. 5. Reduced Rowset Lock Contention Each tablet holds a lock during the commit phase. By spreading the 240k updates across 383 buckets instead of 128, we have reduced the "write density" per tablet. This minimizes the chance of a routine load and a manual update (or compaction) colliding on the same tablet lock. ────────── Metrics to Highlight in the PR: * `starrocks_be_update_apply_queue_count`: Note that while the count of tasks in the queue may have increased (due to more tablets), the drain rate is significantly higher. * `starrocks_be_update_rowset_commit_apply_duration_us`: This metric should show a correlating drop, confirming the "Apply" logic is faster. Referencesbest_practices/primarykey_table.mdadministration/management/monitoring/metrics.mdtable_design/table_types/primary_key_table.md
m
@Rocky My num_version for all tablets in most recent hour partition is now between 438 and 700, steadily climbing. however, compaction score is consistently below a 100. I thought compaction score and num_version for tablets are closely related in primary key tables?
r
This observation is common when using the Size-Tiered Compaction strategy (which is the default for Primary Key tables in recent versions). You are correct that they are related, but they represent different things. In a PK table, a high
num_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.
Copy code
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.
Copy code
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." Referencesadministration/management/BE_configuration.mdknowledge/admin_and_operation/shared_data_compaction.mdadministration/management/compaction.mdadministration/management/FE_configuration.md
m
@Rocky My update compaction per tablet min interval is already low, it is at 10 seconds. compact threads at 12. What else can I do to fix climbing num versions
r
Since you have already tuned the interval to 10s and increased threads to 12, but
num_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.
Copy code
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.
Copy code
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. Referencesbest_practices/primarykey_table.mdknowledge/admin_and_operation/shared_data_compaction.md
m
@Rocky Should I then decrease my bucket count to perhaps a prime number close to 256? Then for 32 cores it is still fine since 256/8 nodes is still 32 tablets per node.
r
Reducing your bucket count to a prime number close to 256 is an excellent move. Here is why this specifically solves your problem and why your intuition about the prime number is correct. 1. The "1 Tablet per Core" Sweet Spot For Primary Key tables, the most intensive phase of ingestion is the "Apply" phase (where the system checks the index and marks deletes). This phase is parallelized at the tablet level. * Current State (383 buckets): ~48 tablets per node. With 32 cores, you have more "tasks" than workers. This improved your P99 latency (parallelism), but it created a management overhead for the Compaction Scheduler. * Proposed State (~256 buckets): 32 tablets per node. This aligns perfectly 1:1 with your 32 vCPUs. It ensures that during a flush, every core can be fully utilized by one tablet's apply process without the OS constant context-switching between 48 different tablet threads. 2. Why a Prime Number (e.g., 251 or 257)? In StarRocks, data is distributed via
CRC32_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." Referencestable_design/data_distribution/Data_distribution.mdbest_practices/bucketing.mdbest_practices/primarykey_table.mdadministration/management/compaction.md Referencesknowledge/usage/best-practice.mdbest_practices/bucketing.mdtable_design/data_distribution/Data_distribution.md
m
@Rocky Is there a grafana metric I can use to track tablet num versions so I know if any is approaching 1000?
r
Yes, there is a specific metric for this. In StarRocks, "num versions" (the total number of files/rowsets in a tablet) is tracked by the metric:
starrocks_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:
Copy code
promql
max(starrocks_be_max_tablet_rowset_num)
Trend per BE node (to see if one node is worse than others):
Copy code
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`:
Copy code
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+. Referencesadministration/management/monitoring/metrics.mdadministration/management/BE_configuration.mdreference/Information_Schema/be_tablets.md Referencesadministration/management/monitoring/metrics.mdadministration/management/FE_configuration.mdadministration/management/monitoring/Monitor_and_Alert.mdadministration/management/monitoring/metrics-materialized_view.md
m
@Rocky So if max(starrocks_be_max_tablet_rowset_num) stays below 1000 I'm okay? How can I separate max by be nodes?
r
Is staying below 1000 "okay"? Technically, yes, your cluster will continue to function and accept data as long as the value is below the
tablet_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:
Copy code
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) Referencesadministration/management/monitoring/metrics.mdadministration/management/BE_configuration.mdadministration/management/monitoring/Monitor_and_Alert.mdfaq/Deploy_faq.md Referencesadministration/management/BE_configuration.mdfaq/Deploy_faq.mdadministration/management/monitoring/metrics.md
m
@Rocky My metrics for max(starrocks_be_max_tablet_rowset_num{component_service="starrocks-core-services"}) by (aws_name) is very low, mostly below 70, but
Copy code
SELECT 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 use
r
The discrepancy you are seeing between the Grafana metric and the SQL query is a common source of confusion in StarRocks. Here is why they diverge and which one you should trust for the "1,000 version" limit. 1. The Core Difference:
NUM_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:
Copy code
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":
Copy code
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. Referencessql-reference/information_schema/be_tablets.mdadministration/management/BE_configuration.md
m
@Rocky But compaction has already compressed the rowsets down, why still tracking metadata? Can I just increase max versions to 2k? Since query performance is impacted by num_rowsets only? So if that healthy, just increase limit and ignore?
r
It is understandable why this feels like an unnecessary hurdle. If the physical files (rowsets) are merged, you’d expect the "version count" to drop. However, StarRocks tracks metadata versions for consistency and query isolation, and hitting the 1,000-version limit is a safety mechanism. Here is the technical breakdown of why they diverge and why you can't just ignore it. 1. Why track metadata if files are compressed? Even after physical compaction (merging 10 files into 1), StarRocks maintains a version history in the tablet metadata. * Query Consistency (MVCC): Long-running queries might still be looking at a "Snapshot" from version 200 while your latest data is at version 280. The BE keeps the metadata for these versions until they are "GC'd" (Garbage Collected). * Cumulative vs. Base Compaction: * Cumulative Compaction (what's likely happening now) merges small rowsets into larger ones, but it doesn't always "collapse" the version range into a single number. * Base Compaction is the "deep clean" that merges everything into a single
version 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:
Copy code
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. Referencesadministration/management/BE_configuration.md
m
@Rocky I am already doing big batches with a merge commit interval of 10s. Each batch is roughly 50k rows. Is it just the case that my ingestion throughput is too high? I have 8 nodes and 256 buckets. Updates are roughly 20k rows per batch too.
r
Your ingestion throughput (50k rows every 10s) is actually quite modest for an 8-node StarRocks cluster. StarRocks is designed to handle millions of rows per second. The reason you are seeing 280+ versions despite a low rowset count is not because your data volume is too high, but because of the frequency of your commits and the granularity of your distribution (buckets). 1. The Math: Why are there 280 versions? With a 10s merge commit interval, you are creating 1 new version every 10 seconds for every tablet involved in that load. * 10 seconds = 6 versions per minute. * 280 versions ÷ 6 versions/min = ~46 minutes of history. StarRocks keeps a certain amount of version history (metadata) for MVCC (query consistency). Even if compaction has merged the 280 physical files into 70 (as shown by your
num_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. Referencesloading/Flink-connector-starrocks.mdbest_practices/primarykey_table.mdknowledge/admin_and_operation/shared_data_compaction.mdadministration/management/BE_configuration.md Referencesbest_practices/primarykey_table.md
m
@Rocky But you previously mentioned 1GB per tablet is the sweet spot. Each node has 32 vCPU. For 8 nodes, 8*32 = 256 tablets. If I reduce to 128 tablets, I have tablet level parallelism issues.
r
You are absolutely right that there is a delicate balance between query parallelism and metadata health. However, the recommendation for StarRocks has evolved as the engine's internal architecture has matured. Here is the breakdown of why you can safely reduce your bucket count without sacrificing the parallelism of your 256 vCPUs. 1. The "1GB vs 10GB" Confusion The "1GB per tablet" rule is a common starting point for small-to-medium tables or older versions of the system. However, in modern StarRocks (v2.4+), the recommendation for large production tables is 10GB per tablet. * Why the shift? The more tablets you have, the more metadata the BE must manage. If you have 256 buckets and 10 active partitions, that’s 2,560 tablets per node. If each tablet is tracking 300+ versions, your BE is managing 768,000 metadata version entries just for this one table. * The Cost: This is why you are hitting the version limit. The BE spends more time managing the "bookkeeping" of these tiny tablets than it does actually processing data. 2. Parallelism has changed (
enable_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. Referencestable_design/data_distribution/Data_distribution.mdsql-reference/System_variable.mdbest_practices/bucketing.mdfaq/Deploy_faq.mdlimit. Referencestable_design/data_distribution/Data_distribution.mdsql-reference/System_variable.md
m
@Rocky But even with enable tablet internal parallel, aren't there tablet lock contentions between cores?
r
This is a valid concern, as in many traditional databases, increasing parallelism on a single object leads to "lock contention." However, StarRocks’ storage engine is built on MVCC (Multi-Version Concurrency Control), which fundamentally changes how threads interact with data. Here is why you don't need to worry about lock contention between cores when using
enable_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. Referencestable_design/data_distribution/Data_distribution.mdsql-reference/System_variable.mdknowledge/usage/best-practice.mdadministration/management/BE_configuration.md References
m
@Rocky These are my headers for partial updates. Why are they overwriting fields not specified in the columns header?
Copy code
req.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, ","))
	}
r
The issue you're seeing—where fields not specified in the
columns
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:
Copy code
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. Referencesloading/Flink-connector-starrocks.mdloading/Load_to_Primary_Key_tables.mdsql-reference/sql-statements/loading_unloading/STREAM_LOAD.mdrelease_notes/release-3.4.md References
m
@Rocky Are there any important ADMIN commands I should be aware of to troubleshoot the starrocks cluster (or tune it if necessary) For instance ADMIN REPAIR TABLET
r
In StarRocks,
ADMIN
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:
Copy code
sql
-- Check which partitions have the highest "Compaction Score" (version count)
SELECT * FROM information_schema.partitions_meta ORDER BY Max_CS DESC LIMIT 10;
If
Max_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
. Referencessql-reference/sql-statements/cluster-management/tablet_replica/ADMIN_REPAIR.mdadministration/management/resource_management/Replica.mdadministration/management/FE_configuration.mdsql-reference/sql-statements/cluster-management/config_vars/ADMIN_SET_CONFIG.mdsql-reference/sql-statements/cluster-management/tablet_replica/ADMIN_CHECK_TABLET.md
m
@Rocky Is there any grafana metrics I can use to alert me when a tablet is unhealthy? THis stops ingestion altogether because I didn't enable replication
r
Because you are running with Replication Factor = 1 (RF=1), you are in a "zero-fault-tolerance" setup. Any single tablet issue—whether it's a disk failure, a version limit hit, or a node restart—will immediately halt ingestion for the tables affected. To catch these issues before (or as) they happen, here are the most important Grafana/Prometheus metrics and alert rules for your scenario. 1. The "Data Loss" Alert (Critical) Since you have no replicas, an unhealthy tablet usually means the data is physically inaccessible or missing. * Metric:
starrocks_fe_tablet_unhealthy_num
* Metric:
starrocks_fe_tablet_missing_num
* PromQL Alert:
Copy code
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:
Copy code
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:
Copy code
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:
Copy code
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:
Copy code
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. Referencesadministration/management/monitoring/Monitor_and_Alert.mdadministration/management/monitoring/alert.mdadministration/management/monitoring/metrics.md
m
@Rocky THis is for primary key tables. The metrics you mentioend are not available
r
The reason you likely don't see the specific metrics mentioned earlier is that StarRocks organizes its metrics differently for the Frontend (FE) and Backend (BE), and some health metrics are only exported by the Leader FE. For a Primary Key (PK) table with Replication Factor = 1 (RF=1), you should use the following available metrics to detect unhealthy tablets and ingestion bottlenecks: 1. Ingestion-Stopping Metrics (Primary Key Specific) Because you are using PK tables, ingestion usually stops not just because a tablet is "down," but because the Apply mechanism or Compaction is overwhelmed. * Metric:
starrocks_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:
Copy code
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):
Copy code
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)
m
@Rocky I have another events table where all metrics (impressions, clicks, engagement etc.) are consumed with the same metadata attached. However, I do not have a guarantee on which event is the first. Impressions are mostly the first event per record that comes in but sometimes clicks can be registered before impressions. Therefore, I can't treat impressions as an insertion record. Would it hurt performance if I used partial updates for all incoming events and specify all field columns (around 333 columns)? Would ingestion performance be improved if I only did full column partial update for impressions and clicks and just the primary key + event column update for other events guaranteed to come later?
r
Using partial updates for your events table is the correct architectural choice given that you cannot guarantee event order. However, with 333 columns, the performance difference between "Row Mode" and "Column Mode" will be massive. 1. Performance Impact of 333 Columns in Partial Updates If you use the default Row Mode partial update for every single event (even those with only 2 columns): * The Problem: In Row Mode, StarRocks must read the entire existing row (all 333 columns), update the specific fields, and write a new version. Reading 333 columns from a columnar store for every 1-column update is extremely expensive and will likely throttle your ingestion. * The "Full Column" Case: If an event contains almost all 333 columns, Row Mode is efficient because you are replacing most of the data you just read. 2. The Optimized Strategy (Recommended) You will see a significant ingestion performance boost by differentiating your update modes based on the payload size. For "Small" Events (PK + 1-5 columns) Use Column Mode. * Why: Column Mode (available since v3.1) is designed specifically for updating a few columns across many rows. It avoids reading the other 300+ columns entirely. * Performance: For a table with 300+ columns, updating 1-2 columns in Column Mode can be 10x to 50x faster than Row Mode. * How: Set the session variable or load property:
Copy code
sql
    -- 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:
Copy code
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.
Copy code
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. Referencesknowledge/usage/best-practice.mdtable_design/hybrid_table.mdsql-reference/sql-statements/table_bucket_part_index/UPDATE.mdloading/Load_to_Primary_Key_tables.mdrelease_notes/release-3.3.md