← all work

Proteus

A config-driven service that replaced multiple per-team custom compilers. Parses, transforms, and validates heterogeneous input data into structured datasets — entirely from JSONPath-driven config, no code change required to onboard a new client.

200 GB
peak day
100+
concurrent tasks
3d → 4h
client onboarding
99.95%
availability

The problem

Each team had spun up its own custom compiler — different conventions, different validation rules, different storage paths. Every new client meant a 3-day engineering onboarding. The compilers diverged faster than they could be maintained, and bugs in one didn't get fixed in the others.

I built one Proteus service to replace them. Heterogeneous in, structured out, all driven by a JSONPath-style configuration that ops could edit without redeploying.

Architecture

Ingestion · compile · multi-sink
Primary SQS live ingestion Recompile SQS replay after config fix DLQ · 15d bulk replay safe Proteus FastAPI · Factory Pattern parse transform validate sanitize write JSONPath-driven config MongoDB · configs hot reload, in-memory cache WAL · 48h recovery on outage S3 backup always-on MongoDB sink document outputs BigQuery sink analytics outputs S3 sink file outputs
Configs hot-reload from MongoDB. WAL + S3 backup are written before the primary sink so a downstream outage never loses data.

FastAPI microservice with a Factory Pattern

JSONPath-driven configuration

Dual-queue SQS ingestion + DLQ

Autoscaling on AWS ECS

Tasks scaled dynamically on three signals — queue depth, incoming message rate, and container I/O utilization. The third one mattered most: queue-depth-only scaling overshoots when a small number of large messages stall a worker. Adding I/O as a scaling signal was what kept the p99 stable when message-size distribution shifted.

Multi-sink writes

What it ran in production

Daily data volume processed
Average day
100+ GB
Peak day
200 GB
The peak was a real production day, not a stress test.
New-client onboarding time
Before
~72 h
After
≤ 4 h
The ~18× reduction came from moving client-specific logic out of code and into hot-reloadable JSONPath configs in MongoDB.

Why the design held up

1. Config as data, not code

Everything client-specific lives in MongoDB — schema, JSONPath selectors, sanitizers, sink choice. Adding a client is a config commit, not a deploy. That's the only reason 3 days → 4 hours was achievable; if onboarding had stayed in code we'd have been bottlenecked on review and deploy cycles.

2. Re-compilation queue is a separate queue

Mixing replay traffic with live ingestion in one queue is how you turn a config fix into a production incident — replays compete with real-time data for worker capacity. Splitting the queue means replays drain at their own rate without affecting current SLAs.

3. WAL + S3 backup before the primary sink

Downstream storage will fail. Writing the WAL and S3 backup before the primary sink means a MongoDB or BigQuery outage doesn't lose the message — when the downstream comes back, the WAL replays. 48 hours was chosen as the window where a human is reliably going to have noticed an outage.

Stack