Skip to content
On a dark grid, a dense band of thin teal vertical strokes; out of it a dot begins a single flat horizontal line, and to the right stand three rectangles in a row — two outlined, the last one filled.

“The edge is the only place an architecture is actually tested.”

Writes, live reads, and a year of history — pick three

Two million writes a day, read live in a customer’s dashboard, with the annual report returning in under a second — for the accounts holding nine hundred listings.

Get in touch
A US marketplace for truck services · technical leadership · team of 12 · 2022–2026 · same engagement
2M+
writes per day
< 1 s
annual report at 900+ listings
365
rows read, not 700 million
Summary

We designed and built the analytics layer for an advertising marketplace. It took over two million writes a day, fed a dashboard that updated live, and answered a full year of history in under a second.

Those three properties normally exclude one another. A database fast at ingesting is slow at complex queries. A year of raw data at that rate is hundreds of millions of rows. The answer wasn’t a faster database — it was refusing to make one database do all three jobs.

The problem

On an advertising platform the customer isn’t buying software, they’re buying proof that they were visible. When a repair shop pays for a listing, the only question that matters at renewal is how many people saw it and how many called. That means the analytics are the invoice justification.

The constraint

Three things had to be true at once: ingest at volume, read live, and aggregate a year quickly. Each one alone is easy. It also rules out buying something off the shelf — general web analytics measures traffic to a site, not the value delivered to one advertiser, and it cannot put that number back into their account.

What we did

  1. A time-series database for ingest, deliberately with a small index

    Identifiers — listing, user, device — went into fields rather than tags. In a time-series store, tags are indexed and fields are not; putting a listing ID in a tag means every new listing creates a new series and the index eventually suffocates. That is the single most common way these installations die.

    Cost: no grouping by listing
  2. Daily rollup into the relational database

    Raw events live seven days, with a daily export to object storage as archive. A daily per-listing summary lands in the relational store, and that is what the dashboard and the invoices query. The annual report therefore never touches a raw row.

    365 rows per listing
  3. Measured at the worst case, not the average

    Under one second holds for accounts with over 900 listings — a year of daily rollups for nine hundred listings is roughly 330,000 rows aggregated in a single query. “Under a second” means nothing if the account has three listings; the edge is the only place an architecture is actually tested.

    330,000 rows in one query
  4. Totals and uniques in separate buckets

    Unique views keyed by session and by page URL, so someone searching in two cities isn’t counted as one view — they were looking for two different things. Totals are the bigger number and look better; uniques are what actually tells an advertiser how many different people saw them. Keeping both means there’s nothing to hide.

    Nothing to hide
  5. Bots filtered at three layers

    Firewall, known addresses, user agent. On an advertising platform an inflated view count isn’t an accuracy problem, it’s a trust problem. An advertiser who sees five hundred views and no calls stops believing the number, and then stops believing the invoice.

    Trust, not accuracy
THE DATA PATH — THREE JOBS, THREE LAYERS
One event stream enters the time-series store, which splits into two branches: a websocket layer feeding the live dashboard, and a daily rollup feeding the annual report that returns in under a second.Site, app, dashboard2M events per dayTime-series storesmall index, raw 7 daysWebsocket layerfresh data onlyDaily rollupone row per listingLive dashboardreads as it happensAnnual reportunder one second365 rows, not 700Mper listing, per year
The annual report never touches a raw row.
THREE JOBS THAT NORMALLY EXCLUDE EACH OTHER
Ingest time-series store

Two million writes a day needs a database designed for writing. The cost is that it is slow at complex queries, so the read job must not be left to it. Identifiers go in fields rather than tags: a tag per listing means a series per listing, and the index eventually suffocates.

The result

  • 2M+ writes per day
  • Live reads in the advertiser’s dashboard
  • Annual report under one second at 900+ listings
  • Same data feeding the yearly report that goes out before renewal
For context — why under a second
0.1 s
feels instantaneous
1 s
limit for uninterrupted thought
10 s
limit for keeping attention

Nielsen’s response-time thresholds, unchanged since 1993. Under a second is not an arbitrary target — it is the boundary below which a user does not lose their train of thought.

What we’d do differently

Seven days of raw retention was chosen early and never revisited. It was almost certainly right, but we never checked what we lost by it — whether anyone ever needed to reprocess a window longer than a week, and what it would have cost to keep thirty days. We picked a number and moved on.

Related
Four re-platformings, zero downtimeThe engagement this layer was built inside.A 15-minute report to 0.2 sThe same move — precompute, don’t recompute — under a fixed interface.
All 18 case studies
  • InfluxDB
  • MySQL
  • WebSockets
  • Node.js
  • AWS

One database doing three jobs?

Get in touch

Systems that can’t stop — from architecture to production.

© 2026 Micro Tech, Sarajevo