свёртка доставки в объекты и сшивка с приёмом
- internal/fold — свёртка по идентификатору доставки, тело из архива: тот же код, каким пойдёт пересборка витрины - приём зовёт свёртку на context.WithoutCancel с собственным дедлайном; исход разбора на код ответа не влияет - слой доставки хранится в delivery.derived_layer и наследуется от ПРЕДШЕСТВУЮЩЕЙ доставки автоматизации: без границы по времени свёртка переставала быть функцией от префикса журнала (1737 объектов против 1742) - task verify:archive — сходимость на живом архиве, 99 доставок из 99
This commit is contained in:
@@ -457,6 +457,30 @@ func (s *Store) MarkSealed(ctx context.Context, metric, layer string, hour time.
|
||||
return nil
|
||||
}
|
||||
|
||||
// BucketHours возвращает часы, за которые есть объекты метрики в слое, по
|
||||
// возрастанию. Первое, что понадобится каталогу разрезов, и то, чем проверка
|
||||
// сходимости находит объект, не зная дат в фикстуре.
|
||||
func (s *Store) BucketHours(ctx context.Context, metric, layer string) ([]time.Time, error) {
|
||||
const q = `
|
||||
SELECT hour_utc FROM bucket
|
||||
WHERE metric = ? AND layer = ? ORDER BY hour_utc`
|
||||
|
||||
var raw []string
|
||||
if err := s.db.SelectContext(ctx, &raw, q, metric, layer); err != nil {
|
||||
return nil, fmt.Errorf("select bucket hours: %w", err)
|
||||
}
|
||||
|
||||
out := make([]time.Time, 0, len(raw))
|
||||
for _, s := range raw {
|
||||
t, err := ParseTime(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CountBuckets возвращает число часовых объектов. Нужно проверке сходимости и
|
||||
// healthcheck.
|
||||
func (s *Store) CountBuckets(ctx context.Context) (int64, error) {
|
||||
|
||||
@@ -8,10 +8,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Статусы разбора доставки.
|
||||
// Статусы разбора доставки. Код ответа приёма от них не зависит: сохранили —
|
||||
// значит приняли.
|
||||
const (
|
||||
// ParsePending — тело сохранено, разбора ещё не было.
|
||||
ParsePending = "pending"
|
||||
// ParseDone — тело разобрано, точки разложены по объектам.
|
||||
ParseDone = "parsed"
|
||||
// ParseFailed — разобрать не удалось. Тело лежит в архиве, доставку
|
||||
// подберёт пересборка.
|
||||
ParseFailed = "failed"
|
||||
)
|
||||
|
||||
// Delivery — учётная запись одного принятого пакета.
|
||||
@@ -86,6 +92,92 @@ func (s *Store) LastDelivery(ctx context.Context) (Delivery, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// FinishParse записывает исход разбора доставки.
|
||||
//
|
||||
// Слой сохраняется здесь же, потому что он нужен следующей доставке той же
|
||||
// автоматизации: без плотных метрик выводить его не из чего, и наследовать
|
||||
// приходится от прошлого раза.
|
||||
func (s *Store) FinishParse(ctx context.Context, id, status string, points int64, layer string) error {
|
||||
const q = `
|
||||
UPDATE delivery SET parse_status = ?, points = ?, derived_layer = ?
|
||||
WHERE id = ?`
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, status, points, layer, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update parse status: %w", err)
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("update parse status: %w", err)
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastDerivedLayer возвращает слой, выведенный для этой автоматизации ПЕРЕД
|
||||
// указанной доставкой. Пустая строка означает, что наследовать нечего.
|
||||
//
|
||||
// «Перед» здесь существенно, а не для красоты: слой обязан быть функцией от
|
||||
// префикса журнала, иначе повторный прогон даёт другое состояние, чем живой
|
||||
// приём. Запрос без границы по времени брал бы последний слой вообще — и при
|
||||
// пересборке доставка наследовала бы слой от будущего. Проверено прогоном
|
||||
// архива: 1737 объектов превращались в 1742.
|
||||
func (s *Store) LastDerivedLayer(ctx context.Context, automationID string, before time.Time, beforeID string) (string, error) {
|
||||
if automationID == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const q = `
|
||||
SELECT derived_layer FROM delivery
|
||||
WHERE automation_id = ? AND derived_layer != ''
|
||||
AND (received_at, id) < (?, ?)
|
||||
ORDER BY received_at DESC, id DESC LIMIT 1`
|
||||
|
||||
var layer string
|
||||
err := s.db.GetContext(ctx, &layer, q, automationID, FormatTime(before), beforeID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("select derived layer: %w", err)
|
||||
}
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
// DeliveryBody — что нужно знать о доставке, чтобы разобрать её тело.
|
||||
type DeliveryBody struct {
|
||||
ID string
|
||||
ReceivedAt time.Time
|
||||
RawPath string
|
||||
AutomationID string
|
||||
Aggregation string
|
||||
}
|
||||
|
||||
// DeliveryForParse возвращает сведения о доставке, нужные разбору.
|
||||
func (s *Store) DeliveryForParse(ctx context.Context, id string) (DeliveryBody, error) {
|
||||
const q = `
|
||||
SELECT id, received_at, raw_path, automation_id, aggregation
|
||||
FROM delivery WHERE id = ?`
|
||||
|
||||
var d DeliveryBody
|
||||
var receivedAt string
|
||||
err := s.db.QueryRowxContext(ctx, q, id).
|
||||
Scan(&d.ID, &receivedAt, &d.RawPath, &d.AutomationID, &d.Aggregation)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return DeliveryBody{}, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return DeliveryBody{}, fmt.Errorf("select delivery for parse: %w", err)
|
||||
}
|
||||
d.ReceivedAt, err = ParseTime(receivedAt)
|
||||
if err != nil {
|
||||
return DeliveryBody{}, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// CountDeliveries возвращает число принятых пакетов. Нужно для healthcheck и
|
||||
// быстрой проверки «данные вообще идут».
|
||||
func (s *Store) CountDeliveries(ctx context.Context) (int64, error) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- +goose Up
|
||||
-- Слой, выведенный для доставки. Нужен не отчётности, а самому выводу слоя:
|
||||
-- доставка без плотных метрик наследует последний надёжно выведенный слой той
|
||||
-- же автоматизации, и без хранения этой памяти первая же такая доставка после
|
||||
-- перезапуска сервиса осталась бы без слоя (измерено: 2 доставки из 89).
|
||||
--
|
||||
-- Пустая строка означает «слой не выводился» — такие доставки в наследование
|
||||
-- не участвуют.
|
||||
ALTER TABLE delivery ADD COLUMN derived_layer TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- Поиск идёт «последний слой этой автоматизации»: сортировка по времени приёма
|
||||
-- внутри автоматизации.
|
||||
CREATE INDEX delivery_automation_layer ON delivery (automation_id, received_at DESC);
|
||||
|
||||
-- +goose Down
|
||||
DROP INDEX delivery_automation_layer;
|
||||
ALTER TABLE delivery DROP COLUMN derived_layer;
|
||||
Reference in New Issue
Block a user