How tracking works
What happens between a pageview firing in the browser and a number changing on your dashboard, and why mrkr's queries stay fast at scale.
Every event (a pageview, an outbound click, a track() call) is queued in the browser and POSTed in a batch. What happens to it after that is the part worth understanding, because it shapes what you can and can't do with your data.
Batched POSTs, not a stream
The tracker POSTs to /api/collect (or the equivalent path on a proxied domain). There's no persistent connection and no socket. Events are queued in the page and flushed as one batch inside a shared envelope, so an ordinary pageview costs about three requests rather than one per signal: the pageview itself (which needs the server's reply to decide about session replay), one coalesced batch a second later, and a final flush when the tab is hidden or closed.
- The queue flushes after a 1 second debounce, or immediately once 10 events are waiting.
- Each queued entry carries its own path, so an outbound event queued on
/aand flushed after a route change to/bis still attributed to/a. sendBeaconis used where possible, so the final flush survives the tab closing mid-request.- The body is sent as
text/plain, which makes it a CORS simple request and skips the preflight round trip entirely. The server parses it as JSON regardless.
Rollups and retained detail
On the server, that event is turned into a handful of counter increments against a table of daily rollups: one row per (site, day, metric, dimension), each holding a running total. A single pageview might bump pageviews for the day, visitors_by_path for the page it landed on, and visitors_by_country for the visitor's country, each as a +1 against an existing counter rather than a new row.
Rollups make many unfiltered aggregate reads inexpensive. Reports that need cross-dimensional filters, unique visitors, conversion timing or a specific currency also query indexed sessions and retained events. The dashboard and MCP tools use the same reporting rules rather than assuming every answer can be recovered from a counter.
What this means for you
- Reports reuse aggregate work where possible. Indexed detail queries, bounded rankings and short-lived caches support the filters and attribution that require retained records. Larger ranges and more selective filters can still change query cost.
- Dimension cardinality is bounded, on purpose. Metrics with an unbounded number of possible values (page paths, referrer hosts, UTM campaigns, custom event names) keep at most the top 200 distinct values per site per day; the rest fold into a single "Other" bucket. For realistic traffic this cap is invisible. It exists so that one flood of unique URLs or a UTM-spam campaign can't make a single day's storage for one site grow without bound.
- Captured history can be reinterpreted, within its limits. Filters and conversion definitions can use retained sessions and events. Changing a custom event's conversion flag or revenue property can affect historical reports where the required payload was recorded. New properties cannot be reconstructed for older events that never included them; see Custom events.
Where the numbers come from
Two extra steps happen before an event ever reaches a counter: bot filtering (see Bots and spam) drops anything that isn't a real visitor before it's stored anywhere, and excluded paths drop specific pages you've opted out of tracking. Both checks run before the rollup write, so filtered traffic never inflates a counter you'd have to later subtract from.