Share this article
How to Optimize ClickHouse Costs? {2025 Edition}
June 11, 2026
e6data team
ClickHouse
Cost
Beginner

ClickHouse's columnar architecture and blazing-fast query performance make it the go-to choice for real-time analytics at scale, but that same power can lead to unexpected costs if not properly managed. We've worked with dozens of teams running ClickHouse clusters processing terabytes daily, and the pattern is consistent: costs often spiral when teams optimize for query speed without considering resource efficiency, leading to oversized clusters, inefficient data layouts, and unnecessary data retention.
The good news? ClickHouse's granular control over storage, compute, and memory means every optimization directly translates to cost savings. Unlike black-box analytics platforms, ClickHouse gives you complete visibility into resource consumption at the table, partition, and query level. Whether you're running real-time dashboards, analytical workloads, or high-volume ETL pipelines, the key is leveraging ClickHouse's advanced features like TTLs, materialized views, and adaptive indexing to match resource usage to actual business needs.
This playbook focuses on practical optimizations that data engineering teams can implement immediately, organized by workload patterns. Each tactic includes runnable SQL configurations and ClickHouse-specific tuning parameters, so you can start reducing costs without sacrificing the sub-second query performance your users expect.
When does ClickHouse spending become "high cost"?
Before diving into optimizations, it's crucial to establish what qualifies as "high cost" in ClickHouse. These yardsticks help identify inefficient usage patterns:
Storage growth rate · Rule-of-thumb threshold: >20% monthly without proportional data growth · Why it matters & How to check: Indicates poor compression, missing TTLs, or inefficient data types. Check system.parts size trends.
Memory usage · Rule-of-thumb threshold: >80% of available RAM consistently · Why it matters & How to check: High memory pressure slows queries and can cause OOM kills. Monitor via system.metrics and max_memory_usage.
Disk I/O wait · Rule-of-thumb threshold: >30% I/O wait time during queries · Why it matters & How to check: Suggests undersized storage or poor table layout. Check system.query_log for queries with high read_bytes.
Replication lag · Rule-of-thumb threshold: >10 minutes between replicas · Why it matters & How to check: Network or compute bottlenecks affecting data consistency. Monitor system.replicas delay metrics.
Background merge activity · Rule-of-thumb threshold: >50% CPU time on merges · Why it matters & How to check: Too many small parts or aggressive insert patterns. Check system.merges and parts_to_do.
Query queue depth · Rule-of-thumb threshold: >100 concurrent queries regularly · Why it matters & How to check: Undersized cluster or inefficient queries. Monitor system.processes and max_concurrent_queries.
Network egress costs · Rule-of-thumb threshold: >$0.05/GB for inter-AZ traffic · Why it matters & How to check: Multi-AZ setups with inefficient data locality. Track data transfer costs in cloud billing.
What workload are you paying for?
ClickHouse costs primarily spike when one of three usage patterns dominates:
BI dashboards · Typical pattern: High-frequency analytical queries with strict latency SLOs · Cost risk: Oversized clusters for peak concurrency, inefficient materialized views
Ad-hoc Analytics · Typical pattern: Exploratory queries with unpredictable resource needs · Cost risk: Full table scans, unnecessary data retention, inefficient compression
ETL / Streaming · Typical pattern: High-volume inserts and complex transformations · Cost risk: Excessive replication, merge storms, inefficient batch sizes
BI Dashboards
Typical cost pitfalls: Oversized clusters for peak concurrency and inefficient query patterns that don't leverage ClickHouse's materialized views and projection optimization
1. Implement query-driven materialized views for reduction in dashboard query costs
Real-time dashboards often re-run the same aggregations on large tables (tens of millions of rows), wasting CPU and GBs of RAM each refresh. ClickHouse will scan and aggregate the source on every query unless the results are persisted.
The solution is to create a materialized view that pre-computes those common aggregates on ingestion. In the example below, we create a summing MergeTree MV to aggregate daily transaction metrics (count, sum, uniques) by date, merchant, type. The dashboard queries then hit this pre-aggregated view (hundreds of rows) instead of the raw table (50M rows):
Materialized view optimization architecture:
Alternatives:
Projections for additional aggregation patterns with automatic selection
Incremental refresh using
TTLto drop stale partitionse6data's auto-scaling compute for variable dashboard workloads with per-vCPU billing
2. Add multi-projection schemas for reduction in scan volume
If your table’s primary sort key doesn’t align with common query filters, ClickHouse will end up scanning many unnecessary parts. For example, an events table (200 GB) sorted by time might be frequently queried by category or region, forcing full scans because the data isn’t sorted to match those filters.
Add multiple projections to pre-sort and aggregate the data by those other dimensions. Projections are like materialized secondary indices: they store another sorted copy of your data (or aggregated data) that ClickHouse’s optimizer can automatically use for queries that match the projection’s sort key. Here we add two projections on a product_events table - one by category, one by region - then materialize them:
Fix: targeted projections
Alternatives:
Manual Aggregate Tables - Maintain separate summary tables (via ETL or triggers) for key dashboard query patterns. This achieves a similar result at the cost of more complex pipelines.
ClickHouse Automatic Projections - Rely on ClickHouse’s optimizer to automatically choose projections at query time. If projections are defined without explicit query hints, ClickHouse will pick the best one if the query matches its pattern (reducing manual query rewrites).
3. Apply adaptive TTL policies for hot/cold data for storage savings
You’re paying premium SSD rates to keep 18 months of detailed events hot even though about 90% of queries only touch the last 30 days. Storage keeps growing, merges and backups slow down, cache churn increases, and restore times stretch out. This sets you up for out-of-space incidents, throttling under load, and missed dashboard SLOs during peak hours.
Fix: tiered TTL
Guardrails:
Ensure
cold_storage/archivemap to cheaper disks or object-backed volumes.Schedule heavy TTL moves during low traffic.
Track partition counts and
system.partsto watch move progress.
Alternatives:
External object storage for archive partitions.
Separate historical table with async ETL (cheap + simple restores).
4. Enable query result cache to reduce duplicate computation
In many BI environments, users unknowingly issue hundreds of identical queries (e.g. the same dashboard refresh) per minute during business hours. For instance, if 50 users open the same dashboard around 9 AM, that could trigger the same heavy query 50 times. This hammers the database with duplicate work - CPU spikes and query queues build up even though the underlying data hasn’t changed. The result is wasted compute credits and slower performance for everyone.
To fix this, enable the built-in query result cache. ClickHouse’s query cache can store the result set of a query so that subsequent identical queries (with the same SQL text) can be served from cache instantly, without scanning or computation. It’s disabled by default, but simply toggling these settings can yield big savings:
Fix: router-level cache
Alternatives:
Application-Layer Caching - Implement caching at the application or API layer (e.g. in a web server, using Redis, or in-memory caching in your service). This can be more flexible (e.g. cache partial results or API responses) but requires extra development.
Materialized Result Sets - For reports that are almost static, you can periodically materialize their result set into a table and query that. This is essentially manual caching: e.g. compute a daily summary table every hour and have the dashboard query that table. It offloads the database from doing the heavy work on every query.
5. Switch to asynchronous inserts for live metrics for lower ingest overhead
ClickHouse performs best with batch inserts, but many real-time use cases (metrics, event feeds) send a stream of single-row or small-row inserts. For example, producers sending thousands of tiny inserts per second will create a huge number of small parts on disk. This triggers “merge storms” - constant background merges that spike I/O and CPU, and can even slow down read queries due to the fragmented data. In short, row-at-a-time writes are easy for producers but very expensive for ClickHouse.
By turning on async_insert, the ClickHouse server will accumulate incoming inserts in memory and flush them as larger batches. This essentially auto-batches small inserts without requiring changes on the client side. Key settings include:
Fix: async insert batching
Alternatives:
Buffer Engine - Use the
Buffertable engine which temporarily buffers incoming data in memory and periodically flushes to a MergeTree. This achieves a similar batching effect automatically.External Queue - Insert into a messaging system like Kafka or Pulsar, and let ClickHouse ingest from that in larger batches. This decouples producers from direct writes and allows grouping many small messages into one insert per batch.
6. Route queries by workload class for better resource utilisation
A common source of inefficiency is mixing heterogeneous workloads on the same cluster. For instance, low-latency dashboards and heavy ad-hoc analytical queries might share the same pool of resources. A single long-running analytical join can hog CPU or memory and make real-time dashboard queries slow or timeout. Conversely, a spike of dashboard queries could starve an analyst’s large query. Without isolation, everything competes, leading to missed SLAs and frustrated users.
ClickHouse allows setting resource limits per user or profile. We can create different profiles for, say, “dashboard_users” vs “analytics_users” and enforce separate quotas. In the configuration snippet below, we set stricter limits for the dashboard profile (e.g. lower memory and timeout) and higher limits for the analytics profile:
Fix: resource-isolated user profiles
Alternatives:
Dedicated Clusters per Workload - Use separate ClickHouse clusters or nodes for different workloads (one for BI, one for heavy analytics). This physically isolates resources but increases management overhead.
Query Priority Queueing - Implement external orchestration to queue or throttle lower-priority queries during peak times. For example, a custom query gateway could delay some large queries when the cluster is busy serving dashboards.
Ad-hoc Analytics
Typical cost pitfalls: Exploratory queries that scan entire tables and inefficient data sampling strategies that don't leverage ClickHouse's built-in optimization features
1. Use TABLESAMPLE for efficient data exploration to reduce scan costs
Data scientists and analysts often run ad-hoc queries to test hypotheses (e.g. “what’s the conversion rate by traffic source?”) on very large tables. Scanning an entire 500 GB events table for each hypothesis test can take minutes and gigabytes of CPU/memory. However, in many cases approximate answers from a sample would suffice during exploration.
ClickHouse’s TABLESAMPLE clause allows querying a random sample of the table’s data. You can specify a percentage or an absolute number of rows. For exploration, try sampling 1% or 0.1% of the table - this cuts scan size ~100×. For example:
Intelligent sampling architecture:
Alternatives:
Pre-computed Sample Tables - Periodically materialize sample datasets (e.g. a table with 1% of each day’s data) for analysts. Queries can hit the smaller sample tables directly.
Approximate Algorithms - Use approximate aggregate functions (sketches) like HyperLogLog for cardinalities (see Tactic 9) to get quick estimates instead of exact numbers.
e6data Platform - Use e6data’s engine to query over external data formats with sampling. e6data can perform cross-format sampling on data in data lakes without full ingestion, so you can test hypotheses on raw data cheaply before deciding to ingest or process it in ClickHouse.
2. Leverage dictionaries for efficient lookup tables to reduce join costs
Joins in ClickHouse, while fast, can become expensive when a large fact table joins with a very large dimension table on each query. For example, joining a transactions fact with a 50 million-row product_catalog dimension to get product names can use 8-12 GB of memory per query. Traditional join processing will repeatedly scan the big dimension and build hash tables, etc., consuming a lot of CPU and RAM.
ClickHouse dictionaries allow you to load a dimension table (like product data) into memory (or on demand) and then use the dictGet() function to do key-value lookups, instead of a SQL join. This is much more efficient for high-frequency lookups. For instance, we can create a dictionary from the product_catalog table:
Dictionary-based lookup architecture:
Alternatives:
Cache in Application - If the reference data is relatively static, one can cache it in the application layer (in memory or a local cache) and simply send the needed values (like product names) with queries, avoiding the join/dictionary entirely.
Temporary External Data - For one-off analyses, use ClickHouse’s external data feature to load a small reference dataset for the session and join to it (if the table is not too large). This avoids permanently storing a huge dimension in ClickHouse if it’s rarely used.
3. Implement query complexity scoring to prevent runaway costs
Some analytical queries can “explode” in cost - e.g. a join with no selective filter or a cartesian product by mistake - and consume enormous resources (10+ GB RAM, hours of CPU) without the user realizing. In a pay-per-use environment, a single runaway query can rack up a big bill or impact other workloads.
One strategy is to use the system.query_log to identify patterns of heavy queries and then enforce rules. For example, you can detect queries that used >10 GB memory or ran >5 minutes in the last hour and flag them:
Query complexity analysis architecture:
Alternatives:
Query Approval Workflow - Require manual review/approval for queries above certain thresholds (e.g. if a query is predicted to scan > X GB or no partition filter). This can be done via a query submission portal that flags heavy queries.
Resource Quotas - Use ClickHouse user quotas to limit total resource usage per user (e.g. max memory or CPU time in a interval). This won’t prevent a single heavy query, but it can stop repeated abuse and encourage users to optimize their queries.
4. Use approximate algorithms for faster exploration on large datasets
When dealing with billions of rows, exact computations (exact COUNT(DISTINCT), medians, top-K, etc.) can be slow and costly. Often for exploratory analysis or dashboards, an approximate result (with error <1-2%) is good enough and can be obtained in a fraction of the time. For instance, calculating exact distinct user counts or exact medians on 1B+ events might take 10-20 minutes, whereas approximate methods yield almost the same insight in seconds.
ClickHouse offers approximate algorithms like uniq() (HyperLogLog-based) for distinct counts, quantile() and quantileTDigest() for percentiles, and topK() for top-K frequency estimation. These trade a tiny error for big speed gains. Example:
Approximate algorithm optimization:
Alternatives:
Pre-compute exact statistics in batch jobs or use sampling combined with exact algorithms for balanced accuracy and performance.
e6data's query engine automatically chooses between exact and approximate algorithms based on query patterns and data size.
5. Implement progressive data loading for iterative analysis for faster iteration cycles
Iterative feature engineering workflows repeatedly loaded the same 200 GB dataset, spending 15-20 minutes on data loading before each analysis iteration. Iterative analysis workflows reload the same base datasets repeatedly, wasting time and compute resources on redundant I/O operations.
Progressive loading and caching architecture:
Alternatives: Use external caching systems like Redis for intermediate results or implement custom checkpointing with cloud storage.
6. Optimize join strategies for large analytical queries to reduce execution time
Complex multi-table joins (e.g. joining a 500M+ row fact table with multiple 10M+ dimension tables) can consume tens of GBs of memory and run slowly if not executed with the ideal strategy. ClickHouse has different join algorithms (hash, merge, broadcast, etc.) and the query planner’s default choice might not be best for large-scale joins.
ClickHouse join performance depends heavily on join order, join algorithms, and data distribution, but default strategies often aren't optimal for large analytical workloads.
Sample Join optimization architecture:
Alternatives:
Pre-aggregate data before joining or use ClickHouse's distributed join capabilities for multi-node performance.
e6data's cost-based optimizer automatically selects optimal join strategies and execution plans without manual query tuning.
ETL / Streaming Workloads
Typical cost pitfalls: Inefficient data ingestion patterns, excessive replication overhead, and poor partitioning strategies that lead to merge storms and degraded query performance
1. Optimize batch insert sizes to prevent merge storms to reduce background CPU usage
Inserting 50K small batches per hour into ClickHouse created thousands of tiny parts that consumed 60% of cluster CPU in constant background merges, slowing queries and inflating costs. Frequent small inserts create excessive part files that trigger constant background merging, consuming CPU resources and degrading overall cluster performance.
Optimal batch sizing architecture:
Alternatives:
Use Buffer engine for automatic batching, implement application-level buffering with periodic bulk inserts, or use e6data's auto-scaling compute for variable batch workloads with granular cost control.
2. Implement efficient replication strategies to reduce network and storage costs
By default, replicating data across many nodes can incur huge costs in both network and storage. For instance, one team was replicating ~5TB of new data daily across 6 nodes (for high availability), resulting in ~30TB/day of network traffic and 6× storage usage - much of it unnecessary. If your high availability (HA) needs are over-provisioned, you are paying a lot for little gain. Each extra replica multiplies the storage (and hence cost, if on cloud) and uses cluster bandwidth to sync data.
Sample Optimized replication architecture:
Alternatives:
Single-node with Backups - For non-critical datasets, consider using a single replica (or even a non-replicated MergeTree) and rely on daily backups or snapshots rather than real-time replication. This eliminates runtime replication cost and you pay only storage for backups.
Selective Cross-DC Replication - If you use replication across data centers or AZs, do it only for essential tables. Don’t replicate an entire 50TB cluster to another DC if only 5TB of that is mission-critical hot data. Use isolated replication channels for the important subset.
3. Use TTL and data lifecycle policies for automatic cost optimization
Many ClickHouse deployments retain huge volumes of historical data “just in case,” even though the vast majority of queries only hit recent data. For example, 50 TB of device telemetry logs might be kept for 2 years, but 95% of queries only ever touch the last 30 days. Keeping all that data on fast SSD storage and constantly merging it is extremely costly and unnecessary.
ClickHouse’s TTL feature can automatically move partitions to different disks/volumes or delete data once it’s older than a certain threshold. By defining a tiered retention policy, you ensure recent data stays on fast (expensive) storage for quick access, while older data moves to cheaper storage, and really old data is dropped entirely.
Automated data lifecycle architecture:
Alternatives:
External Archival Storage - Instead of keeping cold data in ClickHouse at all, offload it entirely to an external data lake or archive (Parquet files on S3, etc.). You can query it via external tables or engines when needed, or with tools like e6data which can query over files directly. This removes cold data carrying cost in ClickHouse.
Custom Cleanup Jobs - If TTL doesn’t fit a complex retention scheme, implement your own cron jobs or scripts to roll older data to another place or delete it. For example, periodically COPY out the last month’s data to a backup, then DROP PARTITION older than X in ClickHouse.
4. Optimize partitioning strategies for better query performance
Inefficient partitioning can be very costly. For example, daily ETL jobs that needed only the last day of data ended up scanning entire tables because the tables were partitioned by month instead of day. This led to multi-hour processing windows and high I/O bills. Partitioning that doesn’t match query patterns means ClickHouse reads lots of partitions (parts) that could be skipped.
Analyze your query patterns to choose a partition key that filters data as much as possible. Time-based partitioning is common, but pick the appropriate interval (daily vs monthly, etc.). You can also partition by an additional key like region, category, etc., if many queries filter by those.
Example Query-optimized partitioning architecture:
Alternatives:
Expression-based Partitioning - If your filtering logic is more complex (say queries often request “last 7 days” or a specific customer segment), you can partition on an expression (e.g. a week number, or a hashed key). This allows custom partition schemes beyond simple columns.
Dynamic Partitioning - In some scenarios, you might adjust partitioning strategy as data evolves (e.g. partition by day for recent data, by month for older data). This isn’t directly a ClickHouse feature, but you can achieve it by periodically merging older daily partitions into monthly ones (reducing partition count) - essentially dynamic management based on data age or size.
5. Implement efficient streaming ingestion with proper buffering to reduce ingestion overhead
Streaming 100K events per second directly into ClickHouse overwhelmed the cluster with constant small inserts and caused query performance degradation. High-frequency streaming inserts create excessive merge overhead and can destabilize cluster performance if not properly buffered.
Streaming optimization architecture:
Alternatives:
External Stream Processors - Use platforms like Apache Flink or NiFi to pre-aggregate or batch events before they reach ClickHouse. By the time data gets to ClickHouse, it’s already grouped into larger transactions.
Custom Client-side Buffering - If not using Kafka, implement a buffering mechanism in the data producers or an intermediate service. For example, have producers send events to an API that batches them for a second or two and then writes to ClickHouse in one go. This reduces insert frequency at the cost of a tiny delay.
Use an Alternative Engine (e6data) - While not directly an ingestion tool, an elastic query engine like e6data can handle querying data directly from message streams or intermediate storage. In scenarios where ClickHouse struggles with ingestion, you might offload some real-time processing to a system built for streaming ingestion and then periodically load aggregated results into ClickHouse.
When ClickHouse Optimization Reaches Its Limits: The e6data Alternative
While ClickHouse optimization can dramatically reduce costs, some workloads demand even more granular resource control and cross-engine flexibility than any single platform can provide. That's where e6data's compute engine becomes a strategic complement for cost-conscious data teams.
Why teams are adding e6data alongside ClickHouse:
per-vCPU pricing: Instead of paying for entire ClickHouse clusters during low-utilization periods, e6data scales per-vCPU based on actual query demand. A recent customer reduced their analytical workload costs by 55% by moving variable-load queries to e6data while keeping real-time dashboards on ClickHouse.
Cross-format query optimization: While ClickHouse excels with columnar data, e6data's vectorized engine delivers comparable performance on mixed file formats (Parquet, Delta, even CSV) without requiring data migration. One media company eliminated their ETL preprocessing entirely, saving 4 hours of pipeline time daily.
Hybrid deployment flexibility: e6data queries the same data that ClickHouse uses, with no vendor lock-in. Teams use e6data for cost-sensitive batch analytics while keeping ClickHouse for low-latency operational queries, optimizing each workload independently.
Zero infrastructure overhead: Unlike ClickHouse cluster management, e6data provides fully managed compute that auto-scales from zero. You focus on queries, not infrastructure tuning.

Ready to compare? Run your most resource-intensive ClickHouse queries on a free e6data trial. Most teams see immediate cost improvements on variable analytical workloads while keeping ClickHouse for what it does best.
Book a demo on your own workloads
Reach out to book a demo, share challenges you're facing, and tell us how this fits into what you're currently working on or thinking about.
Prefer to self-serve? Problems we're solving →