Giving my tracker a country column without making it fat
What this is about. I run my own small, cookieless analytics tracker, Muntra, on all the sites I host. You should probably too. Its dashboard has had a Countries table since day one, and it has been empty since day one, because nothing ever filled it in. This is the short story of filling it in.
Why. If you roll your own analytics, country is the first thing people ask for. It is also where "privacy-friendly" quietly turns into "we know which town you live in". I wanted the first without the second.
The brief
My one rule for Muntra is that it stays small. So I put it to my AI like this:
"The Muntra tracker should be kept minimalistic and fast, so weigh pros and cons. Is there anything else a tracker would need?"
The code already had a column, a daily rollup and an API for countries. The only missing piece was a TODO(geo) comment in the request handler. So this was one lookup, not a project.
MaxMind or DB-IP?
I also asked Gemini, who said what everyone says: MaxMind GeoLite2 is the industry standard, and DB-IP Lite is easier to automate. So I asked:
"DO we need thextra ummph in GeoLite2?"
No. GeoLite2's edge is at city level, and I only store the country. At country level they agree on nearly every address. The ones either gets wrong are mostly VPN and corporate-proxy users, and no database can place those. DB-IP Lite needs no account and no license key, so there is no new secret to keep on the server. Both use the same file format, so switching later means swapping one file, not the code.
The numbers
One country file, about 8 MB, loaded into memory and refreshed monthly by a cron job.
About 215 nanoseconds per lookup, next to a Redis write that already happens on every page view. Nobody will feel it.
The visitor's IP is still thrown away right after it is turned into a daily, salted hash. Muntra stores the two-letter country and nothing finer.
There was one subtle bit. The library normally maps the file into memory, and you have to close an old copy when a new month's file arrives. Close it while a request is still mid-lookup and the process crashes. Reading the whole file into memory instead means old copies are simply cleaned up by the garbage collector once nobody uses them. It costs 8 MB of RAM and removes a whole class of bug.
Google's DNS lives in Canada
I wanted to test against the real file, not just a toy one. That was worth it twice:
The library's integrity check is "stricter than the spec", so a perfectly usable vendor file could have failed it, leaving geo switched off without anyone noticing. The real DB-IP file passed, in 0.3 seconds.
My test said Google's public IPv6 DNS address is in the US. DB-IP says Canada. Neither is wrong: that address is served from many countries at once, so there is no single answer. The test now checks for a valid country code, not my guess.
What happened, and how else you could do it
Problem: an empty Countries table. Fix: a country-only lookup from a free, monthly IP database, hot-reloaded without a restart, with an "IP geolocation by DB-IP" credit on the dashboard as the license asks.
Other ways to get a country:
Approach | Better at | Costs |
|---|---|---|
MaxMind GeoLite2 | The standard, refreshed weekly, best at city level | An account, a license key on the server, license terms about deleting old copies |
Browser time zone ( | No IP processing at all, nothing to download | It is a device setting, not a location, and some countries share a zone |
Your CDN's country header | Free, no code | Only if you run behind that CDN. I don't. |
City-level data | Prettier maps | On a small site, town plus browser plus day is close to naming the visitor. Not for a privacy-first tracker. |
Rule of thumb: store the coarsest location that answers your question. For "where do my readers come from", that is the country.