reindex: пересборка витрины проигрыванием журнала
- `healthlog reindex` собирает витрину из журнала (тела архива + учёт доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую базу читает без наката миграций и не трогает вовсе. Подмену делает человек при остановленном сервисе: переименование поверх открытого дескриптора портит базу молча. - Журналом считается архив, а не таблица доставок: тело без учётной записи заводится заново (метка из ULID, размер и хеш по распакованному телу), запись без тела переносится, но не сворачивается. Оракул сходимости встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не считается. - Прогон живого архива переехал на новый пакет: второго проигрывателя журнала в проекте не осталось, а его утверждение о ключе сна перестало быть константой, протухающей с каждой доставкой.
This commit is contained in:
@@ -105,6 +105,62 @@ func (s *Store) LastDelivery(ctx context.Context) (Delivery, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ListDeliveries возвращает учёт доставок в порядке журнала — `(received_at,
|
||||
// id)`, тем же, в котором их проигрывает пересборка.
|
||||
//
|
||||
// Отдаются только **факты журнала**: то, что пришло вместе с доставкой.
|
||||
// Производные от разбора поля (`parse_status`, `points`, `derived_layer`,
|
||||
// `uncovered_sections`) сюда не попадают намеренно — перенос их в пересобранную
|
||||
// базу сделал бы витрину функцией предыдущего прогона. Особенно `derived_layer`:
|
||||
// доставка, чей повторный разбор отказал, отдала бы в наследование слой
|
||||
// прежнего разбора, и следующая доставка той же автоматизации унаследовала бы
|
||||
// его молча.
|
||||
func (s *Store) ListDeliveries(ctx context.Context) ([]Delivery, error) {
|
||||
const q = `
|
||||
SELECT id, received_at, automation_name, automation_id, aggregation,
|
||||
period, session_id, bytes, sha256, raw_path, headers
|
||||
FROM delivery ORDER BY received_at, id`
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, q)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("select deliveries: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var out []Delivery
|
||||
for rows.Next() {
|
||||
var d Delivery
|
||||
var receivedAt string
|
||||
if err := rows.Scan(&d.ID, &receivedAt, &d.AutomationName, &d.AutomationID,
|
||||
&d.Aggregation, &d.Period, &d.SessionID, &d.Bytes, &d.SHA256,
|
||||
&d.RawPath, &d.Headers); err != nil {
|
||||
return nil, fmt.Errorf("scan delivery: %w", err)
|
||||
}
|
||||
d.ReceivedAt, err = ParseTime(receivedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, d)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("select deliveries: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeliveryStatus возвращает статус разбора доставки.
|
||||
func (s *Store) DeliveryStatus(ctx context.Context, id string) (string, error) {
|
||||
var status string
|
||||
err := s.db.GetContext(ctx, &status, `SELECT parse_status FROM delivery WHERE id = ?`, id)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("select parse status: %w", err)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// FinishParse записывает исход разбора доставки.
|
||||
//
|
||||
// Слой сохраняется здесь же, потому что он нужен следующей доставке той же
|
||||
|
||||
Reference in New Issue
Block a user