Красота кода

August 26, 2016

После того как я поработал в проекте из нескольких десятков миллионов строк на C++, представления о красоте програмного кода у меня сильно поменялись. Чисто технически невозможно сделать так, чтобы на таких масштабах сотни разработчиков создавали "красивый код" с точки зрения перфекционизма. Я долго думал над метафорой, которая бы точнее описывала мое сегодняшнее отношение к данному вопросу, пока нашел только такую.

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

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

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

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

Поэтому последнее время я редко думаю, как правильно назвать метод, сколько пробелов поставить или что лучше ООП или ФП. Лучше и правильнее то, что позволяет создать суровую, индустриальную красоту в каждом конкетном взятом проекте.

0 Comments

Маленькая тайна грепа

August 25, 2016

Есть вещи, которые очень легко гуглятся, но иногда в голову не приходит их гуглить. Например я всегда считал, что такая конструкция, вполне нормальная практика:

ps auxww | grep ruby | grep -v grep

Чтобы найти процесс с руби, нужно сначала исключить процесс с грепом, который ищет процесс с руби. Ну вы понимаете.

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

ps auxww | grep [r]uby

В таблице процессов отображается grep [r]uby, а ищем мы регексп, в котором первая буква r и потом строка uby. Таким образом процесс самого грепа не попадает в поиск. Теперь я ищу только так.

0 Comments

Крон, Великий и Ужасный

August 24, 2016

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

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

PATH=/root/bin:/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
23 7 * * 1-5 cd /root/blog && (echo STARTED_`date "+\%Y-\%m-\%d__\%H-\%M-\%S"` && git pull && NOCACHE=1 ./publish next && echo FINISHED_`date "+\%Y-\%m-\%d__\%H-\%M-\%S"`) >> /root/crontab.log 2>&1 

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

0 Comments

Большие апдейты

August 23, 2016

Один долгий руби процесс у нас пишет логи своего выполнения в базу.

Сделано было просто и прямолинейно, создали колонку log:text в таблице скажем jobs и вот такой метод логирования:

class Job
  def log(msg)
    self.log += msg + "\n"
    safe!
  end
end

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

Оказалось, что кто-то добавил отладочной информации и количество записей в лог резко выросло. Поле log стало занимать 2-3 мегабайта, новые строки добавляются примерно раз в секунду, а Postgres устроен так, что UPDATE = INSERT + DELETE, DELETE же не сразу же освобождает место. То есть мы стали со скоростью несколько мегабайт в секунду забивать диск, автовакуум не успевал чистить.

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

0 Comments

Переписывать нельзя модифицировать

August 22, 2016

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

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

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

0 Comments

Лига лени в гит-конфиге

August 19, 2016

Чем старше я, тем ленивей становлюсь. Боюсь представить, что будет дальше.

Недавно в моем .gitconfig появилась команда, которую я стараюсь использовать пореже... Но она такая клевая... :heart_eyes_cat:

[alias]
  # перенес на несколько строк для наглядности, в оригинале она у меня в одной
  y = !(git add -A . && 
        git commit -amsaved_at_`date +%Y-%m-%d__%H:%M:%S` &&
        git pull --rebase && git push && 
        echo "" && git log -1 --stat) || git pull --rebase

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

Название алиаса от слова sYnc. Букву s я не стал использоваться, чтобы не путать с git st => git status.

0 Comments

Бдительность не бывает лишней

August 18, 2016

Как-то раз я копировал postgres-базу с продакшена на тестовый сервер.

Продакшен работал уже на версии 9.4 и там даже добавили таблицу с jsonb-колонкой, тестовый сервер был все еще 9.3. Этот факт у меня был где-то на краю сознания, поэтому я решил проверить как перенеслась эта таблица.

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

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

0 Comments

Как помыть слона

August 17, 2016

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

В такие моменты обычно вспоминаю замечательную книгу Дизайн пользовательского интерфейса. Искусство мыть слона.

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

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

0 Comments

Коварный backtick или как определить размер используемой памяти

August 16, 2016

В одном жирненьком руби-воркере мы логировали размер памяти, которую расходует процесс, с помощью команды `ps -o rss -p #{$$}`. Такой способ легко найти в интернете и он обычно работает.

Однако сам Kernel#` сделан так, что он форкает руби процесс, соответственно на доли секунды резервируя еще столько же памяти, сколько процесс занимает уже. Наш воркер в ходе работы раздувался до 2.5-3GB и начал падать на 4GB оперативной памяти.

Я подсмотрел как сделано в ньюрелике (файл new_relic/agent/samplers/memory_sampler.rb):

proc_status_file = "/proc/#{$$}/status"
proc_status = File.open(proc_status_file, "r") {|f| f.read_nonblock(4096).strip }
proc_status =~ /RSS:\s*(\d+) kB/i
rss = $1.to_f 

На линуксе вместо запуска ps лучше использовать информацию из раздела /proc. Такой способ не требует избыточной памяти и работает у нас по сей день.

0 Comments

Когда все слишком хорошо

August 15, 2016

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

Может быть сломалось вообще все?!?!

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

И когда действительно сломалось, то как бы не наивно это звучало, я становлюсь сильно спокойнее. Молния же не попадает в одно место дважды? :smile:

···

Расскажу пару историй из жизни.

Мы оптимизировали тормозной кусок кода, который сильно нагружал базу. Выкатили и сразу смотрим заббикс. Немного ожидания и... Красота! База работает великолепно, CPU уменьшилось в 10 раз, все ровненько, гладенько. Чудесно.

Захожу на сайт - ошибка 500 в кабинете ученика на всех страницах. Естественно нагрузка тут же снизилась на порядок, база отдыхает. Хотфиксим, катим, ждем немного пока трафик вернется и заббикс отрисует новые данные. Вот теперь ок. База нагружена, но чтение с диска уменьшилось раза в полтора, как мы и планировали.

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

Через неделю проводим тестирование заново и выясняем, что в прошлом запуске сломалась авторизация. Нагрузка тестировала как здорово NGINX отдает 302 на if current_user.nil?. Чиним, и совсем другая история - пора бежать докупать сервера.

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

0 Comments