Conversation
…entrances Both entrance-in-bounds queries closed with ORDER BY size_coef DESC, so that a truncating LIMIT would keep the most significant caves. It never truncated, and the sort it forced is what spilled to disk in #1812. - The LIMIT is hardcoded at 100 000 while MAX_BBOX_AREA_KM2 holds a legal box to roughly 16 000 entrances over the densest karst measured, so the clause only reordered the JSON array - size_coef is not a ranking: calcule_size_coef sums a depth bucket (0/4/5) and a length bucket (0/1/2/4/5), and a live response for that box carries only 10,9,8,7,6,5,4,2,1,0,null across 15 834 rows - It ranked the wrong way round: id_cave is nullable and NULLS FIRST is the default under DESC, so a truncating LIMIT would have preferentially kept the cave-less entrances - No consumer depends on the order: the web app's tile cache concatenates tiles in arbitrary order, MapClusters filters by quality without taking a prefix, no test asserts ordering, and Swagger promises none - Deleting the sort node makes the 26 MB external merge impossible at any bounding box size, with no work_mem change on a 4 GiB Standard_B2s Closes #1821 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul-AUB
left a comment
There was a problem hiding this comment.
Code Review — fix/1821-drop-dead-order-by
Review target: branch vs origin/develop (source: override)
Scope: single file, api/services/GeoLocService.js — removal of
ORDER BY size_coef DESC from PUBLIC_ENTRANCES_IN_BOUNDS and
PUBLIC_ENTRANCES_IN_BOUNDS_AND_MASSIF, plus an explanatory comment.
Note: the review was invoked as issue 1822 but the branch name and the
in-code comment both reference #1821. The diff matches issue 1821.
Findings
No P0 / P1 / P2 findings.
Verifications performed
- Comment accuracy —
id_cavenullability.api/models/TEntrance.js:244-247
declarescavewith norequired: true, soid_caveis nullable. The
LEFT JOIN t_cavetherefore yieldsNULL size_coeffor cave-less entrances,
and PostgreSQL'sNULLS FIRSTdefault underDESCis standard — the
"wrong way round" claim holds. - Comment accuracy — hardcoded limit.
api/controllers/v1/geoloc/find-entrances.js:28
passes100000togetEntrancesMap, matching the "hardcoded at 100 000"
claim in the comment. - No internal consumer of order. The only caller is
find-entrances.js,
which forwards rows straight tores.json— no top-N truncation, no slicing,
no aggregation that would care about row order. - No test relies on order.
test/integration/1_services/GeoLocService.test.js
andtest/integration/1_services/GeoLocNameFilter.test.jsassert shape,
cardinality, and uniqueness — never ordering. - No public contract violated.
assets/swaggerV1.yaml:5204-5279documents
the/geoloc/entrancesresponse as an unordered array; no ordering guarantee
is advertised.
P3-adjacent observation (not raised as a finding)
A frontend that rendered rows in received order will now see a different
visual order. This is addressed directly by the added comment, which points
maintainers at the client-side sort using the quality field that
formatEntrances (line 269) still populates from size_coef on every row.
Absent concrete evidence of a frontend regression in this repo, it does not
meet the finding bar under the skill's no-speculation rule.
Final Summary
- Overall correctness: correct
- Overall explanation: The diff removes a genuinely dead sort clause and
documents why it was harmful; every factual claim in the added comment is
verifiable from the surrounding code, and no in-repo consumer or test
depends on the removed ordering. - Overall confidence: 0.9
Removes the
ORDER BY size_coef DESCfrom both entrance-in-bounds queries onGET /api/v1/geoloc/entrances. Two deleted lines plus a comment explaining why they must not come back.Closes #1821.
🤔 What
PUBLIC_ENTRANCES_IN_BOUNDSandPUBLIC_ENTRANCES_IN_BOUNDS_AND_MASSIF(api/services/GeoLocService.js). The?massif=variant matters as much as the plain one — it backs the massif page, the consumer most likely to sit over dense karst.qualityfield. Only the array order becomes unspecified.🤷♂️ Why
work_memand Postgres falls back to an external merge on the data volume. There is no other reason for a sort node in this query, so deleting the clause makes the spill structurally impossible at any bounding box size.LIMITit existed to serve never truncates. It is hardcoded at100000(find-entrances.js:28), whileMAX_BBOX_AREA_KM2(bug(api): unbounded bbox on GET /api/v1/geoloc/entrances blocks the event loop and degrades every route #1810) holds any legal box to roughly 16 000 entrances. Probing/geoloc/countEntrancesfor the densest box that still passes at 34 900 km² — a north-south strip along the Jura/Alps axis — returns 15 834 rows. A result set that cannot reach the limit means the ordering could never change which rows were returned.size_coefis not a ranking.calcule_size_coef(sql/0_triggers.sql:7) sums a depth bucket (0/4/5) and a length bucket (0/1/2/4/5), so it has at most 11 values. The live response for that box confirms it:qualitycarries only10,9,8,7,6,5,4,2,1,0,nullacross 15 834 rows.t_entrance.id_caveis nullable, so a cave-less entrance has aNULLsize_coef, andNULLS FIRSTis the default underDESC. The first row of that live response is{"caveId":null,"quality":null,…}— had theLIMITever truncated, it would have preferentially kept the cave-less entrances, the opposite of the intent.mapTileCache.computeUnionconcatenates tiles in Map-iteration order, so cross-tile order was already arbitrary;MapClustersfilters byqualitywithout taking a prefix;MapMassif.jsxanduseNearbyEntrancespass the array straight through;reducers/Map.jsstores it verbatim. Swagger documents no ordering guarantee.work_memchange on a 4 GiB box. The alternative fix was raisingwork_mem, but it applies per node: the perf(db): geoloc entrances sort spills to disk on large bounding boxes (work_mem undersized) #1812 plan claims 4 MB (sort) + 3 × 8 MB (hashes,hash_mem_multiplier = 2) ≈ 28 MB for one connection. At 32 MB that becomes ~224 MB per connection, withmax_connections = 429, on a Standard_B2s already committing 1 GiB toshared_buffers. This PR needs no server-parameter change and carries noProductionlabel.🔍 How
Deleted the two
ORDER BY size_coef DESClines.grep -rn "ORDER BY size_coef" api/now matches only the explanatory comment.NETWORKS_IN_BOUNDSkeeps its own unrelatedORDER BY c.id, en.id— different query, different endpoint, untouched. Every other geoloc query never had the clause.🧪 Testing
npx eslint api/services/GeoLocService.js— clean.GeoLocService.test.js,GeoLocNameFilter.test.js,Geoloc.test.js— 105 passing, 0 failing. These already assert the returned set for a bbox, order-independently, which is what proves there is no behavioural regression.No new test. The only direct guard would assert the absence of a
Sortnode viaEXPLAIN, and that is unsound: on the small test dataset the planner can legitimately introduce a sort for a merge join, so the test would fail for reasons unrelated to this clause. The comment in the source carries the invariant instead.Manual check against production, for the record — the densest legal bbox:
📸 Previews
None — no user-visible change. The map renders the same markers; only the order they arrive in is no longer specified.
Out of scope
That same request takes 4.4–6.1 s for a 4.2 MB response. That is row volume and Node serialisation — the #1810 mechanism, not the sort — and no option in #1812 or #1821 addresses it. It needs its own issue: lower the cap, paginate, or stop shipping 14 data-quality columns per row to a map that renders one.