Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Keep alive для ssh

Подключаешься такой по ssh к серваку, отвлекся на пару минут, возвращаешься в консольку с ssh, а она уже на ввод не реагирует. Бесит жутко. Чтобы такого не было, можно настроить keep alive на сервере (но кто ж вам даст) или у себя в ~/.ssh/config:

Host *
    ServerAliveInterval 200

Почему это не включено по умолчанию — загадка :(

СсылкаКомментировать

Dead letter queue для очередей RabbitMQ

Простейший вариант обработки сообщений из очереди — слать ack после успешной обработки и nack с requeue=true при временной неудаче. Однако такой подход работает только для временных/разовых ошибок, при серьезной проблеме потребитель будет пытаться обработать сообщение до посинения с маленьким интервалом между попытками.

Чтобы этого избежать, можно создать для таких сообщений специальную очередь — dead letter queue (dlq) и для серьезных ошибок слать nack с requeue=false. А чтобы сделать несколько попыток, для каждого сообщения в dlq можно назначить TTL, и установить в качестве dlq для dlq оригинальную очередь. Тогда при истечении TTL сообщение из DLQ попадает обратно в оригинальную очередь с заголовком, содержащим число “смертей”. Потребитель может проверять это число и, если оно превысило предел, не обрабатывать сообщение совсем, но послать ack. Таким образом, TTL для dlq служит интервалом между попытками.

Если в сообщениях есть ценная информация и выкидывать их совсем нежелательно, то при превышении числа попыток потребитель может их складывать в третью очередь (parking lot) для последующего ручного исправления.

С точки зрения настроек RabbitMQ нужно добавить к параметрам оригинальной очереди

x-dead-letter-exchange = ""
x-dead-letter-routing-key = "dlq_queue_name"

а к параметрам dlq

x-dead-letter-exchange = ""
x-dead-letter-routing-key = "original_queue_name"
x-message-ttl = 100500 # milliseconds

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

Разумеется, возможны и другие сценарии (с общей dlq для всего, например), но описанный — один из самых простых.

СсылкаКомментировать

Выбор коммита для добавления изменений в git

Одна из устоявшихся практик добавления изменений — rebase + fast-forward. Часто со схлопыванием всех коммитов в один жирный. История конечно получается красивая и это просто, но сваливать в одну кучу бизнес-изменения и рефакторинг — так себе затея, атомарные коммиты все-таки не зря придумали.

Но тогда после очередного rebase на основную ветку может возникнуть проблема, когда ваши красивые коммиты перестают компилироваться (например, кто-то тоже порефакторил от души). И после этого делать коммит fix after rebase или лепить все исправления в последний коммит через --amend как-то уныло. В этом случае поможет --fixup:

git add someChangedFiles
git commit --fixup=OLD_COMMIT_HASH
git rebase --interactive --autosquash OLD_COMMIT_HASH^

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

СсылкаКомментировать

Порядок просмотра файлов при код-ревью

Довольно интересное исследование показало:

  1. Большинство разработчиков ревьюят изменения тупо по порядку следования файлов (и обычно это алфавитный).
  2. Тесты комментируют реже, их качество обычно меньше волнует разрабов.
  3. Если тесты ревьюить после основного кода (как это обычно происходит, потому что t — в конце алфавита), то в них реже находят баги (для основного кода существенной разницы нет).
  4. Больше всего комментариев оставляют к первым файлам в списке, и баги в них обнаруживают с большей вероятностью.

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

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

СсылкаКомментировать

Ковариантность и контравариантность

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

Если термины из теории категорий — “сложна”, то можно мыслить в терминах потребитель/производитель, в Kotlin и C# параметры так пишут — in и out вместо всяких плюс-минусов, и это гораздо читаемее.

СсылкаКомментировать

Вариации сна для компьютера

  1. Бездействие (aka S1, idle) — CPU остановлен, система потребляет чуть меньше энергии: почти все устройства переключены в режим низкого энергопотребления или выключаются (ЖД, подсветка экрана).
  2. Сон (aka S2) — как S1, только CPU отключен от питания (разница с S1 минимальна, на некоторых материнских платах его даже не реализовывают).
  3. Ждущий режим (aka S3, sleep, suspend) — почти все устройства, кроме RAM отключены, состояние хранится в памяти. RAM обновляется медленнее. Жрет питание/батарею, но чуть-чуть.
  4. Гибернация (aka S4, hibernate) — состояние сохраняется на диск (например, в swap), питание полностью отключается. Однако не все оборудование корректно может восстановить свое состояние, поэтому этот режим обычно отключен по умолчанию.
  5. Мягкое выключение (aka S5, soft off) — все выключено, но некоторые порты/контроллеры включены (чтобы включить комп по LAN или по нажатию кнопки на клавиатуре, например).
  6. Гибридная гибернация (aka hybrid suspend) — состояние сохраняется на диск, как в гибернации, а потом комп переводится в ждущий режим. Позволяет быстрее выходить из сна, но не терять состояние, если питание пропадет. Есть еще вариация, когда сначала выполняется suspend, а потом, по таймеру или по триггеру (например, низкий заряд батареи) кратенько просыпается и уходит в гибернацию.
  7. Modern Standby (aka S0 low power idle, S0ix) и PowerNap — маркетинговые названия для S0 (полностью работающий комп), просто часть функций системы не работают и часть периферии отключена. Но при этом выполняются фоновые задачи и остается подключение к сети (“как у смартфона”). Питание потребляет много, и ноутбук в таком режиме в сумку класть не стоит — может перегреться.
СсылкаКомментировать

Псевдонимы для типов и value-классы

Почти бесплатно повысить читаемость кода можно за счет псевдонимов типов (type aliases). Например, можно заменить в бизнес-логике безликий UUID на UserId. Есть это почти во всех языках: typedef или даже #define в С, using в C++ и C#, type alias в Kotlin, type в Scala/Haskell и даже в питоне, но не в Java. Можно еще использовать локально: импортировать тип с другим именем. Но у псевдонимов есть банальный недостаток: ничто не помешает в UserId запихать другой UUID, например ObjectId.

Чтобы компилятор проверял подобные несоответствия, можно использовать value-типы (они же Single case discriminated union). Обычно это класс-обертка над целевым типом, который имеет единственное поле и при возможности заменяется после компиляции на целевой тип. Например, в Kotlin это будет

@JvmInline
value class UserId(val value: UUID)

В некоторых языках еще можно добавить логику (например, проверку в init блоке или добавить методы-геттеры). Если value-типов нет, можно достичь примерно того же, используя data-классы с одним полем (а где-то может и вообще разницы не будет).

Очевидная проблема оберток в том, что к содержимому всегда придется обращаться через поле или паттерн-матчинг, а не напрямую (и какой-нибудь делегацией это, увы, не решается). А из-за потенциального оборачивания страдают библиотеки, использующие рефлексию, например, сериализация в JSON. В Scala это решили с помощью opaque-типов: рядом с объявлением класса он работает прозрачно, как type alias, а снаружи — непрозрачно, и доступа к целевому типу нет. При этом там есть и “обычные” value-классы, которые почти такие же, как в Kotlin.

А еще и в Scala и в Kotlin ждут Project Valhalla, чтобы можно было создавать пользовательские типы, доступ к которым осуществляется по значению, а не по ссылке. В комбинации с inline это позволит решить часть проблем с оборачиванием value-типов.

СсылкаКомментировать

Help me, step-container, I'm stuck!

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

docker commit %container_id% %some_name%

и стартовать его с командной строкой:

docker run -it %some_name% /bin/sh

А потом уже дебажить, что там внутри не так.

СсылкаКомментировать

Из-за чего команда профессионалов может работать фигово

В продолжение заметки про продуктивность — а почему в команде из кучи замотивированных сеньоров все равно может получиться что-то не то?

Причин может может быть масса [1, 2, 3]:

  1. Они делают не то, что важно (в т.ч. из-за того, что увлекаются чем-то интересным для себя), часто из-за отсутствия четких целей, отсутствует фокус или пытаются сделать все и сразу.
  2. Они ждут другую команду (из-за плохого разделения, из-за мутных процессов или из-за чрезмерной специализации, когда не можешь помочь другой команде).
  3. Нет четкого понимания, что нужно сделать, тратится время на выяснение этого (хорошо еще, если до разработки, плохо, если на миллионе встреч).
  4. Нет четких зон ответственности и ролей — непонятно, кто должен что сделать, чаще всего превращается в “не делает никто”. В том числе когда что-то можно было предотвратить малой кровью вместо героического расхлебывания последствий.
  5. Они не умеют говорить “нет”.
  6. Нет долговременного планирования, слишком большая сосредоточенность на скорости, а не качестве.
  7. Они плохо понимают предметную область и конечного потребителя.
  8. Нет признания результатов, чувства достижения, мотивация делать нормально падает, появляется куча новых проблем.
СсылкаКомментировать

Визуализация explain для SQL-запроса и рекомендации по индексам

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

СсылкаКомментировать