Background: CMR's UMM-JSON results are incomplete and mislabelled¶
Since October 2026, NASA's Common Metadata Repository (CMR) returns unreliable
granules.umm_json search results. It leaves some granules out while still
counting them in the CMR-Hits header, and under other concept ids it serves
a different granule's record. The same search in atom-JSON (granules.json)
returns every granule under its own name. The bug is on the CMR side and also
affects other ASF collections, such as SENTINEL-1_BURSTS (C2709161906-ASF),
which is how Vertex comes to show existing bursts as missing.
nisar_db works around it. Every UMM-JSON search is checked against the atom-JSON listing, and every record that is missing or carries the wrong name is fetched again by concept id.
Three ways a record goes wrong¶
| Failure | What UMM-JSON does | What still returns the right record |
|---|---|---|
| Paging skip | Leaves the granule out of a paged search, but returns it when asked for its concept id | UMM-JSON with concept_id[] |
| Never returned | Counts the granule in CMR-Hits but returns zero records, however it is asked |
search/concepts/<id> (native UMM-G), atom-JSON |
| Mislabelled | Returns a record under the granule's concept id, but the record is another granule's, so that other granule appears twice | search/concepts/<id>, atom-JSON |
For example, concept id G4246696294-ASF is
NISAR_L2_PR_GUNW_023_055_D_066_... in atom-JSON and from the concepts
endpoint. UMM-JSON serves the record of
NISAR_L2_PR_GUNW_023_059_A_038_... under it. A list keyed by granule name
then holds track 059 frame 038 twice and has no track 055 frame 066.
echo10 is not a reliable fallback: in a batch search it returned only 403 of
3,607 never-returned NISAR granules. The concepts endpoint returned the right
record in every case checked.
Scope¶
Full-archive walks on 2026-10-21. The counts changed from one run to the next during the day, so treat them as a snapshot.
| Collection | Granules | Left out of UMM-JSON | Mislabelled in UMM-JSON |
|---|---|---|---|
NISAR_L2_GUNW_PROVISIONAL_V1 |
89,911 | 6,024 | 3,470 to 12,600 |
NISAR_L2_GSLC_PROVISIONAL_V1 |
155,910 | 197 | 13,769 |
NISAR_L2_GSLC_BETA_V1 |
23,450 | 0 | not checked |
The dropped GUNWs are mostly composite release P05023. Of the 602 dropped
GUNWs already in the viewer's flag cache, 597 had an orbit type from an
earlier CMR lookup, so the problem appears recent.
CMR can also list one granule name under two concept ids, which is a duplicate in CMR itself. None were found in the atom-JSON listings on 2026-10-21, but the search checks for them on every run.
What nisar_db does about it¶
| Path | Endpoint | Status |
|---|---|---|
Catalog builders (create_gslc_catalog, create_gunw_catalog) |
granules.json |
Never affected |
nisar-db search, search_nisar_products, the weekly viewer's GSLC list, local search-and-rebuild |
granules.umm_json |
Recovered by fetch_umm_granules |
QA orbit types (scripts/collect_granule_qa.py) |
granules.umm_json by granule_ur[] |
Recovered by fetch_umm_by_granule_ur; cached flags missing an orbit type are backfilled on the next run |
The recovery in search/cmr.py runs in four steps:
- Walk the search in UMM-JSON and in atom-JSON. Compare each UMM-JSON
record's
GranuleURwith the atom-JSON title of the same concept id, and drop the records whose names disagree. - Ask UMM-JSON for the missing and mislabelled ids, 100 per POST, and keep only the records whose names now agree. This recovers the paging skips.
- Fetch whatever is still missing from
search/concepts/<id>, one granule per request, with 4 workers. This recovers the never-returned and mislabelled granules. - Keep one record per granule name. When CMR lists a name under several concept ids, keep the most recently ingested one, which has the highest concept id.
The page walk also no longer ends at a short page while CMR-Hits says more
remain, and it warns whenever it comes up short of CMR-Hits. Before this
change, a page shortened by drops ended the walk and lost every later page.
Reporting what CMR got wrong¶
The search keeps one record per granule, but it does not hide CMR's mistakes.
CMRSearchReport collects them for a report to CMR, and every search also
logs them as warnings.
| Field | Content |
|---|---|
left_out |
Concept ids UMM-JSON did not return |
mislabelled |
Concept id, and the other granule's name UMM-JSON served under it |
duplicate_names |
Granule name, and every concept id CMR lists under it |
unrecovered |
Concept ids no endpoint returned, usually deleted mid-search |
Write it from the command line with --cmr-report:
Runtime cost¶
The extra cost is the atom-JSON listing, which every search pays, plus one concepts-endpoint request per missing or mislabelled granule. It therefore grows with how badly CMR is misbehaving.
| Search on 2026-10-21 | Concepts-endpoint lookups | Time |
|---|---|---|
| Full GUNW archive, before the fix (estimated) | none | ~1.5 min |
| Full GUNW archive, morning | 3,603 | 5.1 min |
| Full GSLC archive, before the fix (estimated) | none | ~2.7 min |
| Full GSLC archive, morning, before the name check | 4 | 4.1 min |
| Global viewer build (GSLC and GUNW), afternoon | ~32,000 | 33.7 min |
"Before the fix" is the UMM-JSON walk plus parsing from the same runs, which is all the old code did. A global build spends most of its time on the concepts endpoint. A local search-and-rebuild over one box stays small. If the cost matters, the atom-JSON listing could run at the same time as the UMM-JSON walk, and the concepts endpoint could take more workers, which CMR may rate-limit.
How to check one granule¶
A left-out granule shows one hit and no records in UMM-JSON, and comes back from the concepts endpoint:
id=G4245361810-ASF # a dropped P05023 GUNW
curl -s "https://cmr.earthdata.nasa.gov/search/granules.umm_json?concept_id=$id"
# {"hits":1,"took":34,"items":[]}
curl -s "https://cmr.earthdata.nasa.gov/search/granules.json?concept_id=$id" | head -c 200
curl -s "https://cmr.earthdata.nasa.gov/search/concepts/$id" | head -c 200
When to remove the workaround¶
Keep it after CMR fixes the bug. When nothing is wrong, it costs only the
atom-JSON listing, and it catches the bug if it comes back. The warnings
CMR UMM-JSON left out or mislabelled N of M granules and
CMR UMM-JSON served another granule's record in a search's log show whether
the bug is still present.