Loading learning content...
Traditional databases require constant operational attention. As data grows, DBAs manually split tables, redistribute shards, and balance load. When nodes fail, they trigger failovers and repair replication. When traffic patterns change, they reconfigure routing rules.
CockroachDB eliminates this burden through automatic load balancing. The database:
This self-managing behavior is one of CockroachDB's most powerful features. A 3-node cluster and a 300-node cluster operate the same way—you just have more capacity.
Understanding how this works helps you:
By the end of this page, you will understand the range abstraction, how leaseholders coordinate access, how CockroachDB splits and merges ranges, the rebalancing algorithms that distribute load, and how to configure policies for your specific requirements.
All data in CockroachDB is organized into ranges—contiguous portions of the key space. Understanding ranges is fundamental to understanding how CockroachDB distributes and manages data.
What is a Range?
A range is:
Default range size is approximately 512 MB (64 MB minimum to 512 MB maximum, configurable). When a range exceeds this size, it automatically splits.
Range Key Space:
CockroachDB encodes all data—table rows, indexes, system metadata—into a single sorted key-value store. The key format is:
/Table/{table_id}/{index_id}/{primary_key_columns}/{column_family}
Because keys are sorted, rows from the same table (same table_id) are stored together. Rows with similar primary keys are in the same or adjacent ranges.
Range Metadata:
Each range has metadata stored in a range descriptor:
RANGE ORGANIZATION IN COCKROACHDB═══════════════════════════════════════════════════════════════════ KEY SPACE OVERVIEW:────────────────────────────────────────────────────────────────────The entire keyspace is divided into ranges: Key Space: [min_key ────────────────────────────────────── max_key] ├─ Range 1 ─┤├─ Range 2 ─┤├─ Range 3 ─┤├─ Range 4 ─┤ [a, g) [g, m) [m, t) [t, z] RANGE CONTENTS EXAMPLE:────────────────────────────────────────────────────────────────────Database: mydbTables: users, orders, products Key encoding: /Table/56/1/1 = users table, primary index, row id=1 /Table/56/1/2 = users table, primary index, row id=2 /Table/56/2/... = users table, secondary index /Table/57/... = orders table /Table/58/... = products table Range 42 (example):┌─────────────────────────────────────────────────────────────────┐│ Range 42 ││ Start Key: /Table/56/1/1000 ││ End Key: /Table/56/1/2000 ││ ││ Contents: ││ /Table/56/1/1000 → {id: 1000, name: "Alice", ...} ││ /Table/56/1/1001 → {id: 1001, name: "Bob", ...} ││ /Table/56/1/1002 → {id: 1002, name: "Carol", ...} ││ ... ││ /Table/56/1/1999 → {id: 1999, name: "Zara", ...} ││ ││ Replicas: Node 1 (leaseholder), Node 4, Node 7 ││ Size: 487 MB ││ Raft Leader: Node 1 │└─────────────────────────────────────────────────────────────────┘ RANGE DISTRIBUTION ACROSS NODES:──────────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐│ 5-NODE CLUSTER │├───────────┬───────────┬───────────┬───────────┬───────────────┐│ Node 1 │ Node 2 │ Node 3 │ Node 4 │ Node 5 │├───────────┼───────────┼───────────┼───────────┼───────────────┤│ R1 (L) │ R1 │ R1 │ │ ││ R2 (L) │ │ R2 │ R2 │ ││ │ R3 (L) │ R3 │ │ R3 ││ R4 │ R4 │ │ R4 (L) │ ││ │ R5 │ R5 │ R5 (L) │ ││ R6 │ │ R6 (L) │ │ R6 ││ │ R7 │ │ R7 │ R7 (L) ││ R8 (L) │ │ R8 │ │ R8 │└───────────┴───────────┴───────────┴───────────┴───────────────┘ Legend: R# = Range #, (L) = Leaseholder for that range Observations:- Each range has 3 replicas (replication factor = 3)- Replicas are distributed across different nodes- Leaseholders are distributed to balance read/write load- No node has all leaseholders (avoiding hot spots)Viewing Ranges:
CockroachDB provides commands to inspect range distribution:
-- Show ranges for a table
SHOW RANGES FROM TABLE users;
-- Show range for a specific key
SHOW RANGE FROM TABLE users FOR ROW (12345);
-- Detailed range info in the admin UI
-- http://localhost:8080/#/debug/range/42
Range Size Considerations:
The 512 MB default works well for most workloads. However:
You can configure range size per table:
ALTER TABLE hot_table CONFIGURE ZONE USING
range_max_bytes = 134217728, -- 128 MB max
range_min_bytes = 16777216; -- 16 MB min
Unlike application-managed shards (where the application knows which shard holds which data), ranges are an internal abstraction. Applications issue SQL queries; CockroachDB internally routes to the appropriate ranges. You don't shard tables—CockroachDB creates and manages ranges automatically.
Each range has multiple replicas, but one replica is special: the leaseholder. Understanding the leaseholder's role is crucial for understanding performance.
What is a Leaseholder?
The leaseholder is the replica that:
Leases vs. Raft Leadership:
CockroachDB separates two concepts:
Typically, the same replica is both Raft leader and leaseholder, but they can diverge temporarily during transitions.
Why Leases?
Leases enable consistent reads without running Raft consensus for every read:
This design provides strong consistency while minimizing read latency.
LEASEHOLDER OPERATION FLOW═══════════════════════════════════════════════════════════════════ RANGE SETUP:────────────────────────────────────────────────────────────────────Range 42: accounts rows 1000-2000Replicas: Node 1 (Leaseholder), Node 3, Node 5Lease expiry: current_time + 9 seconds READ OPERATION (Strongly Consistent):────────────────────────────────────────────────────────────────────Client: SELECT * FROM accounts WHERE id = 1500 1. Gateway receives query └── Locate leaseholder for range containing key /accounts/1500 └── Found: Node 1 is leaseholder 2. Gateway sends read request to Node 1 └── DistSQL routes read to Node 1 3. Node 1 (leaseholder) processes read: ├── Check: Lease still valid? Yes (6 seconds remaining) ├── No Raft needed—read directly from local storage └── Return result to gateway 4. Gateway returns to client Total latency: Gateway → Node 1 → Gateway (2 hops) WRITE OPERATION:────────────────────────────────────────────────────────────────────Client: UPDATE accounts SET balance = 500 WHERE id = 1500 1. Gateway receives query └── Locate leaseholder: Node 1 2. Gateway sends write to Node 1 3. Node 1 (leaseholder) coordinates write: ├── Acquire write intents ├── Propose entry to Raft log │ └── Replicate to Node 3 and Node 5 ├── Wait for majority acknowledgment (2 of 3) ├── Apply to local state machine └── Return success to gateway 4. Gateway returns to client Total latency: Gateway → Node 1 → (Node 3, Node 5 parallel) → Node 1 → Gateway LEASEHOLDER LOCATION MATTERS:──────────────────────────────────────────────────────────────────── Scenario A: Gateway on Node 2, Leaseholder on Node 1 (same region) Latency: ~1-2 ms per hop × 2 = ~2-4 ms read, ~5-10 ms write Scenario B: Gateway on Node 2 (US), Leaseholder on Node 7 (EU) Latency: ~70 ms per hop × 2 = ~140 ms read, ~200 ms+ write Optimization: Connect clients to nodes near their data's leaseholders LEASE TRANSFER:────────────────────────────────────────────────────────────────────CockroachDB can transfer leases to optimize performance: 1. Observe: Most reads for Range 42 come from Node 5's region2. Decide: Transfer leaseholder from Node 1 to Node 53. Execute: ├── Node 1 proposes lease transfer via Raft ├── All replicas acknowledge ├── Node 1 stops serving reads └── Node 5 starts serving reads4. Result: Reduced latency for majority of readsLeaseholder Preferences:
You can influence where leaseholders are placed:
-- Prefer leaseholders in us-east region
ALTER TABLE accounts CONFIGURE ZONE USING
lease_preferences = '[[+region=us-east]]';
-- Constrain replicas to specific regions, prefer us-west lease
ALTER TABLE user_data CONFIGURE ZONE USING
num_replicas = 5,
constraints = '{"+region=us-east": 2, "+region=us-west": 2, "+region=eu-west": 1}',
lease_preferences = '[[+region=us-west], [+region=us-east]]';
Follower Reads:
For applications that can tolerate slight staleness, follower reads bypass the leaseholder:
-- Read from any replica (with bounded staleness)
SELECT * FROM products
AS OF SYSTEM TIME follower_read_timestamp();
Follower reads:
Uneven leaseholder distribution causes hot spots. Check the 'Leaseholder' metric in the admin UI. If one node holds significantly more leaseholders than others, investigate zone configurations and rebalancing thresholds. CockroachDB automatically rebalances, but constraints or recent changes may cause temporary imbalance.
As data grows and shrinks, CockroachDB automatically splits and merges ranges to maintain optimal sizes.
Automatic Splitting:
A range splits when it exceeds the maximum size (default 512 MB):
Split Example:
Before split:
Range 42: [/users/1, /users/10000)
Size: 600 MB
Leaseholder: Node 1
After split:
Range 42: [/users/1, /users/5000) Size: 300 MB, Leaseholder: Node 1
Range 97: [/users/5000, /users/10000) Size: 300 MB, Leaseholder: Node 1
Splits are online—queries continue during the split. The leaseholder initially owns both new ranges; rebalancing may move them later.
Load-Based Splitting:
CockroachDB also splits based on load, not just size. A range with very high query volume splits even if it's small:
This prevents a single popular key from becoming a bottleneck.
RANGE SPLITTING AND MERGING═══════════════════════════════════════════════════════════════════ SIZE-BASED SPLITTING:──────────────────────────────────────────────────────────────────── Timeline of table growth: T=0 (Initial): Table: users Range 1: [/users/, /users/ÿ) Size: 10 MB (all users in one range) T=1 (Growth): Range 1: [/users/, /users/ÿ) Size: 480 MB (approaching threshold) T=2 (Split triggered at 512 MB): Splitting Range 1 at key /users/id_50000... ├── Range 1: [/users/, /users/id_50000) Size: 256 MB └── Range 2: [/users/id_50000, /users/ÿ) Size: 256 MB T=3 (Continued growth): Range 1: Size 400 MB Range 2: Size 550 MB (exceeds threshold) Splitting Range 2 at key /users/id_75000... Table now has 3 ranges: ├── Range 1: [/users/, /users/id_50000) Size: 400 MB ├── Range 2: [/users/id_50000, /users/id_75000) Size: 275 MB └── Range 3: [/users/id_75000, /users/ÿ) Size: 275 MB LOAD-BASED SPLITTING:──────────────────────────────────────────────────────────────────── Scenario: Popular product page causes query spike Range 42: [/products/1000, /products/2000) Size: 150 MB (well under threshold) QPS: 50,000 reads/sec (hot!) Hot key analysis: /products/1500 → 40,000 QPS (viral product) Other keys → 10,000 QPS combined Action: Load-based split at /products/1500 Result: Range 42: [/products/1000, /products/1500) QPS: ~5,000 Range 98: [/products/1500, /products/1501) QPS: ~40,000 (isolated hot key) Range 99: [/products/1501, /products/2000) QPS: ~5,000 Now Range 98 can be served by dedicated resources. AUTOMATIC MERGING:──────────────────────────────────────────────────────────────────── Scenario: Data deletion causes underfilled ranges Before (after bulk delete): Range 42: [/old_data/1, /old_data/1000) Size: 5 MB Range 43: [/old_data/1000, /old_data/2000) Size: 8 MB Merge condition: Both ranges < range_min_bytes (64 MB) Adjacent in keyspace Same zone configuration After merge: Range 42: [/old_data/1, /old_data/2000) Size: 13 MB Benefits: - Fewer ranges to manage - Less metadata overhead - Reduced Raft group count SPLIT/MERGE CONFIGURATION:──────────────────────────────────────────────────────────────────── -- View current zone configSHOW ZONE CONFIGURATION FOR TABLE users; -- Configure split thresholdsALTER TABLE users CONFIGURE ZONE USING range_max_bytes = 536870912, -- 512 MB (default) range_min_bytes = 67108864; -- 64 MB (default) -- For high-throughput tables, consider smaller rangesALTER TABLE hot_table CONFIGURE ZONE USING range_max_bytes = 134217728, -- 128 MB (smaller = more granular) range_min_bytes = 16777216; -- 16 MBSplit Triggers:
ALTER TABLE ... SPLIT AT for explicit splitsMerge Triggers:
Pre-Splitting for Bulk Loads:
When loading large amounts of data, pre-splitting avoids the bottleneck of one range handling all inserts:
-- Pre-split table by key values
ALTER TABLE large_import SPLIT AT VALUES
(1000000), (2000000), (3000000), (4000000);
-- Now bulk load distributes across 5 ranges
IMPORT INTO large_import ...;
Load-based splitting can isolate hot keys, but if the hot spot is a single key (like a global counter), even splitting doesn't help—that key still requires coordination through one leaseholder. For true single-key hot spots, consider application-level solutions like sharded counters or batching.
CockroachDB continuously rebalances data across nodes to maintain even distribution. The rebalancing algorithm considers multiple factors.
Rebalancing Goals:
The Store Rebalancer:
The store rebalancer runs continuously on every node, evaluating whether to:
Rebalancing Decisions:
The algorithm computes a score for each potential action:
score = storage_score + load_score + constraint_score + diversity_score
REBALANCING ALGORITHM IN ACTION═══════════════════════════════════════════════════════════════════ INITIAL STATE (UNBALANCED):──────────────────────────────────────────────────────────────────── Storage Ranges Leaseholders QPSNode 1 (new) 50 GB 100 20 2,000Node 2 200 GB 400 180 18,000Node 3 180 GB 360 150 15,000Node 4 170 GB 340 150 15,000 Problem: Node 1 is underutilized (just added or recovered) Node 2 is slightly overloaded REBALANCING PROCESS:──────────────────────────────────────────────────────────────────── Step 1: Identify imbalance Target per node: ~150 GB, ~300 ranges, ~125 leaseholders Node 1: -100 GB under, -200 ranges under → needs MORE replicas Node 2: +50 GB over, +100 ranges over → needs FEWER replicas Step 2: Select ranges to move Algorithm considers: ├── Range size (prefer medium-sized for efficient transfer) ├── Range activity (prefer moving cold ranges) ├── Constraint compatibility (can Node 1 host this range?) └── Network cost (prefer ranges where Node 1 is already a replica) Step 3: Execute transfers (throttled) ├── Move Range 42 replica: Node 2 → Node 1 ├── Move Range 58 replica: Node 2 → Node 1 ├── Move Range 67 replica: Node 3 → Node 1 └── ... (continues until balanced) Step 4: Lease transfers (after replicas stable) ├── Transfer leaseholder for Range 101: Node 2 → Node 1 ├── Transfer leaseholder for Range 102: Node 3 → Node 1 └── ... (balances read/write load) AFTER REBALANCING:──────────────────────────────────────────────────────────────────── Storage Ranges Leaseholders QPSNode 1 150 GB 300 125 12,500Node 2 150 GB 300 125 12,500Node 3 150 GB 300 125 12,500Node 4 150 GB 300 125 12,500 REBALANCING RATE LIMITING:────────────────────────────────────────────────────────────────────Rebalancing is throttled to avoid overwhelming the cluster: Configurable parameters: kv.snapshot_rebalance.max_rate: 32 MB/s (default) kv.snapshot_recovery.max_rate: 32 MB/s (default) Impact: Moving 100 GB of data at 32 MB/s ≈ 52 minutes Trade-off: - Higher rate: Faster rebalancing, more network/disk impact - Lower rate: Slower rebalancing, less production impact LOCALITY-AWARE REBALANCING:────────────────────────────────────────────────────────────────────Configuration: ALTER TABLE user_data CONFIGURE ZONE USING num_replicas = 5, constraints = '{ "+region=us-east": 2, "+region=us-west": 2, "+region=eu-west": 1 }', lease_preferences = '[[+region=us-east]]'; Rebalancer ensures: ├── Exactly 2 replicas in us-east (never more, never fewer) ├── Exactly 2 replicas in us-west ├── Exactly 1 replica in eu-west └── Leaseholder preferably in us-eastRebalancing Triggers:
Viewing Rebalancing Status:
-- Check range distribution
SELECT node_id, count(*) as range_count
FROM crdb_internal.ranges_no_leases
GROUP BY node_id;
-- Check for under-replicated ranges
SELECT * FROM crdb_internal.ranges
WHERE array_length(replicas, 1) < 3;
-- Admin UI: Replication dashboard
-- http://localhost:8080/#/metrics/replication
Rebalancing happens continuously in the background. During major operations (bulk loads, migrations), you may want to temporarily increase rebalance rates or pause rebalancing. Use SET CLUSTER SETTING to adjust rates, and drain nodes gracefully before removal to trigger proactive rebalancing.
Zone configurations control where data is placed and how it's replicated. They're the primary mechanism for implementing data locality, disaster recovery, and compliance requirements.
Zone Configuration Hierarchy:
Configurations can be set at multiple levels:
More specific configurations override more general ones.
Key Zone Configuration Options:
ALTER TABLE accounts CONFIGURE ZONE USING
-- Replication
num_replicas = 5,
-- Placement constraints
constraints = '{
"+region=us-east": 2,
"+region=us-west": 2,
"+region=eu-west": 1
}',
-- Lease preferences (for read/write performance)
lease_preferences = '[[+region=us-east], [+region=us-west]]',
-- Range sizing
range_max_bytes = 536870912,
range_min_bytes = 67108864,
-- Garbage collection
gc.ttlseconds = 90000; -- 25 hours
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
-- ═══════════════════════════════════════════════════════════════════-- ZONE CONFIGURATION PATTERNS-- ═══════════════════════════════════════════════════════════════════ -- PATTERN 1: Multi-Region Table with Regional Leaders-- ─────────────────────────────────────────────────────────────────────-- User data should be geographically distributed with local reads CREATE TABLE user_profiles ( user_id UUID PRIMARY KEY, region STRING NOT NULL, data JSONB); -- Partition by user regionALTER TABLE user_profiles PARTITION BY LIST (region) ( PARTITION americas VALUES IN ('us-east', 'us-west', 'brazil'), PARTITION europe VALUES IN ('eu-west', 'eu-central'), PARTITION asia VALUES IN ('asia-east', 'asia-south')); -- Configure each partition for localityALTER PARTITION americas OF TABLE user_profiles CONFIGURE ZONE USING num_replicas = 3, constraints = '[+region=us-east, +region=us-west]', lease_preferences = '[[+region=us-east]]'; ALTER PARTITION europe OF TABLE user_profiles CONFIGURE ZONE USING num_replicas = 3, constraints = '[+region=eu-west]', lease_preferences = '[[+region=eu-west]]'; ALTER PARTITION asia OF TABLE user_profiles CONFIGURE ZONE USING num_replicas = 3, constraints = '[+region=asia-east]', lease_preferences = '[[+region=asia-east]]'; -- PATTERN 2: REGIONAL BY ROW (Automatic Partitioning)-- ─────────────────────────────────────────────────────────────────────-- CockroachDB can automatically partition and configure based on a column -- Set database to multi-regionALTER DATABASE mydb PRIMARY REGION "us-east";ALTER DATABASE mydb ADD REGION "us-west";ALTER DATABASE mydb ADD REGION "eu-west"; -- Create table that auto-partitions by crdb_region columnCREATE TABLE orders ( order_id UUID PRIMARY KEY, customer_id UUID, crdb_region crdb_internal_region AS ( CASE WHEN customer_id::TEXT < '50000000' THEN 'us-east' WHEN customer_id::TEXT < 'a0000000' THEN 'us-west' ELSE 'eu-west' END ) STORED) LOCALITY REGIONAL BY ROW; -- Each row automatically placed in its computed region -- PATTERN 3: Global Tables (Optimized for Reads)-- ─────────────────────────────────────────────────────────────────────-- Reference data that's read frequently, written rarely CREATE TABLE country_codes ( code STRING PRIMARY KEY, name STRING, continent STRING) LOCALITY GLOBAL; -- GLOBAL tables:-- - Replicas in ALL regions-- - Reads served locally (no cross-region latency)-- - Writes must propagate to all regions (slower) -- PATTERN 4: SSD vs HDD Tiering-- ─────────────────────────────────────────────────────────────────────-- Hot data on SSD, cold data on HDD -- Assuming nodes are labeled with storage type-- Node 1-3: storage:ssd-- Node 4-6: storage:hdd -- Hot transactional data on SSDALTER TABLE active_sessions CONFIGURE ZONE USING constraints = '[+storage=ssd]'; -- Cold archive data on HDDALTER TABLE archived_logs CONFIGURE ZONE USING constraints = '[+storage=hdd]'; -- PATTERN 5: Survival Goals-- ─────────────────────────────────────────────────────────────────────-- Configure what level of failure the database survives -- Survive region failure (strongest)ALTER DATABASE critical_db SURVIVE REGION FAILURE;-- Requires: Replicas across 3+ regions-- Impact: Higher write latency (cross-region consensus) -- Survive zone failure only (faster writes)ALTER DATABASE standard_db SURVIVE ZONE FAILURE;-- Requires: Replicas across 3+ zones-- Impact: Lower write latency (intra-region consensus)Constraint Types:
Required constraint (+): Replica MUST be placed here
+region=us-east → Must have replica in us-eastProhibited constraint (-): Replica MUST NOT be placed here
-region=eu-west → No replicas allowed in eu-westConstraint count (n): Exactly n replicas in this location
"+region=us-east": 2 → Exactly 2 replicas in us-eastViewing Zone Configurations:
-- Show all zone configs
SHOW ALL ZONE CONFIGURATIONS;
-- Show config for specific table
SHOW ZONE CONFIGURATION FOR TABLE accounts;
-- Show effective config (including inheritance)
SHOW ZONE CONFIGURATION FROM TABLE accounts;
Zone configurations are powerful but can't fix a schema that ignores locality. Design tables with locality in mind: use REGIONAL BY ROW for user-facing data, GLOBAL for reference data, and consider partitioning for tables accessed by geographic region. The schema design determines what zone configurations can achieve.
Effective operation of CockroachDB requires monitoring the automatic load balancing systems and knowing how to diagnose issues.
Key Metrics to Monitor:
1. Range Distribution:
2. Leaseholder Distribution:
3. Rebalancing Activity:
4. Storage Health:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
-- ═══════════════════════════════════════════════════════════════════-- MONITORING AND TROUBLESHOOTING QUERIES-- ═══════════════════════════════════════════════════════════════════ -- CHECK RANGE DISTRIBUTION-- ─────────────────────────────────────────────────────────────────────-- See how ranges are distributed across nodes SELECT node_id, count(*) as range_count, sum(range_size_mb) as total_mb, count(*) FILTER (WHERE lease_holder = node_id) as leaseholder_countFROM crdb_internal.ranges_no_leases, LATERAL (SELECT (range_info->>'rangeBytes')::INT / 1048576 as range_size_mb)GROUP BY node_idORDER BY node_id; -- Healthy output shows similar numbers across nodes -- CHECK FOR UNDER-REPLICATED RANGES-- ─────────────────────────────────────────────────────────────────────-- Ranges with fewer replicas than configured (DATA AT RISK) SELECT range_id, start_key, end_key, array_length(replicas, 1) as replica_count, lease_holderFROM crdb_internal.rangesWHERE array_length(replicas, 1) < 3 -- assuming replication factor 3ORDER BY range_id; -- Any rows here = immediate investigation needed -- CHECK FOR UNAVAILABLE RANGES -- ─────────────────────────────────────────────────────────────────────-- Ranges that cannot serve reads/writes (OUTAGE) SELECT range_id, start_key, replicas, lease_holderFROM crdb_internal.rangesWHERE NOT problems @> '{}' -- has any problemsORDER BY range_id; -- CHECK REBALANCING PROGRESS-- ─────────────────────────────────────────────────────────────────────-- See ongoing snapshot transfers SELECT node_id, metrics->>'range.snapshots.generated' as snapshots_sent, metrics->>'range.snapshots.applied-voter' as snapshots_received, metrics->>'range.snapshots.send-queue' as send_queueFROM crdb_internal.kv_node_status; -- IDENTIFY HOT RANGES-- ─────────────────────────────────────────────────────────────────────-- Find ranges with highest query activity SELECT range_id, start_pretty, end_pretty, queries_per_second, writes_per_second, lease_holder_nodeFROM crdb_internal.rangesORDER BY queries_per_second DESCLIMIT 20; -- CHECK ZONE CONSTRAINT VIOLATIONS-- ─────────────────────────────────────────────────────────────────────-- Find ranges not satisfying their zone configs SELECT range_id, start_key, database_name, table_name, learner_replicas, split_enforced_untilFROM crdb_internal.rangesWHERE array_length(learner_replicas, 1) > 0 -- has pending learner replicas OR constraint_conformance != 'compliant' -- violates constraintsORDER BY range_id; -- STORAGE CAPACITY CHECK-- ─────────────────────────────────────────────────────────────────────-- Check storage utilization per node SELECT node_id, store_id, capacity / 1073741824 as capacity_gb, available / 1073741824 as available_gb, used / 1073741824 as used_gb, (1 - available::FLOAT / capacity) * 100 as pct_usedFROM crdb_internal.kv_store_statusORDER BY pct_used DESC; -- Alert if any node > 80% usedCockroachDB's Admin UI (default port 8080) provides excellent visibility into replication, rebalancing, and range distribution. The Replication Dashboard, Node List, and Range Debug pages surface most issues immediately. Use it as your first stop for troubleshooting.
We've explored how CockroachDB automatically manages data distribution across the cluster. Let's consolidate the key concepts:
What's Next:
We've now covered CockroachDB's core architecture. In the final page, we'll step back and answer the practical question: When should you use CockroachDB? We'll examine use cases, alternatives, and provide a decision framework for evaluating whether CockroachDB is the right choice for your system.
You now understand how CockroachDB automatically distributes and balances data—ranges, leaseholders, splitting, merging, and zone configurations. This knowledge enables you to design schemas that cooperate with the balancing algorithms and troubleshoot distribution issues. Next, we'll discuss when CockroachDB is the right choice and how it compares to alternatives.