Однажды у нас появилась относительно несложная (на вид) задача - отображать прогресс перевода документа не по количеству переведённых сегментов (относительно небольшие куски на которые разбивается документ для более удобной обработки), а по количеству слов в этих сегментах. Идея здравая, информация насчёт слов у нас вроде бы и так была. В целом казалось что сделать всё проще простого.
Однако реальность внесла свои коррективы. Сразу же выяснились 2 вещи:
1. Старая статистика хранилась в уже агрегированном виде в таблице документов и обновлялась при каждом изменении сегмента (что увеличивает количество запросов при обновлении, потенциально опасно и ухудшает и так не слишком читаемый код внутри персистентности, но это лирика)
2. Количество слов у сегмента у нас было только в C# коде (высчитывалось по примитивному алгоритму) - в базе эти данные не хранились, только общий агрегат.
Так что мы поняли что придётся всё же писать количество слов для каждого сегмента + обновлять кусок, который считает статистику для документа.
Это был один из первых звоночков, когда я стал понимать что так дальше жить нельзя - вся логика работы с базой была размазана по немалому количеству хранимых процедур, причем искать зависимости в базе - это совсем не find usages внутри студии. При этом даже статистика вытаскивалась как минимум двумя разными хранимками (мы с этим отлавливали баги - в одном месте были одни значения, а в другом - другие).
Но в общем и целом кое-как была таки дополнена логика на сервере, на клиенте, написана миграция базы переписывающая штук 5 хранимых процедур и вьюшку, а так же добавляющая недостающую колонку. Ну и следующей миграцией (накатываемой сразу же после первой) был написан SQL скрипт, который с помощью несложной функции считающей количество слов (аналог string.Split(' ').Count() в нашем коде) проходил по всем сегментам и прописывал количество слов в них.
После довольно немалого (как для такой несложной фичи) периода стабилизации на тестовом сервере мы таки решили выливаться в продакшн. По грубым прикидкам (банально поленились полезть и посмотреть, положившись на память) на продакшене было раза в 3-4 больше данных чем на тестовом сервере. Учитывая что скрипт на тестовом сервере шел порядка 5-10 минут, мы решили что в целом ничего ужасного нет и мы вполне можем подождать полчасика.
И вот наступает день Х. Мы ждём пока выльется другая команда, у них это затягивается до 10-11 вечера (как обычно, впрочем). После чего начинаем свою выливку - быстренько выливаем код и запускаем миграцию. Первая миграция (со структурой базы) проходит быстро, а вот вторая как-то затягивается - 30 минут, 40... В итоге примерно к концу часа, миграция падает с довольно невнятным сообщением об ошибке. Ладно, пробуем прогнать её еще раз и попутно пытаемся разобраться в чем там было дело. Толком непонятно, но к концу следующего часа миграция опять падает. Становится понятно что это не случайная ошибка, а какой-то систематический просчёт у нас в миграции.
Хуже того, Sql сервер начинает как-то уж очень плохо шевелиться, причем не только на нашей базе. Делать нечего, вызваниеваем одного из админов. Он сообщает что раздел диска выделенный под TempDB забит под завязку. После чего добавляет туда места, рестартует сервер и вроде бы всё начинает нормально работать. Уже около половины второго ночи. Мы выдыхаем чуть спокойнее и решаем что нам необязательно "вот прямо сейчас" иметь все данные обновлёнными и поэтому достаточно будет если мы смигрируем документы только за последние несколько месяцев, а остальное отложим на завтра. Собственно, это мы и сделали - в ручном режиме (запустив скрипт напрямую) прогоняем миграцию только части данных, быстренько прогоняем тесты и разъезжаемся по домам.
Утром я быстренько набросал консольное приложеньице, которое мигрирует сегменты подокументно (т.е. пачками 10-10к штук) и уже в рабочее время в течении дня прогнал миграцию, на всякий случай попросив админов мониторить нагрузку на сервер (там всё оказалось нормально).
Т.е. в общем и целом - особо ужасного конечно ничего не случилось - вряд ли много пользователей страдали от того что наш сервис плохо работал в районе полуночи, никаких данных мы не потеряли и к утру большинство сценариев даже отрабатывали как надо.
Но для нас это было неприятно и нетипично - крайне редко выливки у нас длились дольше чем до 20:00, при том что начинались не раньше чем в 18:30, а 90% времени тратилось на регрессивное тестирование автотестом + немного руками.
Так что мы сделали кое-какие выводы:
1. Иногда размер (базы) таки имеет значение. То что довольно легко проходит на тестовых данных, может застрять на реальных объёмах.
2. Всегда стоит внимательнее посмотреть - насколько отличаются размеры тестовых данных и насколько может деградировать производительность с ростом их количества. И если миграция на тестовом сервере проходит больше 3-5 минут, то это уже повод для беспокойства, особенно если при увеличении количества записей время растёт нелинейно (проще всего это проверить запустив миграцию сперва на части тестовых данных, а потом на всех).
3. Слона лучше есть по частям - не стоило запихивать такие масштабные изменения в один запрос. Именно это привело к распуханию TempDB. Так что следующие масштабные миграции мы решили делать в фоне и подокументно, т.е. относительно небольшими порциями. Кстати, мне стоило послушать Лёху, который предлагал это изначально ;)
4. Не совсем относящийся к миграции пункт - слой работы с базой был крайне плохоподдерживаемым. И мы затратили уйму сил чтобы всё это заработало и заработало без багов. И это на довольно простой задаче. Так что зачастую косвенные затраты на внесение новых фич в плохо поддерживаемых участках кода могут в совокупности довольно быстро перевесить затраты на нормальное переписывание. Надо будет не забыть про этот аргумент в следующий раз, когда буду выбивать время на рефакторинг кода.
Так что одним из моих сильнейших желаний стало желание переписать слой работы с базой по человечески. Но это случилось уже примерно через полгода, до того момента мы пытались жить с тем что было. Да и довольно сложно выбить около месяца работы на задачу не приносящую никакой прямой пользы бизнесу.
Однако реальность внесла свои коррективы. Сразу же выяснились 2 вещи:
1. Старая статистика хранилась в уже агрегированном виде в таблице документов и обновлялась при каждом изменении сегмента (что увеличивает количество запросов при обновлении, потенциально опасно и ухудшает и так не слишком читаемый код внутри персистентности, но это лирика)
2. Количество слов у сегмента у нас было только в C# коде (высчитывалось по примитивному алгоритму) - в базе эти данные не хранились, только общий агрегат.
Так что мы поняли что придётся всё же писать количество слов для каждого сегмента + обновлять кусок, который считает статистику для документа.
Это был один из первых звоночков, когда я стал понимать что так дальше жить нельзя - вся логика работы с базой была размазана по немалому количеству хранимых процедур, причем искать зависимости в базе - это совсем не find usages внутри студии. При этом даже статистика вытаскивалась как минимум двумя разными хранимками (мы с этим отлавливали баги - в одном месте были одни значения, а в другом - другие).
Но в общем и целом кое-как была таки дополнена логика на сервере, на клиенте, написана миграция базы переписывающая штук 5 хранимых процедур и вьюшку, а так же добавляющая недостающую колонку. Ну и следующей миграцией (накатываемой сразу же после первой) был написан SQL скрипт, который с помощью несложной функции считающей количество слов (аналог string.Split(' ').Count() в нашем коде) проходил по всем сегментам и прописывал количество слов в них.
После довольно немалого (как для такой несложной фичи) периода стабилизации на тестовом сервере мы таки решили выливаться в продакшн. По грубым прикидкам (банально поленились полезть и посмотреть, положившись на память) на продакшене было раза в 3-4 больше данных чем на тестовом сервере. Учитывая что скрипт на тестовом сервере шел порядка 5-10 минут, мы решили что в целом ничего ужасного нет и мы вполне можем подождать полчасика.
И вот наступает день Х. Мы ждём пока выльется другая команда, у них это затягивается до 10-11 вечера (как обычно, впрочем). После чего начинаем свою выливку - быстренько выливаем код и запускаем миграцию. Первая миграция (со структурой базы) проходит быстро, а вот вторая как-то затягивается - 30 минут, 40... В итоге примерно к концу часа, миграция падает с довольно невнятным сообщением об ошибке. Ладно, пробуем прогнать её еще раз и попутно пытаемся разобраться в чем там было дело. Толком непонятно, но к концу следующего часа миграция опять падает. Становится понятно что это не случайная ошибка, а какой-то систематический просчёт у нас в миграции.
Хуже того, Sql сервер начинает как-то уж очень плохо шевелиться, причем не только на нашей базе. Делать нечего, вызваниеваем одного из админов. Он сообщает что раздел диска выделенный под TempDB забит под завязку. После чего добавляет туда места, рестартует сервер и вроде бы всё начинает нормально работать. Уже около половины второго ночи. Мы выдыхаем чуть спокойнее и решаем что нам необязательно "вот прямо сейчас" иметь все данные обновлёнными и поэтому достаточно будет если мы смигрируем документы только за последние несколько месяцев, а остальное отложим на завтра. Собственно, это мы и сделали - в ручном режиме (запустив скрипт напрямую) прогоняем миграцию только части данных, быстренько прогоняем тесты и разъезжаемся по домам.
Утром я быстренько набросал консольное приложеньице, которое мигрирует сегменты подокументно (т.е. пачками 10-10к штук) и уже в рабочее время в течении дня прогнал миграцию, на всякий случай попросив админов мониторить нагрузку на сервер (там всё оказалось нормально).
Т.е. в общем и целом - особо ужасного конечно ничего не случилось - вряд ли много пользователей страдали от того что наш сервис плохо работал в районе полуночи, никаких данных мы не потеряли и к утру большинство сценариев даже отрабатывали как надо.
Но для нас это было неприятно и нетипично - крайне редко выливки у нас длились дольше чем до 20:00, при том что начинались не раньше чем в 18:30, а 90% времени тратилось на регрессивное тестирование автотестом + немного руками.
Так что мы сделали кое-какие выводы:
1. Иногда размер (базы) таки имеет значение. То что довольно легко проходит на тестовых данных, может застрять на реальных объёмах.
2. Всегда стоит внимательнее посмотреть - насколько отличаются размеры тестовых данных и насколько может деградировать производительность с ростом их количества. И если миграция на тестовом сервере проходит больше 3-5 минут, то это уже повод для беспокойства, особенно если при увеличении количества записей время растёт нелинейно (проще всего это проверить запустив миграцию сперва на части тестовых данных, а потом на всех).
3. Слона лучше есть по частям - не стоило запихивать такие масштабные изменения в один запрос. Именно это привело к распуханию TempDB. Так что следующие масштабные миграции мы решили делать в фоне и подокументно, т.е. относительно небольшими порциями. Кстати, мне стоило послушать Лёху, который предлагал это изначально ;)
4. Не совсем относящийся к миграции пункт - слой работы с базой был крайне плохоподдерживаемым. И мы затратили уйму сил чтобы всё это заработало и заработало без багов. И это на довольно простой задаче. Так что зачастую косвенные затраты на внесение новых фич в плохо поддерживаемых участках кода могут в совокупности довольно быстро перевесить затраты на нормальное переписывание. Надо будет не забыть про этот аргумент в следующий раз, когда буду выбивать время на рефакторинг кода.
Так что одним из моих сильнейших желаний стало желание переписать слой работы с базой по человечески. Но это случилось уже примерно через полгода, до того момента мы пытались жить с тем что было. Да и довольно сложно выбить около месяца работы на задачу не приносящую никакой прямой пользы бизнесу.