Skip to content

fix: DailySunTimesAdmin field, commute_tick + map viewport N+1s; document expand/contract for DB column drops - #788

Merged
gaidheal1 merged 13 commits into
developmentfrom
task/fix-sentry-issues
Aug 16, 2026
Merged

fix: DailySunTimesAdmin field, commute_tick + map viewport N+1s; document expand/contract for DB column drops#788
gaidheal1 merged 13 commits into
developmentfrom
task/fix-sentry-issues

Conversation

@gaidheal1

@gaidheal1 gaidheal1 commented Aug 16, 2026

Copy link
Copy Markdown
Member

Summary

Continuation of the previous branch (claude/prod-redis-query-errors-c0dzdg,
closed as #787) renamed to task/fix-sentry-issues, fixing four Sentry
issues from progress-backend-production (two of them the same underlying
incident, see below).

  • docs (can_link UndefinedColumn): root-caused the production
    errors from 2026-08-14 to a deploy-ordering race between the
    independently-auto-deploying web, celery, and celery-beat Render
    services (only web runs migrate). Application code was already
    correct at HEAD. Documented the expand/contract process for dropping DB
    columns in docs/operations/deployment-runbook.md.
  • fix (PROGRESS-BACKEND-PRODUCTION-ED): DailySunTimesAdmin.fields
    referenced "name", a field that doesn't exist on DailySunTimes.
    Changed to "date".
  • fix (PROGRESS-BACKEND-PRODUCTION-EJ and -EK, same commute_tick
    run, same root cause): target_role_for/work_hours_for queried each
    idle character's WORK CharacterLocation individually, and
    sync_character_location redundantly re-queried it again per character
    even when the caller already knew the role - two separate per-character
    query sites in the same tick, which is why Sentry grouped them as two
    issues (each keyed off a different preceding span in the trace).
    Batch-fetched every character's primary (home + work)
    CharacterLocations in one query in commute_tick and threaded the
    pre-fetched work_location/target_role through
    work_hours_for/target_role_for/sync_character_location.
  • fix (PROGRESS-BACKEND-PRODUCTION-EH): PathFeatureSerializer.get_geometry
    reads obj.geom, but both MapViewportView and PopulationCentreMapView
    fetched Paths via .only("id", "from_node__location", "to_node__location"),
    omitting geom - every access triggered a separate deferred-field query
    (364 extra queries, 75% of transaction time, on one /api/v1/map/viewport/
    request). Added "geom" to both .only() calls.

Also noted, not fixed here (already resolved, explained to the user in
conversation rather than in a diff): a ValueError: Unable to configure handler 'file_debug' issue from Aug 2 turned out to be stale - the
file_debug handler has been commented out of settings/dev.py since
December 2025, and the traceback shows it running manage.py test from
/testbed on Python 3.14, consistent with a sandboxed test run rather than
real production traffic.

Test plan

  • Load /admin/gameworld/dailysuntimes/add/ and confirm the add form
    renders without a FieldError
  • Run locations/tests/test_schedule.py and
    locations/tests/test_map_viewport.py - not run locally in this
    session (no Docker/GDAL available in this sandbox), so CI should be
    the first real run
  • N/A for the docs change (documentation-only)

gaidheal1 and others added 11 commits August 10, 2026 01:46
The hour/minute duration inputs shared a .row class with the
completed-date/time row. The small-screen media query flipped all
.row elements to flex-direction: column, stacking the duration
fields instead of keeping them side by side. Scope the column
layout to the date/time row only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Publishing an Announcement now pushes a WebSocket "announcement_published"
event to online players, which invalidates the announcements/unread-count
queries so the Navbar badge appears immediately instead of only on refresh
or remount.

- Announcement.save() broadcasts on the unpublished->published transition
- publish_selected_announcements admin action saves rows individually
  (not queryset.update()) so the broadcast actually fires
- frontend wires the new action into handleGlobalWebSocketEvent /
  WebSocketContext to invalidate the relevant react-query keys

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…realtime-badge

feat: broadcast announcement badge updates in real time
Root-cause the 2026-08-14 production UndefinedColumn errors on
Character.can_link: web, celery, and celery-beat deploy independently
on Render, and only web runs migrate via preDeployCommand. The
migration dropping can_link landed in the same deploy as the code
change that stopped using it, so celery/celery-beat kept erroring on
the old column reference until their own deploys caught up. The
current can_link code (derived property, is_reserved field) is
already correct - this documents the two-deploy process needed to
avoid the same stale-worker window on future column removals.
DailySunTimesAdmin.fields referenced "name", a field that doesn't
exist on DailySunTimes (it has date, world, sunrise, sunset, dawn,
dusk) - list_display/list_filter already correctly use "date". Every
GET to /admin/gameworld/dailysuntimes/add/ raised FieldError before
rendering the form.
target_role_for (via work_hours_for) queried each character's WORK
CharacterLocation individually, and sync_character_location
redundantly called target_role_for again per character even when the
caller already knew the role - both run once per idle character every
commute_tick, reported by Sentry as an N+1 (67 extra queries in one
tick, PROGRESS-BACKEND-PRODUCTION-EJ). Introduced in e12199f when
target_role_for started resolving hours from the character's work
building instead of fixed constants.

Batch-fetch every character's primary (home + work) CharacterLocations
in commute_tick with one query, and thread work_location/target_role
through to work_hours_for/target_role_for/sync_character_location so
they use the pre-fetched values instead of querying per character.
Other callers (behaviour_services.generate_day, existing tests) are
unaffected - the new parameters default to the previous per-character
lookup behaviour.
@gaidheal1 gaidheal1 changed the title fix: correct DailySunTimesAdmin field name + document expand/contract for DB column drops fix: DailySunTimesAdmin field + commute_tick N+1 query; document expand/contract for DB column drops Aug 16, 2026
PathFeatureSerializer.get_geometry reads obj.geom, but both
MapViewportView and PopulationCentreMapView fetched paths with
.only("id", "from_node__location", "to_node__location"), omitting
geom. Every access triggered a separate per-object deferred-field
query - Sentry reported 364 extra queries (75% of transaction time)
on a single /api/v1/map/viewport/ request (PROGRESS-BACKEND-PRODUCTION-EH).
Add "geom" to both .only() calls.
@gaidheal1 gaidheal1 changed the title fix: DailySunTimesAdmin field + commute_tick N+1 query; document expand/contract for DB column drops fix: DailySunTimesAdmin field, commute_tick + map viewport N+1s; document expand/contract for DB column drops Aug 16, 2026
Resolves a conflict in Announcement.save(): development's version
(d37f377) auto-sets published_at but was branched before the
WebSocket broadcast-on-publish behavior (c2e8a7c, already live on
main/staging) landed, so its rewrite of save() dropped that behavior.
Combined both - auto-set published_at, then broadcast on the
draft-to-published transition - rather than silently losing the
already-shipped broadcast feature.
@gaidheal1
gaidheal1 marked this pull request as ready for review August 16, 2026 09:59
@gaidheal1
gaidheal1 merged commit 3657ea0 into development Aug 16, 2026
2 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.

2 participants