Commit 513918c
authored
hide unavailable work from explore challenges results (#1274)
* Match explore keywords with a semi-join, not a tag join
A keyword names a tag on the *challenge*, but the queries matched it by
joining `tags_on_challenges` into a per-task query. That returns every task
of a challenge once per requested tag the challenge carries, so anything
downstream that counts or clusters rows sees the same task several times.
Demonstrated on a 166k-task database by giving an eligible challenge a
second real keyword and asking for both: the join returns 22,430 rows for
11,215 tasks, the semi-join 11,215. Any challenge tagged with two of the
selected categories is enough, which is ordinary -- challenges routinely
carry several keywords and the explore UI lets more than one be selected.
What the duplication reached:
- Cluster counts on the keyword-filtered explore map were inflated, and
cluster centroids were pulled toward multi-tagged challenges, since the
duplicate rows inflate the centroid weight as well as the count.
- At zoom 12, where features are grouped by location, a task counted twice
reported 2 and rendered as an overlap stack rather than a single
clickable task, with its id repeated in `task_ids_str`.
- queryTaskMarkersWithOverlaps returned each task once per matching tag, and
since it groups by location with DBSCAN, the duplicates of one task became
a phantom overlap group.
exploreChallenges had the same join, hidden behind a SELECT DISTINCT. It
moves to the semi-join and drops the DISTINCT, which nothing else needs: the
only other join there is many-to-one on the parent project. That also lifts
a restriction the DISTINCT imposed -- Postgres requires every ORDER BY
expression to appear in the select list under SELECT DISTINCT, so a sort
could not order by an expression.
The other three keyword call sites in TaskClusterRepository were already
protected by SELECT DISTINCT / COUNT(DISTINCT), so they were correct; they
move to the semi-join too, to leave one spelling of the question in the
file.
This is a correctness fix, not a performance one -- the two forms measure
the same. Best of 3 at zoom 0 on that database: 9.19ms vs 9.17ms for a
keyword matching one challenge of 31, and 38.0ms vs 36.0ms where the join
was duplicating every row. Cost there is dominated by the scan over tasks,
which neither form avoids at low zoom.
Not covered: ProjectRepository.getSearchedClusteredPoints joins the tag
tables the same way with no DISTINCT, so it can still return a challenge's
clustered point once per matching tag.
* Claim a review bundle with a single lock row
A reviewer claiming a bundle locked each member task individually, which
left one `locked` row per task. Locking.lockBundle already models a bundle
as one row on the primary task with the members in `bundled_tasks`, so the
two representations disagreed and the singleOpt lookups in
resolveLockHolder/resolveLockBundle could see two rows covering the same
task.
- claimTaskReview now takes the whole list through lockBundle with
reviewClaim = true, so one row covers the bundle. Review claims stay
exempt from the one-lock-per-user invariant, which is what the new
reviewClaim parameter on lockBundle carries through.
- lockBundle checks every task the row will cover, not just the primary,
and folds any of our own overlapping rows into that one row. Without this
a claim over tasks already covered by another of our rows would leave two
rows covering the same task.
- claimTaskReview drops leftover rows from a previous claim. The unclaim it
runs first only clears task_review, so those rows accumulated and kept
their tasks locked.
- unclaimTaskReview clears the claim on every member of the bundle, since
releasing the single row releases them all, and runs its unlock inside the
surrounding transaction.
- A lock conflict response now carries parentId alongside parentName, so a
client can link to the challenge holding the conflicting lock.
* Cluster explore tiles with k-means and coarsen the grid
Tile clusters were the grid bins themselves, so a cluster's position was
its cell centroid and its size was fixed by the grid. Neighbouring cells
holding a handful of tasks each stayed separate markers no matter how far
apart their tasks actually were.
The repository now runs k-means over the cells feeding a tile and returns
the resulting clusters, so markers land on where the tasks are and nearby
cells merge into one cluster instead of sitting side by side. A separation
pass then collapses centroids closer together than 25 screen pixels, which
is what makes zooming out consolidate clusters: k-means returns exactly k
clusters however tightly packed its input is, so k is only a ceiling.
Evolution 121 coarsens the grid to match: CELL_BITS 4 -> 3, moving the leaf
level from slippy zoom 15 to 14 so a display tile holds 8x8 = 64 cells
instead of 16x16 = 256, each cell twice as wide. Only the cell<->slippy-zoom
mapping changes -- the roll-up is untouched -- so the leaf functions from
evolution 107 are redefined at zoom 14 and the pyramid is rebuilt. The Downs
restores the zoom 15 leaf and rebuilds again.
Cluster weight is the filtered count, not the cell total. A cell's stored
sums cover every task in it, so its centroid is the right position either
way, but carrying the total into the merge let a cell drag a cluster in
proportion to the tasks the filter had just excluded -- a marker reporting a
handful of expert tasks could sit on top of a thousand easy ones. This did
not arise before, when every cell was its own marker and a mis-weighting
could not cross cell boundaries. Measured against the base tables on a
166k-task database under a difficulty filter, mean cluster displacement
drops from 563m to 2m at z=8 (worst 2345m -> 4m) and from 68m to 3m at z=11
(worst 298m -> 12m).
A new `clusterMarkers` shares the whole src -> k-means -> separation chain
with the MVT paths and returns the markers as plain numbers, so a test can
assert where one landed without decoding protobuf. The repeated-request test
now seeds more cells than MAX_CLUSTERS, since at or below the ceiling
k-means returns one cluster per input and the clustering step is an identity.
* Stop surfacing work that cannot be worked
A paused challenge's tasks cannot be locked, completed or reviewed until it
is resumed, and a finished challenge has no tasks left at all. Both still
showed up as available work.
- Paused challenges drop out of all four tile paths: this repository's live
MVT queries, and the cached pyramid's rebuild_leaf_cell and
rebuild_all_tile_cells (evolution 122). That evolution also widens the
challenge dirty-marking trigger from evolution 107 to fire on `paused`,
without which a pause never reaches the pyramid, and marks the cells of
already-paused challenges stale so the scheduled drain recomputes them
rather than rebuilding everything.
- Paused challenges also drop out of the task cluster queries and out of
exploreChallenges, which additionally omits STATUS_FINISHED challenges.
NULL status predates the column and counts as unfinished.
- Locking a task or a bundle in a paused challenge is rejected: there is no
work to hold a lock for.
- Evolution 121 reconciles challenges left at READY while showing 100%
complete. updateFinishedStatus keeps status in sync as tasks are worked,
but paths that never run it (bulk status changes, deletions, restores)
could leave a done challenge listed as work.1 parent fda0e83 commit 513918c
6 files changed
Lines changed: 399 additions & 10 deletions
File tree
- app/org/maproulette
- controllers/api
- framework/repository
- models/dal
- conf/evolutions/default
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
220 | 221 | | |
221 | 222 | | |
222 | 223 | | |
| |||
243 | 244 | | |
244 | 245 | | |
245 | 246 | | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
246 | 253 | | |
247 | 254 | | |
248 | 255 | | |
| |||
343 | 350 | | |
344 | 351 | | |
345 | 352 | | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
346 | 359 | | |
347 | 360 | | |
348 | 361 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
385 | 385 | | |
386 | 386 | | |
387 | 387 | | |
| 388 | + | |
388 | 389 | | |
389 | 390 | | |
390 | 391 | | |
| |||
466 | 467 | | |
467 | 468 | | |
468 | 469 | | |
| 470 | + | |
469 | 471 | | |
470 | 472 | | |
471 | 473 | | |
| |||
572 | 574 | | |
573 | 575 | | |
574 | 576 | | |
| 577 | + | |
575 | 578 | | |
576 | 579 | | |
577 | 580 | | |
| |||
660 | 663 | | |
661 | 664 | | |
662 | 665 | | |
| 666 | + | |
663 | 667 | | |
664 | 668 | | |
665 | 669 | | |
| |||
Lines changed: 9 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
65 | 67 | | |
66 | 68 | | |
67 | 69 | | |
| |||
447 | 449 | | |
448 | 450 | | |
449 | 451 | | |
| 452 | + | |
450 | 453 | | |
451 | 454 | | |
452 | 455 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2642 | 2642 | | |
2643 | 2643 | | |
2644 | 2644 | | |
| 2645 | + | |
| 2646 | + | |
| 2647 | + | |
| 2648 | + | |
| 2649 | + | |
2645 | 2650 | | |
2646 | 2651 | | |
2647 | 2652 | | |
| |||
2680 | 2685 | | |
2681 | 2686 | | |
2682 | 2687 | | |
| 2688 | + | |
2683 | 2689 | | |
| 2690 | + | |
| 2691 | + | |
| 2692 | + | |
| 2693 | + | |
2684 | 2694 | | |
2685 | 2695 | | |
2686 | 2696 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
0 commit comments