Skip to content

perf: replace get_discussion tree walk with single recursive CTE - #383

Open
ety001 wants to merge 2 commits into
masterfrom
debug/get-discussion-tracing
Open

perf: replace get_discussion tree walk with single recursive CTE#383
ety001 wants to merge 2 commits into
masterfrom
debug/get-discussion-tracing

Conversation

@ety001

@ety001 ety001 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

Fixes two issues found while validating the debug-get-discussion-tracing image in production:

1. get_discussion tree walk: level-by-level BFS → single recursive CTE

The old _load_discussion walked the comment tree one depth level at a time, issuing one _child_ids query per level. Production logs showed 500-1300ms for depth 4-10 threads — each level adds a pool acquire + parse + round-trip.

Replaced with a single WITH RECURSIVE query fetching the whole subtree in one DB round-trip:

  • Hidden authors (list_type='3') and hidden posts (list_type='1') are filtered inside the recursion, so their subtrees are never traversed
  • MAX_DEPTH / MAX_THREAD_POSTS caps preserved (LIMIT inside the CTE), BFS ordering kept so truncation semantics match the old implementation
  • Whole tree cached under discussion_tree_<root_id> (120s TTL), replacing the per-level _child_ids_* cache

2. [DB_SLOW] log: SQL body was empty in production

db.py logged args[1][:200] directly — multi-line SQL starts with \n, so syslog truncated every entry after the timestamp. Collapse whitespace before logging (same as _normalize_sql in stats.py).

Verification

  • tests/bridge_thread/: 9/9 passed (rewritten for single-CTE semantics)
  • CTE SQL tested against real Postgres 16 (hidden-post filtering, depth cap, post cap, deep-chain BFS order)
  • Wide-thread perf: 5000-child tree resolves in 27ms single query (vs ~1s level-by-level)
  • LIMIT :max_posts binding verified with production SQLAlchemy 1.4.54
  • tests/utils: 3 pre-existing failures unrelated to this change (confirmed via git stash baseline)

Notes

  • Debug timing logs (DISCUSSION_SLOW / DISCUSSION_BREAKDOWN / POSTS_KEYED_SLOW / DB_SLOW) remain in this branch for continued validation
  • Go rewrite (internal/api/bridge/post.go) still has TODO: Fetch all replies recursively — this change is the reference implementation for that

ety001 added 2 commits August 10, 2026 10:15
Add structured WARNING logs to diagnose connection-pool exhaustion:

- [DISCUSSION_SLOW] in get_discussion(): total time + per-stage breakdown
  (post_id lookup, hide checks, load_discussion), logged when total > 1s
- [DISCUSSION_BREAKDOWN] in _load_discussion(): tree_walk time (with query
  count, depth, post count) vs load_posts time, logged when either > 500ms
- [POSTS_KEYED_SLOW] in load_posts_keyed(): fetch/author/comm_roles
  breakdown, logged when total > 500ms
- [DB_SLOW] in sqltimer decorator: any DB query slower than 3s with SQL

All logs use WARNING level so they appear in /var/log/messages and are
collected by Scalyr. No business logic changes.
Replace the level-by-level BFS (one _child_ids query per depth level,
observed 500-1300ms for depth 4-10 threads) with a single WITH RECURSIVE
query that fetches the whole comment subtree in one DB round-trip.

- Filter hidden authors (list_type='3') and hidden posts (list_type='1')
  inside the recursion so their subtrees are never traversed
- Keep MAX_DEPTH / MAX_THREAD_POSTS caps (LIMIT inside the CTE) and BFS
  ordering so truncation semantics match the old implementation
- Cache the whole tree under discussion_tree_<root_id> (120s TTL)
- Fix [DB_SLOW] log: collapse SQL whitespace so multi-line queries are not
  truncated at the first line break by syslog (observed empty SQL body)
- Update bridge_thread unit tests for single-CTE semantics
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.

1 participant