Пыхап #4 × Lamoda Tech. Конкурс факапов
Пых
1. Сорвал резьбу на исходнике
Я заруинил исходник, зашифровав его код через phpBolt и потеряв пару дней работы.
2. Яйца в облачной корзине
На одном проекте у нас было примерно 20 клиентских серваков, которые хостились у одного хостера (в облаке). Наш основной факап был в том, что там же находился и сайт проекта, через который автоматом эти серваки управлялись.
В один прекрасный день приходит оповещение что все серваки упали.
Обратились к хостеру. Его ответ, что его взломали, все виртуалки потерли, а бэкапы зашифровали и восстановить нельзя, наших данных примерно 1.5-2 Тб, а у самого хостера 2 Пт.
Нас спасло то, что мы отдельно делали бэкапы клиентских серваков, но бэкап сайта мы не делали, понадеялись на хостера. В итоге сайт проекта со всеми клиентами, данными и прочим канул в лету.
Восстанавливали проект неделю где-то, хостер тоже оклемался, наладил свои сервера, систему безопасности усилил и так далее. Мы благополучно восстановили сервера, а сайт перенесли на другого хостера (и, как оказалось, правильно).
Проходит неделя-две, и хостера опять ломают и сносят всё под ноль…
В этот раз восстановили все свои серваки и клиентов дня за 2-3 (хорошо, что сайт был у другого хостера).
Мораль сей басни: мало бэкапов не бывает. На хостера надейся, а сам не плошай.
3. Собрал мусор — разбросал перформанс
Я ускорил Temporal PHP 2.7.4 в 3 раза. Но сначала замедлил.
У нас появился клиент, который жаловался на утечки памяти. Мы всё перебенчили вдоль и поперёк — ну нет утечек! Я и предположил, что проблема в клиентском коде. Например, возникают циклические ссылки через кложуры и промисы в Worfklow и не дают памяти самоочищаться.
Какое самое гениальное решение с отстрелом всех трёх конечностей ниже пояса можно придумать? Конечно же вызывать gc_collect_cycles() на каждую команду DestroyWorkflow!
А DestroyWorkflow вызывается крайне часто, особенно если Workflow короткие или sticky_cache небольшой!
Усугубляется всё тем, что при прогреве воркера создаются тысячи постоянных объектов маршаллера и прочих сервисов, а значит каждый вызов gc_collect_cycles() делает просто кучу ненужной работы.
Эту штуку я отправил в патч-релиз посмотреть, что будет. Ну и забыл там на полгодика или около того, хотя знал, что проблемы с перформансом будут.
Когда об этом все забыли, я запилил стратегию вызова gc_collect_cycles() на каждые 30 секунд или 1000 DestroyWorkflow, получив медаль за буст перформанса.
Когда и об этом все забудут, сделаю ещё более редкий вызов gc_collect_cycles() и ещё раз бустану все продакшены мира.
4. Слэш судьбы
На одной прошлой работе был курьёзный случай.
К нам пришел крупный клиент — немецкий сайт Vileda (клининговая компания). Клиент пришел на техническую поддержку (контент и SEO). Сайт разрабатывали немецкие подрядчики, и нам был предоставлен только доступ в административную систему (для ру-региона). Доступа к коду у нас не было.
Сразу же первой задачей было размещение баннера на главной странице. Админ-система была крайне доисторической на тот момент. Задачу дали нашему отделу, который занимался технической поддержкой, а исполнителем был наш новый контент-менеджер, который вышел буквально неделю назад.
В мессенджере в этот момент генеральный директор и все остальные поздравляли менеджеров, которые привлекли этого клиента.
В этот же момент наш контент-менеджер пытается вставить картинку в админ-системе, но каким-то образом при вставке картинки в окне указания пути прописывает просто слэш /. И картинка полностью заменяет весь текущий контент сайта. Сайт в этот момент просто удалился, на проде 500, ни одна страница больше не работает. Сайта просто больше нет.
Для ясности всей картины, в чате в этот момент восторженные поздравления менеджерам, которые привлекли крупного клиента, а с нашей стороны - сайта нет, бэкапов нет, доступа к коду нет, поддержка только немецкая.
Я в этот момент находился рядом с этим человеком, и мы так смеялись в офисе с этого, а он был очень грустный. Благо офисы были в разных городах, вся верхушка сидела в Москве, а мы в другом городе.
Восстанавливали сайт около недели, с помощью поддержки в Германии. Сайтом больше контент-менеджеры не занимались, всю ответственность по контенту возложили на меня как программиста. Следующие полгода приходилось менять баннеры на главной самостоятельно.
5. Генdick
Надо было сделать редизайн всего нашего главного сайта.
Вёрстка уже была готова, осталось только прикрутить бэк с моей стороны. Я решил все обновления произвести вечером, на тот момент у нас не было гита, все изменения заливались с локальной машины на сервер напрямую.
И вот я заливаю все правки, убеждаюсь, что всё работает корректно. И со спокойной душой еду домой.
Где-то через полчаса, я только пришёл домой, мне названивают все коллеги, в чате пишут сообщения, в задаче ген. директор пишет, что я всё сломал, что у него ничего не работает и не отображается. А также пишет в задаче и чате, что надо применить финансовое взыскание с меня за то, что я так халатно ко всему отнёсся, а лучше меня уволить за такое отношение. Мой начальник звонит мне, говорит, что надо срочно всё откатывать. Я захожу на сайт, там всё корректно, у начальника тоже корректно, у других коллег также всё корректно отображается.
В этот момент кто-то пишет гендиру, чтобы он сбросил кэш. Он сбрасывает кэш и пишет в чате: "Сорян, кэш не сбросил. Вопрос закрыт."
Надо было видеть моё лицо в тот момент, когда я чуть не постарел, учитывая, что это была одна из моих первых серьёзных задач. Ну и вот такое отношение директора к своим подчинённым.
6. Финальный статус
Однажды мы с коллегой писали систему, которая занималась выплатами на карты. Мы реализовывали некий адаптер к стороннему шлюзу, а затем запускали выплаты по новому каналу.
Функционал с нашей стороны максимально простой:
- Создание платежа
- Опрос статуса платежа
Нашим клиентам мы предоставляли API, которое позволяло использовать данный функционал для выплат.
И вот при реализации очередного адаптера, используя документацию API, мы неправильно трактовали один из ошибочных статусов, полагая, что он является финальным. При получении ошибки мы на своей стороне переводили платеж в ошибку, что являлось финальным статусом с нашей стороны. Соответственно, клиенты при получении подобного статуса должны были повторить попытку выплаты (что считалось уже новым платежом).
Итак, все тесты проведены, финальное тестирование на проде также произведено. И мы запускаем новый канал выплат для наших клиентов.
Платежи пошли, вместе с тем пошли и ошибки (по неправильно описанному в API статусу), клиенты повторяют платежи, суммы растут. Когда сумма перевалила за несколько миллионов, люди стали повторно получать деньги на карту, а в нашей системе все платежи отображались с ошибкой, мы поняли, что сделали что-то не так.
В срочном порядке мы отключили канал выплат, а затем вместе с бухгалтерией пытались разобраться во всём и вернуть потраченные деньги. Вроде, если мне не изменяет память, удалось вернуть все выплаты (благо клиенты лояльные), но комиссию за подобные переводы — нет.
В общем, мораль такова: если возникают вопросы в некоторых формулировках в API, то лучше несколько раз переспросить, чем потом терять неожиданно деньги.
7. Текущая конкатенация
Когда-то я был совсем зелёным и работал на проекте, который не мог похвастаться хорошим флоу разработки, информационной безопасностью и т. д.
У меня была задача сгенерировать порядка 100 аккаунтов для личного кабинета сотрудников заказчика и разослать каждому на email их логин/пароль (да, это небезопасно, но так было нужно). Как и всегда, всё нужно было вчера, и чтобы закрыть задачу в срок и получить похвалу от руководства, пришлось много работать допоздна.
В один прекрасный вечер, или, скорее, уже ночь, написал php-скрипт с перебором всех новых пользователей, текст email-сообщения представлял собой конкатенацию нескольких строк (приветствие, ФИО, логин, пароль) и всё собиралось через $text .= "какой-то текст". Задача была выполнена в срок. Все доступы отправлены. Все довольны. И какого же было моё удивление, когда через пару недель руководитель ставит задачу еще раз отправить всем тем сотрудникам доступ к ЛК, мол, почему-то некоторые не получили письмо.
Было решено отправить всем им новые доступы, предварительно предупредив, что старые будут неактуальны. После апрува иду выполнять задачу через тот же скрипт. В тот день меня никто не подгонял, и я решил сделать еще раз тестовый прогон скрипта на email-ы коллег, и себя поставил в копию, чтобы посмотреть, что отправится каждому адресату. И как же я был увивлен, когда увидел, что текст сообщения, который записывался в ту самую переменную $text, не очищался в начале тела цикла. То есть каждый следующий пользователь по списку получал приветствие и логин/пароль всех пользователей ранее отправленных по списку. По шапке я за это не получил, руководство об этом не узнало и ни один контракт компании не пострадал. Мою реакцию в тот день можно было описать как "немой крик".
8. Тест-рассылка 18+
Как-то раз тестировщик решил протестировать отправку писем. В качестве тестовых данных он использовал картинки грудей и фото своего паспорта. Дилеры оценили рассылку 😂
9. Баг — залог здоровой нации!
По моей ошибке в if у клиентов не списывались тренировки с абонементов. Клиенты были рады, а стейкхолдеры не очень.
10. Три топора
Выполнил chmod 777 / на проде. Ручками выставлял права на всех файлах и папках назад.
11. С широко закрытыми портами
Попросил разработчика развернуть типовую vm для инстанса клиента. Смена дефолт паролей — это стандартная процедура, думал я, разработчику не стоить про неё напоминать, думал я. Спустя два года на прод клиента не встаёт обновление, ругается какая-то процедура-чекер. Оказалось, 2 года продовая база клиента была открыта всему интернету.
12. Ифовые миллионеры
В общем, есть тарифы списания денег с карт по привязанным картам по арендам. Разработчик пропустил одно условие if, и без оповещения от тех. поддержки мы не заметили, как списалось более 500 тысяч рублей с карт пользователей за 30 минут.
Я пропустил при ревью, что на тариф условие сломано. Чисто человеческий фактор. А главная проблема, что на такой важный функционал не был написан тест.
Пишите тесты на важный код, они действительно важны, это важный тейк )))
13. Уверенность, стирающая товары
Однажды, ещё до ковида, я работал над своим первым большим e-commerce проектом. Уверенности у меня было очень много, и я решил, что сделаю проект, в котором не будет багов. Вообще ноль. Тестировщик ничего не найдёт.
Проект был реально сложный, мы работали над ним полгода. Мы его сдаём, всё ок, и мой тимлид говорит: "Сейчас будем запускать в эксплуатацию, надо очистить тестовые заказы и товары".
Я говорю: "Окей". Открываю первый попавшийся скрипт, пишу SQL-запросы, чтобы всё подчистить. Данные были связаны, там не одна таблица, а много, поэтому нужно было кодить, а не просто один запрос написать. Я всё написал, закоммитил и ушёл домой.
Запускаем в эксплуатацию, всё вроде хорошо. Через какое-то время клиент говорит: "У нас странности — иногда пропадают товары, а иногда — заказы. Мы не понимаем, почему". Мы спрашиваем, что перед этим происходит. "Ну ничего особенного, мы просто запускаем импорт из нашей 1С-ки, всё обновляется, а потом часть товаров исчезает".
Я своему начальнику говорю: "Да это они что-то кривое высылают, мы же не криворукие". Он: "Точно не мы?" — "Да, точно". Он говорит: "Ок, давай тестировать". Разворачиваем чистый сервер, поднимаем всё с нуля, запускаем импорт, всё работает идеально. А на проде всё равно пропадают данные.
И вот тут я начинаю в голове отматывать назад. Думаю: так, что я мог сломать? Открываю PhpStorm, вспоминаю тот скрипт, Ctrl+F, нахожу... Как вы думаете, где? В обработчике формы обратного звонка! То есть когда кто-то отправлял заявку на звонок — вызывался тот самый скрипт и чистил данные. Я вспомнил, что тогда открыл первый попавшийся скрипт, засунул туда очистку, потом закоммитил какую-то задачу, отправил на ревью — никто не посмотрел, всё вмержили. Ощущение было, будто не только затылок вспотел — зубы вспотели!
Если бы кто-то хоть мельком глянул тот PR — ничего бы не случилось. Я реально думал, что меня уволят. Но не уволили.
14. Dev написал, прод ответил
У нас был проект, где люди подписывались на услугу, а ближе к дате её окончания получали оповещения.
Мы подняли тестовый стенд, и нашим клиентам начали сыпаться письма, что у них заканчиваются их услуги. Стали разбираться — оказалось, что мы на тестовый стенд вылили dev-окружение со всеми почтовыми кредами. И все оповещения из фоновых скриптов уходили наживую — не на какие-то тестовые SMTP-серверы, а прямо клиентам, база-то у нас была склонирована. Мы тогда хорошо получили от клиентов...
15. Ассоциативное расстройство
Нам на фронтенд надо было отдавать список неких объектов в формате JSON. Порядок имел значение для логики вывода. Когда я делал эндпойнт, то подумал, что помимо обхода списка фронты наверняка будут искать объекты по идентификатору, и решил облегчить им жизнь. Перед json_encode() я добавил переиндексацию массива объектов по полю id, превратив таким образом список в карту.
У меня сознание пхпшное, поэтому я не подумал, что объект JSON по спецификации не обязан сохранять порядок ключей, хотя в 99% он действительно будет это делать. Как ни странно, выстрелила эта ошибка уже после моего ухода.
16. phpMyLogin
В далёком 2000 году я использовал phpMyAdmin — это такой браузерный скрипт на PHP, с помощью которого можно админить базу. И вот я через него что-то правлю, кажется, какой-то индекс создаю. Делаю всё как обычно, нажимаю кнопочку save, у меня создаётся мой индекс.
Через какое-то время начинают сыпаться письма о том, что нет таблицы users. Я думаю, что, возможно, я как-то к этому причастен. Захожу в базу данных, там действительно нет таблицы users. И тут у меня подсознание достаёт информацию, что перед тем, как я нажал save, я видел где-то в углу экрана жёлтенькое поле, которое обычно было беленьким. Я смотрю список таблиц, скроллю вниз и вижу таблицу solodkiy. Это таблица users, судя по её содержимому. И тут я понимаю, что браузерный менеджер паролей в какой-то момент принял поле переименования таблицы в phpMyAdmin за логин! И прямо перед тем, как я нажал кнопку save, он его заполнил.
17. Ретрай меня полностью
Однажды я узнал, что не всё надо ретраить. Опубликовал на странице в соцсети с несколькими десятками миллионов подписчиков более 3000 дублей поста. Пришлось будить CTO чтобы он руками все подчистил.
18. Avro Кедавра
Был факап при получении инфы с топика кафки. При разработке и тестировании всё пров
еряли на JSON схеме, было всё ок. Залили на прод — консьюмер падает с ошибками. Оказывается, использовалась схема avro.