For Consumers
How registries, PBMS and other services should read datasets and geography from MDS: latest, pinned or as-of reads, caching, the change feed, pinning in configuration, storing the version used, retire
This page is for teams building on MDS: registries, PBMS, reporting, the Composite, and any other service that reads datasets or geography. Open-data portals and other websites without a token use the public catalogue instead.
Latest, pinned or as of
Every read says which version it wants (see Concepts). Choose by what the data is used for:
Data-entry dropdowns for new records
latest
New records should use the current entries
Displaying an existing record
latest with include_retired: true
The record's code must still show a label, even if retired
A programme's eligibility or entitlement rules
Pinned version (or a release)
The rules were defined against that version and must not change silently
A report for a past period
as_of the period's date, or pinned to the version used
Reproducible: run it again next year and get the same answer
A survey, programme cycle or national report that needs everything consistent
A catalogue release
All datasets and the geography at one agreed set of versions
Pin in configuration
A pin belongs in configuration, where it can be reviewed, not in code:
catalogue:
release: "2027.1" # pin everything at once, or
datasets:
SEED_VARIETY: 7 # pin individual datasets
CROP_COMMODITY: latest
geography: 3Moving a pin is a deliberate change: read the diff (get_list_diff) between the pinned and the new version, check what it does to your rules, then change the configuration.
Store the version with the record
Where a record stores a code, also store which version the code came from, or at least the geography version for unit codes. For example, an entitlement record might hold crop: MAIZE with catalogue_versions: { "CROP_COMMODITY": 4, "geo": 3 }, or a record might hold the release code it was created under.
That lets you later:
resolve the code exactly as it was meant (
get_list_valueat that version);map a unit code to current geography with the crosswalk, which needs the from-version;
explain an old decision.
If storing a version per record is not possible, the record's date and an as_of read are the fallback.
Cache by version
A published version never changes. So:
a response for a specific version number can be cached indefinitely, keyed by dataset (or geography) and version number;
a
latestresponse should be cached only until the change feed says something new was published (or for a short time), then re-read. Theversionobject in the response tells you which version you got, so the cache can be keyed by it;boundary files under a version key are immutable and can be cached the same way.
The legacy /attributes and /geo reads are cached inside MDS, per API worker. A publish clears the cache at once on the worker that handled it. Every other worker (other processes and other replicas) watches a counter, legacy.generation in g2p_catalogue_state, which the database bumps whenever it refreshes the current-state tables (a publish on any worker, a future-effective version coming into effect, or a country-pack load); each worker checks it every catalogue_outbox_relay_seconds (30 s by default) and clears its own cache when it has moved. So a legacy read is stale for at most that interval after a publish, plus, after a future effective_from passes, up to effectiveRefreshSeconds (300 s by default) until MDS notices the date and refreshes the tables. Cache entries also expire after cache_expire_seconds (300 s) in any case. The /catalogue reads are not cached.
Following changes
Do not poll datasets to notice changes. Use the change feed:
Keep the last
event_idyou processed (start with0).Call
get_changeswith it as thecursor, process the events in order, and store the new cursor.On a
*.version.publishedor*.version.effectiveevent for a dataset or the geography you use: droplatestcaches, and, if you pin, flag that a newer version exists for someone to review.
Events are ordered and the cursor is monotonic, so a consumer that was down simply catches up from its last cursor.
If the deployment has a WebSub hub configured, a consumer can also subscribe to the topics openg2p.master-data.list.published, openg2p.master-data.list.effective, openg2p.master-data.geo.published, openg2p.master-data.geo.effective and openg2p.master-data.release.published (the prefix is configurable) to be told immediately, and use get_changes to fill any gaps. Notifications are delivered at least once: the same event may arrive twice, so de-duplicate on its event_id.
A version with a future effective_from is announced twice: when it is published (*.version.published; check effective_from before treating it as current, and use the time to prepare), and when it takes effect (*.version.effective, on the change feed and on the *.effective WebSub topic).
Retired entries
Do not offer retired entries for new data. Reads leave them out by default.
Do not reject records that hold a retired entry. The entry was valid when recorded. Read with
include_retired: true(orget_list_value) to show it.A registry's coded-value check should validate new values against
latestand accept existing values that are retired. (The registry platform validates new values against the version in effect and shows retired codes' labels at the version recorded on an activity. Accepting an unchanged retired value when an entity record is edited is still to do.)
Geography changes
When a new geography version is published:
keep the recorded unit codes; do not rewrite records automatically;
map through the crosswalk for current reporting; splits need the record's location or a review;
recompute aggregates keyed by unit against the new version;
re-pin deliberately anything that targets units (programmes, campaigns);
rebuild maps with the new version's boundaries, keeping the old ones for historical views.
The details, with examples, are in Geography and boundary changes.
Checklist for a new consumer
Last updated
Was this helpful?