ClickPy at 2 Trillion rows: Scaling ingestion and fixing the past

A look at how ClickPy handles over 2 trillion Python package downloads, from ingestion redesign to fixing historical data at scale.

Overview

This article details how ClickPy, a free Python download statistics platform powered by ClickHouse, scaled to over 2 trillion rows by replacing its legacy cron-based ingestion pipeline with ClickPipes, a managed ingestion service. The post covers the staged hot-swap migration strategy, the discovery of historical data gaps, and the process for correcting past data without rebuilding the entire dataset or interrupting ongoing operations.

What You'll Learn

1

How to perform a zero-downtime hot-swap of a data ingestion pipeline at trillion-row scale

2

How to use a staging database and parallel pipeline validation to safely migrate ingestion systems

3

How to fix historical data gaps in a column-oriented database without rebuilding the full dataset

4

Why replacing custom cron-based ingestion scripts with managed ingestion services reduces operational burden

5

When to use materialized views with Null engine tables as a transformation layer in ClickHouse

Prerequisites & Requirements

  • Understanding of column-oriented databases and analytical workloads
  • Familiarity with ClickHouse concepts including materialized views, table engines, and data ingestion
  • Basic understanding of data pipelines and ETL/ELT patterns
  • Familiarity with Google BigQuery and Google Cloud Storage (GCS)(optional)
  • Experience operating large-scale data systems with high availability requirements(optional)

Key Questions Answered

How do you replace a data ingestion pipeline at trillion-row scale without downtime?
Clone all table schemas and materialized views into a separate staging database, configure the new pipeline (ClickPipes) to ingest into the staging environment, run both pipelines in parallel for several days comparing daily row counts, and once data matches, disable the old pipeline and update the materialized view to write to production. This staged approach isolates risk from the production system.
How does ClickPy handle 2 trillion rows of Python download statistics?
ClickPy uses ClickHouse as its analytical database, storing each Python package download as a single row with historical data back to 2011. The system uses materialized views to maintain pre-aggregated tables for different query patterns (daily, monthly, by version, by country, etc.), enabling fast analytical queries despite the massive dataset size of over 2.2 trillion rows.
How do you fix historical data gaps in ClickHouse without rebuilding the entire dataset?
For each affected day: delete the day from all daily-aggregated tables, delete from the main table, temporarily drop materialized views for non-daily aggregations (like monthly), re-ingest source data for that day, recreate the dropped materialized views, and rebuild their target tables. The distinction between daily and non-daily aggregations is critical to avoid duplicate data.
Why replace a custom cron-based ingestion script with ClickPipes?
ClickPipes provides built-in retries, backoff, and failure handling instead of hand-rolled logic. Pipeline state and progress become visible for detecting stalled or partial ingestions. Maintenance is delegated to the ClickHouse team, and changes to ingestion logic require fewer moving parts and less custom code. Ingestion becomes a first-class part of the system rather than a separate monitored process.
What is the Null engine pattern in ClickHouse for data transformation?
The Null engine is used for a target table that doesn't persist raw rows. Instead, a materialized view attached to this Null engine table performs all transformation logic (field normalization, type conversions, schema alignment) and writes the transformed data directly to the final destination table. This pattern separates ingestion from transformation cleanly.
How do you handle materialized views during historical data backfills in ClickHouse?
For materialized views that aggregate by day, you can delete specific dates and let the views repopulate during re-ingestion. For views that don't aggregate by day (like monthly aggregates), you cannot isolate rows for a single day, so the views must be dropped before re-ingestion to prevent duplicates, then recreated and rebuilt after the backfill completes.
How do you avoid re-ingesting a full historical dataset when migrating to ClickPipes?
Since ClickPipes supports continuous ingestion but not starting from an arbitrary point in time, the solution is to update the BigQuery export job to write new data to a fresh GCS bucket. ClickPipes is configured to read from this new location, allowing it to start from the migration point rather than processing the entire historical dataset.

Key Statistics & Figures

Total rows in ClickPy main dataset
2.21 trillion
2,214,017,475,506

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

Database
Clickhouse
Analytical database powering ClickPy, storing and querying 2+ trillion rows of Python download statistics
Data Ingestion
Clickpipes
Managed ingestion service replacing custom cron scripts for continuous GCS-to-ClickHouse data loading
Data Warehouse
Bigquery
Source of PyPI download statistics, used as the upstream data source with automated daily export jobs
Object Storage
Google Cloud Storage
Intermediate storage layer between BigQuery exports and ClickHouse ingestion
Application
Clickpy
Free open-source Python download statistics platform built on ClickHouse
Business Intelligence
Metabase
Referenced as a recent addition for chart export functionality

Key Actionable Insights

1
Use a parallel staging environment when migrating data pipelines at scale. Clone table schemas and materialized views into an isolated database, run both old and new pipelines simultaneously, and compare outputs before switching over. This approach ensures zero risk to production data during the transition.
This pattern is especially critical when dealing with datasets too large to re-ingest from scratch, where corruption or data loss during migration would be catastrophic.
2
Leverage the Null engine with materialized views as a transformation layer to decouple ingestion from data transformation. By having ClickPipes write to a Null engine table and using a materialized view to transform and route data to the final table, you get a clean separation of concerns that is easier to evolve and debug.
This pattern centralizes transformation logic inside ClickHouse rather than in external scripts, making it version-controlled and inspectable as part of the database schema.
3
Distinguish between daily and non-daily aggregation tables when performing historical data corrections. Daily-aggregated tables allow targeted deletes and re-ingestion via materialized views, while non-daily aggregations require dropping and recreating materialized views to prevent duplicate data during backfills.
Failing to account for this distinction can lead to silently duplicated data in aggregation tables, producing incorrect analytics results that are difficult to detect.
4
Regularly validate source data against your analytical database to catch ingestion gaps early. At trillion-row scale, missing data is easy to overlook because queries still work and dashboards still render, but results are quietly incorrect. Implement daily row count comparisons between source and destination systems.
The ClickPy team only discovered historical discrepancies when they compared BigQuery and ClickHouse data during the pipeline migration, highlighting the need for ongoing data quality monitoring.
5
Replace custom cron-based ingestion scripts with managed ingestion services to reduce operational overhead. Hand-rolled retry logic, failure handling, and progress tracking are error-prone and require ongoing maintenance, whereas managed services provide these capabilities out of the box.
The original ClickPy pipeline required significant scripting because managed ingestion (ClickPipes) didn't exist yet. Once available, the migration eliminated several categories of operational risk.
6
When migrating to a continuous ingestion service that doesn't support arbitrary start points, redirect new source data to a fresh storage location. Configure the new pipeline to read from this fresh bucket, avoiding the need to reprocess historical data while ensuring no gap in new data ingestion.
This approach was used for ClickPipes, which tracks processed files but cannot start from a specific timestamp, making a fresh GCS bucket the pragmatic solution.

Common Pitfalls

1
Not distinguishing between daily and non-daily aggregation tables during historical data corrections. If you delete and re-ingest data for a specific day without dropping non-daily materialized views first, you will end up with duplicate data in those aggregate tables because the views cannot isolate rows belonging to a single day.
The solution is to temporarily drop materialized views for non-daily aggregations before re-ingestion, then recreate and rebuild them after the backfill is complete.
2
Attempting to re-ingest the full historical dataset when migrating to a new ingestion pipeline. At 2+ trillion rows, full re-ingestion is not feasible, especially when materialized views depend on the main table and would need to be rebuilt as well.
Instead, redirect new source data to a fresh storage location and configure the new pipeline to start from the migration point, keeping existing historical data intact.
3
Relying on working queries and rendering dashboards as indicators of data completeness. At trillion-row scale, missing data for specific days is easy to miss because analytical queries still return results and dashboards still display charts, but the underlying results are quietly incorrect.
Implement explicit validation by comparing daily row counts between the source system (BigQuery) and the destination (ClickHouse) to catch ingestion gaps.
4
Switching directly from an old ingestion pipeline to a new one without a parallel validation period. A direct cutover risks data corruption or loss if the new pipeline behaves differently than expected at production scale.
Run both pipelines in parallel for several days, comparing daily row counts between production and staging databases before making the switch.

Related Concepts

Materialized Views
Column-oriented Databases
Data Pipeline Migration
Etl Vs Elt Patterns
Lightweight Deletes In Clickhouse
Continuous Data Ingestion
Null Table Engine
Data Quality Validation
Staged Rollout Strategies
Historical Data Backfill