Skip to content

Pipeline Overview

The Consumer Pipeline is the core processing engine — a single-process batch consumer that transforms raw Kafka messages into enriched, deduplicated, ML-classified events stored in PostgreSQL.


Pipeline at a Glance

Property Value
Process Single Python process (services/pipeline/main.py)
Concurrency Batch-oriented (no threads, GIL-friendly)
Batch Size 100 messages (default 5000 in code, overridden to 100 in .env)
Batch Timeout 0.5 seconds
Throughput ~2,100 events/sec sustained
Scaling Horizontal (add replicas matching Kafka partitions)
State Drain3 state persisted to /app/state/drain3_state.json

Architecture

%%{init: {'theme':'default','themeVariables':{'primaryColor':'#1a73e8'}}}%%
flowchart TD
  subgraph CONSUMER["Consumer Pipeline"]
    KAFKA[Kafka Consumer<br/>group: aiops-pipeline<br/>topics: raw_*]
    BATCH[Batch Accumulator<br/>100 msgs / 0.5s]

    subgraph STAGES["Processing Stages"]
      F1[Noise Filter]
      S1[1. Parse<br/>Drain3]
      S2[2. Normalize<br/>Pydantic + PII]
      S3[3. Dedup<br/>Redis Lua]
      S4[4. Features<br/>Polars]
      S5[5. Classify<br/>RoBERTa/Rule]
      S6[6. Store<br/>PostgreSQL executemany]
    end

    DLQ[DLQ Producer<br/>Kafka + PostgreSQL]
    STATS[Stats Collector]
  end

  KAFKA --> BATCH --> F1
  F1 -->|Pass| S1
  F1 -.->|Noise| SKIP
  S1 -->|Valid| S2
  S1 -.->|Invalid| DLQ
  S2 --> S3
  S3 -->|Unique| S4
  S3 -.->|Duplicate| SKIP
  S4 --> S5
  S5 --> S6
  S6 --> PG[(PostgreSQL)]
  S6 -->|Notify| REDIS[Redis Pub/Sub]
  DLQ -.->|Reject| KAFKA_DLQ[(Kafka DLQ)]
  DLQ -.->|Persist| PG_DLQ[(dlq_events)]
  STATS -.->|Metrics| LOG[Structured Logs]

Stage Summary

# Stage File Input Output Key Tech
0 Noise Filter main.py Raw Kafka msg Filtered msg Heuristics
1 Parse parser.py Message text template_id, template_text Drain3
2 Normalize normalizer.py Raw + template CanonicalEvent Pydantic v2, Regex
3 Dedup dedup.py CanonicalEvent Unique events Redis Lua (SHA-256)
4 Features feature_engineering.py Event Enriched event Polars
5 Classify ml_classifier.py Event + template Priority P1–P4 RoBERTa + Cache
6 Store storage.py Enriched batch PG rows executemany INSERT

Batch Processing Flow

sequenceDiagram
  participant CONS as PipelineRunner
  participant KAFKA as Kafka
  participant STAGES as Stages
  participant REDIS as Redis
  participant PG as PostgreSQL
  participant DLQ as DLQ

  loop Forever
    CONS->>KAFKA: poll_batch(100, 0.5s)
    KAFKA-->>CONS: Messages
    CONS->>CONS: Filter noise
    par Process each message
      CONS->>STAGES: Parse → Normalize → Dedup → Features → Classify
    end
    CONS->>STAGES: Batch write (executemany)
    STAGES->>PG: INSERT ... ON CONFLICT
    PG-->>STAGES: OK
    STAGES->>REDIS: PUBLISH events:new
    CONS->>KAFKA: commit offsets
    CONS->>LOG: Log batch stats
  end

Graceful Shutdown

# Signal handling
_SHUTDOWN_REQUESTED = False

def _handle_shutdown(signum, frame):
    global _SHUTDOWN_REQUESTED
    _SHUTDOWN_REQUESTED = True

signal.signal(signal.SIGTERM, _handle_shutdown)
signal.signal(signal.SIGINT, _handle_shutdown)

Shutdown Sequence: 1. Signal received → _SHUTDOWN_REQUESTED = True 2. Finish current batch (process + write) 3. Commit Kafka offsets 4. Save Drain3 state (parser.shutdown()) 5. Close PostgreSQL connection 6. Exit


Statistics (Logged per Batch)

{
  "timestamp": "2026-01-15T10:30:00Z",
  "level": "info",
  "event": "batch_processed",
  "received": 100,
  "written": 96,
  "duplicates": 3,
  "noise_filtered": 1,
  "ml_cache_hits": 84,
  "ml_predictions": 10,
  "ml_fallbacks": 2,
  "templates_known": 3847,
  "processing_time_ms": 125
}

Counters (in PipelineRunner._stats): - received_total, written_total, dlq_total - noise_filtered_total, duplicate_total - ml_cache_hits_total, ml_predictions_total, ml_fallback_total - batches_total, processing_errors_total


Configuration

Parameter Env Var Default Description
Batch Size PIPELINE_BATCH_SIZE 5000 (100 in .env) Messages per poll
Batch Timeout PIPELINE_BATCH_TIMEOUT_SECONDS 0.5 Max wait for batch
Dedup Window DEDUP_WINDOW_SECONDS 300 Redis key TTL
Drain3 Max Clusters DRAIN3_MAX_CLUSTERS 100000 Template limit
Drain3 State Path DRAIN3_STATE_PATH /app/state/drain3_state.json Persistence
ML Min Confidence ML_MIN_CONFIDENCE 0.80 Cache threshold
Kafka Group KAFKA_CONSUMER_GROUP aiops-pipeline Consumer group
Kafka Topics KAFKA_TOPIC_RAW_* raw_logs, etc. Input topics

Scaling

Replicas Partitions Required Throughput
1 1 ~2,100 eps
2 2 ~4,200 eps
3 3 ~6,300 eps
6 6 ~12,600 eps

Rule: replicas ≤ partitions for each input topic. Increase partitions in kafka-init for production.

# Scale pipeline
docker compose up -d --scale consumer-pipeline=3

Monitoring

Health

# Check if running
docker compose ps consumer-pipeline

# View logs
docker compose logs -f consumer-pipeline

Key Metrics (from logs)

Metric Healthy Range
written / received > 90%
duplicates / received 5-20% (depends on source)
ml_cache_hits / (ml_predictions + ml_cache_hits) > 95%
ml_fallbacks / ml_predictions < 5%
processing_time_ms < 2000 ms

Drain3 State

# Check state file size
docker compose exec consumer-pipeline ls -lh /app/state/drain3_state.json
# ~300 KB typical

Kafka Lag

docker compose exec kafka-broker \
  kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --group aiops-pipeline \
  --describe