The codebase has TWO cost caching mechanisms:
- Database fields (
rating_current,stash_current,dirty) - KEEP - central to facts system - In-memory Django cache (
caches["core_list_cache"]) - REMOVE - no longer needed
The in-memory cache is updated by signals on every List/ListFighter/Assignment change. These signals are expensive and redundant now that the facts system uses DB fields.
Facts System (DB fields):
facts()returns cached DB values ifdirty=Falsefacts_from_db(update=True)recalculates and updates DB fieldsfacts_with_fallback()triesfacts()first, falls back to calculation- Display methods (
cost_display,rating_display) use facts whencan_use_facts()is True
In-Memory Cache (to be removed):
update_cost_cache()writes tocaches["core_list_cache"]cost_int_cachedproperty reads from cache- Signals fire on every List/ListFighter/Assignment change
- Falls back to expensive
cost_int()recalculation if cache miss
Once all lists have ListAction tracking and views prefetch latest_actions:
can_use_facts()always returns True- Display methods use
facts()which reads DB fields - In-memory cache is never read
- Signal overhead becomes pure waste
Without latest_actions prefetch, can_use_facts() returns False and code falls back to the in-memory cache.
can_use_facts() implementation:
def can_use_facts(self) -> bool:
if hasattr(self, "latest_actions"):
return bool(self.latest_actions)
return FalseHigh Priority Gaps:
| File | Line | Issue |
|---|---|---|
gyrinx/core/views/__init__.py |
66-68 | Dashboard user lists - missing prefetch |
gyrinx/core/views/__init__.py |
85-93 | Dashboard campaign gangs - missing prefetch |
gyrinx/core/views/list.py |
171-173 | ListsListView - missing prefetch |
gyrinx/core/views/campaign.py |
473-477 | Available lists for campaign |
gyrinx/core/views/__init__.py |
251-253 | User profile public lists |
Fix Pattern:
# Before
List.objects.filter(...).select_related("content_house")
# After
List.objects.filter(...).with_related_data(with_latest_action=True)All lists need at least one ListAction so can_use_facts() returns True.
- Enable
FEATURE_LIST_ACTION_CREATE_INITIAL=True(already done) - Use admin filter "Missing action tracking" to find lists without actions
- Run "Initialize action tracking" admin action on all lists
Files to modify:
gyrinx/core/views/__init__.py- Dashboard and profile viewsgyrinx/core/views/list.py- ListsListViewgyrinx/core/views/campaign.py- Campaign list selection
After backfill is complete, remove these signals from gyrinx/core/models/list.py:
| Signal | Line | Trigger |
|---|---|---|
update_list_cost_cache_from_list_change |
~1165 | List post_save, m2m_changed |
update_list_cost_cache |
~3350 | ListFighter pre_delete, post_save, m2m_changed |
update_list_cache_for_assignment |
~4244 | ListFighterEquipmentAssignment post_delete, post_save |
update_list_cache_for_weapon_profiles |
~4262 | M2M weapon_profiles_field |
update_list_cache_for_weapon_accessories |
~4273 | M2M weapon_accessories_field |
update_list_cache_for_upgrades |
~4284 | M2M upgrades_field |
Methods to delete from gyrinx/core/models/list.py:
List.cost_cache_key()(~770)List.update_cost_cache()(~774)List.cost_int_cachedproperty (~335) - replace usages withfacts_with_fallback().wealthList.cost_int()method - verify no remaining callers first
Views to update:
gyrinx/core/views/list.py-refresh_list_costview (~847) - remove or update to use facts
Settings to potentially remove:
CACHE_LIST_TTLif only used by this cachecore_list_cachefrom CACHES config (if dedicated)
Tests to update:
gyrinx/core/tests/test_models_core.py- remove cache assertionsgyrinx/core/tests/test_list_refresh_cost.py- update or remove
-
After Phase 1: Verify
can_use_facts()returns True for all list displays- Add temporary logging if needed
- Check that
cost_int_cachedfallback is never hit
-
After Phase 2: Verify no errors, list costs still display correctly
- All display methods should use
facts()path - No performance regressions
- All display methods should use
-
After Phase 3: Verify all
cost_displaycalls work without cache- Run full test suite
- Manual smoke test on key pages
- Phase 1: Fix prefetch gaps (can do now)
- Run backfill in production
- Verify all lists have actions via admin filter
- Phase 2: Remove signals
- Phase 3: Remove cache infrastructure
credits_currentis NOT a cache - it's the authoritative balance. Keep it.rating_current,stash_current,dirtyare DB cache fields - KEEP them, they're central to facts system- Only the in-memory Django cache (
caches["core_list_cache"]) is being removed