Pull to refresh

Comments 174

Последняя картинка позабавила)

Я когда пару раз прод ломал, безумно нервничал и корил себя.

Я однажды уронил прод, поднял через 15 минут и ... уволился) Не выдержал упреков со стороны бывших коллег

упреков? прод обычно роняет не один человек (ну разве что это тимлид в одно лицо чтото накатить решил), это обычно фейл в процессах.

Я за свою карьеру несколько раз прод ронял, последний раз это было года два назад, оч крупный сервис, как раз наверное размером со skyeng, лежали гдето полчаса

Главный факап был в том что решили упростить сложный процесс деплоя выкинув препрод тесты... и после мерджа внезапно соседние мерджи стали несовместимы с моим...из-за чего после срабатывания алертов в прод вылилось еще пара других релизов...которые нельзя откатывать...

формально виноват типа был я, потому что именно на моём MR-е упал прод

но вы понимаете что это не тоже самое? кто делал ревью? кто запускал тесты? где интеграционные тесты?

Может он его физически уронил

у меня было похожее ;)) я зашел в серверную, услышал за стойками треск (сгорела розетка, физически)..и погасла коммуникационная стойка, потом на меня начальник пристально смотрел - не я ли чё дернул или задел...;) я тогда второй месяц чтоли работал... и видеонаблюдение у нас тогда барахлило и записей с камер не было ;)) было стрёмно ;))

p.s. Роман, я знаю что ты в комментах бываешь на хабре, это честно был не я ;) оно реально само, больше 10 лет прошло, я до сих пор это помню

- Коллеги, мы развернули 3 сервера!

- А они что, раньше не той стороной стояли?

У нас так в начале нулевых городской чат уронили. Сервак был на компе у админа в офисе, начали двигать мебель и уронили 😀

Прод может единолично уронить, например, сетевик, или безопасник, или дежурный админ, да любой админ сервера, может и DBA... да много кто может - разлей по узлам SAN кривой сертификат - и voilot!

первый раз в жизни уложив прод (всего на пару минут, зато целиком и полностью все сервисы) я ожидал сурового нагоняя от начальства и криков "позор" от коллег. но начальник улыбаясь сказал что-то вроде "с почином, больше так не делай" а коллеги стали вспоминать кто и когда ложил прод и как это было. я расслабился, порадовался что работаю в хорошем коллективе и... начал корёжить себя изнутри. совесть заедала меня ещё пару дней.

Я в таких случаях заедаю совесть.

Из ещё возможных вариантов - запивать и засыпать - я предпочитаю теперь последнее.

Ложил прод. Совесть не мучает :). Автору, а зачем докидывать новую запись, если можно апдейт сделпть. Судя из описания вашего это флоу который у вас был: удалил, добавил с новым значением. Это тоже самое что обновил. Или вашей обезличенной статистике нужгэна история. Так сожно логер мусорку использовать и проблема решена.

Потому что soft delete только вводят, а фунцикляр нужен был еще до этого. И он был.

Настоящий DBA prod не ложит а кладет!

Потому-что на старую запись могуть быть ссылки из других таблиц. Чтобы ваше предложение работало, надо ещё позаботиться о том, чтобы, во-первых, был каскадный soft delete, во-вторых, чистились все ссылки на удалённую запись. А ещё могут где-то во внешних системах остаться ссылки на удалённую запись... Т.е. проблем с таким подходом потом можно столько огрести...

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

и получить еще + 300 гб и несколько часов создания нидекса.

Не лучше ли было после создания partial индекса, но до удаления основного индекса:

  • выкатить релиз с руками добавленными ...and deleted_at is null

  • потом удалить основной индекс

  • а потом выкатить релиз с включением soft delete и удалением ручного условия?

Кажется, по времени и по месту это будет быстрее. Конечно, релиза два, но это может не быть такой уж проблемой.

В этом случае (в статье это, кстати, описано), будет период, когда старый уникальный индекс не позволяет заново создать ранее удалённые сущности. Это можно починить восстановлением удалённых записей (как предлагают в соседнем комментарии), но не всегда применимо (например, нужно сохранить историю).

Впрочем, если подумать, такая «багуля» лучше, чем полтора часа даунтайма)

Не будет. Soft delete выключен, одновременно существует два индекса. Промежуточный релиз заставляет использовать новый. Потом старый индекс удаляется и только потом включается soft delete.

Непонятно, чем ручной фильтр отличается от фильтра из DoctrineExtensions.
В период, когда старый индекс жив и ограничивает уникальность без учёта deleted_at, будет невозможно создать новую запись с тем же ключом, что и у уже существующей удалённой.

Или я не до конца понял идею.

Он ничем не отличается, просто с этим условием будет использоваться новый индекс, а не старый. При этом записи будут удаляться как и раньше, через delete, а не update, не нарушая уникальность первого индекса при создании в последствии записи с теми же значениями.

Всё, понял, спасибо. Да, рабочий вариант. Но много лишней ручной работы.

Ну тут основной профит в отсутствии создания еще одного (неуникального) индекса, что дорого и по времени и по месту. И который все равно придется удалять.

При этом можно код подготовить заранее и сделать два pr, например из разных веток и применить их по очереди, при этом удаление индекса можно вообще в миграцию положить (оно быстрое).

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

Так он предлагает собственно мягкое удаление в первой итерации не катить.

На эту тему как раз

Старый уникальный индекс не даст этого сделать, пока не будет удален. При RPS близком к 1000 даже за минуту вы поймаете несколько ошибок.

RPS тут ни при чем, а при чем только частота удаления/повторного создания записей.

В идеале, логику на удаление которое не удаление, нужно было добавить в последнюю очередь. Тогда бы все заработало.

+300Гб индекса временно. И на фоне размера базы >3Тб это выглядит уже не так уж и страшно. Пара часов выполнения фоновой задачи тоже не то чтобы выглядело прямо очень критично. Подозреваю что дописать везде в коде условие руками и выкатить новую версию было бы дольше, причем уже активной работы. И с большим шансом где-то что-то пропустить в процессе.

+300Гб индекса временно. И на фоне размера базы >3Тб

Если +1 индекс это +10% базы и два часа, то +2 индекса - это +20% базы и 4 часа. Не знаю, как у вас, но у нас накатывание/сборка собственно кода занимает минуты.

Накатить быстро. Править исходники руками потенциально долго и больше шансов что-то упустить.

Насчёт +20% - ну я сомневаюсь что там места впритык прям, чисто из здравого смысла стоит всегда запас хотя бы в 50% от размера базы иметь.

Про "править руками" - ниже я написал про скоупы ларавеля и, скорее всего, в джанге это все аналогично. Иначе расширением бы софтделит не сделать. Поэтому править надо только в одном месте.

В оракле можно так: alter index indexname unusable; Но ведь оракл стоит слишком дорого :)

Подешевле есть Firebird. Там тоже можно индексы отключать. Да и в Postgres, если постараться, можно какой-нибудь indisvalid выставить, или точечно констрейнты снести.

так это точно также не даст запросам использовать этот индекс, и в итоге прод упадет

Главное сказать, что "вы нищеброды" )

вероятно, можно будет вернуть индекс назад меньше, чем за пять часов

неа, если у тебя unusable index, его включение обратно будет rebuild. Что займет времени как создание с нуля

а какой тогда паттерн использования этой оракловой фичи? она ведь существует для решения какой-то задачи?

Чтобы эксперты по Ораклу знали что они не зря потратили жизнь на изучение Оракла =)

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

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

Из платности/цены какого-то продукта не следует автоматически, что он идеален/лучше более дешёвых вариантов.

Небось айфоном пользуетесь?

  1. Создать индекс

  2. Не проверить, что он используется.

Классика, или почему всегда руками дёргаю explain analyze для всех запросов ещё на этапе разработки.

а где вы берете ВСЕ запросы ?

Для Laravel есть пакет lanin/laravel-api-debugger, который добавляет к ответу API список SQL-запросов и обращений к кэшу. Помогает выявить долгие запросы (которые затем будут исследованы EXPLAIN ANALYZE), циклы, проблему N+1 и потенциальных кандидатов на кэширование.

Для постгреса есть расширение, которое если немного допилить, то можно собирать даже автоматически запросы и даже explain по ним всем сделать и записать в готовую табличку. Даже эффективнее порой, чем методом пристального взгляда собирать статистику по данным и запросам. Я просто запускал в docker-compose окружение, прогонял тестовые сценарии, потом смотрел табличку с сортировкой по проблемным запросам (время выполнения и наличие seq scan).

Если оно ещё и чанки всякие(`where id in(?, ?, ...,?)`) группировать умеет, цены ему нет. Что за расширение? И что значит немного допилить? Это про конфигурацию или код писать надо?

ChatGPT говорит, что это pg_stat_statements =)

Именно так. Только надо допилить ручками некоторые вещи, чтобы работало как надо. В интернете полно мануалов. Я хотел статью написать, но руки не доходят. Там очень много нюансов надо объяснять.

Это про конфигурацию или код писать надо?

Да. Там нужно для больших запросов кое-что подправить в конфиге и для автоматического анализа пару-тройку функций написать.

Так напишите

Ниже комментаторы уже накидали хороших вариантов автоматизации, а я обычно чаще всего писал запросы руками и потому прогрепать исходники на предмет нужной таблички было достаточно просто.
Тем более, что большая таблица в коде часто довольно хорошо локализована, в отличие от небольших справочников и запросы известны в лицо.
Когда использовал SQLAlchemy, то включал дебаг и вытаскивал запросы из консоли. В этом случае просто прогонял только небольшую ветку тестов, касающихся этой таблицы, чтоб не утонуть.
С ORM вообще смотреть в дебаг-консоль полезно, чтобы сразу увидеть, что генерируется плохой запрос.

деструктивная операция удаления индекса и отсутствие внятного плана отката конечно никого не насторожили

и на препроде никто конечно не проверял

прод упал? ДИХСН

я бы вас депремировал и провел инструктаж всему отделу разработки

или в наказание заставил бы вас проводить инструктаж всему отделу

Последнее однозначно нужно, разбор инцидента и выводы. И как я вижу, они пошли дальше - инструктаж провели на весь хабр ))

Автор верные выводы не сделал

Ну да, куда же бюрократии. Ребята уже "хапнули" проблем, уже поняли в чем была их ошибка, сделали выводы и поняли как в следующий раз этого избежать. Нужно просто в рабочую вики записать информацию "для будущих поколений", и всё. Но нет, давайте обязательно сделаем совещание, да не одно, на котором это все будем несколько часов обсуждать и клевать всем мозги вопросами: "А вы уверены, что в следующий раз подобной проблемы не произойдет. А вы можете дать гарантии?"

да не поняли он в чем ошибка

это как на Чернобыле или Фукусиме - в чем ошибка

в многочисленных нарушениях техники безопасности

вместе с разработчиком из соседней команды — она тоже недавно прокололась на таком кейсе.

Непонятно только, почему эти выводы не были сделаны ещё в первый раз ) Надеюсь, об инциденте остальные команды узнают не через эту статью )

У вас ник сам за себя говорит.

А кто работать будет после этого ? )

Ну и из классики - "теперь ты стоишь миллион долларов".

Тот кто уронил прод, хорошо научился "как делать не нужно". Это вам не бумажные "не делай так" читать и дискутировать.

на предпроде все хорошо отработало - мало кто при обновлениях еще и нагрузочное тестирование проводит...

я бы вас депремировал и провел инструктаж всему отделу разработки

Так есть шанс депремировать каждый квартал новую команду.

Зато там премию получают те, кто ничего не делают)

Hidden text

Не ошибается тот, кто ничего не делает. Ничего не делаем = прод не падал = премии не лишили. Сидим дальше.

Смех-смехом, а часто так и бывает. Кто больше всех делает, тот и косячит больше, это неминуемо. Каким бы продвинутым спецом человек не был, при интенсивной работе все равно нет-нет да накосячит. От усталости, невнимательности, да и просто оттого что все знать невозможно, и знания часто появляются в результате ликвидации последствий своих-же ошибок.

Вот и получается в некоторых командах, что один пашет и постоянно огребает за косяки, а другой разгребает лайтовые некритичные баги и с начальством в курилке общается. Первый кругом неправ, а второй премии получает (реально из жизни пример, я был в качестве первого и мне не понравилось, уволился после выдачи халявщику очередной премии за успехи в работе, с которой меня прокинули).

Можно получить премию и только после нее релизиться. К следующей премии начальство остынет и можно будет опять что-то уронить без урона для кошелька.

А потом в таких коллективах вводят оценку производительности за количество строк написанного кода. Если бы меня хоть раз депремировали за ошибку, я гарантированно уволился

А вариант: создать вместо старого новый уникальный индекс в user_id, resurce_id, deleted_at ?
а то потом среди удаленных надо будет чего-нибудь найти

И сколько таких записей будет? 100? 1000? Дешевле достать эту 1000 записей и отфильтровать, чем делать доп колонку в индексе на поллярда записей. Скорей всего, при таком количестве строк, индекс может даже скипнуться и сработать фул скан.

для аналитики такой паттерн - фактически антипаттерн. Всю базу переколбасить и индексы и запросы еще переделать. Для аналитики достаточно удаления оставить как и были просто логгировать в отдельную таблицу (ну или много таблиц) вида - [имя_таблицы] , [pk] , [дата удаления]. Ну и на стороне аналитического солюшена доработать ETL для обработли этого лога делитов. В итоге прод. база не меняется, все работает, все довольны.

p.s. А еще лучше иметь CDC солюшен который сам аптоматически трекает все измениния включая делиты, и из него сорсится аналитическая база

Кажется, что ребята экономят и на дата инженерах, и на аналитической инфре. Строить агрегаты в таком постгресе - представляю удовольствие. А потом ещё появляются шарды...

Ещё лучше, не трекать абсолютно все изменения в принципе, а завести условный clickhouse, в который отправлять необходимые аналитикам данные в фоне.

Аналитика в принципе не должна работать с боевыми данными никогда.

deleted_at = NULL

Это выражение всегда ложно.

Уверен все об это спотыкались )

....роняли прод, но потом быстро чинили

Поправил опечатку, спасибо.

это выражение дает не ложь, а null

Речь тут идет о значении логического выражения в WHERE. А оно ложно, в том числе и тогда, когда его значение NULL.

Нет, конечно. Я же написал, что значение логического выражение в WHERE "ложно, в том числе и тогда, когда его значение NULL"

Мне кажется вы уже давно пережили день, в который нужно было обзаводится микросервисами и развивать событийную архитектуру, и конечно же шардировать таблицы. Это позволило бы выбрать некоторый объем данных дабы не было ожидания в 5 часов.

Чем богаты тем и рады.

Вот поэтому когда я вижу в резюме C#, дотнет, или там джаваскрипт, ксс и подобные вещи при этом в каждом стоит "знаю SQL" всегда бессознательно появляется улыбка.

Самое главное, о чём умолчала история - а где КуА-окружение? там где банально в пару кликов тестов (благодаря аналитике "частых кнопок") это всплыло просто в первый десяток минут прогона.

я не автор, но прям с негодованием просится моя собственная боль

"Мы пишем код достаточно стабильный чтобы его еще и тестировать"

"тесты замедляют выход в фичей в прод"

;)) вангую что у автора чтото из этого

Мы не можем проводить нагрузочное тестирование , у нас сроки горят .

Если не тестировать то потом может подгореть что-то помимо срОков.

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

от сроков подгорит быстрее чем от падений и нестабильной работы...это реалии

У меня один вопрос - если вы написали какой-то код, вы в тот же день его кидаете в прод?

Вспоминая все места где приходилось работать - стабильно раз в день ночью весь мастер просто гоняется функциями (в параллели несколько потоков есс-но), которые основные. И приходящий человек с утра когда видит, что обычные 2 часа может гарантировать всё ок. А если бы увидел 2.5-3 часа, уже встали бы вопросы.

Я говорил не про тесты новых фич - может они будут бесполезны (простите все кому наступил на больное) и их уберут, что вообще не надо тестировать. Но если ты вносишь изменения в основное, то основными тестами (которые опять же будут если написанное выше "обмазаны аналитикой" нужно именно "для понимания что делает пользователь" а не для красивых картинок руководству) его прогнать за пару дней. Тем более бывает что сделал что-то на коленке, просто чтоб текущий зазор закрыть а потом надо с мыслью "переспать" чтоб сделать по-другому и это лучше делать явно не тогда когда оно ушло в прод и начало обрастать костылями от коллеги.

Каждый день пишу код и кидаю его в прод. И товарищи по работе тоже так делают. Несколько раз в день.

Так проблема возникала не из-за того, что код не работает. А из-за того, что не используются индексы и он работает медленнее чем должно. И на одном пользователе это может быть незаметно, но вылезает потом в проде. А нагрузочное тестирование на каждый апдейт обычно не проводят.

Удаление индекса - это не "каждый апдейт", ИМХО. Как раз тот случай когда надо проводить нагрузочное.

Не так-то просто провести нагрузочное тестирование переходного состояния, когда схема у БД по сути от одной версии ПО, а само ПО другой версии.

Особенно когда переходов много (и все автоматически не потестируешь), а разработчик не видит конкретно этот переход опасным.

Понятно что непросто. Но потом чинить прод с горящей жопой и стоящим за спиной крайне недовольным тобой руководством, это куда как сложнее и неприятнее.

Понимаю, конечно, почему такое случается, сам так иногда так встревал. Кажется что исправление ерундовое, никакие проблемы не должно вызвать, а тестить все очень тщательно лень, да и хочется фикс побыстрее накатить.. А потом вдруг выясняется что этот ерундовый фикс ломает что-то очень важное..

Даже этот, в общем-то, простейший переход состоит из 4х шагов. Которых на самом деле больше (то же развёртывание нового кода включает в себя кучу промежуточных шагов).

Сколько ручных нагрузочных тестирований вы собираетесь проводить при каждом релизе?

Еще раз - удаление индекса на нагруженной базе это не "каждый релиз", странно что приходится это повторять. Вы реально не видите разницы между фиксом какой-либо формочки и удалением индекса на таблице? Это изменение которое напрямую влияет на производительность, и делать такое наживую, не проверив на тестовой базе, это прямой путь к таким вот инцидентам, о чем собственно автор и написал.

И выводы были сделаны какие-то странные: "плохо что это случилось, хорошо что мы все починили".. КМК вывод тут главный должен быть такой - Теперь мы все подобные фиксы сначала проверяем на специальной тестовой БД, имитирующей нагрузку прода, и только потом выкатываемся на прод.

"Если бы я был такой умный тогда, как моя жена потом!"

Я не говорил про нерабочий код и не говорил про медленнее, мной было написана мысль "работает не так как ожидается" (для меня это равносильно не работает, код вроде бы есть и он работает, но не так как задумывалось) и вот это проверяется именно что авто-тестированием в КуА (или как его кто называет).

На одном пользователе, то есть КуА контур гоняет авто-тесты в один поток? Ну ладно, ваше дело. Я выше написал комментарий другому человеку, посмотрите, там есть ещё пара смежных мыслей.

Ну тогда в резюме не стоит ничего вообще писать, раз вы не знакомы с эдж кейсами каждого фреиворка, надеюсь у вас имено такое, «правильное» cv 😁

Использование фильтрованного индекса это эйдж-кейс. Ну ок. Хотел написать что-то наподобие "пойду добавлю знание C#, потому что неплохо знаю повершелл", но решил просто добавить вопрос к списку на собес в дополнение к супер-неизвестным штукам (любой индекс отличный от b-tree). Сомневаюсь в какую категорию, или в уровень "супер-сеньор" или в уровень "бог".

Я бы решал эту задачу проще.

  1. Для каждой таблицы добавил бы её аналог с дополнительным полем времени удаления в первичном ключе: deleted timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP.

  2. На каждой таблице создал бы AFTER DELETED триггер FOR EACH STATEMENT с функцией, вставляющей все удаленные записи в таблицу из п.1

И для клиентов абсолютно прозрачно, и индексы вообще трогать не надо.

Это хорошо но поддерживать сложно потом

Можете более развернуто объяснить суть этих сложностей?

Как по мне, поддерживать обязательное наличие в выражениях WHERE и ON на клиентах условие частичного индекса и не забывать там же выполнять UPDATE вместо DELETE - куда сложней для поддержки.

Я работал с биллингом провайдера, где логика была размазана между кодом приложения и хранимкам/триггерами в базе, лично мне (возможно от недостатка квалификации) это было сложно поддерживать и модифицировать. Да, никакого стейджинга у нас конечно не было, по финансовым соображениям.

Меня смущает размазывание логики по разным местам, ничего более

Вообще исключить логику из реляционной БД невозможно и даже вредно. Первичные и уникальные ключи, внешние ключи, различные ограничения (CONSTRAINT), секционирование, репликация и т.п. - это тоже логика. Обычно все же избегают реализации в БД только бизнес-логики, да и то не всегда это удается без существенного ущерба производительности.

В предложенном моем сценарии бизнес-логика не затрагивается, а логика в БД прозрачна для приложения.

Во-первых я с вами совершенно согласен в вопросе того, что логику вынести из базы нельзя

Во-вторых я согласен с утверждением что часто более производительные решения - это использовании возможностей базы

Но я не cогласен что триггер это не бизнес-логика, с точки зрения приложения данные перемещаются какой-то магией.

Вот представьте что пришел новый разработчик и условно решил "погрепать" код что бы найти место, где заполняется табличка с удаленными записями? И что он найдет? Догадается в базе посмотреть или нет? Где код этих функций/триггеров храните, вместе с кодом приложения или отдельно в другом репозитории? Если вместе/отдельно по почему так? А если отдеьная группа разработчиков их пишет, и эта группа хочет использовать свой флоу какой-то, да и сидят они в другом городе/стране (тут я уже конечно перегибаю, но бывает всякое)

А потом в базе случилась неконсистентность, допустим из-за бага, и нужно руками выполнить запрос(ы) - не забудете триггер отключить?

В общем, кроме очевидных приимуществ использование триггеров может нести еще и некоторые неудобства, о которых я собственно и говорю.

И что он найдет?

Миграцию с соответствующим триггером же.

Но я не cогласен что триггер это не бизнес-логика, с точки зрения приложения данные перемещаются какой-то магией.

Вы, конечно, вправе иметь свое мнение и давать свою семантику термину "бизнес-логика". Но тогда "какая-то магия" - это так же ошибки дублировании ключей, внешних ключей, заполнение значениями по умолчанию, вычисляемые поля, секционирование и многое другое.

Вот представьте что пришел новый разработчик и условно решил "погрепать" код что бы найти место, где заполняется табличка с удаленными записями?

Если разработчик не в состоянии запустить поиск по регулярному выражению в локальной копии репозитория проекта, то, боюсь - это не разработчик.

Где код этих функций/триггеров храните, вместе с кодом приложения или отдельно в другом репозитории? Если вместе/отдельно по почему так?

Обычно, проект в одном репозитории, так как метаданные, переносимые данные, миграции и т.п. непосредственно связаны с кодом приложения и являются его неотъемлемой частью. Исключения рассмотрены ниже и требуют контрактов.

А если отдеьная группа разработчиков их пишет

Если при этом почему-то всё равно требуется прямой доступ к БД, то они будут работать с витринами данных и подробности внутренней организации последних для разработчиков будут скрыты. Будет контракт подразумевать, что конкретное приложение видит только не удаленные записи - вторая таблица им будет недоступна. Будет контракт подразумевать видимость в одной таблице и тех и других записей - это будет предоставлено через секционирование или представление.

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

А в чем тут сложность? Один раз создал таблицу и триггер, и больше поддерживать там нечего, просто записи сыпятся в эту "корзину". Причем создать все это можно в миграции, т.е. в репозитории кода эта операция будет присутствовать и будет понятно откуда что появилось.

ЗЫ ну разве что, вижу доп. сложность в том, что при изменении исходной таблицы надо будет делать такие-же изменения в "корзине" что-бы данные соответствовали. Тут да, некоторая сложность появляется.

Еще плюс, при вашем варианте основные таблицы не будут распухать, если записи часто удаляют, будут пухнуть таблицы с удаленными записями, но они для аналитики и наверняка не так активно юзаются.

Хорошо, когда точно известна причина падения .

В жизни как правило, чуть другой сценарий - рабочий день начался , пришли пользователи - прод упал.

И начинается самое интересное - "Кто виноват? Что делать?"

хорошо еще, что день начался, а не закончился

По житейскому опыту - когда день кончился и все упало - легче и проще.

Клиентов почти нет , нагрузки нет , топов нет , манагеров почти нет . Проблема как правило в железе или виртуализации.

А вот когда днём - совсем другие шаманские танцы начинаются . Основной мотив - мы упёрлись в СУБД, дайте нам серебряные пули и волшебный эликсир . И конечно стандартная игра в футбол , переходящая в волейбол - "у нас всё хорошо , проблема не в приложении".

Местами даже весело ...

а еще бывает, это у тебя в европе день уже закончился, а у клиентов в штатах только начался, и они давай тебе ошибки сыпать до полуночи

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

никакого контроля от старших коллег по цеху не было

Вот тут автор в статье очень подробно расписал какую задачу он решал, какой план работ придумал. Читая этот план работ сразу на уме возникала только одна мысль: "ну вроде логично, проблем вроде не предвидится". Так что можно сказать "весь хабр проконтролировал". Но ошибся. Человеческой фактор. И мы с вами не знаем - был ли "контроль тимлида" или нет - факт остаётся фактом - прод падал.

Спихивать ответственность на тимлидов - плохая практика. Разработчики так разленятся думать и будут писать говнокод, который должен будет настырно контролировать лид. "Пропустил мой плохой код - сам виноват!".

Другими словами - неважно был ли там контроль, или нет - от инцидента бы это маловероятно что спасло.

Подтверждение тому в словах автора что кто то из коллег уже ронял прод с похожим кеййсом

Нет, не было у них похожих кейсов. Там же написано, что все роняли прод по такой то причине (другой).

ну вроде логично, проблем вроде не предвидится"

"на бумаге" всё логично вот как раз и должны быть люди которые под сомнения поставят эту логичность обычно у которых есть опыт такого, например лиды

Спихивать ответственность на тимлидов - плохая практика. Разработчики так разленятся думать и будут писать говнокод, который должен будет настырно контролировать лид.

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

Нет, не было у них похожих кейсов. Там же написано, что все роняли прод по такой то причине (другой).

цитата автора из текста: "Но есть более правильный вариант, к которому мы пришли вместе с разработчиком из соседней команды — она тоже недавно прокололась на таком кейсе."

А как же тестирование на stage с меньшим размером БД? Это же вроде как база должна быть для таких изменений, которые быстро не откатить, даже если кажется что всё ОК будет.

ну ты еще спроси "а где был ваш dba?"

Ну вот нельзя так просто взять серебряную пулю из коробки, и убить ей зомби всех мастей. К каждой гадине нужно подбирать свою :).

У данных есть степень динамичности (текучка кадров), как и у плохих разработчиков, или плохих руководителей. Если ваш бизнес a priori отправляет в утиль 90% генерированных данных (не в обиду, а в силу особенностей бизнеса - чел закрыл онлайн-сессию обучения - ее, и весь ее контекст - в треш) - то это значит, что в рабочих таблицах 90% строк будут забиты ботвой, и лишь 10% - текуще-полезны.

А теперь вброс - R/W хранилище с поддержкой ACID - самый дорогой (во всех смыслах) и вычислительноемкий способ хранения данных (в том числе ботвы). Использование его механизмов (втч ссылочной целостности) на 90% для хранения отработки - сильное архитектурное расточительство.

Аналитике нужны R/O данные - поэтому правы те, кто советуют сносить удаленное в специальные таблицы и/или R/O базы для анализа. Кесарю-кесарево.

Далее, если вам комбинированная работа с данными "как-то не очень" - неважно, шардирование, часть логики в SP итд - то за это рано или поздно придется кому-то сильно заплатить - либо железом, либо переделкой архитектуры, либо троттлом бизнеса. Все конкуретноспособные системы уходят от средних или паттерновых подходов в пользу эджевых - ибо дьявол в деталях, и соревнуются уже далеко не на пределе возможностей коробочных решений, а либо на уровне своих кастомных IP, либо тех же коробок, обложенных по кругу своими же воркэраундами ;)

PS - а что вы будете делать со ссылочной целостностью, которая теперь не гарантируется на уровне RDBMS? Как трактовать ссылки FK на PK, который помечен, как удаленный?

План бесшовного релиза

Когда новый код с soft delete на проде, старый уникальный индекс нужно удалить. Иначе он не даст добавить новые записи, аналогичные удаленным с помощью soft delete. C другой стороны, нельзя удалять старый уникальный индекс, пока работает старый код. 

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

...

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

Вы не пробовали оценить вероятность появления в таблице по-настоящему дублирующих записей (с deleted_at is null)? А то ведь буде такая появится, то ваш замечательный частичный индекс просто не создастся.

PS Есть еще пара вопросов, чисто технических, но они не в тему IMHO. Потому что тема - она как уронить бой и выжить после этого.

Вообще ситуация с этим удручает, повсюду девопсов выгоняют с обслуживания проектов, скидывают в нагрузку эти задачи разработчикам + менеджеры часто не дают время на нормальное написание тестов, особенно интеграционных, и пайплайнов адекватных (а бывает еще что и инфраструктура швах, документации нет, дофига самописных инструментов, сиди настраивай), чтобы все это проверить автоматически перед выпусками часто или нет совсем, или минимум. А потом крупные системы ложатся вот так... И так в +- среднем по больнице даже в самых крупных IT компаниях на рынке рф, особенно на новых проектах, где фичи надо было выпустить еще позавчера, хотя и на моей памяти мертвых более чем на 5 минут продов практически не бывало, но вот выкатка в прод. не рабочего функционала или когда что-то отваливается в процессе, сколько угодно

а потом люди удивляются почему это у "девопса" в каморке стоит пара стареньких серваков на которых актуальны бекап системы в полном конфиге крутится.... +)))

А потом крупные системы ложатся вот так...

Возможно потому, что на самом деле "упущенная прибыль" от таких падений не так велика? Ну вот сколько на самом деле таких людей, которые именно в эти 1.5 часа хотели расстаться со своими деньгами, и в итоге отказались от этой идеи? Может быть большинство просто подумало, "ну, это интернет, тут бывает сайты лежат", и зашли на следующий день и всё оплатили.

А где прогон всех этапов на дев/стейдж стенде с проверкой включения индексов после удаления основного?

И ещё, после добавления поля и новых индексов уже сразу можно код править на предмет корректировки WHERE чтобы partial index заработали до удаления основного?

как я понял, то эта фича в WHERE добавляется тем самым расширением, так что для такого сценария нужно было все запросы вручную править, а после включения расширения - убирать (как уже было предложено ранее в комментах)

Ну не то, чтобы нужно править "все запросы". Наверняка в джанге (или что там используется) есть аналог ларавеловского скоупа, который во все, что сделано квери билдером добавит нужное.

Софт делит, скорее всего, ровно так и реализован (как и в ларавеле).

Добавлю свои 5 копеек:

1) Уронил прод на Оракле запросом к недокументированному представлению

2) ошибся с pid для kill -9 и прибил системный процесс PostgreSQL

Сорри, но это всё.

потребовалось внедрить soft delete для корректной работы отчетов аналитики

Как говорится, есть 2 типа разработчиков: те, кто думает, что soft delete - это крутое решение, и другие.

не ну я конечно тот еще ретроград, но ИМХО - а для аналитики нужна Нагрузка на продуктивную базу? и у вас настолько критична Задержка Аналитики в пару суток?

почему нельзя - просто поставить старенький сервак с 6 SSD SOHO класса и выгружать на него БД раз в 2-3 дня для аналитики через бекапы? За одно еще и точку бекапов получите дополнительную и регулярный тест консистентности бекапов и Песочницу для условно нагрузочного тестирования....

А может там как раз для какой-нибудь инкрементальной выгружалки с реплики эти таймстемпы нужны.

хм, тогда опять же вопросы к Архитектуре БД и отсутствию конкретной роли DBA..... да и нагрузки получаются кошмарные - если в целом эта "выгружалка с реплики" оперирует существенными объемами... в целом всё равно получается пачка вопросов с Общим смыслом - "А нафига так-то?!"

Ничего не понимаю. В соседней ветке ты такую же выгружалку называешь «идеальной», а тут «нафига так-то?». В чём разница?

Варианты конечно возможны всякие - но там система описана другая и там аналитика сбоку от Frontend - который шлет туда весь поток событий именно нужных для бизнес-аналитики не касаясь БД Прода - вот такая система "идеальная" - она собирает Событийно полно и не грузит продуктивную БД и в любой момент может отвалится, сломаться и/или сдохнуть не повлияв на работу Прода.

а периодическая "выгружалка" дергающая данные из БД прода ради такой "логики" Ущербна концептуально - потому что по сути это тот же бекап, но куском....

Событийка с фронта и аналитика по данным в БД — это две разные системы, разные источники данных для аналитики. И зачастую, нужны обе. Не все бизнес-процессы инициирует конечный пользователь.

с отдельной БД, в которую из основной данные периодически подгружаются.

А это как раз про выгружалку, которая «по сути тот же бэкап»

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

именно - и поэтому неправильно сваливать всю аналитику в Прод - аналитика по данным БД делатеnся отдельно от прода по RO реплике(бессмысленно, но или если так уж Хочется потратить лишние деньги - то по Реплике для Бекапа) или по Бекапу - в реальности и в 99.9999% случаев - Аналитика легко может отставать на неделю от реального времени без потери эффективности. да и точность для аналитики в масштабе плюс минус 5% за глаза - всё равно "накопленная ошибка" с момента съема данных до формирования и тем более до начала влияния отчета "съест" точность....

А это как раз про выгружалку, которая «по сути тот же бэкап»

конкретно в предлагаемой реализации нет работы с БД прода - просто поток событий Фронта дублируется на систему аналитики.

И зачастую, нужны обе. Не все бизнес-процессы инициирует конечный пользователь.

туту опять же вопрос архитектуры - если возникает ситуация которую нужно крутить БИЗНЕС аналитикам не отражающуюся в фронте - это странно - нужно ли аналитикам её видеть? и как клиенты(тут в широком смысле - пользователи системы не только внешние но и внутренние) узнают о таком процессе?

или тут Бизнес аналитика увязана с техничкой? опять же а нафига? Техничка ни в коем случае не должна пересекаться с бизнесом и выходить за рамки контура безопасности прода - черепова-то, однако....

Можно и чуть сложнее, зато более актуальная аналитика будет. У нас для аналитики крутится как отдельный сервис, с отдельной БД, в которую из основной данные периодически подгружаются. Т.о. любой факап на БД аналитики никаким образом не заденет прод. И если аналитика слишком уж начнет активно все анализировать, то на скорости работы прода это тоже никак не отразится.

ну судя по отсутствию нагрузочного тестирования и анализа работы БД после Глобальных реорганизаций мой вариант Ребятам будет Крайне не лишним - ваш вобще идеален - но тут вопрос реализации им счас для его внедрения надо завести 301-ю БД и поправить сбор аналитики во всем frontend

А где доказательства (ну или предпосылки), что у них это сейчас не так?

А мы в суде что-ли, зачем вам доказательства? Мы статью обсуждаем, может быть там вообще какой-то художественный вымысел и никакой БД не существует вовсе..

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

Вместо создания неуникального индекса сработало бы временное создание RULE на выборку с предикатом deleted_at IS NULL?

Паттерн soft delition предполагает окурки, а не полноценные записи. Которые числятся через определённое время. Так же и другие логированные действия сохраняются в отчеты за периоды с необходимвм бизнесу уровнем детализации и чистятся. Отчеты существуют в отдельной БД от сбора статистики и от основной прод бд тоже.

Основная бд тоже должна быть распилена, даже если там одна таблица ивообще ничо не придумать - распиливайте по датам, первым цифрам id аккаунтов и проч (пример тупой, обычно можно придумать как распилить так, чтобы снизить нагрузку и не потерять бизнес смысл).

Дальше - отчеты собираются и анализируются в отдельных СУБД. Для анализа хороши колоночные СУБД типа клик хаус, для сбора можно использовать даже не СУБД, а тупо писать на диск в соответствующие смысловой нагрузке файлы и пути байты (дозаписывать файлы). Потом собирать все это раз в сутки ночью. Ну или использовать более что-то распространённое под highload.

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

Типичные события в журнале аналитиков: пользователь открыл страницу, пользователь кликнул по кнопке и так далее. Это помогает понять, на какие кнопки кликают чаще других, а какие пора выкинуть за ненадобностью. Бизнес, в свою очередь, может делать выводы, какие фичи в приложении нравятся пользователям, а какие «не взлетели»

Есть подозрение, что логирование не обезличенное. СкайЭнг и еще Тинькофф - два сайта куда боишься лишний раз мышкой кликнуть - т.к. потом будет шквал звонков от менеджеров и обращений во все мессенджеры...

План бесшовного релиза

угу, а за время c не уникальным индексом (после дропа old и до релиза) - наделать дублей в базу, сразу этого даже не заметить и делать задумчивое лицо на ломающемся очередном реиндексе/full vacuum/(упаси) наливки дампа)

В окне после drop old index и до релиза soft delete, в БД уже живет новый уникальный partial index. Он не даст сделать дубликат.

Потому что все записи в БД с deleted_at IS NULL и все эти записи уже под новым уникальным partial индексом.

Вывод: тестируйте миграции на препроде, запускайте нагрузочные тесты после изменений.

C "мягким удалением" необходимо понимать факт, что FK в таблице уже не обеспечивают целостности данных. Если одна строка уже "мягко" удалена, при этом есть строки в других таблицах, которые на нее ссылаются - эти строки валидны или нет?

Эм, тестовый стенд? Погонять с нагрузкой, проверить как все заработает после деплоя? Не? Сразу хехрачим в прод, ну а чо?

А потом спрашивают, почему так относятся в прогерам...

Так после деплоя-то всё заработало, проблема случилась в процессе.

Так для того и нужен стенд, чтобы проверить с нагрузкой как такие серьезные изменения с базой будут отрабатывать. По загрузке базы можно было бы предугадать насколько загрузится и прод. И увидеть заранее проблему с индексом.

Проблем с индексами на тестовой БД и без нагрузки можно было бы увидеть, просто посмотрев план выполнения типичных запросов.

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

Как я понял, установка обновления программ с поддержкой Soft Delete занимает заметное время. И пока старые программы работают со временным неуникальным индексом, в БД могут появиться записи, от которых раньше защищало требование уникальности. Поэтому, наверное, этот метод тоже нельзя считать идеальным?

Старый-то уникальный ещё работал

Вопрос в лоб: т е не делалась проверка гипотезы, что с новым индексом будет лучше и что вообще новый индекс будет использоваться?

Здесь проблема значительно шире, а именно что не делается проверка гипотезы в том числе даже если написано так в документации. Нет! Всё нужно перепроверять с учётом специфики системы.

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

а как быть с тем, что код при этом в случае дубликатов будет получать двойные строки -- где одна старая, а одна новая? То есть вместо 5хх для ситуаций, которые код не умеет обрабатывать, получаем гораздо менее заметные и более подлюччие ошибки.

Лучше окно с 5хх пока код мигрируется.

Еще лучше -- делать миграцию кода в несколько шагов:

  1. Добавяем deleted_at default null. Если что пошло не так -- дропнуть поле быстро.

  2. Пушим код, в котором запросы с "and deleted_at is null". Если что не так -- всегда можно откатить флаг включающий его.

  3. Добавляем новый индекс. Если внезапно пошло не так -- можем быстро дропнуть.

  4. Удаляем уникальный индекс. Кочка невозврата (если что-то пошло не так -- откат это 5 часов).

  5. Флипаем флаг на soft delete. Если что не так -- флаг можем флипнуть взад, и/или ручками "delete where deleted_at is not null".

Еще не забыть про каскадное проставление признака deleted_at в связанных записях.

Каскады в highload? Но зачем? Это ж верный путь в tail latency hell

Так что же, если highload - пускай будут в данных несоответствия?

Это конечно тоже можно скрыть, если во всех селектах джойнить и все таблицы на которые идут ссылки в FK. И в них уже deleted_at проверять.

Если хайлоад, то eventual consistency как правило приемлемо, либо полагаемся на денормализацию, либо на offline чистку (вне потока обработки). Так же как банк показывает последний известный баланс и норм провести транзакции ночью :)

Когда ты сам роняешь прод - это опыт, но часто его оплачивает компания по дорогому прайсу, обидно когда тебе роняют прод внешним влиянием и кроме боли никто ничего не получает при тех же затратах.

Вот я смотрю на всё это, и у меня вопрос: может ли бизнес исключить использование SQL в принципе и перейти на самописную базу данных, хотя бы на новых проектах? Плюсы очевидны же, сам написал индекс - сам знаешь как оно работает.

Бизнесу в подавляющем большинстве случаев по барабану на то, что у тебя там на серверах крутится. Сможешь обеспечить не худшие циферки стоимости, производительности и надёжности — убеди и вперёд. Хоть по файлику итерируйся.

Ещё один вопрос со стороны бизнеса будет, конечно, «А где мы найдём спецов по этому велосипеду, когда ты уйдёшь?»

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

Иначе имеем случай, когда на 10 тыс клиентов все летало, а на 1 млн вдруг затормозило.

~0,5 млрд записей в одной таблице БД

Как такая большая платформа ещё не упала? Про шардинг и репликации не слышали?

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

Для аналитики логично использовать OLAP, все же специально для этого созданы как никак. Смысл в том, что данные для аналитики почти никогда не нужны в реальном времени, они требуются для ее дальнейшего отложенного анализа. Общепринятый вариант - писать аналитику в отдельный сервис, чтобы если тот упадет или нагрузится, то это не мешало работать основным критически-важным риалтайм сервисам.

P.s.: с одной стороны мы имеем мидлов, которым на собесах задают вопросы по видам и тонкостям шардинга, а с другой - топовые платформы, на которых ребята никогда не слышали даже об OLAP или о выделении некоторой логики в отдельные сервисы

Был у меня подобный опыт вставки новых индексов и прод шакалило несколько часов пока все записи не проиндексировались. После такого все запросы тупо встали и была не просто задержка а все по таймауту улетало с клиента. Но это был не самый серъезный мой фейл.
Полностью ронять прод получалось только на несколько минут но по мне более опасная ситуация когда все вроде работает но есть "нюанс") Этим нюансом было состояние заказа. В какой-то момент клиент пожаловался что он оплатил одну и туже покупку 5 раз. Просто тыкал на кнопку и ждал результата. Все оказалось до банальности тупо - отсутствовала проверка состояния заказа а фронт не подстраховал, потом конечно кнопку оплаты после нажатия дизейбили но факт проблемы вскрылся. Менеджер мне ничего не сказал, типа херня случается. Клиенту потом конечно деньги вернули) Но масштаб проблемы мог быть спокойно в разы больше. Были и другие косяки примерно такого же уровня но у кого они не было по опыту? Автора с посвящением )

Не душноты ради, просто интересно. Имеем исходные данные: high load и аналитические запросы и стек под это : PHP 8.1, Symfony 5, orm Doctrine, PostgreSQL, почему ни click house + golang, например?

click house

Например, потому что нужен ACID. Причем не только в пределах одного INSERT в одну не распределенную таблицу.

  • добавить поле deleted_at

  • добавить в запросы "and deleted_at = null"

А дальше можно следовать плану автора.

Ну и повторю умные мысли из комментариев:

  1. Нет отката = двойная проверка на стейджинге. Особенно про БД.

  2. Брать аналитику с прод БД чревато падениями этой БД. Даже с реплики не стоит брать.

Логика нашего приложения допускает повторную вставку записи после ее
удаления. Но после внедрения soft delete мы больше не сможем вставить в
БД запись с такой же парой (user_id и resource_id) из-за нарушения уникальности — БД выдаст ошибку.

А можете мне объяснить, как непрофессионалу в БД, почему мы хотим вставлять новую запись с таким же идентификатором? Разве нельзя использовать новый?

Потому что это внешние ключи, т.е. идентификаторы других записей. Как можно "использовать новый идентификатор", когда нам нужен именно этот?

Понял, сразу не въехал.

А как насчёт варианта при удалении обнулять deleted_at?

Шарить продуктовую базу с другими командами большая ошибка темболее с аналитиками. Как минимум можно им дать async реплику для этого.

Статья отличная и опыт полезный для рефлексии. Жалко что не сделаны (или не написаны) правильные выводы.

К проблемам привело применение неподходящего паттерна, стрельнуть могло в других местах и тонкостях. 

Как можно было бы это условно почувствовать -  для реализации необходим набор «костыликов» вида «тут не индексируй», «тут добавь условий»,  уникальный индекс не уникальный и прочее… Это и должно наводить на подозрения/сомнения.

Можно было просто держать отдельную базу для аналитики, все работало бы без «особенностей».

Не вижу в списке действий "как не допустить подобного" еще парочки:

  • настроить мониторинг работоспособности платформы. Алерт "пользователи жалуются и я пошел в мониторинг" - это фаил.

  • все подобные изменения структуры проверять с HypoPG

  • задуматься, не стоит ли при таких объемах данных не смешивать прод и аналитическое хранилице, и завести warehouse. Аналитики и дальше будут предлагать "улучшить" структуру данных, как удобно им. А архитектура должна быть удобной для массовой обработки запросов от прода, а не от аналитика.

Думаю, что вся эта история возникла как раз для возможности инкрементальной подгрузки данных в какой-нибудь варехауз. Как иначе понять, что данные в исходных таблицах удалены и варехаузе больше не валидны? Или каждый раз делать полный импорт?

Есть на это алгоритмы синхронизации. Для постгри из головы помню реализацию через WAL и через поле xmin. C одним xmin не узнать, удалены ли строки, это правда.

Если репликацию делали по логу постгри, то там можно в логе детектировать факт удаления и в варехаузе помечать их удаленными.

Sign up to leave a comment.