Analytics that costs a cent
Two prices for knowing your visitors
Free analytics is paid for with the data; hosted analytics is paid for per event. A small product should have to pay neither way.
Every product needs the same handful of answers. How many people came today, where they came from, which pages they read, where they gave up, and what one confused visitor actually did on the screen. Those are not hard questions. What has made them expensive is where the answers are kept.
The free answer sends every visit to an advertising company and asks the visitor to agree to it with a banner. The hosted answer keeps the data in a vendor's account and bills by the event, so the price rises with every success and never falls when a month is quiet. Session replay, the one feature that shows what really happened, is usually the most expensive line.
HanuAnalytics is the third answer. It is one open-source binary that runs on the smallest virtual machine a public cloud gives away, keeps every event as files in a storage bucket you own, and serves the dashboards, the live view, the funnels and the replays from there. The same shape as HanuDB, applied to the visitor book.
A museum's visitor book, posted to the archive
Think of a museum that counts visitors in a notebook at the door and posts every finished page to an archive across town. The desk can be lost; the pages cannot.
The analogy for the whole system is a museum with one desk. The clerk at the door writes each visitor in a notebook: when they came, which door, which rooms they walked through. Every minute the finished page is sealed and posted to an archive across town. The clerk also keeps a copy of recent pages in a drawer, so questions about this week are answered from the drawer, not by a trip to the archive.
If the desk burns down overnight, a new clerk arrives with an empty drawer, asks the archive for the pages they need, and the museum is counting again by morning. Nothing written on a sealed page was ever lost, because a page was only ever counted as written once the archive had it.
The desk is the free virtual machine. The notebook pages are Parquet files. The archive is the bucket. The drawer is the machine's disk, which mirrors the files the dashboards need. And the clerk never writes a visitor's name or street address, only a daily code that cannot be turned back into one.
What one pageview goes through
An 8 KB script batches events to one endpoint; the server checks the origin, drops bots, uses the IP once and forgets it, and buffers the event for a Parquet file.
The install is one script tag. The script is 8 KB compressed. It records the pageview, single-page-app navigations, clicks, outbound links, downloads, form submits, rage clicks, JavaScript errors and Web Vitals on its own, and batches them to one ingest endpoint. Your own events and people are two calls: capture and identify.
The server accepts a batch only from the domains listed for that project, or from a native app that names itself in a header and is on the project's list. Known crawlers, headless browsers and automation send nothing at all. The IP address is used once, in memory, to look up the country and city and to compute the visitor's daily hash, and is then dropped. It is never written anywhere.
The event then sits in a buffer for up to a minute with everything else that arrived, and is written as one Parquet row group. That single buffering decision is most of the cost model: a bucket charges by the write, and a busy site makes a few hundred writes a day instead of one per event.
Files, not a database
Events are Parquet files, one per project per day after compaction, scanned by a query engine from the machine's local mirror. Only the small, changing things go in a database.
There is no analytics database to run or to pay for. Events are Parquet files compressed with zstd, laid out in the bucket by project and by day. A daily job compacts the day's small files into one, so a year of a project is a few hundred objects. A million events is about 60 MB.
The dashboards are SQL. Apache DataFusion, embedded in the binary, scans the files from the machine's disk, which mirrors whatever the bucket holds for the projects it serves. A fresh machine starts with an empty disk and pulls files on demand, so a dashboard that has been asked once is fast the next time, and a query never costs a bucket read. The same engine is behind the read-only SQL explorer in the admin, where you can ask any question the panels do not.
The things that change often and are read by key, the organizations, projects, people, sessions and the live view, do not belong in daily files. They go in HanuDB, which runs on the same free machine and keeps its own truth in the same bucket. Two engines, one machine, one bucket, and nothing that holds the truth sits on the machine.
Recordings, replayed from the bucket
Session replay records the page in the browser with inputs masked, ships gzip chunks grouped into half-megabyte objects, and replays them in the admin. Nothing masked ever leaves the visitor's machine.
Replay is the feature that usually makes analytics expensive, so it was designed around the bucket's prices. The recording bundle is separate from the SDK and is loaded only for projects that have recording on, at the sample rate they chose. In the browser it captures the DOM and the visitor's movements with rrweb, the open-source recorder.
Every input is masked before it is sent, by default, and a project can add selectors for text to mask and elements to block entirely. Password fields are always masked; e-mail addresses and card numbers that appear in captured click text are masked too. Because masking happens in the browser, the masked text never leaves it, so there is nothing to protect on the server.
The chunks arrive compressed and are grouped into objects of about half a megabyte, so a ten-minute recording is roughly 300 KB of storage and a handful of writes. The admin plays them back with a timeline of the session's events beside the screen. Recordings have their own retention, 30 days by default, and the bucket deletes any recording object older than 90 days whatever the setting, as a backstop.
Where the money would go, and where it does
Line by line: the machine is free, the IP is free while attached, storage is two cents a gigabyte, egress has a free gigabyte, and writes are five dollars a million.
The bill has five lines, and four of them round to nothing. The e2-micro machine with its 30 GB disk is in the cloud's always-free tier, one per billing account in three US regions. Its static address is free while it is attached to that machine. Bucket storage is about two cents a gigabyte a month, so a million events, at 60 MB, costs a tenth of a cent to keep. The first gigabyte of traffic out is free; the 8 KB script served a hundred thousand times is about 0.8 GB, and behind a free Cloudflare plan the edge serves it for nothing.
The fifth line is the one that is billed by the piece: writes to the bucket, at five dollars a million. Every design decision about buffering, compaction and chunk grouping exists to keep that number small. A site with traffic around the clock makes about 1,500 writes a day: 45,000 a month, 23 cents.
HanuDB, which holds the metadata and the sessions, runs on the same machine and the same bucket and adds a few cents of its own writes. These are list prices and arithmetic, not a measured bill; the measured number for HanuDB on the same kind of machine was about a cent a day, and this system writes less often than that one does.
Under a dollar for a small product
A product with a few hundred thousand pageviews a month and recordings on comes to under a dollar, and the price does not climb with events; it climbs, slowly, with gigabytes.
Add the lines up for a small product. A few hundred thousand pageviews a month, custom events on top, and session recording on: the storage grows by a few hundred megabytes a month, the writes stay in the hundreds a day, the script is served from the edge. The guide's estimate is under one dollar a month, all in, and most of that dollar is writes.
The shape of the bill is what matters more than its size. A hosted tool's price is a function of events, so doubling your traffic doubles the line. Here the machine is a fixed zero, writes grow slowly because they are batched, and the only line that tracks your success is storage at two cents a gigabyte, which a year of events barely moves. Retention does the rest: events are deleted after a year by default and recordings after a month, so the bucket does not grow without end.
The ceiling is the machine, not the bill. When a product passes tens of millions of events a month the free machine runs out of memory for queries before the bucket costs anything to speak of, and the answer is a two or four gigabyte machine for a few dollars, with one memory knob raised. Nothing else changes, because the truth was never on the machine.
What is never stored
No raw IP, a visitor hash that changes daily, an optional cookieless mode, masking in the browser, bots excluded, and a token that only works from your domains and apps.
The rule was to build a Google Analytics replacement you can defend. No raw IP address is stored, ever. By default the script keeps a random anonymous id in the browser. In cookieless mode, a project setting, it stores nothing between page loads and the server counts visitors with a daily hash of the salt, the day, the project, the IP and the user agent, the approach Plausible and Fathom made familiar. The salt is a secret of your instance, and the hash is different tomorrow.
Nothing is identified unless your own site calls identify. Do Not Track can be honoured per project, and a visitor can opt out with one call that a cookie banner can make. Known crawlers, headless browsers and automation are detected in the browser and send nothing, so they never reach the statistics.
A project's public key only works from its listed domains and, for native apps, from the bundle ids it lists; a leaked key cannot be used to pollute your data from elsewhere. The data lives in your bucket, in your project, in the region you chose. The only call the server makes out is a monthly download of the free GeoIP database, and you can upload that yourself and make it none.
What you get on day one
Pageviews, sources, campaigns, places, devices, custom events, funnels, retention, errors, Web Vitals, a live view, people, replays, a SQL explorer, and one instance that serves many organizations.
The admin has eighteen pages, and the dashboards are the ones you would expect. Visitors and pageviews over time, sources and UTM campaigns, pages and entry pages, countries and cities, browsers and devices. Then the product questions: custom events, funnels with drop-off per step, retention cohorts, JavaScript errors grouped by message, Web Vitals, a live view of who is on the site now, person profiles, session replays, and a read-only SQL explorer over the raw events for everything else.
One instance serves any number of organizations and projects. The first account becomes the instance admin; owners invite by link, or sign-up can be opened to anyone. Sign-in is e-mail and password or Google. A visitor can request an account from the home page, which ships inside the binary, and the admin approves it from a Requests page. That is how one HanuAnalytics serves every product you build, or is offered to the public.
The web SDK is TypeScript with an npm build. An iOS client, one Swift file with no dependencies, reports screens as pageviews under the app's bundle id so an app's traffic sits beside its website's and apart from it. The whole thing deploys to Google Cloud with one script that creates the bucket, the service account, the secrets, the machine and HTTPS, and upgrades with the same script and a flag.
Limits, and where everything is
One machine is the ceiling, there is no benchmark record yet, and everything else is open: the code, the contract and the paper.
HanuAnalytics runs on one machine, and that machine is its ceiling. Queries run in the memory the machine has, which on the free one is a gigabyte shared with HanuDB; tens of millions of events a month want a bigger machine. There is no clustering, no second region for the server, and no automatic failover, because a second machine is not free. The design leaves the door open: the server holds nothing but buffers, the bucket is the truth, and HanuDB has followers.
It should also be said plainly what this is not. HanuDB was shaped by more than a hundred measured experiments against a fixed benchmark; HanuAnalytics was built in a day on top of that engine and has a correctness and smoke suite, not a benchmark record. The cost figures are list prices and arithmetic. The first measured month will be reported when there is one.
It is open source under the MIT licence. The code is at github.com/madhudream/hanuanalytics, the API contract every part is built against is in the repository, and a hosted instance runs at analytics.carebun.com, free while in beta. The paper that follows sets out the design, the storage layout, the cost model and the privacy rules in full.
Step 2: the paper