Skip to content

Show installs down to city level on the /stats globe - #144

Merged
LakshmanTurlapati merged 3 commits into
mainfrom
users-by-location
Sep 29, 2026
Merged

LakshmanTurlapati merged 3 commits into
mainfrom
users-by-location

Conversation

@LakshmanTurlapati

Copy link
Copy Markdown
Collaborator

Why the globe was empty

Prod's /api/public-stats/global returned users_by_region_365d: [{label: "Other", uniq: 55}]. The 55 located installs are spread over 38 states, and the largest has only 4, so the k≥5 floor sent every install to "Other", which has no coordinates to plot.

What changes

  • City-level geo, searched on disk. Labels now carry the city (US-CA/San Jose). At city level the tables would take ~110 MB in RAM, which doesn't fit on the 256 MB VM, so ip-geo.js binary-searches the sorted CSVs in place instead. That's ~35 µs per lookup with one 64 KiB buffer per file, and the server also drops the ~66 MB the state-level tables used. The refresh script adds a city column and a places file (a centroid for every country, state, and city label).
  • k≥5 floor applied level by level. Each install is published under its city if at least 5 installs share it, otherwise its state, otherwise its country, otherwise "Other". Buckets never overlap, and none stands for fewer than 5 installs. On today's prod data: US 14, JP 6, AU 5, IN 5, Other 25 (previously Other 55). Cities will appear as installs report in.
  • Globe plots each place at its server-supplied centroid, with a tighter scatter for cities. The caption credits DB-IP.
  • Privacy policy now describes city-level labels and the level-by-level floor, and says the globe marks the place's centre, never an install's location. The Sept 28 text is archived, and all six locales are updated.

Deploy notes

  • The city dataset is already on the prod volume at /data/dbip-city/, checksums verified. The running server ignores it; fly.toml in this PR switches to it on deploy.
  • /data is at 80% (186 MB free). After the deploy looks good, the old /data/dbip-city-lite*.csv files and dbip-city-lite.csv.2026-07.bak (~250 MB) can be deleted.

Test plan

  • server-ip-geo (city, 4-column legacy rows, CRLF/long header/mid-file junk, centroids), -scale (lookup RSS 51 MB for 1M ranges), -merge (city merge, places file, antimeridian), server-client-ip-geo, server-region-aggregation (hierarchy, disjointness, centroids), housekeeper, public-stats, server-no-ip-leak, showcase-privacy-page
  • 60,000 real IPv4/IPv6 samples on the 2026-09 release: identical country/state to the old lookup, 100% city and centroid coverage
  • End-to-end: local server on the real dataset, seeded from real city IPs, and the globe renders city nodes
  • Angular test:ci 67/67, prod build for all locales, lint:i18n, translation drift/quality, CI extraction diff clean

Region labels stopped at state level, and at 55 located installs no state
reached the k>=5 floor, so every install fell into "Other" and the /stats
globe had nothing to plot. Labels now carry the city ('US-CA/San Jose'),
giving the publication floor a finer level to try first.

City-level tables are ~3.4 M IPv4 and ~3.8 M IPv6 ranges -- about 110 MB
as typed arrays, which does not fit beside the server on the 256 MB VM.
ip-geo.js therefore no longer loads the dataset: it binary-searches the
sorted CSVs in place with positioned 4 KiB reads (~35 us per lookup once
cached) and holds one 64 KiB buffer per file. The server also sheds the
~66 MB the state-level tables used and the first-request load pause.

The refresh script emits a city column and a places file mapping every
country, state and city label to an approximate centroid (spherical
mean, 0.1 degree), keyed by the same regionLabel() the ingest route
stores, now shared from utils/region-label.js. Four-column state-level
rows still parse. On the 2026-09 release, 60,000 sampled addresses
resolved to the same country and state as the old in-memory lookup.

fly.toml points at the new files under /data/dbip-city/.
The floor used to fold every sub-5 state straight into "Other". It now
walks city -> state -> country: a label with at least five installs is
published, and the installs of one below the floor move up to its
parent, so two Californian cities of 3 and 2 publish as 'US-CA' 5.
Countries still short pool into "Other", which is dropped under five.
Each install lands in exactly one bucket, so no published label ever
stands for fewer than five installs.

On today's production data this turns [Other 55] into US 14, JP 6, AU 5,
IN 5 and Other 25, and cities appear as installs report from them.

Public entries also carry the place's centroid from the places file so
the globe can plot cities it has no built-in table for; 'Other' and
'unknown' never do.
The globe plots each published place at the server's centroid, falling
back to its own country/state tables, with a tighter scatter for a city
than for a state or country. City labels read "San Jose, US-CA" in the
accessible data list, and the caption and aria label say the globe is
by city, state, or country, crediting DB-IP for the place data.

The privacy policy now says the location label goes down to city, how
the k>=5 floor applies level by level, and that the globe marks the
place's centre rather than any install's location. The September 28
text is archived under Policy History, and all six locales are updated.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@LakshmanTurlapati
LakshmanTurlapati merged commit ce1df3e into main Sep 29, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant