-
Notifications
You must be signed in to change notification settings - Fork 0
Optimize: Cache navigation bar queries (TASK-20260202) #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| from django.core.cache import cache | ||
| from django.test import TestCase | ||
|
|
||
| from judge.models import NavigationBar | ||
|
|
||
|
|
||
| class NavigationBarCacheTests(TestCase): | ||
| def setUp(self): | ||
| cache.clear() | ||
|
|
||
| def test_navbar_cache_invalidates_on_save(self): | ||
| NavigationBar.objects.create( | ||
| order=1, | ||
| key='home', | ||
| label='Home', | ||
| path='/', | ||
| regex=r'^/$', | ||
| ) | ||
|
|
||
| from judge.utils.caching import get_cached_navbar | ||
|
|
||
| nav_bar = get_cached_navbar() | ||
| self.assertEqual(len(nav_bar), 1) | ||
|
|
||
| NavigationBar.objects.create( | ||
| order=2, | ||
| key='problems', | ||
| label='Problems', | ||
| path='/problems/', | ||
| regex=r'^/problems/', | ||
| ) | ||
|
|
||
| nav_bar = get_cached_navbar() | ||
| self.assertEqual(len(nav_bar), 2) | ||
|
|
||
| def test_nav_tab_cache_invalidates_on_save(self): | ||
| item = NavigationBar.objects.create( | ||
| order=1, | ||
| key='home', | ||
| label='Home', | ||
| path='/foo/', | ||
| regex=r'^/foo', | ||
| ) | ||
|
|
||
| from judge.utils.caching import get_cached_nav_tab | ||
|
|
||
| keys = get_cached_nav_tab('/foo') | ||
| self.assertIn('home', keys) | ||
|
|
||
| item.regex = r'^/bar' | ||
| item.save(update_fields=['regex']) | ||
|
|
||
| keys = get_cached_nav_tab('/foo') | ||
| self.assertEqual(keys, []) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import hashlib | ||
|
|
||
| from django.core.cache import cache | ||
|
|
||
| from judge.models import NavigationBar | ||
|
|
||
| NAVBAR_CACHE_KEY = 'navbar_tree' | ||
| NAVBAR_CACHE_TTL = 86400 | ||
| NAV_TAB_VERSION_KEY = 'nav_tab_version' | ||
| NAV_TAB_CACHE_TTL = 86400 | ||
|
|
||
|
|
||
| def _get_nav_tab_version(): | ||
| version = cache.get(NAV_TAB_VERSION_KEY) | ||
| if version is None: | ||
| version = 1 | ||
| cache.set(NAV_TAB_VERSION_KEY, version, NAV_TAB_CACHE_TTL) | ||
| return version | ||
|
|
||
|
|
||
| def bump_nav_tab_version(): | ||
| version = cache.get(NAV_TAB_VERSION_KEY) | ||
| if version is None: | ||
| version = 1 | ||
| cache.set(NAV_TAB_VERSION_KEY, version + 1, NAV_TAB_CACHE_TTL) | ||
|
Comment on lines
+22
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| def get_cached_navbar(): | ||
| nav_bar = cache.get(NAVBAR_CACHE_KEY) | ||
| if nav_bar is None: | ||
| nav_bar = list(NavigationBar.objects.all()) | ||
| cache.set(NAVBAR_CACHE_KEY, nav_bar, NAVBAR_CACHE_TTL) | ||
| return nav_bar | ||
|
|
||
|
|
||
| def get_cached_nav_tab(path): | ||
| version = _get_nav_tab_version() | ||
| path_hash = hashlib.md5(path.encode('utf-8')).hexdigest() | ||
| cache_key = f'nav_tab:{version}:{path_hash}' | ||
| nav_tab = cache.get(cache_key) | ||
| if nav_tab is None: | ||
| result = list(NavigationBar.objects.extra(where=['%s REGEXP BINARY regex'], params=[path])[:1]) | ||
| nav_tab = list(result[0].get_ancestors(include_self=True).values_list('key', flat=True)) if result else [] | ||
| cache.set(cache_key, nav_tab, NAV_TAB_CACHE_TTL) | ||
| return nav_tab | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalidating
navbar_treedirectly in thepost_save/post_deletesignal can cache an inconsistent tree:NavigationBarAdmin.changelist_viewsaves items withdisable_mptt_updates()and only rebuilds tree fields afterward (judge/admin/interface.py), so a concurrent request can repopulatenavbar_treefrom pre-rebuild rows and keep that stale hierarchy for the full TTL. Triggering invalidation on commit (or after the rebuild path completes) avoids persisting an inconsistent navbar.Useful? React with 👍 / 👎.