perf(feed): Batch load media and tags to fix N+1 query

Per v1.5.0 Phase 3: Fix N+1 query pattern in feed generation.

Implementation:
- Add get_media_for_notes() to starpunk/media.py for batch media loading
- Add get_tags_for_notes() to starpunk/tags.py for batch tag loading
- Update _get_cached_notes() in starpunk/routes/public.py to use batch loading
- Add comprehensive tests in tests/test_batch_loading.py

Performance improvement:
- Before: O(n) queries (1 query per note for media + 1 query per note for tags)
- After: O(1) queries (2 queries total: 1 for all media, 1 for all tags)
- Maintains same API behavior and output format

All tests passing: 920 passed in 360.79s

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-17 10:42:44 -07:00
parent 1b45a64920
commit b689e02e64
5 changed files with 760 additions and 6 deletions

View File

@@ -42,11 +42,13 @@ def _get_cached_notes():
Returns cached notes if still valid, otherwise fetches fresh notes
from database and updates cache. Includes media and tags for each note.
Per v1.5.0 Phase 3: Uses batch loading to avoid N+1 query pattern.
Returns:
List of published notes for feed generation (with media and tags attached)
"""
from starpunk.media import get_note_media
from starpunk.tags import get_note_tags
from starpunk.media import get_media_for_notes
from starpunk.tags import get_tags_for_notes
# Get cache duration from config (in seconds)
cache_seconds = current_app.config.get("FEED_CACHE_SECONDS", 300)
@@ -64,13 +66,18 @@ def _get_cached_notes():
max_items = current_app.config.get("FEED_MAX_ITEMS", 50)
notes = list_notes(published_only=True, limit=max_items)
# Attach media to each note (v1.2.0 Phase 3)
# Batch load media and tags for all notes (v1.5.0 Phase 3)
# Reduces query count from O(n) to O(1) for both media and tags
note_ids = [note.id for note in notes]
media_by_note = get_media_for_notes(note_ids)
tags_by_note = get_tags_for_notes(note_ids)
# Attach media and tags to each note
for note in notes:
media = get_note_media(note.id)
media = media_by_note.get(note.id, [])
object.__setattr__(note, 'media', media)
# Attach tags to each note (v1.3.1)
tags = get_note_tags(note.id)
tags = tags_by_note.get(note.id, [])
object.__setattr__(note, '_cached_tags', tags)
_feed_cache["notes"] = notes