Как не сломать продакшен. Рассказ о двух ошибках и советы, как их избежать

0
56
views

Перевод первой части статьи «How Not to Break Production – My Two Big Coding Mistakes and How to Avoid Them».

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

К счастью, их было всего две.

Не то чтобы я не совершал кучу других ошибок в продакшене — совершал! — но именно эти две привели к тому, что всей команде пришлось сообща работать над устранением последствий.

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

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

Я делюсь этими историями с вами, чтобы вы знали, что делать, когда сломаете что-то в продакшене. А вы точно сломаете — если, конечно, задержитесь в сфере разработки достаточно долго.

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

Тестируйте, надейтесь и молитесь

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

Мы пишем автоматизированные тесты. Проверяем изменения в стейджинге. Проводим код-ревью. Систематизируем процедуру деплоймента или даже пишем сценарии для нее.

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

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

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

Первая работа — первая ошибка

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

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

Короче, мы отвечали за множество действительно важных и сложных (даже слишком) SQL-запросов и баз данных.

Это была моя первая работа в качестве разработчика, и я старался как можно быстрее изучить SQL.

У нас была QA-среда, т. е. тестовая среда с копиями всех баз данных, на которых все проверялось. Новички вроде меня не имели прав на запись в базах данных в продакшене (и совершенно правильно).

Чтобы изменить что-то в продакшене, я сначала должен был написать и протестировать запрос в своей локальной базе данных. Затем, когда я был уверен, что все сделано правильно, я обращался к более опытному коллеге с просьбой сделать ревью моего «кода». Если код-ревью проходило успешно, коллега запускал структурные миграции в QA-среде. После этого я мог снова тестировать свой запрос в QA. Когда я был уверен, что в QA мой запрос работает правильно, я запрашивал еще одно код-ревью и просил выполнить мой запрос в продакшене.

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

Photo by nrd on Unsplash

И тут я «дорос» до права записи

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

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

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

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

Большинство запросов, которые вы пишете, это SELECT — запросы на получение информации. Лишь изредка вы выполняете INSERT, UPDATE или DELETE.

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

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

UPDATE прошел неудачно

Я даже не помню, какая, собственно, задача у меня была. Единственное, что осталось в памяти, это проклятая инструкция UPDATE.

Я обновлял что-то, связанное с информацией о пользователе, что-то типа имени, адреса или email. Написал инструкцию и, посчитав, что все написано верно, проверил ее в виде SELECT-запроса:

--UPDATE users SET name = 'blah', ...
SELECT * FROM users
WHERE ...

Обратите внимание на закомментированную инструкцию UPDATE над SELECT. Это удобный способ проверки, которому я научился на той работе. Благодаря ему удается существенно снизить число ошибок в запросах.

Сначала вы пишете закомментированную инструкцию UPDATE со значениями, которые хотите установить. А затем под ней пишете SELECT и используете его для проверки результата запроса.

Закомментировав первое предложение, вы исключаете возможность случайного запуска UPDATE до момента готовности. Единственный способ запустить эту инструкцию — раскомментировать ее (а это нужно сделать сознательно) или выделить текст после комментария вместе с остальным запросом и затем его запустить (а это тоже сознательное действие).

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

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

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

В тот раз мой запрос был по-настоящему сложным. В нем было много join-ов и подзапросов, там проверялись диапазоны заказов или продуктов или чего-то еще: я уже не помню, но это точно не был запрос вроде WHERE id = blah.

Мой код скорее был примерно таким:

--UPDATE users SET name = 'blah', ...
SELECT * FROM users
JOIN something
  ON something.user_id = users.id
JOIN another_thing
  ON another_thing.something_id = something.id
WHERE 1=1
  AND something.blah = 'bleh'
  AND another_thing.bleh = 'blah'
  OR (
    users.name <> 'Karen'
    AND thing = thing
  );

(Клянусь, я не привираю, как рыбаки, рассказывающие о своем улове).

Я убедился, что пользователи, которых я получаю в результате, — именно те, что мне нужны. Я проверил это локально, протестировал в QA-среде и получил разрешение от другого члена команды запустить этот код в продакшене (вот видите, это была не моя вина! (шучу!)).

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

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

Почему? Хороший вопрос.

Снизу вверх

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

Что произойдет в таком случае?

Ничего.

Я не выделил валидный запрос. Парсер выбросит ошибку и скажет: «Джон, дурачина ты, простофиля, это ж неправильный запрос!»

Ну спасибо, парсер! Впрочем, не важно. Я попробую снова:

Теперь я выделил запрос целиком, и все запустилось нормально. Ура!

Все в порядке, верно?

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

Запрос запускается, но — упс! — остаток запроса не вошел в зону видимости, и в результате я выделил только половину предложения WHERE!

Вот в моем случае нечто подобное и произошло. UPDATE был выполнен криво.

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

Каждый пользователь в системе теперь стал «John Smith at 1234 Main Street Ave».

Я прокололся, да еще и слишком поздно заметил проблему.

И что я сделал?

Секреты до добра не доводят

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

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

Вы осознаете свой промах и вас охватывает ужас. О нет, что ж я сделал-то?!

Когда такое происходит, нужно припомнить несколько фактов:

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

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

Многие из этих ошибок «зависимы от времени». Это значит, что чем быстрее вы их исправите, тем меньше вреда они причинят. Если будете медлить с признанием, в результате будет только сложнее все исправить.

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

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

Итак, вот что следует сделать:

  • Признать ошибку.
  • Признать ее быстро.
  • Работать, помогая все исправить.

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

Предупредите босса или тимлида сразу же, как обнаружите проблему. Серьезность предупреждения, пожалуй, должна быть пропорциональна серьезности ошибки.

«Упс, я накосячил с валидацией этого поля, я пойду и сделаю повторный пул-реквест, чтобы это исправить».

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

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

Обновление целой таблицы

Сразу после момента «о, нет!» я пошел к тимлиду и рассказал ему о том, что произошло.

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

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

Когда с данными что-то пошло не так, у вас есть всего несколько вариантов:

  1. Написать новый запрос, чтобы восстановить данные (если возможно).
  2. Загрузить бэкап базы данных, найти правильные данные, вытянуть их оттуда и запустить новое обновление.
  3. Бежать в Мексику.

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

Это случилось практически десять лет назад, так что devops несколько отличался от теперешнего.

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

Надо ли говорить, что ожидалась еще та головная боль.

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

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

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

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

(И выделяйте снизу вверх).

Часть 2: рассказ о второй ошибке.

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here