StarRocks Bucketing (Data Distribution)
When to Use
Load this skill when the user needs to:
- Design a new StarRocks table and choose distribution method and bucket count
- Investigate tablet skew or uneven data distribution across BEs
- Set up colocate joins to eliminate shuffle for frequently joined tables
- Change the bucket count of an existing table after data growth
- Choose between
DISTRIBUTED BY HASHandDISTRIBUTED BY RANDOM - Understand the auto bucket sizing feature (StarRocks 3.2+)
- Diagnose join performance issues where EXPLAIN shows
SHUFFLEinstead ofCOLOCATE
Bucketing Concepts
What a Bucket Is
In StarRocks, bucket = tablet. A tablet is the fundamental unit of:
- Parallel scan: each tablet is processed by one BE thread independently
- Data storage: data files (segments) physically live inside a tablet
- Load balancing: the FE distributes tablets evenly across BE nodes
When a table has N buckets, the data is split into exactly N tablets per partition. For non-partitioned tables, there are exactly N tablets total.
How Hash Bucketing Works
INSERT row → hash(distribution_key_columns) → bucket_id = hash_value % bucket_count
- Every row with the same distribution key value always lands in the same tablet
- Tablets are assigned to BEs by the FE tablet scheduler
- Each BE hosts
bucket_count / BE_counttablets on average (assuming even assignment)
Relationship Between Buckets and BE Nodes
BE_1: tablet_0, tablet_3, tablet_6, ...
BE_2: tablet_1, tablet_4, tablet_7, ...
BE_3: tablet_2, tablet_5, tablet_8, ...
- A query that touches all tablets will fan out to all BEs — full parallel scan
- If
bucket_count < BE_count, some BEs will have no work for that table — wasted parallelism - If
bucket_count >> BE_count, each BE handles many tablets — more overhead per query but better parallelism for large scans
Tablet Sizing Target
StarRocks documentation recommends keeping each tablet between 1 GB and 10 GB of compressed data. The sweet spot is ~5 GB per tablet.
- Tablets smaller than 1 GB: too many metadata operations, excessive tablet scheduling overhead
- Tablets larger than 10 GB: compaction pressure, slower individual tablet scans, harder to rebalance
DISTRIBUTED BY HASH
Syntax
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(32),
amount DECIMAL(18, 2),
created_at DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(order_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
"replication_num" = "3"
);
For a composite distribution key:
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
item_id BIGINT NOT NULL,
sku_id BIGINT,
quantity INT,
unit_price DECIMAL(18, 2)
)
ENGINE = OLAP
DUPLICATE KEY(order_id, item_id)
DISTRIBUTED BY HASH(order_id, item_id) BUCKETS 64
PROPERTIES (
"replication_num" = "3"
);
Distribution Key Selection Rules
Rule 1 — High cardinality (millions of distinct values)
The distribution key must spread rows evenly across buckets. Low-cardinality keys create skew.
-- GOOD: user_id has millions of distinct values → even distribution
DISTRIBUTED BY HASH(user_id) BUCKETS 32
-- BAD: status has ~10 distinct values → most rows pile into a few buckets
DISTRIBUTED BY HASH(status) BUCKETS 32
-- BAD: country_code for a dataset with 80% traffic from one country
DISTRIBUTED BY HASH(country_code) BUCKETS 32
Rule 2 — Frequently used in JOIN ON conditions
Rows that join together should land on the same BE to enable local joins without shuffle. Choose the column(s) that appear in the most common JOIN ON clause.
-- If queries frequently do: orders JOIN order_items ON orders.order_id = order_items.order_id
-- Then distribute both tables on order_id
CREATE TABLE orders (...) DISTRIBUTED BY HASH(order_id) BUCKETS 32;
CREATE TABLE order_items (...) DISTRIBUTED BY HASH(order_id) BUCKETS 32;
-- Now joins on order_id are colocate-eligible
Rule 3 — Avoid skewed columns
Do not use columns where a small number of values account for a large percentage of rows.
Common skew traps:
status(e.g., 95% rows have status = 'active')country_codefor datasets with one dominant countrytenant_idfor single-tenant SaaS deploymentsevent_typefor heavily imbalanced event streamsis_deletedboolean columns
Rule 4 — Use composite keys when no single high-cardinality column exists
-- Neither order_id nor product_id alone is skewed,
-- but together they have very high cardinality and match the JOIN pattern
DISTRIBUTED BY HASH(order_id, product_id) BUCKETS 48
Rule 5 — Distribution key should be immutable
StarRocks does not support updating the distribution key columns of an existing row without a full rewrite. Choose keys that never change (IDs, not mutable status/date fields).
Verifying Distribution Evenness
Check tablet distribution across BEs after loading data:
-- Show all tablets for a table with their BE assignment and size
SHOW TABLET FROM orders;
-- Output columns include:
-- TabletId, ReplicaId, BackendId, SchemaHash, Version, DataSize, RowCount
-- Identify skewed tablets (large DataSize outliers)
-- If one tablet has 10x the DataSize of others, the distribution key is skewed
To get per-BE size aggregates, query the information_schema system table:
-- Tablet size distribution across BEs for a specific table
SELECT
be_id,
COUNT(*) AS tablet_count,
SUM(data_size) / 1073741824.0 AS total_size_gb,
AVG(data_size) / 1073741824.0 AS avg_tablet_size_gb,
MAX(data_size) / 1073741824.0 AS max_tablet_size_gb
FROM information_schema.be_tablets
WHERE table_name = 'orders'
AND db_name = 'sales'
GROUP BY be_id
ORDER BY total_size_gb DESC;
DISTRIBUTED BY RANDOM (StarRocks 2.5.7+)
Syntax
CREATE TABLE events_log (
event_id BIGINT,
event_type VARCHAR(64),
payload VARCHAR(65535),
event_time DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(event_id)
DISTRIBUTED BY RANDOM BUCKETS 16
PROPERTIES (
"replication_num" = "3"
);
When to Use RANDOM Distribution
Random distribution assigns rows to buckets using an internal counter or random assignment rather than a hash. Use it when:
- Append-only ingestion with no join requirements — event logs, raw CDC streams, sensor data
- The table will never be joined to other tables on a common key
- All columns are skewed and no suitable hash key exists
- Write performance matters most — random distribution avoids hot-bucket writes for skewed-key insert workloads
Trade-offs
| Aspect | HASH | RANDOM |
|---|---|---|
| Write distribution | Even only if key is high-cardinality | Always even |
| Colocate join eligible | Yes | No |
| Local JOIN performance | Best (with colocate) | Requires shuffle |
| Skew risk | Yes (depends on key choice) | None |
| Suitable for | Dimension tables, fact tables with joins | Append-only logs, wide tables |
Bucket Count Selection
Formula
bucket_count = max(BE_count, ceil(estimated_compressed_size_GB / tablet_target_size_GB))
Where:
BE_count= number of BE nodes in the clusterestimated_compressed_size_GB= expected compressed table size in GBtablet_target_size_GB= 5 (recommended; acceptable range: 1–10)
The max() ensures at least one tablet per BE for full parallelism.
Quick Reference by Table Size
| Estimated Table Size | 3 BEs | 6 BEs | 10 BEs | 20 BEs |
|---|---|---|---|---|
| < 1 GB (small dim) | 4 | 6 | 10 | 20 |
| 1 – 10 GB | 4 | 6 | 10 | 20 |
| 10 – 50 GB | 10 | 12 | 12 | 20 |
| 50 – 100 GB | 20 | 20 | 20 | 24 |
| 100 – 500 GB | 32 | 48 | 48 | 64 |
| 500 GB – 1 TB | 64 | 96 | 100 | 128 |
| 1 – 5 TB | 128 | 192 | 200 | 256 |
| > 5 TB | 256 | 256 | 512 | 512 |
Note: round up to a power of 2 or a multiple of BE count for even distribution.
Worked Examples
Example 1: 3 BE nodes, 30 GB table
bucket_count = max(3, ceil(30 / 5)) = max(3, 6) = 6 → use 8 (nearest power-of-2 ≥ 6)
Example 2: 10 BE nodes, 400 GB table
bucket_count = max(10, ceil(400 / 5)) = max(10, 80) = 80 → use 80 (multiple of 10)
Example 3: 6 BE nodes, 500 MB lookup/dimension table
bucket_count = max(6, ceil(0.5 / 5)) = max(6, 1) = 6 → use 6
Minimum Bucket Count Rules
- Always use at least
BE_countbuckets so every BE has work - For small tables (< 1 GB): use exactly
BE_countor a small multiple - Do not use 1 bucket even for tiny tables — this serializes all queries to one BE
Auto Bucket Sizing (StarRocks 3.2+)
StarRocks 3.2 introduced BUCKETS AUTO, which lets the system compute the optimal bucket count at table creation time and re-evaluate it at each partition load.
Syntax
CREATE TABLE events (
event_id BIGINT,
user_id BIGINT,
event_type VARCHAR(64),
event_time DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(event_id)
PARTITION BY RANGE(event_time)(
PARTITION p20240101 VALUES LESS THAN ("2024-01-02"),
PARTITION p20240102 VALUES LESS THAN ("2024-01-03")
)
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO
PROPERTIES (
"replication_num" = "3",
"autobucket_size_gb" = "5" -- target tablet size in GB; default 10
);
How Auto Bucketing Works
- At table creation, StarRocks creates an initial bucket count based on
BE_count - When a new partition is created (or data is loaded into the first partition), StarRocks estimates the data size
- It calculates
bucket_count = ceil(partition_size_GB / autobucket_size_gb)with a floor ofBE_count - Subsequent partitions may get different bucket counts if data volume changes significantly
Configuring the Target Tablet Size
-- Set globally (FE configuration)
-- In fe.conf:
-- autobucket_size_gb = 10
-- Per-table override via PROPERTIES
PROPERTIES (
"autobucket_size_gb" = "5"
)
-- For RANDOM distribution with auto bucketing
DISTRIBUTED BY RANDOM BUCKETS AUTO
PROPERTIES (
"autobucket_size_gb" = "5"
)
When to Use Auto vs. Manual Bucketing
- Use AUTO for new tables where data volume is uncertain or grows over time
- Use manual when you have precise size estimates and want full control
- Use manual for colocate join groups — all tables in the group must have the same bucket count, and AUTO may diverge between tables
Skew Detection and Prevention
Detecting Skew
Step 1: Check overall tablet size distribution for a table
SELECT
tablet_id,
be_id,
data_size / 1073741824.0 AS size_gb,
row_count
FROM information_schema.be_tablets
WHERE table_name = 'orders'
AND db_name = 'sales'
ORDER BY data_size DESC
LIMIT 20;
Step 2: Compute skew ratio
SELECT
MAX(data_size) AS max_tablet_bytes,
AVG(data_size) AS avg_tablet_bytes,
MAX(data_size) / NULLIF(AVG(data_size), 0) AS skew_ratio
FROM information_schema.be_tablets
WHERE table_name = 'orders'
AND db_name = 'sales';
-- skew_ratio > 3.0 is problematic; > 10.0 is severe
Step 3: Find the skew-causing key value (for HASH distribution)
If skew ratio exceeds 3x, find which distribution key values are overrepresented:
-- Find top distribution key values by row count
SELECT
user_id,
COUNT(*) AS row_count
FROM orders
GROUP BY user_id
ORDER BY row_count DESC
LIMIT 20;
-- If one user_id has billions of rows, that key value is causing skew
Skew Thresholds
| Skew Ratio (max / avg) | Assessment | Action |
|---|---|---|
| 1.0 – 1.5 | Healthy | None |
| 1.5 – 3.0 | Acceptable | Monitor |
| 3.0 – 10.0 | Problematic | Investigate and fix distribution key |
| > 10.0 | Severe | Immediate redesign required |
Preventing Skew
Strategy 1: Add a secondary high-cardinality column to the composite distribution key
-- Instead of just user_id (skewed for large users):
DISTRIBUTED BY HASH(user_id, order_id) BUCKETS 64
-- order_id adds entropy; large users still produce many rows but spread across buckets
Strategy 2: Switch to RANDOM if no join is needed
-- For write-heavy, query-light log tables:
DISTRIBUTED BY RANDOM BUCKETS 32
Strategy 3: Filter skewed keys before writing to a hot table and route them separately
-- Write the 99th-percentile users to a separate table with RANDOM distribution
-- Route analytical queries to union both tables
Changing Bucket Count (ALTER TABLE)
Syntax
StarRocks 3.2+ supports modifying the distribution of existing tables. This rewrites the table data.
-- Change bucket count for a non-partitioned table
ALTER TABLE orders
DISTRIBUTED BY HASH(user_id) BUCKETS 64;
-- Change both the distribution key and bucket count
ALTER TABLE orders
DISTRIBUTED BY HASH(user_id, order_id) BUCKETS 128;
-- Change to RANDOM distribution
ALTER TABLE orders
DISTRIBUTED BY RANDOM BUCKETS 32;
For partitioned tables, StarRocks rewrites all partitions:
-- Increase bucket count after data growth
ALTER TABLE events_partitioned
DISTRIBUTED BY HASH(user_id) BUCKETS 128;
When to Change the Bucket Count
- After significant data growth: table has grown 5× since original design and tablets exceed 10 GB
- After adding BE nodes: the original bucket count is now a bottleneck (e.g., 8 buckets on 20 BEs)
- After discovering severe skew: the distribution key was poorly chosen
- Before setting up a colocate join group: both tables must have the same bucket count
Operational Considerations
- Data rewrite:
ALTER TABLE ... DISTRIBUTED BYrewrites all table data. This is a heavy operation. - Disk space: temporarily requires 2× disk space (original + rewritten data)
- Concurrent queries: queries continue to work against the original data during rewrite (StarRocks uses MVCC-style visibility)
- Duration: proportional to table size; plan during off-peak hours for large tables
- Monitoring progress: use
SHOW ALTER TABLEto track the operation status
-- Monitor ALTER TABLE progress
SHOW ALTER TABLE COLUMN FROM sales_db;
-- or for distribution changes:
SHOW ALTER TABLE DISTRIBUTED FROM sales_db;
Colocate Join
What Colocate Join Does
Normally, joining two tables requires a shuffle step: the FE inserts exchange nodes into the query plan to redistribute rows to the correct BE before the join executes. This causes network I/O and reduces parallelism.
With colocate join, two tables in the same colocate group are guaranteed to have:
- The same distribution key columns and types
- The same bucket count
- The same replication factor
This means rows with the same join key value are always on the same BE in both tables. The FE can emit a local join (no shuffle) for any query joining those tables on their distribution key.
Creating a Colocate Group
-- Table 1: orders — distribution key = user_id, 32 buckets
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(32),
amount DECIMAL(18, 2),
created_at DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(order_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
"replication_num" = "3",
"colocate_with" = "user_group" -- colocate group name
);
-- Table 2: user_profiles — must use SAME distribution key type, SAME bucket count, SAME replication_num
CREATE TABLE user_profiles (
user_id BIGINT NOT NULL,
username VARCHAR(128),
email VARCHAR(256),
region VARCHAR(64),
created_at DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
"replication_num" = "3",
"colocate_with" = "user_group" -- same group name
);
-- Table 3: user_events — same group
CREATE TABLE user_events (
event_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
event_type VARCHAR(64),
event_time DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(event_id, user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
"replication_num" = "3",
"colocate_with" = "user_group"
);
Colocate Group Requirements
All tables in the same colocate group MUST have:
| Requirement | Example Compliant | Example Non-Compliant |
|---|---|---|
| Same distribution key column count | Both use 1 column | One uses 1, other uses 2 |
| Same distribution key column types | Both use BIGINT |
One uses BIGINT, other VARCHAR |
| Same bucket count | Both use 32 | One uses 32, other uses 64 |
| Same replication factor | Both use "replication_num" = "3" |
One uses 3, other uses 1 |
| Same engine type | Both OLAP |
Mixed OLAP / MYSQL |
If any requirement is violated, StarRocks rejects the table creation with an error about incompatible colocate group properties.
Checking Colocate Group Status
-- List all colocate groups and their status
SHOW PROC '/colocation_group';
-- Output columns:
-- GroupId, GroupName, TableIds, BucketsNum, ReplicationNum,
-- DistributionCols, IsStable
-- A group is "stable" when all its tablet replicas are properly placed.
-- An "unstable" group cannot use colocate join until rebalancing completes.
Example output interpretation:
GroupId: 12345
GroupName: sales_db.user_group
TableIds: [101, 102, 103]
BucketsNum: 32
ReplicationNum: 3
DistributionCols: [user_id]
IsStable: true ← colocate join is active
If IsStable = false, the FE falls back to shuffle join automatically.
Verifying Colocate Join in Query Plans
-- Use EXPLAIN to verify the join strategy
EXPLAIN SELECT
o.order_id,
o.amount,
u.username,
u.region
FROM orders o
JOIN user_profiles u ON o.user_id = u.user_id
WHERE o.created_at >= '2024-01-01';
Look for the join node type in the output:
-- GOOD: colocate join active (no shuffle)
HASH JOIN (join op: INNER JOIN (COLOCATE))
| equal join conjunct: o.user_id = u.user_id
-- BAD: shuffle join (colocate group unstable or tables not in same group)
HASH JOIN (join op: INNER JOIN (BUCKET_SHUFFLE))
| equal join conjunct: o.user_id = u.user_id
-- ALSO BAD: broadcast join (small table broadcast, not colocate)
HASH JOIN (join op: INNER JOIN (BROADCAST))
| equal join conjunct: o.user_id = u.user_id
Enabling and Disabling Colocate Join
-- Enable colocate join at session level (default is enabled)
SET enable_colocate_join = true;
-- Disable colocate join for a specific session (debugging)
SET enable_colocate_join = false;
-- Disable globally in FE config (not recommended)
-- In fe.conf: enable_colocate_join = false
Colocate Group Rebalancing
When the cluster topology changes (BE added, BE removed, BE crashes), the colocate group may become unstable because tablet placement no longer satisfies the colocate constraint (tablets for the same bucket index must be on the same set of BEs for all tables in the group).
StarRocks automatically rebalances colocate groups via the tablet scheduler. During rebalancing:
- The group is marked
IsStable = false - All queries fall back to shuffle join
- No manual intervention is required
To check rebalancing progress:
-- Monitor tablet migration status
SHOW PROC '/tablet_scheduler';
-- Check group stability
SHOW PROC '/colocation_group';
-- View backend status to confirm all BEs are alive
SHOW BACKENDS;
To manually trigger re-evaluation of a colocate group (after BE recovery):
-- No direct SQL command; the scheduler runs automatically.
-- Ensure all BEs are ALIVE via SHOW BACKENDS.
-- The FE will automatically schedule migrations to restore colocate placement.
Removing a Table from a Colocate Group
-- Remove a table from its colocate group by clearing the property
ALTER TABLE orders SET ("colocate_with" = "");
-- Move a table to a different colocate group
ALTER TABLE orders SET ("colocate_with" = "new_group");
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
DISTRIBUTED BY HASH(status) BUCKETS 16 — low cardinality key |
Most rows hash to a few buckets; massive skew; one BE does all the work for JOIN | Use a high-cardinality column: HASH(user_id) |
BUCKETS 2 on a 10-BE cluster |
Only 2 tablets → 8 BEs idle for every query on this table; no parallelism | Use at least BE_count buckets |
BUCKETS 1024 on a 1 GB table |
1024 tablets × 3 replicas = 3072 tiny tablet files; metadata explosion; slow compaction | Use max(BE_count, ceil(1/5)) = BE_count |
| Different bucket counts for colocate tables | Colocate group is permanently unstable; all joins shuffle | Ensure all tables in the group have the same bucket count |
| Different distribution key types in colocate group | Group creation fails or group is unstable | Match types exactly: both BIGINT, not one INT and one BIGINT |
Using DISTRIBUTED BY RANDOM for tables you join |
Cannot use colocate join; every join shuffles | Use HASH on the join key for tables that participate in joins |
| Never revisiting bucket count after 10× data growth | Tablets grow to 50–100 GB; compaction stalls; slow scans | Periodically check information_schema.be_tablets and run ALTER TABLE ... DISTRIBUTED BY when tablets exceed 10 GB |
Choosing distribution key that changes (e.g., updated_at) |
Updates require delete + reinsert with a new bucket assignment; unpredictable behavior | Use immutable identifier columns (IDs, UUIDs) as distribution key |
Using BUCKETS AUTO in a colocate group |
AUTO may assign different bucket counts for different partitions across tables, breaking colocate constraint | Use explicit BUCKETS N for all tables in a colocate group |
| Joining colocate tables on a non-distribution column | StarRocks cannot use colocate join; falls back to shuffle | The JOIN ON column must match the distribution key column for colocate to activate |
Production Workflow: Initial Table Design
-- Step 1: Identify join patterns
-- Most frequent join: orders JOIN users ON orders.user_id = users.user_id
-- Most frequent join: orders JOIN order_items ON orders.order_id = order_items.order_id
-- Step 2: Estimate data sizes (year 1)
-- orders: ~500 GB compressed
-- users: ~10 GB compressed
-- order_items: ~2 TB compressed
-- Step 3: Calculate bucket counts (6 BE cluster)
-- orders: max(6, ceil(500/5)) = max(6, 100) = 100 → use 96 (multiple of 6)
-- users: max(6, ceil(10/5)) = max(6, 2) = 6 → use 6
-- order_items: max(6, ceil(2000/5)) = max(6, 400) = 400 → use 396 (multiple of 6)
-- Step 4: Create colocate groups
-- Group "order_group": orders + order_items (same distribution key: order_id, same bucket count needed)
-- Problem: orders needs 96 buckets, order_items needs 396 → can't colocate!
-- Resolution: prioritize the most impactful join; if order_id join is more frequent:
-- Use 128 buckets for both (round up to power-of-2, tablets will be ~4 GB for orders, ~16 GB for items)
-- Or: keep them separate and accept shuffle for order_items join
-- Step 5: Create tables
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(32),
amount DECIMAL(18, 2),
created_at DATETIME
)
ENGINE = OLAP
DUPLICATE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 128
PROPERTIES (
"replication_num" = "3",
"colocate_with" = "order_group"
);
CREATE TABLE order_items (
item_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
sku_id BIGINT,
quantity INT,
unit_price DECIMAL(18, 2)
)
ENGINE = OLAP
DUPLICATE KEY(item_id, order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 128
PROPERTIES (
"replication_num" = "3",
"colocate_with" = "order_group"
);
-- Step 6: Verify colocate group is stable
SHOW PROC '/colocation_group';
-- Step 7: Verify with EXPLAIN
EXPLAIN SELECT o.order_id, SUM(i.unit_price * i.quantity) AS revenue
FROM orders o
JOIN order_items i ON o.order_id = i.order_id
WHERE o.created_at >= '2024-01-01'
GROUP BY o.order_id;
-- Expect: HASH JOIN (COLOCATE) in plan
Monitoring Bucket Health (Ongoing)
-- Weekly health check: tablet size distribution
SELECT
db_name,
table_name,
COUNT(*) AS tablet_count,
ROUND(AVG(data_size) / 1073741824.0, 2) AS avg_size_gb,
ROUND(MAX(data_size) / 1073741824.0, 2) AS max_size_gb,
ROUND(MIN(data_size) / 1073741824.0, 2) AS min_size_gb,
ROUND(MAX(data_size) / NULLIF(AVG(data_size), 0), 2) AS skew_ratio
FROM information_schema.be_tablets
WHERE db_name NOT IN ('_statistics_', 'information_schema')
GROUP BY db_name, table_name
HAVING avg_size_gb > 0.1 -- skip trivially small tables
ORDER BY max_size_gb DESC;
-- Alert thresholds:
-- max_size_gb > 10 → consider ALTER TABLE to increase bucket count
-- skew_ratio > 3.0 → investigate distribution key skew
-- avg_size_gb < 0.1 → too many buckets; consider consolidation