This document outlines how to scale the crawler from 100,000 to 500 million repositories.
Our current implementation uses adaptive range subdivision which automatically handles GitHub's 1,000 result limit. When a star range hits the cap, it splits into smaller ranges and continues. This design is already scalable in concept, but has practical limitations at 500M scale:
| Limitation | Impact at 500M Scale |
|---|---|
| Single-threaded execution | Would take months to complete |
| Single GitHub token | 5,000 requests/hour = ~20 days minimum |
| Single PostgreSQL instance | Would struggle with 500M row inserts |
| In-memory batch buffering | Memory constraints |
| In-memory range queue | Loses progress on failure |
Current approach: One worker processes ranges from an in-memory queue.
Scaled approach: Replace the in-memory deque with a distributed queue (Redis, Kafka, or SQS).
The current StarRange objects can be serialized and pushed to a message queue. Multiple workers can then:
- Pull ranges from the queue
- Crawl the range
- If hitting the 1000 cap, push subdivided ranges back to the queue
- Continue until queue is empty
This requires minimal code changes because the queue-based architecture is already in place. Each worker operates independently, writing to the same database.
Current approach: Single GitHub token with 5,000 requests/hour.
Scaled approach: Pool of N tokens, rotating between them.
With 10 tokens: 50,000 requests/hour With 50 tokens: 250,000 requests/hour
This directly multiplies our throughput.
Current approach: Synchronous requests library, blocking on each API call.
Scaled approach: Use aiohttp for async HTTP and asyncpg for async database operations.
Benefits:
- Multiple API requests in flight simultaneously
- Non-blocking database writes
- 10-100x throughput improvement per worker
Current approach: Single PostgreSQL instance.
Scaled approaches:
Option A - Partitioning: Partition the table by github_id hash. PostgreSQL handles routing automatically.
Option B - Read Replicas: Write to primary, query from replicas to avoid contention.
Option C - Distributed PostgreSQL: Use Citus or CockroachDB for horizontal scaling across nodes.
For 500M rows, we would need approximately 50GB of storage.
Current approach: Direct API-to-database pipeline.
Scaled approach: Decouple with a message queue.
Flow: GitHub API -> Crawler Workers -> Kafka/Redis -> Consumer Workers -> PostgreSQL
Benefits:
- Backpressure handling (consumers can lag behind producers)
- Retry capabilities without re-fetching
- Multiple consumer groups for different processing
Current approach: Start from scratch on failure.
Scaled approach: Save cursor position for each star range to Redis or a state table.
On restart, workers resume from their last known position instead of re-crawling.
| Component | 100K Scale | 500M Scale |
|---|---|---|
| Workers | 1 | 20-50 (distributed) |
| Tokens | 1 | 10-50 |
| Database | Single node, 10MB | Sharded cluster, 50GB |
| Memory | 1GB | 16GB+ per worker |
| Crawl time | ~15 minutes | ~1 week |
| Architecture | Monolithic | Distributed |
| Configuration | Repos/Hour | Time for 500M |
|---|---|---|
| Current (1 worker, 1 token) | ~20,000 | ~25,000 hours (3 years) |
| 10 workers, 10 tokens | ~200,000 | ~2,500 hours (104 days) |
| 50 workers, 50 tokens, async | ~2,000,000 | ~250 hours (10 days) |
| Optimized distributed system | ~5,000,000 | ~100 hours (4 days) |
To collect issues, PRs, commits, comments, and CI checks, we would add normalized tables:
| Table | Rows at 500M repos | Size Estimate |
|---|---|---|
| repositories | 500M | 50GB |
| issues | ~2B | 200GB |
| pull_requests | ~1B | 100GB |
| comments | ~5B | 500GB |
| commits | ~50B | 5TB |
Each table uses its GitHub node ID as primary key, enabling efficient upserts.
When a PR gets new comments (10 today, 20 tomorrow), we:
- Insert the 20 new comments
- Duplicates are ignored via ON CONFLICT DO NOTHING
- Only 10 new rows are actually written
This minimizes write amplification and keeps updates efficient.
Scaling to 500M repositories requires transformation from a single-threaded script to a distributed system:
- Parallelize - Multiple workers with partitioned workloads
- Multiply rate limits - Token rotation and pooling
- Go async - Non-blocking I/O throughout
- Distribute storage - Sharded or distributed database
- Add resilience - Message queues and checkpointing
- Monitor everything - Metrics, alerting, and dashboards
The core design principles (star range slicing, upsert patterns, immutable DTOs) remain the same; we simply scale each component horizontally.