Skip to content

Commit c86e98f

Browse files
committed
perf: eliminate per-message DB hits and redundant Telegram API calls
- IsUserApproved: use cached approved-user list instead of per-call DB point lookup (called 8x per message across hot-path handlers) - canBot{Restrict,Promote,Pin,Delete}: reuse admin cache via getUserMemberWithCache instead of independent GetMember API calls per check - Add functional index on filters(chat_id, LOWER(keyword)) to fix full-scan on DoesFilterExists - Add warns_users(chat_id) index for chat-only warn lookups bypassed by existing user_id-leading composite index
1 parent a5c1041 commit c86e98f

3 files changed

Lines changed: 60 additions & 15 deletions

File tree

alita/db/approvals/repository.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ func AddApprovedUser(chatID, userID, approvedBy int64, reason string) error {
2929

3030
// IsUserApproved checks if a user is in the approved list for a chat.
3131
func IsUserApproved(chatID, userID int64) bool {
32-
var user models.ApprovedUsers
33-
err := db.GetRecord(&user, models.ApprovedUsers{ChatID: chatID, UserID: userID})
34-
return err == nil
32+
for _, u := range GetApprovedUsers(chatID) {
33+
if u.UserID == userID {
34+
return true
35+
}
36+
}
37+
return false
3538
}
3639

3740
// GetApprovedUsers returns all approved users for a chat.

alita/utils/chat_status/access.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ func canBotRestrict(b *gotgbot.Bot, ctx *ext.Context, chat *gotgbot.Chat) bool {
8282
return false
8383
}
8484

85-
botMember, err := chat.GetMember(b, b.Id, nil)
86-
if err != nil {
85+
botMember, ok := getUserMemberWithCache(b, chat, b.Id, "canBotRestrict")
86+
if !ok {
8787
return false
8888
}
89-
return botMember.MergeChatMember().CanRestrictMembers
89+
return botMember.CanRestrictMembers
9090
}
9191

9292
// canBotPromote reports whether the bot can promote/demote members.
@@ -99,11 +99,11 @@ func canBotPromote(b *gotgbot.Bot, ctx *ext.Context, chat *gotgbot.Chat) bool {
9999
return false
100100
}
101101

102-
botMember, err := chat.GetMember(b, b.Id, nil)
103-
if err != nil {
102+
botMember, ok := getUserMemberWithCache(b, chat, b.Id, "canBotPromote")
103+
if !ok {
104104
return false
105105
}
106-
return botMember.MergeChatMember().CanPromoteMembers
106+
return botMember.CanPromoteMembers
107107
}
108108

109109
// canBotPin reports whether the bot can pin messages.
@@ -116,11 +116,11 @@ func canBotPin(b *gotgbot.Bot, ctx *ext.Context, chat *gotgbot.Chat) bool {
116116
return false
117117
}
118118

119-
botMember, err := chat.GetMember(b, b.Id, nil)
120-
if err != nil {
119+
botMember, ok := getUserMemberWithCache(b, chat, b.Id, "canBotPin")
120+
if !ok {
121121
return false
122122
}
123-
return botMember.MergeChatMember().CanPinMessages
123+
return botMember.CanPinMessages
124124
}
125125

126126
// canBotDelete reports whether the bot can delete messages.
@@ -133,11 +133,11 @@ func canBotDelete(b *gotgbot.Bot, ctx *ext.Context, chat *gotgbot.Chat) bool {
133133
return false
134134
}
135135

136-
botMember, err := chat.GetMember(b, b.Id, nil)
137-
if err != nil {
136+
botMember, ok := getUserMemberWithCache(b, chat, b.Id, "canBotDelete")
137+
if !ok {
138138
return false
139139
}
140-
return botMember.MergeChatMember().CanDeleteMessages
140+
return botMember.CanDeleteMessages
141141
}
142142

143143
// isBotAdminPure reports whether the bot is an admin, without sending messages.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- =====================================================
2+
-- FILTERS FUNCTIONAL INDEX & WARNS CHAT INDEX
3+
-- =====================================================
4+
-- This migration adds targeted indexes to eliminate full-table scans
5+
-- on high-frequency filter watcher and warn lookup queries.
6+
--
7+
-- NOTE: Uses regular CREATE INDEX (without CONCURRENTLY) to remain
8+
-- compatible with Supabase migration system which runs in transactions.
9+
10+
-- =====================================================
11+
-- 1. FILTERS - Functional index for case-insensitive keyword lookup
12+
-- =====================================================
13+
-- DoesFilterExists queries: WHERE chat_id = ? AND LOWER(keyword) = LOWER(?)
14+
-- The existing idx_filters_chat_optimized ON filters(chat_id) only covers
15+
-- the chat_id predicate. Without a functional index on LOWER(keyword),
16+
-- PostgreSQL must heap-scan every filter row for the chat to evaluate
17+
-- the LOWER(keyword) expression on every filter-watcher message.
18+
-- This composite functional index lets PostgreSQL resolve both predicates
19+
-- entirely from the index, eliminating the heap scan.
20+
CREATE INDEX IF NOT EXISTS idx_filters_chat_keyword_lower
21+
ON filters(chat_id, LOWER(keyword));
22+
23+
-- =====================================================
24+
-- 2. WARNS USERS - Chat-only lookup index
25+
-- =====================================================
26+
-- GetAllChatWarns and ResetAllChatWarns query: WHERE chat_id = ?
27+
-- The existing idx_warns_users_composite ON warns_users(user_id, chat_id)
28+
-- leads with user_id, so PostgreSQL cannot use it for chat_id-only lookups.
29+
-- This index allows efficient scans when only chat_id is provided.
30+
CREATE INDEX IF NOT EXISTS idx_warns_users_chat_id
31+
ON warns_users(chat_id);
32+
33+
-- =====================================================
34+
-- 3. UPDATE TABLE STATISTICS
35+
-- =====================================================
36+
ANALYZE filters;
37+
ANALYZE warns_users;
38+
39+
DO $$
40+
BEGIN
41+
RAISE NOTICE 'Filters functional index and warns chat index migration completed successfully';
42+
END $$;

0 commit comments

Comments
 (0)