P2.5: биллинг — воркер+буфер вместо горутины на операцию; корректный msg_count

RecordUsage теперь кладёт запись в буферизованный канал (1024), один воркер пишет
в PG — убран навал горутин, ждущих коннект пула (было 5). При переполнении буфера
запись дропается с логом (биллинг жертвуем ради стабильности). Для batch-операций
(SendMessageBatch/DeleteMessageBatch/ChangeMessageVisibilityBatch) msg_count теперь
реальное число записей, а не всегда 1.
This commit is contained in:
Repinoid
2026-10-06 18:19:17 +03:00
parent 07eace3c6b
commit fb833ef01c
2 changed files with 84 additions and 15 deletions
+40 -2
View File
@@ -200,6 +200,36 @@ func metricsAuth(adminToken string, next http.Handler) http.Handler {
})
}
// countBatchEntries — подсчитывает число записей в batch-запросе SQS.
// Для query-протокола записи идут как SendMessageBatchRequestEntry.N.Id (N=1..).
// Для JSON-протокола считаем по количеству записей в массиве Entries (requestBody).
// Возвращает 0 если определить не удалось (тогда биллинг запишет msg_count=1).
func countBatchEntries(req *http.Request, action string) int {
// Query-формат: считаем уникальные N в SendMessageBatchRequestEntry.N.Id и т.п.
prefix := "SendMessageBatchRequestEntry."
if action == "DeleteMessageBatch" {
prefix = "DeleteMessageBatchRequestEntry."
} else if action == "ChangeMessageVisibilityBatch" {
prefix = "ChangeMessageVisibilityBatchRequestEntry."
}
if req.PostForm != nil || req.Form != nil {
form := req.Form
if form == nil {
form = req.PostForm
}
count := 0
for key := range form {
if strings.HasPrefix(key, prefix) && strings.HasSuffix(key, ".Id") {
count++
}
}
if count > 0 {
return count
}
}
return 0
}
func actionHandler(w http.ResponseWriter, req *http.Request) {
action := extractAction(req)
start := time.Now()
@@ -227,8 +257,16 @@ func actionHandler(w http.ResponseWriter, req *http.Request) {
if queueName == "" {
queueName = req.FormValue("QueueName")
}
// Billing: запись в PostgreSQL (async, no-op если billing выключен)
billing.RecordUsage(t.ID, action, queueName, 1, msgBytes)
// Billing: запись в PostgreSQL (async, no-op если billing выключен).
// msgCount для batch-операций — число сообщений в батче; для прочих — 1.
msgCount := 1
if action == "SendMessageBatch" || action == "DeleteMessageBatch" ||
action == "ChangeMessageVisibilityBatch" {
if n := countBatchEntries(req, action); n > 0 {
msgCount = n
}
}
billing.RecordUsage(t.ID, action, queueName, msgCount, msgBytes)
// Prometheus counters: requests + bytes
metrics.RequestsTotal.WithLabelValues(t.ID, action).Inc()
metrics.RequestBytesTotal.WithLabelValues(t.ID, action).Add(float64(msgBytes))