Documentation
¶
Overview ¶
defaults.go — величины ПЕТЛИ (не пороги) в единственном экземпляре.
Package retention — ОДНА фоновая петля уборки таблиц, чей рост задаёт внешний.
Почему петля живёт в общем фундаменте, а не у владельца ¶
Таблица операций заведена у ВОСЬМИ владельцев, очереди дренажа — у семи (задачи #1360 и #1361). Восемь расписаний уборки и тринадцать расписаний очередей разошлись бы молча, а разойдясь — дали бы разный порог у разных владельцев при ОДНОМ контракте. Поэтому петля, партии, потолок проходов и наблюдаемость заводятся здесь ровно один раз.
Что заводится порознь — и почему это НЕ отступление от «один раз» ¶
Порознь заводится только ПРЕДИКАТ, потому что предикат есть свойство СХЕМЫ таблицы, а она у семей разная: у операций признак терминальности `done`, у очереди дренажа — отметка доставки `sent_at`. Один предикат на обе семьи был бы либо неразбираемым (колонки нет — отказ `42703` на каждом проходе), либо холостым (колонка есть, писателей нет — снимает ноль молча). Оба отказа наблюдались в дереве, и оба живые.
Отсюда раскладка: петля — здесь; предикат операций — в `pkg/operations`, рядом с репозиторием и реконсайлером, владеющими `done`; предикат очереди — в `pkg/outbox`, рядом с дренажем, владеющим `sent_at` и `attempt_count`.
Порог — функция предиката ЧИТАТЕЛЯ, а не свойство колонки срока ¶
Строку позволено снять не раньше момента, после которого НИ ОДИН читатель не изменил бы из-за неё своего исхода. Порог есть ПАРА «величина + источник часов»: уборщик и читатель обязаны судить одними часами, а разница источников входит в порог отдельным слагаемым и берётся из объявленной величины, а не выписывается числом.
Часы уборки — БАЗЫ: момент времени в сигнатуру уборщика не входит, предикат целиком в SQL. Это идиома дерева, а не изобретение — все уборщики, у которых вызывающий есть, приняли ровно эту форму; входом момент принимали ровно два, и это были те самые два, у которых вызывающего не было.
Форма петли (партии, потолок проходов, запись «РЕПЛИКИ:», наблюдаемость) задана приёмкой `services/iam/docs/engineering/acceptance/ retention-sweep-has-a-caller.md` §3.1–§3.4 и здесь не пересказывается: два места об одном предмете разошлись бы молча.
Index ¶
Constants ¶
const ( // DefaultInterval — как часто идёт проход. // // Задаёт не пропускную способность, а задержку замечания: догон внутри // прохода обеспечивает DefaultMaxBatchesPerPass, а интервал определяет лишь, // насколько поздно уборка узнает о том, что появилось после прошлого прохода. DefaultInterval = 5 * time.Minute // DefaultBatch — сколько строк снимает один оператор. // // На порядок ниже потолка MaxBatch: партия задаёт длину одного DELETE, а тот // держит строки, которые в этот момент читает путь запроса. Уборка не вправе // быть причиной отказа на пути запроса. DefaultBatch = 1000 // DefaultMaxBatchesPerPass — потолок партий за проход. // // Даёт скорость догона DefaultBatch × DefaultMaxBatchesPerPass за интервал — // двадцать тысяч строк за пять минут, то есть около 66 строк в секунду // УСТОЙЧИВО. Величина названа здесь затем, чтобы «догоняет ли уборка внешний // темп» был вопросом с числом, а не предположением: темп записи выше этого // означает, что догон не обеспечен, и это видно сравнением двух чисел. DefaultMaxBatchesPerPass = 20 )
Величины петли уборки по умолчанию.
Это величины РАСПИСАНИЯ, а не пороги: порог отвечает на вопрос «когда строку позволено снять» и выводится из предиката читателя своей таблицы, а эти три — на вопрос «как быстро уборка догоняет накопленное, не мешая пути запроса». Поэтому они общие для всех предметов и объявлены здесь один раз: восемь расписаний об одном предмете разошлись бы молча.
const MaxBatch = 10_000
MaxBatch — потолок величины партии.
Он не про вкус: партия задаёт длину одного оператора DELETE, а тот держит строки, которые в этот момент читает путь запроса. Величина выбрана на порядок выше рабочей (1000) — ручка остаётся ручкой, но «убрать всё одним оператором» ею не выражается.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Interval — как часто идёт проход.
Interval time.Duration
// Batch — сколько строк снимает один оператор.
//
// Партии, а не «удалить всё»: первый прогон после выкатки встречает всё,
// что накопилось за жизнь стенда, и DELETE без предела на такой таблице —
// длинная транзакция, удерживающая строки, которые горячий путь читает в
// тот же момент. Уборка не вправе быть причиной отказа на пути запроса.
Batch int
// MaxBatchesPerPass — потолок числа партий за проход.
//
// Одна партия за тик даёт скорость догона `партия / интервал`, и если темп
// записи выше — уборщик не догонит НИКОГДА, оставаясь зелёным по всякой
// проверке «вызвался ли». Проход повторяет партию, пока она уходит полной,
// но не более потолка: тогда длительность прохода ограничена сверху, а
// догон меряется проходом, а не тиком.
MaxBatchesPerPass int
}
Config — величины уборки. ОБЪЯВЛЯЮТСЯ в конфигурации сервиса и проверяются при старте: величина, уезжающая в SQL мимо объявления, невидима оператору.
Порогов здесь нет намеренно — они не настраиваются, а вычисляются из объявленных величин своего пакета. Настраиваемый порог был бы ручкой, которой его молча разводят с предикатом читателя, то есть ручкой заведения дефекта.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig — величины петли по умолчанию.
Отдельная функция, а не литерал у каждого вызывающего: литерал, скопированный в композиционный корень каждого владельца, и есть та копия, которая расходится молча.
type Counts ¶
type Counts struct {
// Passes — сколько проходов исполнено. Ноль здесь отличает «убирать нечего»
// от «петля не идёт вовсе».
Passes int64
// Removed — снято строк по каждому предмету за всё время.
Removed map[string]int64
// Failures — отказов по каждому предмету за всё время.
Failures map[string]int64
}
Counts — накопленное за жизнь процесса. Читается сборщиком метрик: накопитель без читателя считает в никуда, и его ноль не утверждает ничего.
type PassResult ¶
type PassResult struct {
// Removed — снято строк по каждому предмету. Ключ есть у КАЖДОГО предмета
// реестра, даже когда снято ноль.
Removed map[string]int64
// Batches — сколько партий ушло по каждому предмету.
Batches map[string]int
// Errs — отказ по каждому предмету, у которого он был.
Errs map[string]error
}
PassResult — исход одного прохода, ПО КАЖДОМУ ПРЕДМЕТУ ОТДЕЛЬНО.
Раздельность не украшение: ноль по одному предмету означает либо «убирать нечего», либо «уборка не доходит до этой записи реестра», и общая величина не различает эти состояния.
func (PassResult) Err ¶
func (r PassResult) Err() error
Err — объединённый отказ прохода; nil, если ни один предмет не отказал.
type Subject ¶
type Subject struct {
// Name — имя предмета. Совпадает с именем таблицы: имя попадает в отчёт
// прохода и в метку метрики, и оператор обязан узнавать в нём таблицу.
Name string
// Grace — слагаемое порога. ВЫЧИСЛЯЕТСЯ из объявленных величин, а не
// выписывается длительностью: копия разошлась бы с политикой молча и в
// ОПАСНУЮ сторону.
Grace time.Duration
// Sweep — сам уборщик.
Sweep SweepFunc
}
Subject — запись реестра: предмет, порог и уборщик.
type SweepFunc ¶
type SweepFunc func(ctx context.Context, grace time.Duration, batch int) (removed int64, full bool, err error)
SweepFunc — один проход уборщика по одному предмету.
`grace` — слагаемое порога: уборщик снимает строки, чей срок истёк РАНЬШЕ чем `now() − grace` часами БАЗЫ. Момент времени параметром не приходит намеренно.
Возвращает число снятых строк и признак «партия ушла полной». Признак — не удобство: без него проход не отличает «убрал всё, что было» от «упёрся в партию», и уборка со скоростью одна партия за тик не догоняла бы внешний темп НИКОГДА, оставаясь зелёной по всякой проверке «вызвался ли».
type Sweeper ¶
type Sweeper struct {
// contains filtered or unexported fields
}
Sweeper — фоновая уборка по реестру предметов.
func New ¶
New собирает уборщика. Отказывает на негодных величинах: уборка, собранная с нулевой партией, исполняется и не убирает ничего — то есть выглядит работающей, будучи мёртвой.
func (*Sweeper) Pass ¶
func (s *Sweeper) Pass(ctx context.Context) PassResult
Pass — один проход по всему реестру.
Экспортирован НАМЕРЕННО: «сборщик работает» обязано быть проверяемо без ожидания тикера. Довод не изобретён здесь — его говорит шапка живого уборщика дерева (`gateway/internal/idempotencypg/store.go`, `Store.Reap`), и он же снимает нужду подавать уборщику часы входом ради проверяемости.
func (*Sweeper) Start ¶
Start поднимает фоновую петлю уборки.
Первый проход идёт СРАЗУ, не через интервал: он встречает всё, что накопилось за жизнь стенда, и откладывать его на интервал значило бы держать накопленное ещё и это время.
РЕПЛИКИ: на-реплику — уборка есть условный оператор `DELETE … WHERE <срок> < now() − порог` с пределом партии и клеймом строк (`FOR UPDATE SKIP LOCKED`). Строки заперты самим оператором, поэтому вторая реплика уносит только остаток, а на пустой выборке не делает ничего; к соседям проход не ходит. Общий замок не нужен: он купил бы отсутствие дубля ценой одиночной точки, у которой свой отказ.
func (*Sweeper) Wait ¶
Wait ждёт завершения петли после отмены контекста и отвечает, дождался ли.
Нужен пробе останова и останову процесса: петля, которую никто не дожидается, оставляет незавершённый проход в неопределённом состоянии для наблюдателя — хотя частично применённой партии не бывает by construction, партия есть один оператор.