ClickHouse Memory Limit Exceeded: Root Causes and Tuning Guide
Quick Answer
- The fix: When you see
Memory limit (for query) exceeded, addSETTINGS max_memory_usage = <higher_value>to your query, or enable external aggregation withmax_bytes_before_external_group_byfor GROUP BY queries. - First checks: Run
SELECT * FROM system.processesto identify the memory-hungry query, then checksystem.metric_logforMemoryTrackingtrends. Verify yourmax_server_memory_usageandmax_server_memory_usage_to_ram_ratiosettings. - Minimal config: Create
/etc/clickhouse-server/config.d/memory.xmlwith<max_server_memory_usage_to_ram_ratio>0.75</max_server_memory_usage_to_ram_ratio>and a per-querymax_memory_usagein user profiles. - Version boundary: These settings apply to ClickHouse 20.x and later; external aggregation and sort spill-to-disk features are available since ClickHouse 19.x.
What Problem It Solves
ClickHouse is an in-memory columnar database designed for real-time analytics. Its default behavior is to process queries entirely in RAM for maximum speed. When a query requires more memory than available—due to high-cardinality GROUP BY, large JOINs, or high concurrency—ClickHouse throws a Memory limit exceeded error and aborts the query.
This guide provides a systematic approach to diagnosing, fixing, and preventing these errors without sacrificing query performance unnecessarily.
When This Error or Setup Appears
The Memory limit exceeded error typically appears in these scenarios:
- High-cardinality GROUP BY: Aggregating over millions or billions of unique values (e.g.,
GROUP BY user_idon a table with 100M distinct users). - Large JOIN operations: Joining two or more large tables in memory without spill-to-disk support.
- Resource-constrained environments: Docker containers, small cloud instances, or shared servers with limited RAM.
- Multi-tenant systems: One user's heavy query consuming all available memory, starving other users.
- High concurrency: Many simultaneous queries each requesting moderate memory, collectively exceeding the server limit.
Root Cause Analysis
ClickHouse enforces memory limits at three levels, and understanding which limit was hit is the first diagnostic step:
| Error Message Pattern | Limit Type | What It Means |
|---|---|---|
Memory limit (for query) exceeded | Per-query (max_memory_usage) | A single query exceeded its individual memory budget |
Memory limit (for user) exceeded | Per-user (max_memory_usage_for_user) | All queries by a user collectively exceeded their total budget |
Memory limit (total) exceeded | Server-wide (max_server_memory_usage) | The entire ClickHouse process exceeded the global limit |
To diagnose the offending query:
SQL-- Find currently running queries with memory usage SELECT query_id, user, elapsed, read_rows, memory_usage, query FROM system.processes ORDER BY memory_usage DESC LIMIT 10; -- Historical memory tracking SELECT event_time, MemoryTracking AS current_memory, max_server_memory_usage FROM system.metric_log WHERE event_time > now() - INTERVAL 1 HOUR ORDER BY event_time;
Parameters and Environment Variables
Per-Query and Per-User Memory Limits
| Parameter | Scope | Default | Description |
|---|---|---|---|
max_memory_usage | Query/Session/User | 10 GB | Maximum memory for a single query |
max_memory_usage_for_user | User | 0 (unlimited) | Total memory across all queries for a user |
Spill-to-Disk (External Processing)
| Parameter | Scope | Description |
|---|---|---|
max_bytes_before_external_group_by | Query | Threshold (bytes) before spilling GROUP BY intermediates to disk |
max_bytes_before_external_sort | Query | Threshold (bytes) before spilling ORDER BY intermediates to disk |
max_bytes_in_join | Query | Memory limit for JOIN operations; enables external JOIN when exceeded |
tmp_policy | Server | Defines which disks are used for temporary spill files |
Server-Wide Limits
| Parameter | Scope | Description |
|---|---|---|
max_server_memory_usage | Server | Absolute memory limit for the ClickHouse process |
max_server_memory_usage_to_ram_ratio | Server | Ratio of total system RAM (default 0.9) |
Quota Parameters
| Parameter | Scope | Description |
|---|---|---|
execution_time | Quota | Max execution time within an interval |
result_rows | Quota | Max result rows within an interval |
Common Errors and Fixes
Error: Memory limit (for query) exceeded: would use 10.50 GiB (attempt to allocate chunk of 4194304 bytes), maximum: 10.00 GiB
Solution: This is the most common error. Apply one or more of these fixes:
-
Temporarily increase the limit per query:
SQLSELECT ... GROUP BY user_id SETTINGS max_memory_usage = 20000000000; -- 20 GB -
Enable external aggregation (for GROUP BY queries):
SQLSELECT ... GROUP BY user_id SETTINGS max_bytes_before_external_group_by = 5000000000, -- spill at 5 GB max_memory_usage = 20000000000; -- hard limit at 20 GB -
Optimize the query: Reduce cardinality by filtering early, or use approximate functions:
SQLSELECT uniqHLL12(user_id) AS approx_unique_users FROM events WHERE event_date = today();
Error: Memory limit (total) exceeded: would use 105.00 GiB (attempt to allocate chunk of 1048576 bytes), maximum: 100.00 GiB
Solution: The server process hit its global limit.
-
Check and adjust global config (
/etc/clickhouse-server/config.d/memory.xml):XML<clickhouse> <max_server_memory_usage>60000000000</max_server_memory_usage> <!-- 60 GB --> </clickhouse> -
Reduce concurrency: Kill unnecessary queries:
SQLSELECT query_id, query FROM system.processes WHERE elapsed > 300; -- Then kill long-running queries KILL QUERY WHERE query_id = '...'; -
Check for memory leaks: Monitor
MemoryTrackinginsystem.metric_logwhen no queries are running.
Error: Memory limit (for user) exceeded: would use 25.00 GiB (attempt to allocate chunk of 8388608 bytes), maximum: 20.00 GiB
Solution: A specific user exceeded their collective budget.
-
Increase the user's limit in
/etc/clickhouse-server/users.d/memory_profiles.xml:XML<clickhouse> <users> <analyst_user> <max_memory_usage_for_user>30000000000</max_memory_usage_for_user> <!-- 30 GB --> </analyst_user> </users> </clickhouse> -
Use quotas for finer control:
SQLCREATE QUOTA analyst_quota FOR INTERVAL 1 HOUR MAX execution_time = 3600, result_rows = 1000000 TO analyst_user;
Error: Code: 241. Memory limit (for query) exceeded even with external aggregation enabled
Solution: The spill threshold is too high relative to the hard limit, or the query contains JOINs.
-
Ensure threshold < hard limit:
SQLSETTINGS max_bytes_before_external_group_by = 5000000000, -- 5 GB max_memory_usage = 10000000000; -- 10 GB (must be > 5 GB) -
For JOIN queries, configure external JOIN:
SQLSETTINGS max_bytes_in_join = 2000000000, -- 2 GB join_algorithm = 'partial_merge'; -
Check temporary disk space:
SQLSELECT name, path, free_space, total_space FROM system.disks;
Production Notes and Security Checks
Critical Limitations
-
Concurrency conflict: Multiple queries with high
max_memory_usagecan collectively exceed physical RAM. Always setmax_server_memory_usageas a hard ceiling. -
Disk I/O bottleneck: External aggregation/sort spills to disk. Use high-performance local SSD for the temporary directory. Monitor spill frequency:
SQLSELECT metric, value FROM system.asynchronous_metrics WHERE metric IN ('ExternalSort', 'ExternalAggregation'); -
Permission control: Restrict
ALTER USERand config file modification to admins only. Users should not be able to raise their own limits. -
KILL QUERYside effects: Force-killing a query mid-write can cause partial data. PreferSYSTEM KILL QUERYwith a timeout, and design write operations to be idempotent. -
Config management: Never edit configs manually in production. Use version-controlled configuration management (Ansible, Chef, Kubernetes ConfigMaps).
Preventing OOM Killer
The max_server_memory_usage_to_ram_ratio is a soft limit. To prevent the Linux OOM Killer from terminating ClickHouse:
-
Set an absolute hard limit:
XML<max_server_memory_usage>50000000000</max_server_memory_usage> <!-- 50 GB --> -
Lower the ratio to reserve headroom:
XML<max_server_memory_usage_to_ram_ratio>0.75</max_server_memory_usage_to_ram_ratio> -
Use cgroups for the most reliable enforcement:
BASH# Create a cgroup for ClickHouse sudo cgcreate -g memory:clickhouse echo 50G > /sys/fs/cgroup/memory/clickhouse/memory.limit_in_bytes # Start ClickHouse within the cgroup sudo cgclassify -g memory:clickhouse $(pgrep clickhouse-server)
FAQ
Q: My query with max_bytes_before_external_group_by is slower than before. Why?
A: External aggregation writes intermediate results to disk, which is inherently slower than in-memory processing. The performance impact depends on your temporary disk's I/O speed.
Solutions:
- Use high-performance local SSD for the
tmp_policypath. - Raise the spill threshold to keep more data in memory:
max_bytes_before_external_group_by = 40000000000(40 GB) on a 64 GB server. - Optimize the query: add WHERE filters, use materialized views for pre-aggregation.
- If the query cannot be optimized, add more physical RAM.
Q: I set max_server_memory_usage_to_ram_ratio = 0.9, but ClickHouse still gets killed by OOM Killer. Why?
A: This ratio is a soft limit. ClickHouse can momentarily exceed it due to large memory allocations. Additionally, other processes on the server consume RAM.
Solutions:
- Set an absolute
max_server_memory_usagevalue well below total RAM. - Lower the ratio to 0.7 or 0.8.
- Use Linux cgroups for a hard, kernel-enforced limit.
Q: How do I set different memory limits for different users?
A: Create user profiles in /etc/clickhouse-server/users.d/:
XML<clickhouse> <profiles> <analyst> <max_memory_usage>5000000000</max_memory_usage> <max_memory_usage_for_user>10000000000</max_memory_usage_for_user> <max_bytes_before_external_group_by>2000000000</max_bytes_before_external_group_by> </analyst> <etl> <max_memory_usage>20000000000</max_memory_usage> <max_memory_usage_for_user>50000000000</max_memory_usage_for_user> </etl> </profiles> <users> <analyst_user> <profile>analyst</profile> </analyst_user> <etl_user> <profile>etl</profile> </etl_user> </users> </clickhouse>
For advanced isolation, use resource pools to allocate separate CPU and memory for different workloads.