Копии и выкат

Четыреста тридцать миграций и семь номеров, занятых дважды

Номер выбирают в момент создания файла, а применяют через день. Две ветки берут один свободный номер, и коллизия всплывает не у автора, а на чужой машине при накате.

Александр Цапков10 сентября 20262 мин

У нас 430 миграций. Семь номеров из них заняты дважды: 0001, 0017, 0018, 0067, 0109, 0320, 0327.

Ни одна из этих коллизий не была замечена автором. Все всплыли на чужой машине.

Откуда они берутся

Номер выбирают в момент создания файла, а применяют — через час, день или после ревью. Между этими двумя моментами кто-то ещё создаёт файл и берёт тот же свободный номер: на его ветке он действительно свободен.

Дальше обе ветки вливаются, и в каталоге оказываются 0320_a.sql и 0320_b.sql. Порядок применения между ними становится неопределённым — он зависит от сортировки, то есть от имени файла после номера, то есть от случайности.

Пока миграции независимы, ничего не происходит. Как только одна ждёт результата другой — накат падает на чужой машине, и падает не там, где ошибка.

Почему это не ловится обычными способами

Ревью не ловит. Рецензент смотрит на SQL, а не на соседний файл в каталоге; да и второго файла в его ветке ещё нет.

Тесты не ловят. На машине автора накат проходит: у него в каталоге один файл с этим номером.

Слияние не ловит. Git видит два разных файла с разными именами — конфликта нет, сливается молча.

Единственное место, где коллизия становится видимой, — каталог после слияния. То есть уже в общей ветке.

Что помогает

Выбирать номер перед созданием файла, а не после написания SQL. Звучит мелко, но меняет окно: если номер занят сразу, ветки расходятся на минуты, а не на дни.

Смотреть в каталог, а не в свою ветку. Свободный номер — это свободный в main, а не в том, что у вас под рукой.

Держать порядок наката явным. Мы применяем миграции раннером и от нужной роли, а состояние читаем из базы — по максимальной применённой версии, а не по выводу команды. Вывод говорит, что раннер сделал; база говорит, что получилось.

Как проверить у себя

Одна команда покажет все задвоенные номера:

ls migrations/*.sql | sed 's|.*/||' | cut -d_ -f1 | sort | uniq -d

Если вывод не пуст — посмотрите на каждую пару и ответьте на один вопрос: зависит ли одна от другой? Если да, порядок у вас сейчас случайный, и он совпал с нужным по удаче.

Что из этого следует

Это общий класс: идентификатор, который выбирают по состоянию общего ресурса в момент начала работы, а фиксируют в момент её конца. Номера миграций, имена файлов, ключи в общем справочнике, номера версий.

Между выбором и фиксацией лежит время, и в этом времени состояние меняется. Единственные надёжные способы — либо сокращать это окно до минимума, либо брать идентификатор, который не зависит от чужого состояния вовсе.


Мы держим полный слепок схемы в репозитории и пересобираем его самим накатом, а не руками. Как устроена документация проекта, которая переживает такие разборы — Документы.

Документация и база знаний проектаВики проекта со ссылками между документами, графом связей и версией на каждую правку.

Читайте также

Автор

Александр ЦапковОснователь Скоупворк

Веду платформу и её боевой контур сам: разработка, выкат, дежурство. Пишу о том, на чём мы обожглись, — с датами, замерами и ссылками на решения в репозитории.