tgindex
Сохранёнки программиста

Сохранёнки программиста

Статистика

Заметки и ссылки на будущее, чтобы изучить когда будет время. Разместить рекламу: @tproger_sales_bot Правила общения: https://tprg.ru/rules Другие каналы: @tproger_channels Другие наши проекты: https://tprg.ru/med

Последний пост
18:34
Последнее чтение
21:31
Постов за неделю
8
Всего постов
172
Тип
открытый
Язык
русский
Категория
Технологии
В каталоге с
12 авг.
Подписчики
6 560
−9 за 4 дн.
Сутки
−2
−0,03%
Неделя
 
Месяц
 
Просмотров на пост
545
40 постов
Вовлечённость
8,3%
к подписчикам
Постов в день
1,1
всего 172
Упоминаний
4
каналов
Охват размещения
оценка
1/24сутки в ленте
360
1/48двое суток
412
1/72трое суток
444

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 18:3416351

    @prog_stuff

  • 16:052042

    Новак Калуджерович писал арифметику больших чисел для криптографической библиотеки и нашёл ошибку в Algorithm D — том самом длинном делении из книги Кнута, которое перепечатано в сотнях реализаций. Суть места, где всё ломается: очередная цифра частного сначала оценивается по старшим разрядам, а потом оценка исправляется. Кнут утверждает, что после коррекции она гарантированно помещается в один разряд. Контрпример нашёлся в системе счисления по основанию 3: делим 45 на 16, правильное частное 2, а пробная оценка 5. После двух коррекций оценка всё ещё двухразрядная, и дальнейшее умножение берёт только младший разряд, вычитая фактически ноль. Ошибка десятилетиями оставалась незаметной, потому что зависит от того, как именно ведёт себя аппаратное деление на конкретной архитектуре. На современных машинах с основанием два в шестьдесят четвёртой она практически не проявляется. Автор написал Кнуту и получил тот самый гексадецимальный доллар — чек за найденную ошибку — с рукописной пометкой, что Algorithm D читают чаще многих других алгоритмов книги. @prog_stuff

  • 07:052552

    Полгода Tailscale ловила порчу баз: 19 случаев, ни одного общего признака — ни шарда, ни клиента, ни времени суток. Воспроизвести не удавалось, оставалось ждать следующего раза с диагностикой наготове. Зацепку дал побочный инструмент. Чтобы восстанавливаться без потери данных, команда стала писать журнал всех изменяющих запросов и проигрывать его поверх копии. Дважды журнал не проигрался: данные, зафиксированные одной транзакцией, оказались невидимы для следующих — без единой ошибки. Виновата гонка в самом SQLite: при совпадении записи с контрольной точкой часть страниц считается перенесённой в основной файл, хотя туда не попала. Багу оказалось не меньше шестнадцати лет, он настолько редкий, что для тестов его приходится вызывать намеренно. Tailscale попадала в него чаще других, потому что берёт контрольные точки под ручное управление и делает их очень часто. Вывод авторов: скучная технология в нестандартном режиме перестаёт быть скучной. Всё было по документации — просто в стороне от протоптанной дороги. Как искали и что нашли @prog_stuff

  • @prog_stuff

  • Лимит одновременных транзакций подняли с тысячи до десяти тысяч, чтобы убрать ошибки, и получили шестнадцать минут деградации Лиз ван Дейк из PlanetScale разобрала 7 августа инцидент, в котором продакшен-база MySQL складывалась на шестнадцать минут. Ошибки шли по нарастающей: на нулевой минуте 5 ошибок в минуту при 15 000 запросов в секунду, на десятой 1400 ошибок и 1500 запросов, потом транзакцию убил таймаут, накопленное разгреблось примерно за тридцать секунд, и пропускная способность подскочила до 8500. Повод банальный: пакетная задача открыла транзакцию к горячей таблице, взяла блокировки строк и держала их пятнадцать минут без коммита. Интересно то, что происходило вокруг неё. Запросы, копившиеся следом, в основном на блокировках вообще не стояли: это обычные чтения, которым InnoDB отдаёт согласованный снимок. Но построение снимка требует пройти назад по истории версий каждой затронутой строки, а эта история росла все пятнадцать минут. 🔘 чтения, которые обычно занимают миллисекунды, начали упираться в потолок выполнения в 90 секунд, приложение повторяло их в тесном цикле; 🔘 за минуты внутри движка хранения скопилось больше десяти тысяч запросов, каждый восстанавливал всё более длинную цепочку версий; 🔘 чтения страниц обогнали способность буферного пула освобождать память, и падать начали запросы к совершенно другим таблицам, никак не связанным с заблокированными строками; 🔘 предыстория в том, что нагрузку переехали с прежней базы, где перед сервером стоял пул потоков: он ограничивал число одновременных запросов примерно тысячей, а остальных ставил в очередь на миллисекунды; 🔘 на новом месте пул при переполнении не ставит в очередь, а ждёт фиксированное время и возвращает ошибку. Приложение таких ошибок никогда не видело и обрабатывать их не умело, поэтому лимит подняли до десяти тысяч. Число выбрали не по расчёту, а потому что с ним ошибки прекратились; 🔘 лечение оказалось обратным: лимит вернули примерно к тысяче и заменили ошибку ожиданием в очереди с увеличенным таймаутом, то есть повторили поведение прежнего пула потоков. На следующее утро конфигурация прошла проверку на другом шарде, который держит на порядок больше трафика: на пике около 25 тысяч заявок на слот в секунду при тысяче слотов, ноль ошибок и ровные 60 тысяч запросов в секунду. За весь день отклонена одна транзакция, а внутри MySQL в каждый момент выполнялось меньше двухсот команд: по закону Литтла это примерно три миллисекунды на запрос. Автор оговаривает, что это не универсальный совет всегда зажимать параллелизм. Совет работает там, где есть пессимистичные блокировки и общее состояние: горячие строки, SELECT ... FOR UPDATE по популярным ключам, счётчики, балансы, очереди задач. И это не свойство MySQL: в PostgreSQL бывает то же самое, а роль ограничителя там играет PgBouncer. Полная статья: https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql @prog_stuff

  • Половину времени компиляции двух пакетов занимал memcpy, потому что элемент вектора весил 136 байт Николас Незеркот, десять лет занимающийся производительностью компилятора Rust, подвёл 31 июля итоги восьми месяцев работы. Средняя экономия времени сборки с 3 декабря 2025 по 29 июля 2026 составила 5,59 процента, но примерно половину этого дал не сам компилятор, а генератор документации: там среднее время упало на 37,92 процента, а по всем его тестам суммарно на 28. Без учёта документации выигрыш компилятора 2,90 процента. Самая наглядная история из отчёта как раз про memcpy. Автору показали пару пакетов, где новый решатель типажей резко тормозил. Профилирование через Cachegrind показало, что почти половина времени уходит на копирование памяти: 🔘 виноват оказался горячий вектор с элементом размером 136 байт: LLVM для значений больше 128 байт вставляет вызов memcpy вместо обычных инструкций; 🔘 автор убрал две трети перемещений и ужал тип до 104 байт, чтобы порог не срабатывал, и время сборки двух худших пакетов упало на 40 процентов; 🔘 отдельная линия — узлы синтаксического дерева: выражения ужали с 72 до 64 байт, чтобы они помещались в кэш-линию, и на деревоёмких тестах время упало более чем на 10 процентов, а доля промахов кэша до 29; 🔘 автор помнит времена, когда узел выражения весил 104 байта: хорошая производительность набирается такими шагами годами; 🔘 в Clippy нашлась классическая проблема виртуальных вызовов: сотни проверок, каждая объявляет несколько методов обхода, и на каждом узле дерева вызывались все, включая пустые. После объединения проходов время упало на 10–30 процентов, а неверные предсказания ветвлений — на 20–80, а на одном стресс-тесте на 97; 🔘 новый решатель типажей за три месяца прошёл путь с 27 секунд до менее чем секунды на одном из своих тестов. Автор оговаривает, что цифры по Clippy получены локально: в постоянном замере на CI он пока не участвует. Забавная деталь про Clippy: параллельно ту же проблему нашёл другой человек и прислал более аккуратное решение на несколько дней раньше, так что правка автора не влилась. Полная статья: https://nnethercote.github.io/2026/07/31/how-to-speed-up-the-rust-compiler-in-july-2026.html @prog_stuff

  • Время до первого ревью выросло с 3,5 часа до 16, хотя кода стали писать больше Джон Ван, технический директор Assembled, разобрал 7 августа, что случилось с их разработкой после перехода на агентов. Инженеры чувствовали, что стали быстрее, а метрики поставки почти не двигались: пул-реквестов заводили больше, а вливали ненамного больше прежнего. В данных нашлось объяснение. Открытых пул-реквестов стало заметно больше, стопки выросли до двадцати штук на одну задачу, размер одной правки увеличился в 2,5 раза, потому что агент пишет более объёмный дифф на то же изменение. Время до первого ревью поднялось с медианных 3,5 часа в декабре 2025 до 16 с лишним часов в мае 2026. Узким местом оказалось человеческое ревью, а число ревьюеров осталось прежним. В ответ собрали автоматического ревьюера для малорисковых правок: 🔘 каждый пул-реквест независимо смотрят два агента в отдельных песочницах, а третий сводит оба отзыва и применяет политику одобрения; 🔘 в политике есть жёсткие правила, например не одобрять правки больше тысячи изменённых строк, и содержательные: не трогать аутентификацию, биллинг, права доступа, работу с секретами и ключами, не вводить новых архитектурных приёмов; 🔘 первая версия политики оказалась слишком строгой: она отклоняла столько, что люди переставали ею пользоваться, а отключённым ревьюером ничего не ускоришь; 🔘 обратная крайность опаснее: слишком мягкая политика превращается в канал, через который сознательно проводят то, что не хочется показывать человеку; 🔘 после полного запуска автоодобрения дали 43 процента всех влитых правок, а медиана такого ревью — восемь минут; 🔘 инженеры начали подгонять пул-реквесты под критерии политики, так что она работает как система стимулов: чего требуешь, того и становится больше. По результатам за три недели влитых правок стало в 2,4 раза больше, чем до агентов, строк кода в 2,8 раза, то есть работу не просто нарезали мельче. Пропускная способность больших правок от 600 строк выросла в 3,5 раза, хотя их автоматический ревьюер обычно не одобряет. Рефакторинг вырос в 7,5 раза, работа над тестами в 3,4. Сообщений о багах стало на 27 процентов меньше, доля откатов держится на 0,8 процента. Автор сам оговаривает, что это наблюдение за раскаткой, а не контролируемый эксперимент, и окно слишком короткое: баги приходят с задержкой, поэтому по долгосрочному качеству выводов пока нет. Полная статья: https://www.assembled.com/blog/code-review-bottlenecks-how-we-hill-climbed-our-way-to-higher-pr-throughput @prog_stuff

  • Тот же файл, разложенный с 17 155 строк до 3 695, снизил стоимость одной правки со 159 564 входных токенов до 27 360 Мартин Фаулер опубликовал 30 июля эксперимент: можно ли измерить выгоду рефакторинга, если код правит агент. Приложение примерно на 150 тысяч строк, из них около 120 тысяч на Rust. Слой доступа к данным разросся в один файл на 17 155 строк, без выделенных функций, без внутреннего языка и почти без классов, зато с чистой границей и интерфейсом наружу. Замер устроен так: берётся одна показательная правка, описанная одним запросом. Её выполняет свежий подагент и сообщает израсходованные токены, изменения выбрасываются. Дальше применяется один шаг рефакторинга, и та же правка выполняется заново. Агент ничему не учится между шагами, поэтому эксперимент не портится накопленным опытом, как испортился бы с живым инженером. 🔘 базовый замер: 159 564 входных токена, 1705 выходных, 342 секунды на правку; 🔘 после пятнадцати шагов рефакторинга самый большой файл ужался до 3 695 строк, а входные токены упали до 27 360, то есть на 83 процента; 🔘 объём всего слоя почти не изменился: 17 155 строк превратились в 16 608 строк в 19 файлах, так что экономия идёт не от количества кода; 🔘 расход держался ровно, пока падал только общий объём, и обвалился ровно тогда, когда начал уменьшаться самый большой файл; 🔘 по логам видно, что агент с каждым шагом читал всё меньшие куски кода: выигрыш в том, что он стал находить нужный файл, а не перебирать весь слой; 🔘 при цене Sonnet 5 в три доллара за миллион токенов экономия на одной правке составляет 39,7 цента, зато повторяется при каждом следующем изменении в этом слое. Фаулер отдельно оговаривает, что случайная нарезка большого файла на мелкие так не сработает: агенту пришлось бы читать много файлов подряд в поисках нужного места. Счёт токенов приблизительный, Claude не отдаёт надёжный способ считать их на лету, поэтому подагент считал символы и делил на четыре. Эксперимент идёт на одном приложении, написанном агентами с нуля, а стоимость самого рефакторинга в эти 39,7 цента не входит. Полная статья: https://martinfowler.com/articles/exploring-gen-ai/refactoring-economic-benefit.html @prog_stuff

  • Цикл ускорился с 3,1 до 45 гигабайт в секунду после того, как из него убрали ранний выход Александр Нойбек и Грег Орцелл из GitHub рассказали 31 июля, как в поиске по коду приводят текст к одному регистру. Операция простая, но Blackbird, поисковый движок GitHub, прогоняет через неё каждый байт: 180 миллионов репозиториев, больше 480 ТБ исходного кода, и для каждого потенциального результата запроса свёртка регистра нужна снова. Привычный приём выглядит разумно: идти по байтам, а на первом не-ASCII прерваться и отдать остаток полноценному Unicode-пути. На Apple M4 это даёт 3,1 ГиБ/с. Оказалось, что тормозит именно ранний выход: пока условие выхода из цикла зависит от данных, компилятор не может его векторизовать. 🔘 если убрать break, но оставить ветвление в теле, LLVM векторизует наполовину: 7,6 ГиБ/с и 25 векторных инструкций; 🔘 полностью безветвевой проход даёт больше 45 ГиБ/с и 41 векторную инструкцию, это уже пропускная способность памяти; 🔘 безветвевое тело с сохранённым break медленнее наивного варианта, 2,6 против 3,1 ГиБ/с: безусловная запись каждого байта обходится дороже редкой хорошо предсказанной ветки; 🔘 компромисс из стандартных библиотек, когда сначала сканируют машинными словами по 16 байт, а потом сворачивают регистр, читает данные дважды и упирается в 23 ГиБ/с; 🔘 попытка слить эти два прохода в один даёт 8,7 ГиБ/с: ветка, зависящая от данных, возвращается каждые 16 байт, и цикл снова обрабатывает по одному блоку без разворачивания; 🔘 таблица для редкого пути ужата до 1776 байт: 1484 отображения Unicode 16.0 уложились в 238 диапазонов на 59 страницах из примерно 1960, и свёртка идёт арифметикой прямо над байтами UTF-8, без декодирования символа. Авторы оговариваются, что замеры иллюстративны и сильно зависят от микроархитектуры. Библиотека покрывает только простые отображения регистра: немецкое ß в ss и турецкие правила в неё не входят. Арифметика над байтами требует корректного UTF-8 в кратчайшей форме записи. Полная статья: https://github.blog/engineering/architecture-optimization/dont-stop-early-case-folding-source-code-at-memory-speed/ @prog_stuff

  • PostgreSQL нарисовали как 3D-город: буферный пул отражающим бассейном, WAL янтарным районом, грязные страницы красными плитками Николай Самохвалов выложил PGSimCity, браузерную модель кластера PostgreSQL в виде города. Репозиторий создан 25 июля. Кластер показан целиком: 16 backend-процессов с подсветкой состояния, включая idle in transaction, буферный пул, показанный выборкой из 1024 кадров, кварталы WAL, обслуживания, реплик и восстановления. Хранилище доведено до уровня heap-файлов, страницы по 8 КиБ лежат полями, B-деревья нарисованы деревьями, рядом TOAST, FSM, visibility map и страничный кэш ОС. Цвета несут смысл: чистые страницы синие, vacuum фиолетовый, чекпоинты розовые, репликация оранжевая. 🔘 сценарий Cache thrash ставит shared_buffers в 16 МиБ, и clock sweep начинает гонку, а бэкенды сами вытесняют грязные страницы; 🔘 сценарий Checkpoint storm, или шторм контрольных точек, показывает стену full-page writes после каждого чекпоинта; 🔘 сценарий с долгой транзакцией: горизонт xmin тонет, autovacuum до таблиц доезжает, но сообщает ноль удаляемых строк, а таблица продолжает раздуваться; 🔘 Query lab показывает стадии разбора, переписывания, планирования и выполнения запроса, а по желанию запускает PGlite, настоящий PostgreSQL, собранный в WebAssembly; 🔘 модель привязана к PostgreSQL 18: значения по умолчанию сверены с релизом 18.3 и исходниками ветки REL_18_STABLE, прошли три раунда ревью; 🔘 детерминированные тесты падают в CI при расхождении и фиксируют формулы, например долю попаданий в кэш как blks_hit, делённое на сумму blks_hit и blks_read. Автор оговаривает, что это модель, а не эмулятор: код Postgres в городе не исполняется, числа масштабированы, чтобы их можно было разглядеть. Анимация буферов использует фиксированное кольцо из 32 кадров вместо реального правила PostgreSQL 18 для массового чтения. Управление касанием проверялось только в мобильной эмуляции Chrome. Полная статья: https://github.com/NikolayS/PGSimCity#readme Сохранять тем, кому нужно объяснить коллеге или себе, что происходит между чекпоинтом, autovacuum и буферным пулом, когда база вдруг начинает тормозить. @prog_stuff

  • На одних и тех же данных среднее выросло на 9 процентов, а медиана упала на 46 По одному и тому же набору замеров агрегаты latency расходятся в разные стороны, и понять по ним, стало быстрее или медленнее, невозможно. Фарид Закария показал 27 июля, почему так выходит и какими графиками это разбирать. Мотивация прикладная: ускорения линкера lld видны в бенчмарках, но тонут в шуме продакшен-дашбордов. Сценарий: за неделю выкатывают кэширующий слой. Один и тот же набор замеров даёт среднее плюс 9 процентов, со 112 до 122 мс, медиану минус 46 процентов, с 99 до 54 мс, p95 плюс 103 процента и p99 плюс 119 процентов. 🔘 плотность распределения объясняет противоречие: до выката один пик, после два; 🔘 кривые CDF до и после пересекаются около 140 мс, ниже этой границы запросы ускорились, выше замедлились, поэтому ни один перцентиль не описывает эффект целиком; 🔘 shift function, то есть разность по каждому перцентилю, показывает и величину, и знак эффекта по всему распределению сразу; 🔘 ridgeline по дням раскатки с логарифмической осью X ловит, как быстрый пик уезжает влево, а медленный растёт справа, при том что heatmap новую популяцию почти не показывает; 🔘 разрез по попаданию в кэш возвращает унимодальность: попадания быстрее базовой линии, промахи медленнее из-за лишнего сетевого хопа; 🔘 совместный график latency и размера ответа даёт причину: большие объекты не влезают в кэш, бимодальность размера порождает бимодальность времени. Починить можно двумя способами: поднять максимальный размер объекта в кэше или резать большие ответы на части. В рабочей практике автор режет по размеру бинарников, порог 50 MiB. Оговорки самого автора: датасет синтетический с фиксированным seed, данные и графики сделаны с помощью ИИ, а KDE чувствительна к параметру сглаживания. Полная статья: https://fzakaria.com/2026/07/27/the-mean-means-nothing Сохранять тем, кому после выката приносят дашборд, где среднее, медиана и p99 указывают в разные стороны, и просят сказать, стало лучше или хуже. @prog_stuff

  • После 243 одинаковых блоков узлы разошлись, итоговые хеши 0xfaf6ba1e и 0xdab0977c Antithesis 27 июля рассказала, как прогнала через свой фаззинг четыре реализации Raft: HashiCorp Raft, Aeron Cluster, OpenRaft и MicroRaft. Ошибки нашлись во всех четырёх. Авторы сразу оговариваются, что это не претензия к протоколу и не претензия к командам: Raft тяжело реализовать без ошибок в принципе. Raft отвечает за согласие узлов в распределённой системе, и у него есть формальная спецификация на TLA+. Но спецификация описывает не всю реализацию: снапшоты, замена реплик и повреждение пакетов в неё не входят, а живут именно в коде. 🔘 стенд собран из трёх узлов и машины состояний Chain of Blocks, которая просто хеширует случайные байтовые команды; 🔘 инвариант тривиальный: у всех узлов на одной позиции цепочки должен совпадать хеш; 🔘 сбои вносились детерминированно, без убийства узлов, рестартов и порчи диска, хватило сетевых разделений и помех; 🔘 до блока 243 хеши совпадали, на 244 узлы применили разные данные, дальше расхождение только копилось; 🔘 в HashiCorp Raft нашли три ошибки, одну на безопасность и две на живучесть, причём часа тестирования хватило на отчёт; 🔘 асинхронные heartbeats обрабатывались вне основного цикла протокола, и гонка со сменой term могла затронуть четыре из пяти свойств безопасности. Две другие ошибки бытовые по последствиям. Передача лидерства оставляла goroutine заблокированной, а флаг передачи поднятым, и переизбранный узел переставал принимать клиентов до перезагрузки. Обработка InstallSnapshot не отбрасывала расходящийся неподтверждённый лог, отсюда повторные снапшоты, livelock и потенциально неограниченный рост временных файлов. Вывод авторов: формальная модель и тестирование со сбоями закрывают разные дыры, спецификация не заменяет проверку живого кода. Полная статья: https://antithesis.com/blog/2026/finding-bugs-in-raft-implementations/ Сохранять тем, кто выбирает библиотеку консенсуса под свой сервис и собирается верить ей на слово, потому что «протокол доказан». @prog_stuff

  • Первый открытый файл обычно получает дескриптор 3, потому что ядро выдаёт минимальный свободный номер в таблице процесса Хесус Эспино 13 июля объяснил виртуальную файловую систему Linux, тот слой, из-за которого одни и те же вызовы одинаково работают с ext4, с /proc и с примонтированной по сети NFS. Устроен он на C-структурах с указателями на функции: каждая файловая система подставляет свои реализации, а ядро вызывает их через общие имена. Объектов в этой модели четыре, и разделение между ними отвечает на бытовые вопросы вроде «почему у файла может быть два имени». 🔘 superblock представляет конкретную смонтированную файловую систему; 🔘 inode хранит сам файл, каталог или ссылку вместе с метаданными, но собственного имени не знает; 🔘 dentry связывает имя внутри родительского каталога с inode, и ядро кэширует в том числе отрицательные dentry для отсутствующих файлов; 🔘 struct file создаётся на каждое открытие, поэтому два открытия одного inode имеют независимые позиции чтения; 🔘 набор file_operations даёт принцип «всё является файлом»: сокеты, устройства, epoll и таймеры живут за дескрипторами с совершенно разным поведением; 🔘 дескриптор — это просто индекс в таблице указателей struct file *, а номера 0, 1 и 2 обычно уже заняты стандартными потоками ввода, вывода и ошибок. Открытие /etc/hostname идёт компонент за компонентом, и при попадании в кэш каталогов до самой файловой системы дело не доходит. Режим RCU-walk позволяет читателям обходить закэшированные пути почти без блокировок, переключаясь на обычные блокировки, когда путь меняют. Чтение потом проходит через страничный кэш: первый cat поднимает страницу с диска, а следующие обслуживаются из оперативной памяти. Полная статья: https://internals-for-interns.com/posts/linux-kernel-vfs/ Сохранять тем, кто разбирает странности с I/O: файл удалён, а место не освободилось, или две ручки на один файл почему-то читают из разных мест. @prog_stuff

  • Потребитель получил ответ 204, закоммитил offset, а задачу через миллисекунды отклонили Сахан Серасингхе 12 июля разобрал класс потерь в событийных системах, где сообщение пропадает без исключения, без строчки в логе и без попадания в очередь недоставленных. Он называет это разрывом подтверждения: приём запроса подтверждён, а работа так и не началась. Доставка at-least-once держится на повторах и идемпотентных потребителях, и оба механизма опираются на одно допущение: если обработка не удалась, потребитель об этом узнает. Но ответ 202 Accepted значит «запрос принят», а не «работа выполнена», и даже 204 No Content бывает двусмысленным. Коммит offset при этом означает куда более сильное: все сообщения до N обработаны полностью и повторять их нельзя. 🔘 типичный сценарий: потребитель отправляет задачу, получает 204, коммитит offset, а управляющий слой через миллисекунды отклоняет её из-за квоты, admission control или переполнения внутренней очереди; 🔘 проверка только на err != nil опасна, потому что самый разрушительный исход выглядит как nil; 🔘 граница может быть любой асинхронной: брокер очередей, workflow engine, фоновый обработчик, HTTP-диспетчер; 🔘 повторы, идемпотентность и транзакция брокера тут не спасают, раз подтверждается только приём задачи; 🔘 надёжнее подтверждать запуск отдельно: чтение после записи или опрос по correlation ID, а явный отказ считать ошибкой; 🔘 повторы ограничивают бюджетом, а после его исчерпания шлют алерт или отправляют сообщение в очередь недоставленных. Автор отдельно оговаривает обратный риск. Ошибка самой проверки состояния не должна автоматически порождать новую отправку, иначе вместо потерянного сообщения появится дубликат, и вся идемпотентность на стороне обработчика уедет в мусор. Полная статья: https://sahansera.dev/the-acknowledgment-gap/ Сохранять тем, кто отлаживает пропажу заказов или платежей, которые в логах отправителя выглядят успешно обработанными. @prog_stuff

  • Админский токен GitHub уехал в прошивку камеры вместе со сборкой веб-интерфейса Автор блога разобрал прошивку камеры видеонаблюдения Hanwha Vision, бывшей Samsung Techwin, и нашёл внутри токен GitHub с правами администратора на сотни репозиториев компании. Путь до находки: 🔘 внешний архив прошивки открывается паролем HTW плюс номер модели, внутри лежит ещё один зашифрованный fwimage.tgz; 🔘 ключ AES-256-CBC собирается в момент запуска из XOR-таблицы внутри бинарника fwupgrader, вектор инициализации лежит там же открытым текстом, а сам fwupgrader просто вызывает консольный openssl; 🔘 распакованный rootfs автор прогнал через trufflehog, и токен нашёлся примерно в тридцати файлах; 🔘 попал он туда из-за сборки интерфейса на Vite: одной переменной при сборке присвоили целиком process.env, поэтому окружение CI-задачи целиком записалось в файлы интерфейса вместе с токеном npm, служебными переменными Kubernetes и внутренними адресами. Значит, токен с большой вероятностью уходил по сети каждому, кто открывал админку камеры. Чтобы понять масштаб, автор скачал около 500 прошивок примерно из 600 моделей, расшифровал тем же способом 62% и нашёл токен только в трёх, причём везде один и тот же. Hanwha ответили на письмо за 12 часов и отозвали токен. Отдельная деталь: среди служебных переменных оказались адреса из диапазона 55.101.x.x, который принадлежит Министерству обороны США. Компания ответила, что не знала об этом и унаследовала схему адресации от Samsung Techwin, а теперь собирается её менять. Полная статья: https://hhh.hn/hanwha-github-token/ Сохранять тем, кто прокидывает переменные окружения в сборку фронтенда и ни разу не смотрел, что в итоге лежит в собранном бандле. @prog_stuff

  • Postgres упирался в 2900 записей в секунду, не нагружая при этом ничего Команда DBOS разобрала, откуда у LISTEN/NOTIFY репутация нерабочего при нагрузке механизма, и переписала свой стриминг до 60 тысяч записей в секунду на одном сервере. Исходная схема обычная: каждый кусок потока, например токен ответа модели, становится строкой в таблице, а триггер на вставку шлёт NOTIFY, чтобы читатели просыпались вместо опроса. Дальше 2900 записей в секунду эта конструкция не шла, причём ни процессор, ни диск, ни память базы заняты не были. Причина в том, что коммит транзакции с NOTIFY берёт глобальный эксклюзивный лок и держит его до конца коммита вместе с fsync. Postgres обещает доставлять уведомления в порядке коммитов, а сам порядок известен только когда коммит завершён: лок решает это противоречие ценой полной сериализации. Групповой коммит при этом отключается, и записи идут строго по одной. 🔘 патч, который ждут в Postgres 19, глобальный лок не убирает. Он оптимизирует более узкий случай, когда каналов много и каждый слушатель ждёт свой; 🔘 обход: копить уведомления в памяти и сбрасывать пачкой одной транзакцией, тогда лок берётся раз на пачку, а не на каждую запись; 🔘 плата за это — уведомления, потерянные при падении процесса, поэтому читатели вдобавок редко опрашивают таблицу как запасной путь; 🔘 после переделки упор идёт уже в процессор базы, а не в блокировку. Про задержку авторы пишут 15–100 мс, но по их же графику это диапазон для нагрузки до 40 тысяч записей в секунду. Ближе к 60 тысячам медиана уходит примерно к 0,5 с, а 99-й перцентиль к секунде. Код бенчмарка выложен, конкретный инстанс базы в статье не назван. Полная статья: https://www.dbos.dev/blog/postgres-listen-notify-scalability Сохранять тем, кто держит очередь или пуш-уведомления прямо в Postgres и однажды упрётся в потолок, которого не видно ни в одном мониторинге. @prog_stuff

  • SIMD стоит знать не только авторам simdjson Митчелл Хашимото, автор Vagrant, Terraform и терминала Ghostty, разобрал бытовой SIMD на живом коде своего терминала и свёл его к одной схеме из пяти шагов. Примеры на Zig, но идея общая для любого языка с поддержкой векторов. Задача такая: найти конец очередного печатаемого куска текста, то есть первый кодпоинт со значением 0xF или ниже. Скалярный вариант укладывается в одну строку цикла, векторный добавляет к нему двенадцать строк без единого интринсика под конкретный процессор. 🔘 порог сравнения размножается по всем линиям через @splat, а ширину вектора отдаёт хелпер: 4 значения u32 на ARM NEON, 8 на AVX2, 16 на AVX-512; 🔘 цикл шагает сразу на целый вектор, а не на одно значение; 🔘 сравнение values > threshold выполняется для всех линий одной инструкцией процессора; 🔘 свёртка @reduce(.And, ...) отвечает, прошли ли все линии, а @bitCast и @ctz показывают номер первой упавшей; 🔘 остаток данных и процессоры без нужной ширины вектора обслуживает тот же скалярный цикл, с которого всё начиналось. Потолок ускорения равен числу линий: в 4, 8 или 16 раз. В сквозном замере от программы до готового состояния терминала на десктопе с AVX2 вышло примерно в 5 раз. Автор отдельно объясняет, зачем писать это руками: компиляторы векторизуют мало и непредсказуемо, а неявная оптимизация может тихо исчезнуть после правки соседнего кода или обновления компилятора. Полная статья: https://mitchellh.com/writing/everyone-should-know-simd Сохранять тем, кто видит в профайлере горячий цикл по большому массиву и до сих пор считает SIMD чужой территорией. @prog_stuff

  • Сервис на виртуальных потоках упёрся в 420 запросов в секунду при 9% CPU Полевой гайд на Foojay разбирает типичный инцидент с виртуальными потоками Java: сервис перестал масштабироваться на ~420 запросах в секунду, хотя процессор загружен на 9%. Арифметика сходится точно: 8 CPU × (1000 / 19 мс внешнего HTTP-вызова) ≈ 421. Причина — pinning: виртуальный поток занимает carrier thread, и приложение незаметно превращается обратно в ограниченный пул платформенных потоков. До JDK 24 pinning часто вызывают блоки synchronized, ещё один источник — native-код. 🔘 для диагностики в JDK 21–23 есть флаг jdk.tracePinnedThreads, в JFR — событие jdk.VirtualThreadPinned; 🔘 лечение — ReentrantLock вместо synchronized, обновление JDK и отказ от удержания блокировки на время медленных сетевых вызовов. Полная статья: https://foojay.io/today/virtual-thread-pinning-field-guide/ Сохранять тем, у кого Loom в проде и график RPS выходит на плато задолго до загрузки CPU. @prog_stuff

  • Postgres 19 меняет сжатие по умолчанию: с pglz на LZ4 В PostgreSQL 19 планируется смена алгоритма сжатия TOAST, который четверть века по умолчанию был pglz. Разбор от Crunchy Data объясняет, что именно поменяется для TEXT, VARCHAR, BYTEA и JSONB. 🔘 в тесте автора 2000 строк по ~10 КБ сжались за 6 мс с LZ4 против 50 мс с pglz — примерно в 8 раз быстрее, распаковка близка по времени; 🔘 на одном наборе данных LZ4 дал 111 байт из 10 400 исходных (98,9% сокращения) против 186 байт у pglz, но на других данных pglz может выигрывать по размеру; 🔘 статья подробно разбирает порог TOAST около 2040 байт и порядок «сначала сжать, потом вынести наружу», наружу уходит 18-байтовый указатель; 🔘 для индексов есть практический предел: строка длиннее ~2704 байт после сжатия в индекс не помещается — повторяющаяся строка на 5000 символов ужимается до 38 байт и проходит, случайная строка на 2816 символов уже нет. Полная статья: https://www.crunchydata.com/blog/postgres-19-compression-from-pglz-to-lz4 Сохранять тем, кто хранит в Postgres большие JSONB и ни разу не думал, каким алгоритмом они сжаты. @prog_stuff

  • $ORIGIN для загрузчика ELF — без правки ядра Динамический загрузчик в ELF прописан абсолютным путём, поэтому relocatable-бинарники для Nix, Buck и Bazel приходится собирать с костылями. Фарид Закария показал, как научить Linux подставлять интерпретатор относительно расположения самого бинарника — связкой eBPF и binfmt_misc, не трогая VFS. 🔘 программа eBPF проверяет ELF magic, берёт путь исполняемого файла и вызывает bpf_binprm_set_interp до запуска процесса; 🔘 регистрация — struct_ops плюс запись в /proc/sys/fs/binfmt_misc/register; 🔘 при обычной передаче управления через exec могут измениться argv[0], /proc/<pid>/cmdline и /proc/self/exe — здесь этих артефактов нет; 🔘 отдельный сегмент PT_INTERP_NIX включает новую механику только для явно помеченных файлов, старые бинарники работают как раньше. Полная статья: https://fzakaria.com/2026/07/20/linux-kernel-will-support-origin-sort-of Сохранять тем, кто собирает relocatable-бинарники и хочет увидеть, на что ещё способен eBPF. @prog_stuff

Сохранёнки программиста — tgindex