Staff Engineer, Data Infrastructure. The company runs without a formal IC ladder;
the title reflects the scope held: built the data plane and led it under one team. De-facto lead of
the ETL team with one peer, 2021–22; Tech & Team Lead of the ingestion team (5 engineers),
May 2023 – Feb 2025; project lead since, by request.
Mojart — declarative pipeline compiler, Clojure
author · project lead · 2026
- Purpose: make adding or changing a data pipeline an act of authoring a spec
rather than writing pipeline code, and keep changing one afterwards cheap and safe. Content
identity so an edit costs only what changed — a platform where every schema change means a
full recompute stops changing, and ossifies; a run-ledger so failures resume rather than restart;
an atomic generational swap so downstream consumers never see a schema shift or a half-published
dataset. Mojart is the primary write path from raw ingestion to the semantic layer.
- Derived a tensor model for metrics with multi-dimensional variants: ~30
source events explode into 800–1,000+ concrete metrics across axes (attribution window,
type, cohort period…); each metric is a point in that space, a scalar is the degenerate
tensor, and a coalesce/broadcast rule makes ratios of mixed-rank operands well-defined — so
the DSL declares the space generatively instead of enumerating columns, resolved by a
fixed-point loop over the column registry.
- Built the metadata layer that publishes each dataset as a Model — typed
columns, grain, tensor-valued measures — the contract the semantic layer binds to a shared
vocabulary, resolving a widget's query to the right tables and grains with no producer renames
and no redeploy.
- A pure, content-addressed compiler: an EDN program through 11 passes into a task DAG
carrying a Merkle content-identity per node and per field. Deploy diffs that identity against
the last committed generation manifest and swaps atomically; the runtime reconciles a per-cell ledger so only dirty
(artifact × partition) cells
rebuild — dirty meaning missing, definition-changed, or built from an older version
of the source data, which the ledger records alongside the identity.
- The runtime: every task is a DBSP-style circuit with a checkpoint feedback edge —
cursor, output and a page-domain fingerprint saved per page, crash-resume with a monotone undo,
four paging axes (rowid, hash-bucket, branch-rowid, keyset), page-parallelism only for circuits
that declare thread-safety. Declared
persist steps become materialised, versioned
tables that downstream work resumes from.
- Merged and running end to end pre-production — not in production.
Backfilled two years of a customer's data, 88M rows, in roughly one hour after executor optimisations (the first recorded run took ~225 minutes).
- Raised checkpoint write throughput from 2.70 to 10.58M rows/min (3.9×)
at 100M rows by sweeping write-ahead-log budget against merge concurrency and showing
concurrency is the dominant, memory-cheap lever — on a colleague's storage engine.
Mu — distributed ingestion, Scala/Spark
designed and built from scratch · principal author · 2022–2025
- Designed and built it from scratch as the replacement for two systems: a Ruby
ingestion framework grown inside the Rails monolith, and the Go/Apache Drill service beside it
that existed to query flat files the Ruby side couldn't. One general-purpose ingestion layer in
their place, where a channel is a configuration compiled into a distributed plan rather
than a codepath — so adding a source stopped being an engineering project. Mu's rollout drove
Drill's usage to zero; I later deleted the cluster and its front-end service.
- Took a 5M-row weekly customer ingest from ~1.5 days to ~1.5 hours. The old
duration structurally could not meet a daily data-availability deadline. Achieved by batching
dereference at 1,000 rows per request across 32 Spark executors, and reading Parquet and
BigQuery natively instead of transcoding to CSV so filters push down to the source. A 500k-row
daily job went 1 hour → 10 minutes on the same change. Verified rather
than asserted: ran a live channel and a clone through both pipelines and compared rendered
output before merging.
- Reached 100% of the platform's customer-data and custom-analytics channels;
the predecessor was retired. This was the objective I set and owned.
- Still the ingestion path four years on. On 31-day production means:
~680M rows/day (peak day 1.33B; peak hour 120M), ~8,900 jobs/day with 73%
in a six-hour window (peak 1,889/h, 7× the mean), with the 12 workers at 74% occupancy through the wave and ~2 busy otherwise — a
demand-bound figure against a next-morning deadline.
- Benchmarked the per-row validation path (deserialise, type-cast, date-parse, write Parquet)
over a 4.76B-row / 2.2 TB source: 52.1M rows in 3.2 minutes on 16 cores, 8 GB heap
— ~16M rows/minute with date parsing on every row.
- Compiles a channel config into a Spark plan across 11 source types,
10 processors and 3 sinks. A team system — largest single
author, not the only contributor.
- 2024: brought a 17M-object/day feed inside a 4-hour job SLA
— derived the per-request budget (~6 s vs a 20 s median), found a 60-s stall in a metrics
thread, batched cache reads, moved payload parsing off the web framework (~700 ms × 17,500
requests). Dereference from timeouts to 53 minutes; over the next ten months mean job time
140 → 54 min while the feed grew 8 → 12M rows/day (peak 21.7M); annotation
object sync 1,100 → 6,700 objects/s.
- Ported metric annotation from the OLTP stores to the in-house OLAP store: the heaviest
channel 15 h → 4 min (with a colleague's server-side fix).
A rule I earned the hard way
2024–2025
Two account-identifier migrations I led in late 2024 and early 2025 duplicated data for
several customers; the second took ten days to detect. I owned both publicly and wrote the
root-cause analyses. What came out of them: duplicate-account detection that now runs in
production, and a standing rule for any destructive change — a named second reviewer, written
success criteria, and a detection query before the first row is touched.
Per-object annotation
co-designed · led delivery · 2019–2020
- Wrote the v2 specification after an incident review found the tagging system had no
predictable failure rate and no way to assert its own sanity. It names the properties the system
must have — determinism, atomicity, eventual causal consistency, isolation, visibility,
checkpointing, fault tolerance — then evaluates three candidate architectures against a
failure-mode analysis and selects one.
- Built the metadata service that versions the rule graph, so a tag can be attributed to a
specific rule-graph version across a hierarchy of objects; implemented the PostgreSQL-based work
executor, and the JIT-compiled Ruby evaluator — rules compiled to generated
code rather than interpreted one by one. A colleague added the C-codegen sibling and later
optimised the Ruby path further.
- Validated the design where it was most likely to break and published the
results to the team (May 2020). The architecture put Postgres in the critical path as the work
executor, so I measured that component rather than the pipeline: 17–19,000
objects/sec, up from 2–3,000 once I isolated a single Postgres planner setting
costing 6–8× throughput; and the split-finalisation query cut from
20.5 minutes to 111 seconds with throughput flat either side, so the gain was
attributable to the query, not the environment. Held disk health as a controlled variable so a
failing disk could not read as a regression.
- Years later, as team lead, I set the engine's performance objective — full re-tag
under twelve hours for every channel after a customer edits a rule. The engine
holds ~4.7 billion objects across 217 customer schemas and rewrites ~12% of them
daily (~545M updates + 37M inserts); the largest channel, ~440M objects, re-tagged in
~1 h median, 2–5 h p90 across the whole pipeline — rule evaluation,
tagging and persistence, not just the executor. Met September 2024 and beaten on the largest
channels. The annotation team built the mechanism and took the measurements; the target and the
accountability were mine.
- The team wrote most of the implementation; the design, the direction and that benchmark were
mine. Still the platform's annotation engine six years on.
A reversed decision
I led a platform-wide MRI → JRuby migration in 2018 for thread parallelism under an
expression evaluator that was timing out. It measured ~25% faster on the benchmarked query and
roughly halved CPU-heavy job durations, and it still failed — fixed JVM allocation against
Kubernetes' soft limits caused OOM kills, the GIL had been hiding unsafe cross-thread
ActiveRecord use, the test suite tripled, and JVM warmup made releases visibly slow. Reverted
after six months. It produced merged upstream fixes to JRuby, activerecord-jdbc-adapter,
rake-compiler and concurrent-ruby, and one rule I have applied ever since: instrument first, then
decide. The full write-up is here.