Читать в телеге. Когда-то там были посты не только от меня.
Трейсинг
С удивлением обнаружил, что, оказывается, не все знают про существование трейсинга и зачем он нужен. В простейшем варианте он позволяет отследить все операции, которые совершаются в рамках одного логического запроса — например, как он пришел с фронта, какие микросервисы запрашивались, какие запросы к базе выполнялись и т.п. Для этого обычно достаточно создать уникальный идентификатор запроса (traceId), прокидывать его при всех взаимодействиях (например, через http-заголовки) и добавлять в логах.
При продвинутом использовании можно строить карту сервисов (кто кого вызывает), смотреть графическое представление выполнения запроса (сколько времени он “висел” в каждой конкретной операции или микросервисе). Причем это может быть полезно даже не в микросервисной архитектуре (если у вас есть хоть одна зависимость в виде базы или распределенной очереди).
Сравнительно недавно два конкурирующих стандарта, OpenTracing и OpenCensus, объединили в один — OpenTelemetry. Если у вас попсовый стек, то подключается обычно довольно легко с помощью инструментации на уровне какого-нибудь кубера или на уровне приложения (в JVM-мире это java agent). Визуализировать можно с помощью Jaeger. Если некуда девать деньги, то можно использовать монструозный Dynatrace или облачные решения, типа AWS X-Ray.
Нулевая терпимость к багам
В продолжение старого поста про то, что не надо игнорировать баги, даже если они мелкие. Часто слышал высказывание, мол, зачем тратить время на мелкие правки, на «перфекционизм», что ничего страшного не случится, особенно если баг особо не влияет не на что, что баг дешевле терпеть, чем править.
Однако при этом обычно редко рассматриваются такие факторы, как:
- снижение культуры разработки (например, зачем мне заводить баг, если все равно его не пофиксят, зачем мне стараться делать качественно, если на проде полно багов)
- снижение качества (например, мелкий баг может быть симптомом более крупной проблемы или маркером плохого процесса)
- снижение качества мониторинга (надо разделять известные и неизвестные баги в логах, для этого даже инструменты вроде Sentry делают; метрика количества 5xx может уже не показывать нормальную картину)
- некорректная работа прода (даже не из-за самого бага, а из-за сопутствующих проблем. Например, если плохо обрабатывать ошибки пользователя и кидать 500, то балансировщик может прихлопнуть работоспособный узел, при большой нагрузке такое чревато полной неработоспособностью)
- снижение культуры реагирования на алерты («да забей на этот алерт, он всегда орет из-за того бага»)
- увеличение когнитивной нагрузки (надо помнить известные баги, думать, править ли новые или нет и т.п)
Кому повысить зарплату?
Неплохой доклад про стратегии повышения зарплаты, в котором рассказаны базовые принципы и основные примеры, как не надо делать.
Очевидно, что надо повышать зарплату тем, кто больше приносит пользы бизнесу, а не кому больше надо, кто больше попросит или лучше себя продаст. При этом пользу надо как-то оценивать, но любую метрику, особенно количественную, мыслящий программист легко сломает и она перестанет отражать реальность.
Если оценивать что-то абстрактное, то надо трезво оценивать влияние конкретного сотрудника на это (например, продуктовые метрики могут расти сами по себе, а геройски решенная сложная задача могла быть решена и другим разрабом, просто ему назначили другую).
Одна из неплохих стратегий по мнению докладчика - определить текущее узкое место, и премировать по метрике, способствующей его устранению. Дополнительный совет: вместо измерения всех по какой-то общей шкале стоит опрашивать всех членов команды и сравнивать людей между собой (с правильно подобранными вопросами, разумеется). Кто набрал больше первых мест, тот обычно и молодец. Все-таки, относительные оценки людям даются лучше.
Контейнер Морозов
Продолжаю “ломать” изоляцию сетей:) Давным-давно писал про перенаправление портов, там среди прочих указан port-forward
kubectl port-forward some_service 5672:5672
это прекрасно работает, когда сам сервис развернут в кластере k8s. Но что делать, если он виден, но находится в другом кластере/сети? Форварднуть порты еще раз с помощью временного контейнера :)
kubectl run tmp-port-tunnel --rm --image=alpine/socat -it --expose=true --port=5672 tcp-listen:5672,fork,reuseaddr tcp-connect:some_host:5672
Работа с эффектами в Scala
Хороший видос про этапы развития Scala с точки зрения работы с эффектами (с небольшим предисловием про Haskell) — better Java, акторы, Future, Monix, Scalaz-stream, Cats, FS2, Cats Effect, ZIO.
Кроме этого, Нижников еще рассказывает про общие принципы работы IO монады (звучит страшно, но на самом деле все довольно просто) и сравнивает Cats Effect и ZIO.
Значение по умолчанию в SQL-запросе
Как получить данные по запросу, а если их нет, то использовать значение по умолчанию?
Элементарно, но только если не надо потом эти данные сразу же вставить, да все это еще и в одном запросе. Если бы не было нужно значение по умолчанию, то это тоже решалось бы легко подзапросом.
Можно подзапрос объединить через UNION
с временной табличкой, содержащей строку по умолчанию, но тогда надо очень аккуратно выбрать правильное значение дополнительным условием (возможно, через флаги с их последующим вырезанием).
В итоге у меня родился такой монстр:
INSERT INTO stats(day, key, value)
SELECT '2022-08-04', key, COALESCE(stats."value", 0)
FROM (VALUES(key)) as request(key)
LEFT JOIN stats on request.key=stats.key
WHERE (stats.value IS NULL) OR (stats."day" < '2022-08-04')
ORDER BY stats."day" DESC
LIMIT 1
ON CONFLICT DO NOTHING
Тут производится LEFT JOIN
с временной таблицей request
, чтобы всегда получить хотя бы 1 строку, в которой будет либо null
, либо ответ на запрос. Потом COALESCE(stats."value", 0)
выбирает первое не-null значение.
Запретный плод для Java
Один из популярных вопросов начального уровня на Java-собесе — “можно ли переопределить статический метод?”. И правильный ответ — нельзя.
Однако, если очень надо, то это возможно:
public class Foo {
public static void bar(){
System.out.println("Foo");
}
}
Foo.bar()
Mockito.mockStatic(Foo::class.java)
whenever(Foo.bar()).then { println("not foo") }
Foo.bar()
выведет:
Foo
not foo
Разумеется, если вам это вдруг нужно, то с большой вероятностью вы делаете что-то не то (мало того, что у вас рак в виде java, так еще и со статическими методами, которые требуют переопределения). Однако мне это пригодилось, когда понадобилось протестировать работу с переменными окружения, которые как раз засунуты в статический метод System.getenv()
(для этого я использовал system stubs).
Запретный плод
>>> (666).boo()
I'm just a 666, but boo!
>>> "I can be JS too! " + 2
'I can be JS too! 2'
Одна из фишек питона — возможность monkey-patching. Но многие считают, что у него есть предел, и нельзя патчить стандартную библиотеку. Но на самом деле можно:
>>> from forbiddenfruit import curse
>>> curse(int, "boo", lambda x: print(f"I'm just a {x}, but boo!"))
>>> (666).boo()
I'm just a 666, but boo!
>>> curse(str, "__add__", lambda x,y: x + str(y))
>>> "I can be JS too! " + 2
'I can be JS too! 2'
Это знание позволит вам поиздеваться над коллегами и еще меньше доверять написанному на питоне. Ходят слухи, что библиотека была сделана для тестов, но благими намерениями…
Доступ к базе в приватной сети
Как посмотреть что-то в базе, если она в частной сети, в которую нет прямого доступа, а девопсы не настроили ничего вроде VPN? По идее, она должна быть видна приложению, но в стильно-модно-молодежной среде приложение лежит в контейнере, в котором ничего для доступа к базе (например, psql
) нет.
Решение достаточно простое — запустить одноразовый контейнер с чем нужно.
docker run --rm -it postgres:14 /bin/bash
или
kubectl run -it --rm tmp-psql --image=postgres:14 --restart=Never -n prod --command /bin/bash
Интересных флагов тут два: --rm
, который говорит о том, что надо удалить под/контейнер после использования, и --restart=Never
для того, чтобы создался только просто под, а не deployment.
(Как все просто было во времена железных серверов — просто провалился через ssh
на хост и все).
Тестовые флаги
Использование условий вида if (test)
или if (user in testUsers)
— это рак.
Во-первых, половина программирования, если не больше — про управление зависимостями. Если реально нужно отключить какую-то функциональность на тестовой среде (оплату какую-нибудь), то есть куча нормальных способов это сделать: например, подсунуть класс-заглушку или http-заглушку, если вы живете в микросервисной архитектуре.
Во-вторых, тестироваться будет не основной код, а костыли внутри условия. Причем подобные костыли часто просят добавить как раз тестировщики.
В-третьих, это источник багов (а иногда и дырок в безопасности). Сейчас не каждый понимает необходимость юнит-тестов, а уж если начнешь заикаться о тестировании тестового кода — сразу в дурку поведут.
Иногда подобным образом переключают часть функциональности (хотя для этого есть feature-флаги). Иногда так еще “упрощают” жизнь для нагрузочных тестов (т.е. тестируют не настоящую работу) или “для отладки” (хотя для этого есть ленивые логи с уровнями логгирования или внешняя инструментация).
Мне тяжело придумать нормальные обстоятельства, когда использование подобных условий будет оправдано. А поддерживать их накладно, они осложняют рефакторинг и засоряют код.