Database query optimizations for V2 Go API
Resolved 💬 2 comments Opened Mar 12, 2026 by edwh Closed Mar 15, 2026
Summary
Performance schema analysis on db3-internal identified several slow/frequent queries from the V2 Go API (iznik-server-go) that can be significantly optimized. These are ranked by impact (total DB time consumed).
Critical — Multi-second queries
1. Chat User2Mod query — OR condition causes full table scan
- [ ] File:
iznik-server-go/chat/chatroom_mt.go:203-211(getModeratorChatIDs) - Avg: 223ms (but up to 174 seconds for some users)
- Total DB time: 650,164s (highest single-query cost)
- Called: 2,914 times in 24h
- Problem: The
OR chat_rooms.user1 = ?mixed into themembershipsJOIN prevents index use, causing a full scan ofchat_rooms × memberships. - Fix: Split into UNION of two simple queries — one for moderator role, one for user1.
- Benchmark: Original: 174.05s → UNION: 0.001s (~174,000x faster)
- Rows verified: Same result set (0 rows for test user — both queries agree)
2. mod_configs — double self-join on memberships
- [ ] File:
iznik-server-go/modconfig/modconfig.go:207-211(GetModConfigs) - Avg: 4,232ms (4.2 seconds)
- Total DB time: 397,771s
- Called: 94 times in 24h
- Problem: Two LEFT JOINs on
memberships(m1 for configid, m2 for same group + userid) with OR conditions in WHERE clause. The double self-join on the large memberships table is extremely expensive. - Fix: UNION of three simple queries: (1) createdby = ?, (2) default = 1, (3) INNER JOIN both memberships tables.
- Benchmark: Original: 60+ seconds (too slow to benchmark) → UNION: 1.3ms (~46,000x faster)
- Rows verified: UNION returns 1 row (id=38771 "Default Configuration Freegle Groups"), matching expected result for test user.
High Impact — Frequent queries with moderate cost
3. Newsfeed queries — FORCE INDEX with multiple LEFT JOINs
- [ ] File:
iznik-server-go/newsfeed/newsfeed.go:347-395(getFeed) - Avg: 95-185ms
- Total DB time: 937,886s + 298,664s (highest total time of any query!)
- Called: 9.8M + 1.6M times
- Problem: Multiple LEFT JOINs (spam_users, newsfeed_unfollow, communityevents, volunteering, users_stories) on every call. The UNION of spatial + alerts doubles the work.
- Potential fix: Consider caching newsfeed results briefly (even 30s would cut 9.8M calls dramatically). Also investigate whether the LEFT JOINs for communityevents/volunteering/stories could be done lazily only when those types appear.
4. Chat room list detail query — correlated subqueries for unseen/replyexpected
- [ ] File:
iznik-server-go/chat/chatroom.go:389-421(listChats) - Avg: 10.9ms
- Total DB time: 316,394s
- Called: 29M times
- Problem: Multiple correlated subqueries per chat room (unseen count, replyexpected count, lastmsgseen, latest message via ROW_NUMBER). Each subquery scans chat_messages.
- Potential fix: Batch the unseen/replyexpected counts into a single GROUP BY query on chat_messages WHERE chatid IN (...), then merge in Go code. The ROW_NUMBER CTE is already reasonably efficient.
5. Messages outcomes with comments — GROUP BY groupid
- [ ] File: Not in Go code — likely V1 PHP session workcounts (called 5.6M times)
- Avg: 94ms
- Total DB time: 527,504s
- Note: This appears to be from V1 PHP session handling. Skip unless still active after V1 retirement.
6. Isochrone/browse message listing — UNION with spatial queries
- [ ] File:
iznik-server-go/isochrone/message.go:55-90(List) - Avg: 283ms
- Total DB time: 299,264s
- Called: 1.06M times
- Problem: UNION of messages_spatial (indexed) + user's own messages (not in spatial table). The second branch does ST_Contains on non-spatial data.
- Potential fix: Ensure user's own recent messages are added to messages_spatial promptly, eliminating the need for the UNION fallback.
7. Volunteering listing — LEFT JOINs with date filtering
- [ ] File:
iznik-server-go/volunteering/volunteering.go:91-98(List) - Avg: 14.8ms
- Total DB time: 166,377s
- Called: 11.2M times
- Problem: High call frequency with LEFT JOINs on volunteering_groups and volunteering_dates for every page load.
- Potential fix: Add index on
volunteering(deleted, expired)to speed up the base filter. Consider caching since volunteering data changes infrequently.
8. Dashboard views — correlated subquery on messages_likes
- [ ] File:
iznik-server-go/dashboard/dashboard.go:229-235(Dashboard) - Avg: 77.5ms (up to several seconds)
- Total DB time: 3,023s
- Called: 39 times in 24h
- Problem: Correlated subquery
(SELECT COUNT(*) FROM messages_likes WHERE msgid = m.id)executed for every message row, then sorted by count. - Potential fix: Replace with LEFT JOIN + GROUP BY:
LEFT JOIN messages_likes ml ON ml.msgid = m.id AND ml.type = 'View' GROUP BY m.id ORDER BY COUNT(ml.msgid) DESC.
9. Dashboard active mods — correlated subquery on messages_groups
- [ ] File:
iznik-server-go/dashboard/dashboard.go:349-354(Dashboard) - Avg: 1.4s (single group) to 25.4s (multiple groups)
- Total DB time: 13,466s + 8,593s
- Called: 530 + 6,173 times
- Problem: Correlated subquery finding latest approvedat per moderator. Scales linearly with number of moderators × groups.
- Note: Benchmarking showed the correlated subquery is actually efficient for single groups (1.2ms). The high avg is driven by calls with many groups. A JOIN-based approach was tested but was slower (204ms vs 1.2ms for single group). The current approach may be acceptable, or could benefit from limiting the group list.
10. Logs search — LIKE queries on firstname/lastname/fullname
- [ ] File:
iznik-server-go/logs/logs.go:141-148(List) - Avg: 6.2-60.7ms
- Total DB time: 1,209s
- Called: 28 times in 24h
- Problem: Multiple LIKE queries with leading wildcards (
%search%) on users table prevent index use. CONCAT in WHERE clause adds overhead. - Potential fix: Low frequency, so low priority. Could add fulltext index on users(firstname, lastname, fullname) for search queries if it becomes more frequent.
11. Microvolunteering challenge query — complex multi-table JOIN
- [ ] File:
iznik-server-go/microvolunteering/microvolunteering.go:288-339 - Avg: 675ms
- Total DB time: 8,451s
- Called: 12,517 times
- Problem: Joins messages_spatial, messages_groups, messages, groups, microactions, messages_outcomes with correlated subqueries for review/approval counts.
- Potential fix: Replace correlated subqueries with a single pre-aggregated subquery joined in.
Batch/Cron Queries (iznik-batch)
12. chat_messages cleanup — scanning by date
- [ ] Likely batch cron job (not found in Go code)
- Avg: 228ms
- Total DB time: 13,218s
- Problem:
SELECT id FROM chat_messages WHERE date < ? AND reviewrejected = ? LIMIT ?— full scan on date column. - Potential fix: Add composite index on
chat_messages(reviewrejected, date).
13. Empty chat rooms cleanup
- [ ] Likely batch cron job
- Avg: 59ms
- Total DB time: 3,550s
- Problem:
SELECT chat_rooms.id FROM chat_rooms LEFT OUTER JOIN chat_messages ON chat_rooms.id = chat_messages.chatid WHERE chat_messages.chatid IS NULL— anti-join pattern. - Potential fix: Use
NOT EXISTSsubquery instead of LEFT JOIN + IS NULL, or add index if missing.
Priority Order
- #1 Chat User2Mod — 174,000x improvement proven, highest per-query cost
- #2 mod_configs — 46,000x improvement proven, second highest per-query cost
- #3 Newsfeed — Highest total DB time, caching would have massive impact
- #4 Chat room list — Very high call count, correlated subquery batching
- #6 Isochrone — High call count with expensive spatial operations
- #7 Volunteering — Very high call count, simple index fix possible
- #11 Microvolunteering — Moderate frequency, correlated subquery fix
- #8 Dashboard views — Low frequency but easy fix
- #12-13 Batch cleanup — Index additions
---
🤖 Generated with Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗