- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
80 lines
5.0 KiB
Go
80 lines
5.0 KiB
Go
package store
|
||
|
||
import "context"
|
||
|
||
// pickBest выбирает победителя среди кандидатов на одни координаты.
|
||
//
|
||
// **Победитель — функция МНОЖЕСТВА кандидатов, а не порядка их поступления.**
|
||
// Попарная свёртка этого не даёт: полнота (или покрытие) — частичный порядок,
|
||
// тай-брейк — тотальный, и вместе они образуют нетранзитивное отношение победы,
|
||
// то есть цикл. При цикле повторная свёртка одной и той же доставки меняет
|
||
// содержимое витрины, и она перестаёт быть свёрткой журнала. Проверено дважды:
|
||
// сперва на точках, где нетранзитивность нашлась перебором троек, потом на
|
||
// сущностях, где ту же ошибку повторили молча.
|
||
//
|
||
// Отсюда и общий помощник вместо второй рукописной копии: механизм один,
|
||
// отношения разные. Правка одного экземпляра при живом втором уже случилась:
|
||
// тай-брейк точек сменён с байтового порядка на «побеждает пришедшая», а
|
||
// тай-брейк сущностей остался позицией журнала — потому что у сущности есть
|
||
// колонка провенанса, а у точки нет. Механизм при этом не разъехался ровно
|
||
// благодаря общему помощнику; критерий выбора между двумя правилами записан в
|
||
// `architecture.md`, раздел «Разрешение столкновений».
|
||
//
|
||
// dominates(a, b) обязан быть СТРОГИМ превосходством: a не хуже b и b не не
|
||
// хуже a. Иначе взаимно покрывающие друг друга кандидаты выбьют друг друга, и
|
||
// множество непревзойдённых окажется пустым.
|
||
//
|
||
// less обязан быть ТОТАЛЬНЫМ строгим порядком: при неединственном минимуме
|
||
// победителем оказывается просто первый в срезе, то есть порядок элементов на
|
||
// проводе, а он у HAE нестабилен.
|
||
//
|
||
// Отбор непревзойдённых КВАДРАТИЧЕН по числу кандидатов, и это названо вслух,
|
||
// потому что число кандидатов приходит из чужого тела. Отсюда `ctx`: цикл, чья
|
||
// стоимость определяется размером входа, обязан видеть отмену. Без него тело с
|
||
// двадцатью тысячами версий одного ключа занимало бы единственного воркера
|
||
// свёртки дольше, чем длится его же дедлайн, — то есть дедлайн, заведённый
|
||
// ровно против такого случая, не значил бы ничего. Замерено: n=4000 — 3.9 с,
|
||
// n=8000 — вчетверо больше.
|
||
//
|
||
// Вызывающий обязан сокращать множество до входа сюда: совпавших кандидатов
|
||
// схлопывать, а число различных — ограничивать. Помощник этого не делает
|
||
// сам — что считать «тем же» кандидатом, знает только он.
|
||
//
|
||
// Возвращает индекс победителя и индексы непревзойдённых — вторые нужны тем,
|
||
// кто считает несравнимость среди них. Пустой срез кандидатов — нарушенный
|
||
// инвариант вызывающего: оба сегодняшних вызова собирают множество из карты и
|
||
// пустого дать не могут.
|
||
func pickBest[T any](ctx context.Context, cands []T, dominates func(a, b T) bool, less func(a, b T) bool) (winner int, maximal []int, err error) {
|
||
if len(cands) == 0 {
|
||
return 0, nil, errNoCandidates
|
||
}
|
||
|
||
maximal = make([]int, 0, len(cands))
|
||
for i := range cands {
|
||
if err := ctx.Err(); err != nil {
|
||
return 0, nil, err
|
||
}
|
||
beaten := false
|
||
for j := range cands {
|
||
if i == j {
|
||
continue
|
||
}
|
||
if dominates(cands[j], cands[i]) {
|
||
beaten = true
|
||
break
|
||
}
|
||
}
|
||
if !beaten {
|
||
maximal = append(maximal, i)
|
||
}
|
||
}
|
||
|
||
winner = maximal[0]
|
||
for _, i := range maximal[1:] {
|
||
if less(cands[i], cands[winner]) {
|
||
winner = i
|
||
}
|
||
}
|
||
return winner, maximal, nil
|
||
}
|