Skip to content

Distributed Streaming Aggregator with Clustering/Sharding #3

Description

@pristley

Feature Spec: Distributed Streaming Aggregator with Clustering/Sharding

1. Executive Summary

Scale NeuralBudget's Rust engine from 15k to 1M+ metrics/sec via distributed clustering, sharding, and per-node optimization while maintaining consistency and fault tolerance.

2. Current Limitations

  • Single-node throughput: 15k metrics/sec (CPU-bound)
  • Memory overhead: 2GB for 1M windows
  • p99 latency: 500ms+
  • State recovery: 10+ minutes
  • Loss rate: 5% during backpressure

3. Design Goals

  1. Throughput: 1M metrics/sec sustained
  2. Latency: p99 < 100ms
  3. Consistency: At-least-once delivery
  4. Fault Tolerance: Survive single node failure
  5. Scalability: Linear throughput up to 64 nodes
  6. Simplicity: Auto-scaling, minimal ops

4. Architecture Overview

Metric Producers → Load Balancer → Sharding Router → 
Aggregator Cluster (Raft Consensus) → Tiered Storage

Cluster Design

Each Node:
├─ Metric Receiver (Tokio task pool, gRPC/HTTP)
├─ 4 Shards per node (250k metrics/sec each)
├─ In-memory windows + RocksDB persistence
├─ Raft consensus for replication
├─ Async flush to storage
└─ Health monitoring

4-node cluster: 1M metrics/sec (4 nodes × 4 shards × 250k/sec)

5. Sharding Strategy

Deterministic Routing

fn compute_shard_id(metric: &Metric, num_shards: u32) -> ShardId {
    let key = format!("{}:{}", metric.name, metric.labels);
    fasthash::metro::hash64(key.as_bytes()) % num_shards as u64
}

Properties:

  • Same metric + labels → Always same shard
  • No inter-shard coordination
  • Improved cache locality

Consistent Hash Ring

  • Virtual nodes for even distribution
  • Rebalancing: 25% shards migrate on node add
  • Cold start: 30 seconds per shard

6. Data Model

Metric

- name: string
- value: f64
- timestamp: i64 (ms)
- labels: Map<string, string>
- source_id: string

AggregationWindow

- key: MetricKey (name + sorted labels)
- window_time: i64 (aligned bucket)
- sum, count, min, max: aggregates
- percentiles: {p50, p90, p99, p999}
- ingestion_count, dedup_count: quality metrics
- last_update_time: timestamp

Consistency Level

  • Strong consistency: Majority quorum replication
  • Replication latency: <10ms (intra-datacenter)
  • Durability: Survives single node failure

7. Performance Optimizations

CPU (35% improvement target)

  • Lock-free DashMap: 16x vs Mutex (fine-grained buckets)
  • Single-threaded event loops: Tokio pinned to cores
  • SIMD batch operations: Vectorized aggregation
  • Zero-copy deserialization: Protobuf streaming

Memory (65% reduction)

  • CompactWindow: 36 bytes vs 128 bytes per window
    • Use f32 instead of f64
    • Store IEEE 754 bits for min/max
    • Pack percentiles efficiently
  • Ring buffer: Pre-allocate time windows, reuse memory
  • Bloom filter: 100MB for 1B deduplicates (0.01% FPR)

Network

  • Protocol Buffers: ~500ns per metric
  • Batch processing: 1000-metric chunks (50x overhead reduction)
  • Intra-cluster: Optimized RPC, no serialization for internal state

Storage (Tiered)

Hot (7 days):     TimescaleDB (8:1 compression)
Warm (30 days):   S3 Parquet (15:1 compression)
Cold (365 days):  S3 Iceberg (20:1 compression)

8. Raft Consensus

Write Path

  1. Metric arrives at leader (in-memory window update)
  2. Append to WAL (local disk)
  3. Add to Raft log
  4. Replicate to followers (parallel)
  5. Once majority acks: Mark committed
  6. Return success

Latency: <10ms replication, strong consistency

Leader Election

  • Term-based leadership
  • Election timeout: 300ms
  • Failover detection: <500ms

Snapshots

  • Frequency: Every 100k log entries or 5 minutes
  • Size: ~500MB for 1M windows
  • Recovery: 2 seconds (parallel deserialization)

9. Duplicate Detection

Metric ID = SHA256(producer_id + source_timestamp + metric_name + labels)

Detection window: Configurable (default: 1 hour)
Storage: HashMap with cleanup on expiry
Collision probability: < 1 in 2^128

10. API Specification

gRPC (Native Protocol)

service AggregatorService {
  rpc IngestMetrics(IngestMetricsRequest) returns (IngestMetricsResponse);
  rpc StreamMetrics(stream Metric) returns (stream IngestResponse);
  rpc QueryWindows(QueryRequest) returns (QueryResponse);
  rpc GetStatus(StatusRequest) returns (StatusResponse);
}

HTTP (Prometheus Compatible)

POST /api/v1/write                    # Ingest metrics
GET /api/v1/query?metric=name         # Query aggregated windows
GET /api/cluster/status               # Cluster health
POST /api/cluster/config              # Update configuration
POST /api/cluster/snapshot            # Manual snapshot
POST /api/cluster/rebalance           # Shard rebalancing

11. Configuration

tracing:
  otlp_port: 4317
  batch_size: 1000
  batch_timeout_ms: 5000

topology:
  aggregation_window: 3600              # 1 hour
  discovery_interval: 600               # 10 minutes
  retention_days: 30
  max_versions: 500

cluster:
  enable_replication: true
  quorum_size: 3
  election_timeout_ms: 300
  snapshot_interval: 100000
  
sharding:
  shards_per_node: 4
  rebalance_on_scale: true
  
performance:
  worker_threads: 8
  window_buffer_size: 1000
  max_cardinality_per_shard: 10000000

12. Operational Commands

# Cluster status
curl http://aggregator:8080/api/cluster/status | jq

# Add node
kubectl scale deployment aggregator --replicas=5

# Trigger rebalancing
curl -X POST http://aggregator:8080/api/cluster/rebalance

# Create snapshot
curl -X POST http://aggregator:8080/api/cluster/snapshot

# Update config
curl -X POST http://aggregator:8080/api/cluster/config \
  -H "Content-Type: application/json" \
  -d '{"max_cardinality_per_shard": 5000000}'

13. Monitoring Metrics

Throughput:
  aggregator_metrics_received_total
  aggregator_metrics_processed_total
  aggregator_metrics_dropped_total
  aggregator_throughput_per_sec

Latency:
  aggregator_ingestion_latency_ms (p50/p90/p99/p999)
  aggregator_window_completion_latency_ms
  aggregator_flush_latency_ms

Resources:
  aggregator_memory_usage_bytes
  aggregator_active_windows
  aggregator_window_cardinality

Replication:
  aggregator_raft_term
  aggregator_raft_log_index
  aggregator_replication_lag_ms
  aggregator_raft_leader_changes_total

14. Alerting Rules

alerts:
  - name: HighMetricDropRate
    condition: (drop_rate / ingestion_rate) > 0.01
    for: 5m
    severity: critical

  - name: HighIngestionLatency
    condition: histogram_quantile(0.99, latency_ms) > 100
    for: 5m
    severity: warning

  - name: ReplicationLagHigh
    condition: replication_lag_ms > 1000
    for: 2m
    severity: critical

  - name: NodeUnhealthy
    condition: up{job="aggregator"} == 0
    for: 30s
    severity: critical

15. Performance Benchmarks

Throughput Scaling

Single Node (1 shard):     50k metrics/sec, 60% CPU, p99=80ms
Single Node (4 shards):    150k metrics/sec, 85% CPU, p99=90ms
3-node cluster:            450k metrics/sec, 80% CPU, p99=100ms
4-node cluster:            600k metrics/sec, 75% CPU, p99=95ms
8-node cluster:            1.2M metrics/sec, 70% CPU, p99=85ms

Memory Usage

Per node (4 shards, 1M windows each):
  Active windows:    512MB (4M × 36B compact)
  DashMap overhead:  100MB
  RocksDB cache:     200MB
  Bloom filter:      100MB
  Arrow buffers:     50MB
  ──────────────────────
  Total:             1GB (67% reduction from 3GB)

Latency Percentiles

At 1M metrics/sec:
  p50:    8ms
  p90:    35ms
  p99:    95ms
  p999:   200ms

Window completion: 5min window + 100ms = 5:00-5:05

16. Cost Analysis (1M metrics/sec)

Hardware (4 nodes):
  Instance: c5.4xlarge × 4 = $16,400/month compute
  Storage: 100GB EBS + 200GB S3 = $21/month
  Total: $16,421/month

Cost per metric:
  $16,421 / (1M metrics/sec × 2.592B sec/month)
  = $0.0063 per 1M metrics

17. Implementation Roadmap

Phase 1 (Weeks 1-4): Single-node optimization → 50k/sec

  • DashMap integration
  • Tokio tuning
  • Memory optimization

Phase 2 (Weeks 5-10): Distributed clustering → 150k/sec

  • Raft consensus
  • RPC framework
  • Replication

Phase 3 (Weeks 11-14): Sharding layer → 1M+/sec

  • Consistent hashing
  • Shard rebalancing
  • Cross-shard queries

Phase 4 (Weeks 15-18): Storage & export

  • Parquet serialization
  • Tiered storage
  • TimescaleDB integration

Phase 5 (Weeks 19-24): Operations & tools

  • Kubernetes auto-scaling
  • Monitoring dashboard
  • Operational runbooks

18. Node Lifecycle

LEADER/FOLLOWER → UNHEALTHY (unreachable 3s) → 
DEAD (30s) → RECOVERING (snapshot restored) → HEALTHY

19. Scaling Operations

Add Node

  1. Join cluster (empty state)
  2. Leader creates snapshot
  3. Transfer snapshot (streaming)
  4. Replay log from snapshot
  5. Catch up to current state
  6. Rebalance shards
    Time: 2-5 minutes

Remove Node

  1. Mark for decommission
  2. Migrate shards to other nodes
  3. Drain in-flight metrics (5s timeout)
  4. Shutdown gracefully
    Time: <30 seconds

20. Failure Scenarios

Single Node Failure

  • Detection: <500ms (Raft heartbeat timeout)
  • Recovery: Automatic failover, no data loss
  • Service impact: None (quorum consensus)

Network Partition

  • Behavior: Leader steps down if loses majority
  • Consistency: Maintained (some nodes unavailable)
  • Recovery: Auto-heal on connectivity restore

Split-Brain Prevention

  • Raft ensures only one leader per term
  • Leader must have majority support
  • Old leader auto-demotes without quorum

21. Backward Compatibility

  • DAG APIs versioned (/v1/, /v2/)
  • Manual aggregation still supported
  • Gradual user migration (6 months)
  • 12-month deprecation window

22. Success Metrics

  • Throughput: 1M+ metrics/sec sustained
  • Latency: p99 < 100ms
  • Accuracy: >99% consistency across nodes
  • Availability: 99.9% uptime
  • Cost: <$0.01 per 1M metrics stored

23. Comparison: Before vs After

Metric                   Before      After      Improvement
────────────────────────────────────────────────────────
Max throughput           15k/sec     1M/sec     66.7x
Nodes required           1           4-8        distributed
CPU per metric           4μs         0.4μs      10x
Memory per window        128B        36B        3.5x
Latency p99              500ms       95ms       5.3x
Failure detection        ~2min       <500ms     240x
Recovery time            >10min      <30s       20x
Cost per metric          $0.04       $0.006     6.7x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions