mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-08-07 04:24:26 +00:00
fix(stats): fan out per-key GetItem to avoid handler deadline timeout
Sequential List + N GetItem made /stats latency scale with the number of tracked commands. On a cold Lambda container with ~25 commands the cumulative DynamoDB round-trips can exceed the 10s webhook handler deadline; the trailing chathelper.Reply -> b.SendMessage then fails on a cancelled ctx and the dispatcher only logs the error, leaving the user with no reply at all. Fan the per-key GetJSONs out into goroutines joined by sync.WaitGroup. Wall-clock latency collapses to ~1 round-trip while preserving the per-key error isolation (a single GetJSON failure still drops only its own row). Storage interface unchanged; no race regression vs. the prior per-item write pattern.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-telegram/bot"
|
||||
"github.com/go-telegram/bot/models"
|
||||
@@ -77,20 +78,42 @@ func statsCommand(c *counter) modules.Command {
|
||||
return chathelper.Reply(ctx, b, update.Message, "No command stats yet.")
|
||||
}
|
||||
|
||||
// Fan-out the per-key GetJSONs concurrently. Sequential reads make
|
||||
// /stats latency O(N) round-trips to DynamoDB; on a cold Lambda
|
||||
// container with 20+ commands, that can push the synchronous
|
||||
// handler past the 10s webhook deadline and the trailing Reply
|
||||
// SendMessage then fails on a cancelled ctx — the user sees no
|
||||
// reply at all. Fanning out collapses wall-clock latency to one
|
||||
// round-trip while keeping the same per-key error isolation.
|
||||
type row struct {
|
||||
name string
|
||||
n int64
|
||||
ok bool
|
||||
}
|
||||
rows := make([]row, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
name := strings.TrimPrefix(k, countPrefix)
|
||||
var entry countEntry
|
||||
if err := c.kv.GetJSON(ctx, k, &entry); err != nil {
|
||||
log.Error("stats: kv get failed during render", "key", k, "err", err)
|
||||
continue
|
||||
rows := make([]row, len(keys))
|
||||
var wg sync.WaitGroup
|
||||
for i, k := range keys {
|
||||
wg.Add(1)
|
||||
go func(i int, k string) {
|
||||
defer wg.Done()
|
||||
name := strings.TrimPrefix(k, countPrefix)
|
||||
var entry countEntry
|
||||
if err := c.kv.GetJSON(ctx, k, &entry); err != nil {
|
||||
log.Error("stats: kv get failed during render", "key", k, "err", err)
|
||||
rows[i] = row{name: name}
|
||||
return
|
||||
}
|
||||
rows[i] = row{name: name, n: entry.N, ok: true}
|
||||
}(i, k)
|
||||
}
|
||||
wg.Wait()
|
||||
kept := rows[:0]
|
||||
for _, r := range rows {
|
||||
if r.ok {
|
||||
kept = append(kept, r)
|
||||
}
|
||||
rows = append(rows, row{name: name, n: entry.N})
|
||||
}
|
||||
rows = kept
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if rows[i].n != rows[j].n {
|
||||
return rows[i].n > rows[j].n
|
||||
|
||||
Reference in New Issue
Block a user