Приём отвечает 200 до свёртки, свёртку ведёт фоновый воркер
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
This commit is contained in:
+15
-46
@@ -24,7 +24,6 @@ import (
|
||||
|
||||
"git.vakhrushev.me/av/healthlog/internal/archive"
|
||||
"git.vakhrushev.me/av/healthlog/internal/fold"
|
||||
"git.vakhrushev.me/av/healthlog/internal/hae"
|
||||
"git.vakhrushev.me/av/healthlog/internal/ident"
|
||||
"git.vakhrushev.me/av/healthlog/internal/store"
|
||||
)
|
||||
@@ -70,24 +69,11 @@ type Report struct {
|
||||
// Orphans — строк учёта, у которых тела в архиве нет. Станет штатным, когда
|
||||
// появится ретеншен архива.
|
||||
Orphans int
|
||||
// Folded — сколько доставок свернулось.
|
||||
Folded int
|
||||
// Отказы разведены по классам, потому что читаются они по-разному.
|
||||
// FailedLayer — слой не выводится: штатный исход, таких доставок в журнале
|
||||
// заведомо есть. FailedMalformed — содержимое не разбирается. FailedOther —
|
||||
// всё прочее (тело не читается, отказ базы); только оно означает, что с
|
||||
// пересборкой что-то не так. Один общий счётчик отправлял бы человека
|
||||
// искать дефект там, где его нет.
|
||||
FailedLayer int
|
||||
FailedMalformed int
|
||||
FailedOther int
|
||||
// Partial — доставок, в теле которых остались непокрытые разбором секции.
|
||||
// Не отклонение, а половина потока; названо потому, что именно эти тела
|
||||
// ретеншену трогать нельзя.
|
||||
Partial int
|
||||
// Incomparable — столкновений с несравнимыми наборами полей. На живом потоке
|
||||
// их не было ни разу, и на этом стоит отказ от объединения полей.
|
||||
Incomparable int
|
||||
|
||||
// Outcome — счётчики свёртки, те же самые, что считает фоновый воркер
|
||||
// приёма. Встроены, а не продублированы именами: два набора имён для одних
|
||||
// исходов разошлись бы при первой же правке классификации.
|
||||
Outcome
|
||||
|
||||
Buckets int64
|
||||
Fingerprint string
|
||||
@@ -148,6 +134,7 @@ func Run(ctx context.Context, o Options) (Report, error) {
|
||||
}
|
||||
}
|
||||
|
||||
player := Player{Fold: o.Fold}
|
||||
for i, d := range journal {
|
||||
if ctx.Err() != nil {
|
||||
rep.Canceled = true
|
||||
@@ -156,36 +143,17 @@ func Run(ctx context.Context, o Options) (Report, error) {
|
||||
if err := o.Target.CreateDelivery(ctx, d); err != nil {
|
||||
return stopOr(rep, err)
|
||||
}
|
||||
st, err := o.Fold.Fold(ctx, d.ID)
|
||||
switch {
|
||||
case err == nil:
|
||||
rep.Folded++
|
||||
case ctx.Err() != nil:
|
||||
// Отмена, застигшая свёртку, — не отказ доставки: считать её отказом
|
||||
// значило бы обвинить разбор в том, чего он не делал, и отправить
|
||||
// человека искать дефект по логу.
|
||||
out, _ := player.Play(ctx, d.ID)
|
||||
// Отмена, застигшая свёртку, — не отказ доставки: считать её отказом
|
||||
// значило бы обвинить разбор в том, чего он не делал, и отправить
|
||||
// человека искать дефект по логу. Решение принимает цикл по СВОЕМУ
|
||||
// контексту — тому же, на котором шла свёртка; классификатор о нём не
|
||||
// знает намеренно, у воркера тот же признак означает другое.
|
||||
if ctx.Err() != nil {
|
||||
rep.Canceled = true
|
||||
return rep, nil
|
||||
case errors.Is(err, hae.ErrLayerUnknown):
|
||||
// Штатный исход, уже записанный свёрткой в лог и в parse_status:
|
||||
// журнал заведомо содержит тела без плотных метрик. Останов на
|
||||
// первом лишил бы пересборки все остальные.
|
||||
rep.FailedLayer++
|
||||
case errors.Is(err, hae.ErrMalformed):
|
||||
rep.FailedMalformed++
|
||||
default:
|
||||
rep.FailedOther++
|
||||
}
|
||||
if err == nil {
|
||||
// Счётчики читаются только у успешной свёртки: при ошибке поля Stats
|
||||
// заполнены частично (Uncovered у отказавшего разбора всегда пуст,
|
||||
// хотя в базу список записан) — и Partial молча занижался бы. А по
|
||||
// нему принимается решение о ретеншене тел.
|
||||
if len(st.Uncovered) > 0 {
|
||||
rep.Partial++
|
||||
}
|
||||
rep.Incomparable += st.Incomparable
|
||||
}
|
||||
rep.Add(out)
|
||||
if o.Progress != nil {
|
||||
o.Progress(i+1, len(journal))
|
||||
}
|
||||
@@ -218,6 +186,7 @@ func Run(ctx context.Context, o Options) (Report, error) {
|
||||
"folded", rep.Folded,
|
||||
"failed_layer", rep.FailedLayer,
|
||||
"failed_malformed", rep.FailedMalformed,
|
||||
"deferred", rep.Deferred,
|
||||
"failed_other", rep.FailedOther,
|
||||
"partial", rep.Partial,
|
||||
"incomparable", rep.Incomparable,
|
||||
|
||||
Reference in New Issue
Block a user