Search Campaign Builder
Search Campaign Builder lets users create and manage Google Ads search campaigns without leaving the platform. The backend is a Java and Spring Boot service. It sits between the Rails platform and Google Ads, and stores campaign hierarchy data for every entity. As campaign counts grew, the service began to struggle under load, and a customer-reported production incident confirmed it.
I led the investigation and the fixes end to end, pairing with the tech lead at the key decision points. Diagnosis started in Datadog, where the query breakdown showed sequential scans on the hot paths alongside query counts and cumulative execution time. I replicated the same scenarios locally to confirm and isolate them. A benchmark that imported Google Ads campaigns with different entity counts across iterations mapped the scaling curve precisely. Three root causes emerged independently: missing indexes on hot query paths, N+1 queries in the campaign fetch layer, and transaction lock contention.
A fourth issue surfaced during benchmarking. The same long-running campaign collided with itself when two builds imported it at the same time. That was a correctness problem hiding inside the performance failure.
Each issue needed its own fix. I added indexes on the hot paths and consolidated the N+1 queries. I narrowed the transaction scope, accepting a slightly wider consistency window in exchange for shorter lock durations. The idempotency guard went at the import layer instead of into a database constraint, because the coordination had to happen before the transaction opened and across build boundaries. I re-ran the benchmark harness at 4× load to confirm the fixes held.
Delivered across three sprints. The service absorbed 4× workload growth at flat p50, p95, and p99 latency. The Postgres index hit rate rose from 81% to 93%, and query latency on the hot campaign fetch path fell with it.
Media Activation: Delivery & Changelog
Media Activation publishes line item placements to external systems, starting with CM360. The internal media agency team had no way to see what changed in the delivery view without opening CM360 directly, so every discrepancy became a support request. More than 100 users were affected.
The existing changelog was a plain append log. It had no query management, no categorization, and no structured output the UI could consume. Extending it was risky, because the old system had accumulated complexity, so I built a new one. It is 80% net-new and shares only the ActiveRecord integration layer and the table schema. Reusing the schema avoided a data migration, and owning the logic gave full control over query management and event structure. The tradeoff is some duplication with the old system that will need consolidating later.
The core is a before-and-after snapshot. Before a save, the system captures a filtered hashmap of the tracked fields. After the save, a change calculator compares old and new values key by key and produces a structured diff. It includes only tracked fields, which keeps the JSONB payload lean. The diff then flows into a categorization layer, and the server formats it into the structure the UI needs. The diff logic lives in one place, so event grouping stays consistent. Virtual list pagination keeps long histories from slowing the render.
The delivery tab sits on an open-source client-side state chart library that couples the UI tightly to the backend. A full refactor would have risked regressions across every existing delivery flow, so I worked alongside the library and kept contact with its surface to a minimum.
Since launch it has recorded 500 events a day and more than 5 million rows in production, and it removed the CM360 round-trip for over 100 internal agency users.