Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user