Я люблю критику. Если вы не заметили, я, как старый дед, всё поливаю грязью и всем недоволен.
Забавно, но в то же время я люблю, когда критикуют меня самого, потому что именно в такие моменты я что-то начинаю понимать, развиваюсь и становлюсь лучше. А в этой статье я решил совместить приятное с забавным и рассказать вам о своих самых идиотских решениях и самых эпичных провалах за свою карьеру программиста - такая вот само-критика. Возможно, кто-то узнает себя, а если нет, то я просто прошу вас: не делайте так же, как делал я.
Sshuttle
Через ssh соединение можно туннелировать траффик - т.н. SSH jump host. Обычно это используют для безопасности - ну, например, подсоединиться к доверенному серверу, а от него уже заходить на целевой сервер.
У нас на работе как раз есть такой сервер за $5, чтобы просто от него прыгать куда надо к особо параноидальным клиентам. А ещё я живу в России, и у нас тут блокируют интернет, иногда по типу "а забаним-ка мы подсетку". Поэтому иногда сайт клиента не работает не из-за моих кривых рук, а из-за рук (или что у них там) Роскомнадзора. VPN мне было лень покупать, и, использовав весь интеллект, дарованный мне природой, я додумался: а почему ты не гнать http трафик через прыжковый сервер компании? Всё равно ж для клиентских сайтов использую.
Я скачал sshuttle - он позволяет в одну команду создавать SSH туннели. И это работало - я создавал туннель, проверял, что заблокированные ресурсы открывались в браузере, и закрывал его.
Работало, пока однажды я не забыл его закрыть.
В тот вечер я поработал, всё закрыл (хе-хе, ну почти), посидел в Ютубе, поставил что-то качаться через торренты и пошёл спать.
На следующее утро я открываю слак, а там огромный тред. Читаю его, как хронологию.
-
Вчера вечером через SSH доверенного сервера, который используется для безопасного доступа, пошло много траффика.
-
Это привело к всплеску активности CPU.
-
Мониторинг хостера офигел и отправил email уведомление нашему CEO.
-
Тот устроил опрос, кто сейчас пользуется сервером, никто не в курсе (я в оффлайн) - решили, что сервер взломали.
-
Удалили сервер к хренам, создали новый с более жёсткими правилами доступа.
-
Все поменяли свои конфиги.
И тут у меня возникла дилемма: можно было не говорить, что это был я. Старый сервер удалён, так что вроде никто не знает, кто это. Это был не первый фейл в этой компании, а нахождение в компании как правило имеет корреляцию с количеством косяков, так что соблазн промолчать был. С другой стороны, нужно брать на себя ответственность за свои косяки, даже если это больно.
Короче, с тяжёлой душой и паршивыми ожиданиями я написал СЕО, что это я тот самый дебил и осознаю это, давай посчитаем, сколько каждый потратил своего времени в денежном эквиваленте, и я это компенсирую. На что он мне сказал: знаешь, я давно хотел удалить к чёрту тот сервер и перенастроить всё нормально, так что спасибо, что предоставил повод.
Кто бы, блин, мог подумать.
Обновляю sentry
Это был вечер.
На каком-то внутреннем проекте случилась ошибка. Обычно у нас прилетает ошибка в sentry - это сервис для логгирования ошибок и сопутствующей отладочной информации. Но в этот раз ошибка не прилетела.
Я зашёл на сервер и убедился, что ошибки не отправляются. Сервер был довольно старый, там был старый код и какая-то 0.x
версия sentry-sdk
для питона, в то время как актуальная версия уже была что-то вроде 14.x
или типа того. Ну вы понимаете масштаб отставания.
Первая мысль была "они дропнули поддержку старых версий, нужно попробовать обновить библиотеку". Ну я открываю терминал, вбиваю pip install --upgrade sentry
и жду в надежде, что оно обновится, всё заработает, и я пойду отдыхать. Изи фикс.
Оно что-то скачивает, скачивает, потом начинает что-то компилировать rust'ом... Тут я уже прифигел: это же, блин, библиотека для обработки ошибок, хрен ли там нужна компиляция! Совсем тупые разрабы пошли, нихрена не могут такую простую штуку сделать нормально. Я прерываю процесс при помощи Ctrl+C
и в сердцах решаю отложить это на завтра.
На следующий день я, по классике, читаю хронологию событий в слаке.
Короче, sentry-sdk
- это библиотека для обработки ошибок в питоне. А sentry
- это вообще весь сервис для обработки ошибок, если вы хотите хостить его на своём сервере. Я по ошибке начал ставить последний. Ctrl+C
ничего не завершил (хотя визуально казалось, что да), потому что основной процесс успел создать другие процессы, которым сигнал не передался, ха-ха. Началась какая-то адская компиляция на слабой машине с 1 ядром. DigitalOcean продолбал мониторинг и не послал алерт по почте. Всю ночь сервер натужно пытался скомпилировать что-то, обогревал датацентр, но задачу не вывез. Наутро это заметили и с трудом остановили процессы. С трудом - потому что когда убивали одних, порождались другие.
Вот так я победил ещё один внутренний сервер. Изи.
Celery
У нас был скрейпинг. Т.к. у нас синхронная ORM (спасибо, Джанго), мы просто создавали на 8-ядерной машине кучу celery-воркеров (штук 100, потом больше), и они всё параллельно скрапили.
Ну, должны были. Когда их было мало, штук 10 - всё работало быстро. Когда штук 50 - всё тормозило. И я никак не мог отловить, в какой момент оно тормозит. Когда у вас один процесс, то там понятно - замерь время тут, замерь время там, и так как всё линейно, легко понять, какой кусок кода какое время занимает. Когда у вас одновременно 50 процессов, то отладка малость усложняется - может, оно тормозит, а может, оно в IOWAIT
. И надо было именно на 50 процессах замерять, потому что иначе тормоза не воспроизводились.
Ну я и замерил. Http запросы через прокси были медленные. Я начал отлаживать прокси, потому что, походу, они не вывозили тру многопоточность - и это ожидаемо, ведь они стоили $25 за 1000 штук. Я попробовал, наверно, всё, что ваше воображение может выдать по запросу "тестировать прокси", даже пробовал создать другой аккаунт с другими проксями и другим планом. Я всё хотел подловить провайдера, что он не может выдержать нашу нагрузку.
Конечно, как и во всех историях в этой статье, идиотом оказался я, но в этот раз я даже превзошёл сам себя: когда мы это обсуждали с СЕО в самом начале, он сказал, в том числе, проверить настройки celery и попробовать ими поиграться, но я был так увлечён тестированием прокси и был на 100% уверен, что ошибка где-то в них, что не обращал внимания на другие возможные варианты.
Дело решилось настройками - точно уже не помню, то ли PREFETCH_MULTIPLIER
, то ли BROKER_POOL_LIMIT
- но потеряли мы на этом знатно времени, даже пришлось делать скидку заказчику за затягивание сроков. Уже потом, много месяцев спустя, я прочитал 50 оттенков celery и плакал горькими слезами, потому что я всё это уже знал, но по печальному опыту.
Освободил место
Я тогда думал, что индексы - это когда на почтовом конверте пишут 6 циферок. Про индексы в БД я не слышал, или особого значения не придавал. Да, именно настолько тупым я был.
У нас была табличка в базе для "логов" - туда записывался запрос от пользователя, дата, ответ нашего API и ещё какая-то метаинформация, чтобы мы потом могли оценить, насколько всё быстро и хорошо плохо и медленно работает.
А потом эти логи о том, как всё медленно и плохо работает, стали медленно и плохо работать, хех, потому что их стало много, а индекса не было. Этих логов было правда много, мы их тогда редко смотрели, ну я и дропнул их. Меньше данных - быстрее работает. Оно и правда стало быстрее.
На следующий день босс спросил: кстати, а где логи?
Оказывается, я переоценил ненужность логов, и босс имел на них виды. К счастью, я отделался только постоянными шутками типа "у нас есть мастер по логам" и "давай посмотрим это в логах".
Теперь я не удаляю вообще ничего.
MySQL utf8
Как-то я работал с базой и тут вдруг заметил, что кодировка у таблицы была utf8mb. Опять эти джуны на проекте создали таблицу с какими-то дурацкими параметрами!
Я поменял кодировку на utf8.
Да, блин, оказывается, в MySQL utf8
- это какая-то проприетарная говнокодировка курильщика, которая с классическим utf8 имеет только общие буквы в названии. Так что если нужна utf8 - то нужно ставить utf8mb
, где аббревиатура "mb" означает "mysql bullshit" или что-то типа того.
Умный импорт
Как-то у нас был проект, где были "темпоральные объекты" - ну то есть у них была дата начала и конца. Мне нужно было написать систему импорта, и вроде бы было всё неплохо, кроме одной проблемы: временные промежутки объектов могли пересекаться, и в таком случае нужно было слить два объекта в один, с общим временем. Типа если Петя работал с 10 до 12 и с 11 до 15, то в базе должна быть одна строка со временем "с 10 до 15".
Это не так-то просто, ведь нужно запросить базу, найти пересечение временных промежутков (а могли быть промежутки типа "от сегодня и до бесконечности"). Но не на того напали, я ведь крутой программист, поэтому я написал классную логику, что когда объект сохранялся, всё просчитывалось и автоматически объединялось.
А потом я запустил импорт. Простенький файл импортировался 2 минуты, потому что каждый объект импортировался отдельно, при этом делая сверки с базой данных. До сих пор помню, как клиент ждал, пока это загрузится во время тестовой презентации. Мне стыдно.
Через год я уже знал про bulk_create
, постобработку данных и даже про postgres COPY TO
/ COPY FROM
, но время было упущено.
Machine learning
У нас было машинное обучение. Я в этом ничего не понимал (да и сейчас не особо), но по работе приходилось - была куча параметров, по которым нужно было выдать какое-то решение. Кожаный мешок не может охватить все варианты, а вот деревья решений - легко.
И вот я быстренько накидал скрипт для сбора данных, скормил всё модели, и начал её тюнить. Все знают, что самое интересное в машин лёнинге - это играться с методами обучения и их параметрами, фичами и их комбинациями, чтобы выжимать максимальную точность модели. Чем я и занимался.
А потом мы созвонились с боссом (который в ML разбирался намного лучше), я запустил screen share, и он попросил открыть данные для обучения, и мы стали просматривать их вручную. Святые угодники! Там было, наверно, всё: и те данные, которых там быть не должно, и отсутствие данных, которые ващет должны были быть, и неправильные значения... Так-то мои скрипты для генерации данных работали, и на ручных тестах всё было ок, но вот когда они были запущены на больших данных, всплыло много косяков, о которых я и не подозревал.
И вот мы подошли к главному правилу машинного обучения: если ваши данные для обучения - говно, то и вся ваша последующая работа - тоже говно. Именно это со мной и произошло. С тех пор я стал по-другому смотреть на все данные: я их проверяю. Вручную. Вот прям открываю, пробегаю глазами, беру несколько случайных значений и вручную для них считаю фичи или проверяю, что значения более-менее адекватны.
Недавно колесо судьбы сделало оборот, и уже я сам открывал чей-то файл для обучения и говорил: ваши данные говно, сударь, и значит, вся работа тоже. Такие дела.
Декремент значения
У меня был интернет-магазин без БД-транзакций. Конец шутки.
Но было там и кое-что похуже: количество товаров. Количество товаров - это, очевидно, какое-то число, и первая мысль - сделать это числом в базе данных. В те времена я дальше первой мысли, как правило, не заходил, поэтому я так и сделал: вот товар, у него есть int quantity
- количество товаров. Пришёл товар - +1
. Купили товар - -1
. Like a pro!
Но потом наступил пушной зверёк с интересным названием "пересорт". Вкратце: количество товара в базе не совпадало с реальным количеством на складе. Почему? А хрен его знает, есть тыща причин для этого: менеджер ступил и не то значение поставил, или одновременно редактировали товар, или пользователь два раза кликнул, а у нас нет транзакций, да мало ли случаев! Но важнее всего одно: не было никакого способа понять, где и когда что-то пошло не так. Да, у меня было число товаров сейчас, так что я мог просуммировать товары с заказов и понять, что когда-то, на какую-то дату было, кажется, столько-то товаров, но из-за ошибок и эта информация могла быть неправдой. В общем, это полный провал. С такой архитектурой подсчёта товаров можно уничтожить любой магазин, так что берите на заметку.
Потом я плюнул и переписал это, заменив действие (декремент) на объект "перемещение товара" (т.е. строчка в БД). И всё вдруг стало прозрачно: +1
превратилось в перемещение товара от поставщика на склад, а -1
- перемещение со склада к покупателю, и всё это было с датой, временем и другой метаинформацией. Количество товара на складе теперь было не просто числом, а вычисляемым числом - посчитай, сколько пришло, вычти, сколько ушло, и вот тебе ответ. Который можно кэшировать, чтобы не считать каждый раз. Открылись и другие "фишки" - можно добавлять цену товара при поступлении и продаже и смотреть оборот, прикреплять статусы к перемещению товара ("запланировано" / "в процессе" / "завершено" / "хрен знает что происходит"), и всё в том же духе.
Но это была не единственная проблема в магазине, ведь я:
Попытался быть умней пользователей
"Пользователи бывают идиотами, - думал я, - и чтобы обезопасить приложение от их кривых рук, нужно написать валидаторы. Все данные будут строго по ГОСТу. Телефон пусть будет +7xxx-xxx-xx-xx
, адрес - индекс, область, город, улица, дом, квартира
."
Ну да, ну да. Дебил.
-
Телефон может быть в другой стране
-
Телефон может быть с добавочным номером
-
Человек может хотеть указать несколько телефонов
-
"Город" вообще-то может быть селом или деревней
-
У адреса может не быть улицы
-
А дом может быть корпусом
-
Иногда квартиры нет, т.к. частный дом
-
Или потому что это офис
-
...
И я осознал. Эта битва проиграна, и вы точно не умнее своих пользователей - по крайней мере в вопросах их персональных данных. Я удалил к чертям все валидаторы и позволил пользователям вводить именно то, что они хотят туда ввести. И они это делают. У нас заказывали иностранцы, люди из всяких отдалённых мест и по всяким странным адресам. Им виднее.
Иногда, чтобы победить - нужно отступить.
Задеплой это быстренько
Однажды мне написал босс и сказал, что у него через 30 минут разговор с клиентом и нужно добавить какой-то простой функционал в наш CLI, чтобы прям совсем красавчиками быть. Я добавил, запушил в репу. Уверен, вы знаете, что произошло.
Клиент был какой-то "околотехнический", то есть он мог что-то запустить, но только в режиме удалённого управления, типа "откройте окно, нажмите эти кнопочки, нажмите энтер, прочитайте код ошибки". У нас на то время была только CLI библиотека на питоне. Кое-как босс объяснил клиенту, как запустить консоль, как там поставить последнюю версию либы, и как её запустить со всеми флагами. Это заняло прилично времени. И конечно, в моём коммите был баг. Он сломал всё, вообще нифига не работало. Я пытался в реальном режиме пропатчить, но из-за стресса ничего не получилось. Боссу пришлось объяснять клиенту, как поставить работающую версию. Отличная презентация продукта, мать его!
Спустя полтора года, на другой работе, клиент очень просит нашу компанию выкатить срочный патч, потому что на следующий день пройдёт мероприятие, где этот патч понадобится. Но я уже наученный и знаю, как на это смотреть. Я представляю: вот если я сейчас что-то быстро накидаю и задеплою, есть 2 варианта. Первый - это что патч заработает. Нам скажут "спасибо", а я даже сверхурочные не получу. Второй вариант, более реальный - что всё сгорит нахрен и обвалится. Тогда меня ждут стресс, ужас восстановления проекта и недовольство клиента. То есть либо я не получу ничего, либо всё будет плохо. Отличный выбор! О чём я и сообщил нашему CEO - это не моя битва, иногда нужно позволить страдать кому-то другому. И знаете, клиент как-то выжил без патча!
Точка возврата
Есть такое выражение - "точка невозврата". В IT такое случается, например, когда ты долго что-то не обновлял, а потом настала пора и там изменилось вообще всё. Когда я пользовался убунтой, там каждая новая версия - это точка невозврата, потому что обновляется вся система, и назад уже никак не откатиться (или это сделать сложно). Точки невозврата, как вы уже, наверно, знаете по убунте или какой-нибудь macOS, довольно часто сопровождаются поломкой чего-нибудь. Именно поэтому я и перешёл на arch linux, кстати - там почти всегда можно вернуться. И я очень рекомендую при обнаружении точки невозврата создавать "точку возврата".
У меня был проект, где было легаси окружение, и мы его переводили на более современное. Грубо говоря, была одна структура проекта, стала другая, плюс обновились зависимости. Разумеется, я протестировал новую систему на локалхосте. Потом у нас был staging сервер, где я выкатил новую систему и удостоверился, что она работает. Осталось дело за малым: на проде обновить старую систему. Но это точка невозврата, потому что для отката пришлось бы даунгрейдить все зависимости, откатывать миграции и хз что ещё делать. Нереально.
Поэтому я плюнул и создал клон системы, и уже спокойно обновил его. Клон системы уже нельзя было откатить, но мне и не нужно было - у меня всегда оставался оригинал, так что я просто мог удалить клон и вернуться к старой системе.
Конечно, клон не заработал. Несмотря на все тесты, все проверки, всё равно он не завёлся. Да и хрен с ним! Я опять включил оригинальный сервис и, наверно, пол-дня попивал кофе, отлаживал и пытался понять, в чём дело - но это была отладка в спокойной обстановке, зная, что всё работает. Уже потом я обнаружил, что клон не отвечал на порту 80, а балансировщик нагрузки проверял как раз 80 порт и думал, что сервис мёртв. Я пофиксил это, остановил оригинал, включил клон, и оно заработало, а моя попа осталась цела.
Lint crap
Не знаю, как у вас, а я, когда говорю по-английски, не "перевожу" слова на русский, а как бы сразу "думаю по-английски". Соответственно, иногда я знаю слово на английском, но даже не знаю, как его перевести на русский. Это приводит к курьёзам, потому что у нас в команде периодически проскальзывало слово crap, и один раз даже кто-то назвал коммит "lint crap" - там были просто патчи, чтобы линтер на гитхабе не придирался к пробелам и запятым в коде. И я вроде как на интуитивном уровне понял, что crap - это как garbage, типа мусор какой-то. Ну и стал везде это слово говорить.
А потом я даже сделал коммит, который так и назвал - "lint crap" - в нашу официальную библиотеку. Разумеется, через какое-то время босс это увидел и попросил меня это удалить, а так это было через какое-то время, то там уже были коммиты сверху, и мне не хотелось возиться с историей гита. Я тянул сколько мог, каюсь, пока босс не забыл про это, так что оно там так и осталось - lint crap.
А не хотел я это удалять, потому что однажды закоммитил секретный ключ в репозиторий, и потом имел радость с BFG. Было здорово, но больше не надо.
Вы все делаете неправильно
Очень часто при поступлении на работу новые сотрудники обнаруживают, что рабочие процессы налажены не так, как они привыкли, и у них развивается синдром "вы тут всё делаете не так". Сейчас я уже не настолько эмоциональный и когда вижу какую-то дичь на новом проекте, просто говорю wtf и добавляю туда свой код, лишь изредка уведомляя клиента, если вижу прям совсем опасные вещи.
Но раньше было не так! Это была моя вторая работа во фрилансе, и я зашёл на проект, где фронт делали на стандартных шаблонах Джанго. Я уже писал про них, и если вкратце - они говно. Но никто, кроме меня, этого не знал, и я начал про это рассказывать и что-то доказывать. Я даже убедил всех, что jinja - это тру, поэтому у нас на фронте часть шаблонов стала на Джанго, часть - на jinja, и вот это уже совсем клиника. Пытаясь сделать лучше, я сделал ещё хуже. Уволили меня, правда, не за это, но это не важно.
Уже потом я понял верную тактику для изменений, которая позволила в своё время заменить вообще весь паршивый код у клиента: сначала вы затыкаетесь и пишете код, потом делаете маленькое предложение по улучшению и реализуете его, потом чуть больше, а потом, где-то через полгода, вы можете делать с проектом то, что считаете нужным, вплоть до тотального рефакторинга. Но это игра "в долгую" и имеет смысл только если вам нравится проект, и у вас на него большие планы. Иначе просто сделайте свою работу и не пытайтесь никому ничего доказать.
Stripe
Мне сказали: "будешь пилить приём платежей?" Я подумал, что такой функционал я ещё не ломал, и согласился. К счастью, у нас в компании принято делать код-ревью, если изменения не совсем уж тривиальные, и всё, что могло сломаться, было отловлено. Но есть такие вещи, которые не проходят код ревью.
Секреты.
Секреты не попадают в систему контроля версий, поэтому их нет в репозитории и ревьюер их не видит. Обычно они ставятся вручную где-нибудь в дэшборде облачного сервиса, или в env файлах, или ещё где-то, и ввиду их секретности не каждый сотрудник из знает.
А ещё, как и принято среди джентльменов, у нас были staging и prod сервера. Конечно, мы протестировали на staging и всё работало. Потом мы задеплоиди на прод, и всё работало. Ну почти.
В sentry начали сыпаться ошибки типа "платеж не найден". Я потратил некоторое время, чтобы разобраться, какого хрена на сервер приходят уведомления о несуществующих платежах.
Оказывается, я перепутал и записал и в prod, и в staging одинаковые ключи для доступа к платёжной системе.
В итоге на один из серверов приходили платежи от другого. Деталей уже не помню, но, к счастью, это удалось вовремя отловить, и меня не сожгли заживо. Не успели.
В заключение
Мне нравится, что в каждой истории есть какая-то мораль - но я намеренно опустил её в части историй и предоставлю вам право поразмышлять самостоятельно. Но главная идея, как мне кажется, такая:
Чтобы стать хорошим спецом, не нужно бояться облажаться. Нужно бояться не вырасти после этого.
Если вам нравятся мои статьи, то приглашаю вас в то место, где я планирую постить интересные вещи, а Паша Дуров - рекламу: мой телеграм-канал "Блог погромиста".
И традиционно опрос, напрямую связанный со статьёй:
Автор:
kesn