Minn Fong
06/30/2026, 3:12 PMRocky
06/30/2026, 3:13 PMstorage_page_cache_limit is unlikely to be the primary solution for reducing CPU iowait during the Primary Key (PK) upsert apply phase. While the Page Cache stores data and column index pages (useful for queries), the PK "apply" process primarily relies on the Primary Key Index and Delete Vectors, which are managed by a separate memory pool.
Why Page Cache might not help
In a Shared-Nothing PK cluster, the "upsert apply" phase involves looking up the PK index to find existing row locations and updating Delete Vectors (DelVectors).
* PK Index Cache: StarRocks uses a dedicated cache for the Primary Key Index (managed by the UpdateManager), not the general Storage Page Cache.
* Memory Tracking: Memory for the PK index and DelVectors is tracked under the update category in the mem_tracker, while storage_page_cache_limit controls the page_cache category.
Better knobs for reducing I/O wait during PK Upserts
If you have headroom memory and are limited by EBS throughput (high iowait), you should focus on increasing the memory allocated to the PK Index to avoid disk-based lookups:
1. update_memory_limit_percent (be.conf):
* This defines the percentage of BE process memory reserved for the UpdateManager (PK index and DelVector caches). The default is 60%.
* If your PK indexes are large and spilling to disk (Persistent Index), increasing this allows more of the index to stay in memory, directly reducing the random I/O reads that cause iowait.
2. l0_max_mem_usage (be.conf):
* This controls the maximum memory usage of the persistent PK index per tablet. The default is 100MB (104857600 bytes).
* If you have many rows per tablet, the index may exceed this limit and "spill" parts to disk. Increasing this (e.g., to 512MB or 1GB) can significantly reduce I/O pressure during the apply phase.
3. Check for Partial Updates:
* If you are performing Partial Updates (column mode), the BE must read the old data pages to merge them with new ones. In this specific case, increasing storage_page_cache_limit will help, as it caches the data pages being read during the merge.
Recommended Next Steps
* Check MemTracker: Visit http://<BE_IP>:<BE_HTTP_PORT>/mem_tracker?type=update to see if your update memory usage is hitting its limit.
* Monitor Metrics: Look at update_primary_index_num and update_mem_bytes. If the memory used for updates is capped while you have system headroom, increase update_memory_limit_percent.
* Persistent Index: Ensure your table has enable_persistent_index = true (default since v3.1.4). While it uses disk, it is much more memory-efficient than the in-memory index for large datasets.
Summary: Unless you are doing partial updates, focus on increasing l0_max_mem_usage and ensuring update_memory_limit_percent is sufficient rather than adjusting the general Page Cache.
References
• administration/management/BE_configuration.md
• administration/management/monitoring/metrics.md
• best_practices/primarykey_table.mdMinn Fong
06/30/2026, 3:16 PMRocky
06/30/2026, 3:17 PMstorage_page_cache_limit will help.
In Column Mode partial updates, StarRocks must read the values of the columns that are not being updated from existing rowsets to merge them with the new data. This creates a significant amount of "read" I/O during the Apply phase. If these pages aren't in the Page Cache, they must be fetched from EBS, leading to the high iowait you are seeing.
1. Key Knobs for Partial Updates
Given your headroom, adjust these be.conf parameters:
* storage_page_cache_limit: Increase this from 20% to 30-40% if memory allows. This caches the "old" column data, reducing EBS read throughput during the merge.
* update_memory_limit_percent: Ensure this is at least 60% (default). This pool is shared between the PK Index and the "Apply" process.
* partial_update_memory_limit_per_worker: (Default ~2GB). This limits the memory used by a single worker to assemble chunks. If you have very wide tables or large batches, increasing this can prevent frequent flushing to disk.
* column_mode_partial_update_insert_batch_size: (Default 4096). Increasing this can improve throughput but increases memory consumption during the Apply phase.
2. Metrics to Observe (Before & After)
To verify the impact of your changes, monitor these specific metrics via Prometheus/Grafana or the BE HTTP API.
I/O & Cache Efficiency
* page_cache_hit_rate: Use the BE endpoint http://<BE_IP>:<BE_HTTP_PORT>/api/datacache/app_stat.
_ _Goal:* You want to see the page_cache_hit_rate increase and page_cache_miss_count decrease during your upsert window.
* starrocks_be_disks_read_bytes: Compare the total read throughput from EBS. If the cache is working, this should drop significantly while the update throughput remains the same or increases.
Apply Phase Performance
* column_partial_update_apply_duration_us: This is the most direct metric. It tracks the total time spent in the column-mode apply task.
_ _Goal:* A decrease in this duration indicates the I/O bottleneck is being relieved.
* column_partial_update_apply_total: The total count of apply operations.
Memory Health
* starrocks_be_update_mem_bytes: Monitor how much memory the UpdateManager is consuming. If it's hitting the update_memory_limit_percent, the PK index might be competing with the Apply process for memory.
* starrocks_be_storage_page_cache_mem_bytes: Verify the Page Cache is actually growing to your new limit.
Summary Recommendation
1. Check current hit rate: Access /api/datacache/app_stat. If the page_cache_hit_rate is low (< 50%) during updates, the I/O wait is definitely caused by cache misses.
2. Increase `storage_page_cache_limit`: Move it to 40% and watch column_partial_update_apply_duration_us.
3. Monitor EBS: If iowait is still high, check if your Persistent Index is enabled (enable_persistent_index = true). If the PK index is too large for memory, it will also cause random I/O reads during the lookup phase 1 of the apply.
References
• data_source/data_cache_observe.md
• using_starrocks/caching/query_cache.md
• knowledge/usage/best-practice.md
• administration/management/monitoring/metrics.md
• using_starrocks/caching/block_cache.md
• data_source/data_cache.mdMinn Fong
06/30/2026, 3:18 PMRocky
06/30/2026, 3:19 PMstorage_page_cache_limit will help reduce CPU iowait for row mode partial updates.
In StarRocks Primary Key tables, Row Mode partial updates use a "Read-on-Write" mechanism. When you update only a few columns, the BE must fetch the full existing row (all columns) from disk to merge it with the new data before writing the new version. This process is I/O intensive. If your nodes have memory headroom, increasing the Page Cache allows StarRocks to keep more data pages in memory, turning those EBS reads into memory hits.
1. Key Knobs for Row Mode Partial Updates
If you have headroom, adjust these configurations in `be.conf`:
* storage_page_cache_limit: Increase this from 20% to 35-45%. This is the most direct way to reduce EBS read throughput by caching the columnar pages needed for the "Read" part of the merge.
* update_memory_limit_percent: Ensure this is at least 60% (default). This pool handles the UpdateManager (PK Index and DelVectors). If the PK Index is too large and spills to disk, it adds significant random I/O.
* enable_row_cache & row_cache_mem_limit:
* If your table is a Row-Columnar Hybrid (created with "row_store" = "true"), you should enable the Row Cache.
* Row Cache is significantly more efficient than Page Cache for partial updates because it stores the reconstructed row directly, avoiding the need to read multiple columnar pages.
* transaction_apply_worker_count: If I/O wait is still spiking, you can limit the concurrency of the apply phase to smooth out the EBS throughput pressure, though this may increase the "Publish Version" latency.
2. Metrics to Observe (Before vs. After)
Monitor these metrics to validate the impact:
Metric | Target Change | Purpose
page_cache_hit_rate | Increase | View via http://<BE_IP>:<BE_HTTP_PORT>/api/datacache/app_stat. Higher is better.
starrocks_be_disks_read_bytes | Decrease | Measures actual throughput on EBS. Should drop as cache hits rise.
update_rowset_commit_apply_duration_us | Decrease | The total time spent in the Apply phase. This should drop as I/O wait decreases.
starrocks_be_update_mem_bytes | Monitor | Ensure the update pool isn't hitting its limit, causing PK index spills.
Pro-Tip: Consider Row Store
If your workload consists of frequent row-mode partial updates and you are hit by EBS limits, the best long-term solution is to enable the Row Store for that table:
sql
ALTER TABLE your_table SET ("row_store" = "true");
This creates a separate row-based file that is optimized for fetching full rows. When combined with enable_row_cache = true in be.conf, it can reduce the I/O requirement for partial updates by 10x or more.
References
• table_design/table_types/primary_key_table.md
• administration/management/monitoring/metrics.md
• loading/Load_to_Primary_Key_tables.md
• administration/management/BE_configuration.md
References
• loading/Load_to_Primary_Key_tables.md
• sql-reference/sql-statements/table_bucket_part_index/UPDATE.md
• loading/loading_introduction/feature-support-loading-and-unloading.md
• loading/Spark-connector-starrocks.md
• sql-reference/sql-statements/loading_unloading/BROKER_LOAD.mdRocky
06/30/2026, 3:19 PMMinn Fong
06/30/2026, 3:55 PMRocky
06/30/2026, 3:56 PMstorage_page_cache_limit: Yes, it is mutable. You can update it at runtime without restarting the BE using the HTTP API:
bash
curl -X POST http://<be_ip>:<be_http_port>/api/update_config?storage_page_cache_limit=40
* partial_update_memory_limit_per_worker: Yes, it is mutable.
* update_memory_limit_percent: No. This is typically not mutable at runtime in most StarRocks versions (v3.2.0 introduced a partial callback, but for the global memory tracker to adjust correctly, a restart is recommended and usually required for this specific parameter).
2. Is increasing them beyond defaults needed?
If you are seeing high iowait (10-20%) and have headroom, the defaults are likely too conservative for your workload:
* update_memory_limit_percent (Default 60%): You usually don't need to increase this unless your memory tracker shows the update category is hitting its limit. If it is hitting 60% of BE memory, your Primary Key Index might be spilling to disk (look for persistent_index_read metrics). If you have plenty of RAM, keeping it at 60% is fine; only increase it if you have very little else (like queries) running on the BE.
* partial_update_memory_limit_per_worker (Default 2GB): This is worth increasing if you have wide rows or large batches. If this limit is hit, StarRocks flushes chunks to disk prematurely during the "Apply" phase, which adds to your iowait. If you have 256GB+ RAM nodes, increasing this to 4GB or 8GB can help.
3. How many partial update workers are there?
The number of workers performing the "Apply" (the phase where the merge happens) is controlled by:
* transaction_apply_worker_count:
* Default: 0 (which means it automatically scales to the number of CPU cores on your BE node).
* If your node has 32 cores, you have 32 apply workers.
* Metric to watch: starrocks_be_update_apply_thread_pool_queue_size. If this queue is consistently high, your I/O is too slow to keep up with the workers, or you need more workers (though more workers usually increases iowait further if disk throughput is the bottleneck).
Recommendations for your specific situation:
1. Increase storage_page_cache_limit to 40% first (it's mutable and safe).
2. Monitor the PK Index: If you are in Row Mode, the I/O might also be coming from the Persistent Index if it's too large. Check the update_primary_index_num and memory usage.
3. Check l0_max_mem_usage: Since you are in Row Mode, ensure this is high enough (e.g., 512MB-1GB) to keep the index for the most active data in memory.
Summary: Update storage_page_cache_limit via curl now. If iowait stays high, check if your EBS is hitting its IOPS/throughput limit and consider if the Row Store ("row_store" = "true") is a viable long-term architectural change for this table.
References
• administration/management/BE_configuration.md
• administration/management/monitoring/metrics.md
• data_source/data_cache.md
• administration/management/resource_management/Memory_management.mdMinn Fong
06/30/2026, 3:59 PMSTARROCKS-CORE-SERVICES-BE-2 174.129.117.240 // (172.30.75.179) ~ $ curl <http://starrocks-core-services-be-2.us-east-1.stackadapt:8040/api/datacache/app_stat>
{
"block_cache_hit_bytes": 0,
"block_cache_miss_bytes": 0,
"block_cache_hit_rate": 0.0,
"block_cache_hit_bytes_last_minute": 0,
"block_cache_miss_bytes_last_minute": 0,
"block_cache_hit_rate_last_minute": 0.0,
"page_cache_hit_count": 3492073286,
"page_cache_miss_count": 10706420266,
"page_cache_hit_rate": 0.25,
"page_cache_hit_count_last_minute": 167,
"page_cache_miss_count_last_minute": 412110,
"page_cache_hit_rate_last_minute": 0.0
}Rocky
06/30/2026, 4:00 PMstorage_page_cache_limit is often the wrong lever because it caches columnar pages.
The most effective configuration for Row Mode is enabling the Row Store and the Row Cache:
* Row Store ("row_store" = "true"): When you create or alter the table, setting this enables a secondary row-based storage format. This allows StarRocks to fetch the "full row" in a single I/O operation rather than scanning dozens of columnar pages.
* Row Cache: Once Row Store is enabled, StarRocks uses a dedicated Row Cache (not the Page Cache) to store these rows in memory.
Check these instead:
* enable_row_cache: Must be true in be.conf.
* row_cache_mem_limit: Set this to a specific value (e.g., 20G or 10%).
* Stats: Check the Row Cache hit rate via curl http://<BE_IP>:8040/api/row_cache_stat.
3. Are the knobs mutable?
* storage_page_cache_limit: Yes, it is mutable. Your change to 40% took effect immediately, but if your "working set" (the amount of data being updated) is larger than that 40%, you will still see a 0% hit rate because the pages are evicted before they can be reused.
* partial_update_memory_limit_per_worker: Yes, it is mutable.
* update_memory_limit_percent: No, this usually requires a restart to re-allocate the global memory tracker pools correctly.
4. How many partial update workers?
The "Apply" phase (where the reads and hits happen) is handled by a thread pool:
* Metric: starrocks_be_update_apply_thread_pool_queue_size.
* Count: Controlled by transaction_apply_worker_count (Default 0 = number of CPU cores).
* If your iowait is high and your hit rate is 0%, your EBS is likely being choked by the number of parallel "Read-on-Write" workers. Reducing transaction_apply_worker_count to a fixed number (e.g., 8 or 16) might actually decrease your iowait by reducing disk contention, even if it slightly slows down the "Apply" speed.
Summary Recommendation
1. Enable Row Store: If you haven't, ALTER TABLE ... SET ("row_store" = "true"). This is the "silver bullet" for Row Mode partial update I/O issues.
2. Verify Row Cache: Ensure enable_row_cache = true and check api/row_cache_stat.
3. Smooth the load: If hits stay at 0%, your updates are likely too random/sparse for a cache to help. In that case, focus on EBS throughput or reducing the transaction_apply_worker_count to prevent the disk from being overwhelmed.
References
• administration/management/monitoring/metrics.md
• using_starrocks/caching/query_cache.md
• administration/management/BE_configuration.mdRocky
06/30/2026, 4:00 PMMinn Fong
06/30/2026, 4:18 PMRocky
06/30/2026, 4:18 PMrow_store = true will likely push your disk usage past its limits if you are already at 50%.
1. The Storage Overhead of Row Store
When you set "row_store" = "true", StarRocks creates a redundant copy of your data in a row-based format alongside the existing columnar data.
* Double Storage: You are effectively storing the data twice.
* Compression Gap: Columnar storage is highly efficient (often 3x–5x compression). Row-based storage is much harder to compress (often only 1x–1.5x).
* Reality Check: Because the row store compresses poorly, it often takes up more space than the original columnar data. Adding it to an existing table can easily increase total disk usage by 2x to 3x.
If you are at 50% usage now, enabling Row Store will almost certainly trigger "Disk Full" or high-watermark alerts on your BE nodes.
2. Why your Page Cache hit rate is 25% (and 0% last minute)
The low hit rate confirms your current Row Mode updates are "thrashing" the cache.
* Read-on-Write: Since you don't have the Row Store, every partial update requires reading multiple columnar pages to reconstruct a full row.
* Random Access: If your updates are spread across a large table, each batch of updates pulls in "cold" pages that haven't been cached. These pages are then evicted by the next batch before they can be reused.
* High Churn: Your "0.0% last minute" hit rate means almost every single page needed for an update had to be fetched from EBS.
3. Recommendations (If you can't use Row Store)
Since you lack the disk space for a physical Row Store, you must optimize the logical "Row Mode" performance:
* Throttle Concurrency: Reduce transaction_apply_worker_count (default is your CPU core count). If your EBS is hitting IOPS/throughput limits, having 32 workers all trying to "Read-on-Write" at once makes the iowait worse. Lowering this to 8 or 16 may actually speed up the overall ingestion by reducing disk contention.
* Batch Size: Ensure your partial update batches are as large as possible. Small, frequent commits in Row Mode are the primary cause of high I/O overhead.
* Scale Vertically (Disk): If possible, upgrade your EBS volumes to io2 or increase the gp3 throughput/IOPS settings. Row Mode partial updates are fundamentally limited by random read performance.
* Scale Horizontally: Adding more BE nodes will spread the I/O load and the memory/cache pressure across more disks, which is the standard way to handle high-volume PK updates in StarRocks.
Summary: Do not enable row_store = true at 50% capacity. It will solve your iowait but will likely crash the cluster due to disk exhaustion. Stick to tuning transaction_apply_worker_count and increasing your EBS throughput.
References
• table_design/table_types/primary_key_table.md
• best_practices/primarykey_table.md
• administration/management/BE_configuration.md
• deployment/plan_cluster.mdMinn Fong
06/30/2026, 4:27 PMRocky
06/30/2026, 4:27 PMtransaction_apply_worker_count scales to your CPU cores. On a 32-core machine, you have 32 workers simultaneously trying to "Read-on-Write." If your EBS volume is a gp3 with standard throughput, 32 parallel random-read workers will saturate the I/O queue instantly, leading to the high iowait you see.
* Action: Decrease transaction_apply_worker_count.
* Goal: Start at 8 or 16. Counter-intuitively, reducing the number of workers often increases throughput because it reduces disk head contention and allows the Page Cache to be slightly more effective for each worker.
* Command: curl -X POST http://<be_ip>:8040/api/update_config?transaction_apply_worker_count=16
2. Prevent "Memory Thrashing" during Apply
In v3.2.10+, StarRocks introduced partial_update_memory_limit_per_worker. With 550 columns, the "source chunk" (the data being assembled for the update) is very large in memory. If this limit is hit, the worker flushes a partial chunk to disk and starts over, effectively doubling your I/O.
* Action: Increase partial_update_memory_limit_per_worker to 4GB or 8GB.
* Goal: Ensure that the "Apply" phase for a single tablet can happen entirely in memory without intermediate disk spills.
* Command: curl -X POST http://<be_ip>:8040/api/update_config?partial_update_memory_limit_per_worker=4294967296
3. Aggressive Persistent Index Tuning
With 5.5 billion rows, your Primary Key Index is likely 150GB–300GB+ per BE. If this index is not purely in memory (which it likely isn't given your 10TB scale), StarRocks uses the Persistent Index.
* The Bottleneck: If the Persistent Index is on the same EBS volume as your data, the I/O for the index lookups is competing with the I/O for the "Read-on-Write" row reconstruction.
* Recommendation: If you have any local instance storage (NVMe) on your BE nodes, move the Persistent Index there by setting storage_root_path to include an [SSD] tag for the local drive.
* Config: Check l0_max_mem_usage. Increasing this (e.g., to 512MB or 1GB) helps keep more of the "Level 0" index (the most recent updates) in RAM.
4. Optimize the "Working Set"
The 0.0% last-minute hit rate means your 1-minute batches are hitting completely random parts of the 10TB dataset.
* Batching Strategy: If possible, sort your input data by the Primary Key before sending it to StarRocks.
* Why? If the batch is sorted by PK, multiple updates in that batch will likely fall into the same Tablet and even the same Page. This allows the Page Cache to actually work, as the worker will find the required columnar pages already cached from the previous row in the same batch.
5. Horizontal Scaling is the only "Final" Solution
At 10TB and 5.5B rows, you are likely hitting the physical limits of what a single BE node's I/O path can handle for "Read-on-Write" operations.
* If you cannot add disk for Row Store, you must add more BE nodes.
* Adding nodes divides the 5.5B PKs across more RAM (reducing Persistent Index disk I/O) and divides the 10TB of data across more EBS bandwidth.
Summary of immediate actions:
1. Lower transaction_apply_worker_count to 8 or 12.
2. Raise partial_update_memory_limit_per_worker to 4GB.Rocky
06/30/2026, 4:27 PMMinn Fong
06/30/2026, 4:29 PMRocky
06/30/2026, 4:30 PMenable_persistent_index = true, the BE maintains an internal cache for the index. You can find these metrics at the BE metrics endpoint: http://<BE_IP>:8040/metrics.
Look for these two specific counters:
* starrocks_be_persistent_index_read_cache_hit_count: The number of times a PK lookup was satisfied by the index's internal memory cache.
* starrocks_be_persistent_index_read_count: The total number of PK lookup requests.
Calculation:
PK Index Hit Rate =2. General Primary Key Index Metrics You can also monitor the overall health and memory usage of the PK indexes with these metrics: */persistent_index_read_cache_hit_countpersistent_index_read_count
update_primary_index_num: The number of Primary Key indexes (tablets) currently cached or active in memory.
* update_primary_index_bytes_total: The total memory consumed by all PK indexes on that BE.
* primary_key_index_loading_latency: If this is high, it indicates that the BE is struggling to load the PK index from disk into memory, which delays the "Apply" phase of your partial updates.
3. Diagnosing Your Specific Scenario
In your earlier logs, your Page Cache hit rate was 25% (and 0% last minute). It is important to distinguish between the two:
* PK Index Cache: Stores the mapping of Primary Key -> Row Location. If this hit rate is low, your lookups are slow because the BE is reading the index from disk.
* Page Cache: Stores the actual data pages. Even if your PK Index hit rate is 100% (the BE knows exactly where the row is), it still has to go to disk to fetch the data pages to perform the Read-on-Write merge.
Recommendation:
If your persistent_index_read_cache_hit_count is low, you may need to increase the memory allocated to the persistent index cache by adjusting:
persistent_index_read_cache_capacity (default is usually a percentage of memory).
However, at your 550-column width, the Page Cache (data) is almost always the primary bottleneck for partial updates, not the Index cache.
References
• administration/management/monitoring/metrics.md
• table_design/table_types/primary_key_table.md
• data_source/data_cache_observe.md
• administration/management/BE_configuration.md
References
• administration/management/monitoring/metrics.md
• data_source/data_cache_observe.md