На одних и тех же данных среднее выросло на 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
По одному и тому же набору замеров агрегаты latency расходятся в разные стороны, и понять по ним, стало быстрее или медленнее, невозможно. Фарид Закария показал 27 июля, почему так выходит и какими графиками это разбирать. Мотивация прикладная: ускорения линкера lld видны в бенчмарках, но тонут в шуме продакшен-дашбордов.
Сценарий: за неделю выкатывают кэширующий слой. Один и тот же набор замеров даёт среднее плюс 9 процентов, со 112 до 122 мс, медиану минус 46 процентов, с 99 до 54 мс, p95 плюс 103 процента и p99 плюс 119 процентов.
Починить можно двумя способами: поднять максимальный размер объекта в кэше или резать большие ответы на части. В рабочей практике автор режет по размеру бинарников, порог 50 MiB. Оговорки самого автора: датасет синтетический с фиксированным seed, данные и графики сделаны с помощью ИИ, а KDE чувствительна к параметру сглаживания.
Полная статья: https://fzakaria.com/2026/07/27/the-mean-means-nothing
Сохранять тем, кому после выката приносят дашборд, где среднее, медиана и p99 указывают в разные стороны, и просят сказать, стало лучше или хуже.
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
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
Николай Самохвалов выложил PGSimCity, браузерную модель кластера PostgreSQL в виде города. Репозиторий создан 25 июля. Кластер показан целиком: 16 backend-процессов с подсветкой состояния, включая idle in transaction, буферный пул, показанный выборкой из 1024 кадров, кварталы WAL, обслуживания, реплик и восстановления.
Хранилище доведено до уровня heap-файлов, страницы по 8 КиБ лежат полями, B-деревья нарисованы деревьями, рядом TOAST, FSM, visibility map и страничный кэш ОС. Цвета несут смысл: чистые страницы синие, vacuum фиолетовый, чекпоинты розовые, репликация оранжевая.
Автор оговаривает, что это модель, а не эмулятор: код Postgres в городе не исполняется, числа масштабированы, чтобы их можно было разглядеть. Анимация буферов использует фиксированное кольцо из 32 кадров вместо реального правила PostgreSQL 18 для массового чтения. Управление касанием проверялось только в мобильной эмуляции Chrome.
Полная статья: https://github.com/NikolayS/PGSimCity#readme
Сохранять тем, кому нужно объяснить коллеге или себе, что происходит между чекпоинтом, autovacuum и буферным пулом, когда база вдруг начинает тормозить.
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
💯1
Цикл ускорился с 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
Александр Нойбек и Грег Орцелл из GitHub рассказали 31 июля, как в поиске по коду приводят текст к одному регистру. Операция простая, но Blackbird, поисковый движок GitHub, прогоняет через неё каждый байт: 180 миллионов репозиториев, больше 480 ТБ исходного кода, и для каждого потенциального результата запроса свёртка регистра нужна снова.
Привычный приём выглядит разумно: идти по байтам, а на первом не-ASCII прерваться и отдать остаток полноценному Unicode-пути. На Apple M4 это даёт 3,1 ГиБ/с. Оказалось, что тормозит именно ранний выход: пока условие выхода из цикла зависит от данных, компилятор не может его векторизовать.
Авторы оговариваются, что замеры иллюстративны и сильно зависят от микроархитектуры. Библиотека покрывает только простые отображения регистра: немецкое ß в ss и турецкие правила в неё не входят. Арифметика над байтами требует корректного UTF-8 в кратчайшей форме записи.
Полная статья: https://github.blog/engineering/architecture-optimization/dont-stop-early-case-folding-source-code-at-memory-speed/
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
👏1
Тот же файл, разложенный с 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
Мартин Фаулер опубликовал 30 июля эксперимент: можно ли измерить выгоду рефакторинга, если код правит агент. Приложение примерно на 150 тысяч строк, из них около 120 тысяч на Rust. Слой доступа к данным разросся в один файл на 17 155 строк, без выделенных функций, без внутреннего языка и почти без классов, зато с чистой границей и интерфейсом наружу.
Замер устроен так: берётся одна показательная правка, описанная одним запросом. Её выполняет свежий подагент и сообщает израсходованные токены, изменения выбрасываются. Дальше применяется один шаг рефакторинга, и та же правка выполняется заново. Агент ничему не учится между шагами, поэтому эксперимент не портится накопленным опытом, как испортился бы с живым инженером.
Фаулер отдельно оговаривает, что случайная нарезка большого файла на мелкие так не сработает: агенту пришлось бы читать много файлов подряд в поисках нужного места. Счёт токенов приблизительный, Claude не отдаёт надёжный способ считать их на лету, поэтому подагент считал символы и делил на четыре. Эксперимент идёт на одном приложении, написанном агентами с нуля, а стоимость самого рефакторинга в эти 39,7 цента не входит.
Полная статья: https://martinfowler.com/articles/exploring-gen-ai/refactoring-economic-benefit.html
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1🤯1
Время до первого ревью выросло с 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
Джон Ван, технический директор Assembled, разобрал 7 августа, что случилось с их разработкой после перехода на агентов. Инженеры чувствовали, что стали быстрее, а метрики поставки почти не двигались: пул-реквестов заводили больше, а вливали ненамного больше прежнего.
В данных нашлось объяснение. Открытых пул-реквестов стало заметно больше, стопки выросли до двадцати штук на одну задачу, размер одной правки увеличился в 2,5 раза, потому что агент пишет более объёмный дифф на то же изменение. Время до первого ревью поднялось с медианных 3,5 часа в декабре 2025 до 16 с лишним часов в мае 2026. Узким местом оказалось человеческое ревью, а число ревьюеров осталось прежним.
В ответ собрали автоматического ревьюера для малорисковых правок:
По результатам за три недели влитых правок стало в 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
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1
Половину времени компиляции двух пакетов занимал 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
Николас Незеркот, десять лет занимающийся производительностью компилятора Rust, подвёл 31 июля итоги восьми месяцев работы. Средняя экономия времени сборки с 3 декабря 2025 по 29 июля 2026 составила 5,59 процента, но примерно половину этого дал не сам компилятор, а генератор документации: там среднее время упало на 37,92 процента, а по всем его тестам суммарно на 28. Без учёта документации выигрыш компилятора 2,90 процента.
Самая наглядная история из отчёта как раз про memcpy. Автору показали пару пакетов, где новый решатель типажей резко тормозил. Профилирование через Cachegrind показало, что почти половина времени уходит на копирование памяти:
Автор оговаривает, что цифры по Clippy получены локально: в постоянном замере на CI он пока не участвует. Забавная деталь про Clippy: параллельно ту же проблему нашёл другой человек и прислал более аккуратное решение на несколько дней раньше, так что правка автора не влилась.
Полная статья: https://nnethercote.github.io/2026/07/31/how-to-speed-up-the-rust-compiler-in-july-2026.html
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Лимит одновременных транзакций подняли с тысячи до десяти тысяч, чтобы убрать ошибки, и получили шестнадцать минут деградации
Лиз ван Дейк из 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
Лиз ван Дейк из PlanetScale разобрала 7 августа инцидент, в котором продакшен-база MySQL складывалась на шестнадцать минут. Ошибки шли по нарастающей: на нулевой минуте 5 ошибок в минуту при 15 000 запросов в секунду, на десятой 1400 ошибок и 1500 запросов, потом транзакцию убил таймаут, накопленное разгреблось примерно за тридцать секунд, и пропускная способность подскочила до 8500.
Повод банальный: пакетная задача открыла транзакцию к горячей таблице, взяла блокировки строк и держала их пятнадцать минут без коммита. Интересно то, что происходило вокруг неё. Запросы, копившиеся следом, в основном на блокировках вообще не стояли: это обычные чтения, которым InnoDB отдаёт согласованный снимок. Но построение снимка требует пройти назад по истории версий каждой затронутой строки, а эта история росла все пятнадцать минут.
На следующее утро конфигурация прошла проверку на другом шарде, который держит на порядок больше трафика: на пике около 25 тысяч заявок на слот в секунду при тысяче слотов, ноль ошибок и ровные 60 тысяч запросов в секунду. За весь день отклонена одна транзакция, а внутри MySQL в каждый момент выполнялось меньше двухсот команд: по закону Литтла это примерно три миллисекунды на запрос.
Автор оговаривает, что это не универсальный совет всегда зажимать параллелизм. Совет работает там, где есть пессимистичные блокировки и общее состояние: горячие строки, SELECT ... FOR UPDATE по популярным ключам, счётчики, балансы, очереди задач. И это не свойство MySQL: в PostgreSQL бывает то же самое, а роль ограничителя там играет PgBouncer.
Полная статья: https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql
@prog_stuff
Please open Telegram to view this post
VIEW IN TELEGRAM
Полгода Tailscale ловила порчу баз: 19 случаев, ни одного общего признака — ни шарда, ни клиента, ни времени суток. Воспроизвести не удавалось, оставалось ждать следующего раза с диагностикой наготове.
Зацепку дал побочный инструмент. Чтобы восстанавливаться без потери данных, команда стала писать журнал всех изменяющих запросов и проигрывать его поверх копии. Дважды журнал не проигрался: данные, зафиксированные одной транзакцией, оказались невидимы для следующих — без единой ошибки.
Виновата гонка в самом SQLite: при совпадении записи с контрольной точкой часть страниц считается перенесённой в основной файл, хотя туда не попала. Багу оказалось не меньше шестнадцати лет, он настолько редкий, что для тестов его приходится вызывать намеренно. Tailscale попадала в него чаще других, потому что берёт контрольные точки под ручное управление и делает их очень часто.
Вывод авторов: скучная технология в нестандартном режиме перестаёт быть скучной. Всё было по документации — просто в стороне от протоптанной дороги.
Как искали и что нашли
@prog_stuff
Зацепку дал побочный инструмент. Чтобы восстанавливаться без потери данных, команда стала писать журнал всех изменяющих запросов и проигрывать его поверх копии. Дважды журнал не проигрался: данные, зафиксированные одной транзакцией, оказались невидимы для следующих — без единой ошибки.
Виновата гонка в самом SQLite: при совпадении записи с контрольной точкой часть страниц считается перенесённой в основной файл, хотя туда не попала. Багу оказалось не меньше шестнадцати лет, он настолько редкий, что для тестов его приходится вызывать намеренно. Tailscale попадала в него чаще других, потому что берёт контрольные точки под ручное управление и делает их очень часто.
Вывод авторов: скучная технология в нестандартном режиме перестаёт быть скучной. Всё было по документации — просто в стороне от протоптанной дороги.
Как искали и что нашли
@prog_stuff
👍3
Новак Калуджерович писал арифметику больших чисел для криптографической библиотеки и нашёл ошибку в Algorithm D — том самом длинном делении из книги Кнута, которое перепечатано в сотнях реализаций.
Суть места, где всё ломается: очередная цифра частного сначала оценивается по старшим разрядам, а потом оценка исправляется. Кнут утверждает, что после коррекции она гарантированно помещается в один разряд. Контрпример нашёлся в системе счисления по основанию 3: делим 45 на 16, правильное частное 2, а пробная оценка 5. После двух коррекций оценка всё ещё двухразрядная, и дальнейшее умножение берёт только младший разряд, вычитая фактически ноль.
Ошибка десятилетиями оставалась незаметной, потому что зависит от того, как именно ведёт себя аппаратное деление на конкретной архитектуре. На современных машинах с основанием два в шестьдесят четвёртой она практически не проявляется.
Автор написал Кнуту и получил тот самый гексадецимальный доллар — чек за найденную ошибку — с рукописной пометкой, что Algorithm D читают чаще многих других алгоритмов книги.
@prog_stuff
Суть места, где всё ломается: очередная цифра частного сначала оценивается по старшим разрядам, а потом оценка исправляется. Кнут утверждает, что после коррекции она гарантированно помещается в один разряд. Контрпример нашёлся в системе счисления по основанию 3: делим 45 на 16, правильное частное 2, а пробная оценка 5. После двух коррекций оценка всё ещё двухразрядная, и дальнейшее умножение берёт только младший разряд, вычитая фактически ноль.
Ошибка десятилетиями оставалась незаметной, потому что зависит от того, как именно ведёт себя аппаратное деление на конкретной архитектуре. На современных машинах с основанием два в шестьдесят четвёртой она практически не проявляется.
Автор написал Кнуту и получил тот самый гексадецимальный доллар — чек за найденную ошибку — с рукописной пометкой, что Algorithm D читают чаще многих других алгоритмов книги.
@prog_stuff
👏3
Что будет, если в одной транзакции PostgreSQL накопится больше 64 вложенных? Просядет весь кластер — включая соединения, которые об этой транзакции ничего не знают и работают с другими таблицами.
PlanetScale разобрали механику. У каждого процесса есть кэш идентификаторов вложенных транзакций, обычно на 64 записи. Переполнился — снимок помечается переполненным, и проверка видимости каждой строки идёт через служебную структуру
Вложенные транзакции при этом появляются сами: их создаёт каждый блок PL/pgSQL с секцией
Вторая беда приходит позже: записи о работающих транзакциях уезжают в журнал неполными, и новая реплика не может построить снимок. Она проигрывает журнал, но соединения не принимает — то есть поднятая на пике нагрузки реплика не даёт никакой ёмкости.
Следить можно через
@prog_stuff
PlanetScale разобрали механику. У каждого процесса есть кэш идентификаторов вложенных транзакций, обычно на 64 записи. Переполнился — снимок помечается переполненным, и проверка видимости каждой строки идёт через служебную структуру
pg_subtrans: блокировка, а при промахе ещё и диск.Вложенные транзакции при этом появляются сами: их создаёт каждый блок PL/pgSQL с секцией
EXCEPTION, а не только явный SAVEPOINT.Вторая беда приходит позже: записи о работающих транзакциях уезжают в журнал неполными, и новая реплика не может построить снимок. Она проигрывает журнал, но соединения не принимает — то есть поднятая на пике нагрузки реплика не даёт никакой ёмкости.
Следить можно через
pg_stat_get_backend_subxact() и счётчики pg_stat_slru. Готового способа предотвратить переполнение в PostgreSQL 18 нет.@prog_stuff
👍3
Колонка
Тиаго Араужу Силва показывает альтернативу: отдельная таблица переходов, куда каждое изменение добавляется новой строкой и где ничего не обновляется и не удаляется. Текущий статус — это просто самая свежая запись, то есть запрос к временной шкале, а не хранимое значение.
Дальше идёт разбор, ради которого стоит читать. Флаг
Четыре способа достать последнюю запись сравниваются честно, вплоть до планов:
@prog_stuff
status в таблице пользователей отвечает ровно на один вопрос: что сейчас. На вопросы «кого отклонили во вторник», «сколько человек провёл в статусе» и «кому после отказа всё-таки одобрили» она не отвечает никак.Тиаго Араужу Силва показывает альтернативу: отдельная таблица переходов, куда каждое изменение добавляется новой строкой и где ничего не обновляется и не удаляется. Текущий статус — это просто самая свежая запись, то есть запрос к временной шкале, а не хранимое значение.
Дальше идёт разбор, ради которого стоит читать. Флаг
current автор отвергает: его придётся сбрасывать и выставлять при каждом переходе, то есть возвращается второй источник истины. MAX(id) тоже не годится — ломается при заливке исторических данных. Правило выражается через дату и идентификатор как устойчивый разделитель при одинаковых отметках времени.Четыре способа достать последнюю запись сравниваются честно, вплоть до планов:
DISTINCT ON на списке пользователей заставляет базу прочитать и отсортировать всю таблицу статусов, а LATERAL JOIN с подходящим индексом читает по одной строке на пользователя.@prog_stuff
Простой запрос — сумма колонки на 500 миллионов чисел. PostgreSQL считает его 20 секунд, тот же цикл на Rust — 358 миллисекунд. Автор проекта pgrust показывает по шагам, из чего складывается разрыв, собирая уменьшенную копию исполнителя запросов PostgreSQL и добавляя к ней оптимизации по одной.
Исходная модель отдаёт строки по одной, через метод
Честная оговорка от автора: слипшийся узел вшит под конкретный запрос, и в общем случае так не выйдет — нужна компиляция плана на лету. А PostgreSQL медленнее ещё и потому, что делает вокруг запроса много другой работы: блокировки, разбор формата хранения.
@prog_stuff
Исходная модель отдаёт строки по одной, через метод
next() у каждого узла плана — так устроен и настоящий PostgreSQL. Это 1,3 секунды. Дальше: выдавать сразу пачку по 1024 значения в буфер на стеке — 480 мс. Слепить сканирование и суммирование в один узел — 358 мс, то есть ровно обычный цикл. Векторные инструкции — 135 мс.Честная оговорка от автора: слипшийся узел вшит под конкретный запрос, и в общем случае так не выйдет — нужна компиляция плана на лету. А PostgreSQL медленнее ещё и потому, что делает вокруг запроса много другой работы: блокировки, разбор формата хранения.
@prog_stuff
👍2
За неделю сервер отдал 1,28 млн полных страниц, а аналитика насчитала 5977 просмотров: на каждый видимый просмотр приходится около 214 загрузок, которых в статистике нет. Ник Грей воевал со скраперами на сайте из 1,5 млн профилей и описал, что сработало.
22 апреля пришло 3,6 млн запросов с 361 844 IP-адресов, почти все из Китая; Cloudflare за десять часов выдал 1,18 млн проверок. Потом headless Chrome из дата-центров США и сеть домашних IP с Chrome 118–120 образца 2023 года.
Метрика, к которой пришёл автор: страниц на одного приведённого посетителя. Google — 46. Поисковый бот Anthropic за неделю прочитал 420 680 страниц и привёл 12 человек, бот Amazon читал 117 000 страниц в день и не привёл никого; оба заблокированы, первый сбавил с 60 000 запросов в день до 25.
Скрипт JavaScript Detections от Cloudflare стоил 2875 мс на среднем телефоне при собственном JS сайта в 278 мс; без него Lighthouse вырос с 58 до 99. Из 106 437 проверок за двое суток решены 252, то есть 0,24%. Правила WAF в конце.
@prog_stuff
22 апреля пришло 3,6 млн запросов с 361 844 IP-адресов, почти все из Китая; Cloudflare за десять часов выдал 1,18 млн проверок. Потом headless Chrome из дата-центров США и сеть домашних IP с Chrome 118–120 образца 2023 года.
Метрика, к которой пришёл автор: страниц на одного приведённого посетителя. Google — 46. Поисковый бот Anthropic за неделю прочитал 420 680 страниц и привёл 12 человек, бот Amazon читал 117 000 страниц в день и не привёл никого; оба заблокированы, первый сбавил с 60 000 запросов в день до 25.
Скрипт JavaScript Detections от Cloudflare стоил 2875 мс на среднем телефоне при собственном JS сайта в 278 мс; без него Lighthouse вырос с 58 до 99. Из 106 437 проверок за двое суток решены 252, то есть 0,24%. Правила WAF в конце.
@prog_stuff
❤2
В интернете появился репозиторий, опровергающий гипотезу Коллатца на Lean — без единой заглушки, то есть формально проверенный. Оказалось, что это не прорыв в математике, а эксплуатация ошибки в самом проверяющем ядре.
Леонардо де Моура, автор Lean, написал разбор случившегося. Киран Гопинатан свёл находку к короткому доказательству
Отдельно автор объясняет, почему это нельзя закрыть запретами. Внешняя часть языка такой случай ловила, но это не спасает: подсунуть можно и заранее собранный файл, и напрямую построенный терм доказательства. Ядро обязано отвергать плохие объявления само.
Отдельный урок про независимую проверку: сторонний проверяющий на Rust это место проверял, но содержал собственную ошибку и пропускал специально сконструированное выражение. Две реализации помогают, только если обе свежие.
@prog_stuff
Леонардо де Моура, автор Lean, написал разбор случившегося. Киран Гопинатан свёл находку к короткому доказательству
False — то есть в системе можно было доказать что угодно. Ошибка сидела в обработке вложенного индуктивного типа с фиктивными параметрами: параметры терялись во вспомогательном типе и не проверялись. Исправление выпустили примерно через час после минимального воспроизведения.Отдельно автор объясняет, почему это нельзя закрыть запретами. Внешняя часть языка такой случай ловила, но это не спасает: подсунуть можно и заранее собранный файл, и напрямую построенный терм доказательства. Ядро обязано отвергать плохие объявления само.
Отдельный урок про независимую проверку: сторонний проверяющий на Rust это место проверял, но содержал собственную ошибку и пропускал специально сконструированное выражение. Две реализации помогают, только если обе свежие.
@prog_stuff
🔥1
Servo — попытка написать браузерный движок с нуля на Rust. Проект то замирал, то оживал, и по ежемесячным отчётам хорошо видно, как выглядит эта работа изнутри.
В июньском отчёте — 558 коммитов, снова рекорд, и вчетверо больше, чем на перезапуске в 2023 году. Совместимость измеряется не процентами, а конкретными сайтами: Google Photos работает, Google Maps и OpenStreetMap рисуются, но плохо реагируют на нажатия.
Из внутренних изменений: структура, описывающая прямоугольник в разметке, ужалась с 288 до 240 байт, двумерный холст стал потреблять до 23 процентов меньше энергии, одинаковые векторные картинки перестали растеризоваться повторно.
Отдельная строка — фаззинг. Один участник несколько месяцев гоняет движок случайными данными, и только за июнь по его находкам закрыли шестнадцать падений.
@prog_stuff
В июньском отчёте — 558 коммитов, снова рекорд, и вчетверо больше, чем на перезапуске в 2023 году. Совместимость измеряется не процентами, а конкретными сайтами: Google Photos работает, Google Maps и OpenStreetMap рисуются, но плохо реагируют на нажатия.
Из внутренних изменений: структура, описывающая прямоугольник в разметке, ужалась с 288 до 240 байт, двумерный холст стал потреблять до 23 процентов меньше энергии, одинаковые векторные картинки перестали растеризоваться повторно.
Отдельная строка — фаззинг. Один участник несколько месяцев гоняет движок случайными данными, и только за июнь по его находкам закрыли шестнадцать падений.
@prog_stuff
❤🔥1
Перевести номер дня в день недели — это
Любимая у автора — три инструкции плюс загрузка константы:
Всего собрано 280 вариантов под разные цели: пропускная способность против задержки, x86 против ARM. 8-, 16- и 32-битные версии проверены полным перебором заявленного диапазона, 64-битные — четырьмя блоками по миллиарду дат. По сводке автора на Ryzen 9 и M4 Pro новые функции тратят 0,3–0,5 времени алгоритма Нери 2024 года, который до этого считался пределом; на отдельных сочетаниях функции и платформы разрыв доходит до 5,9 раза. Техника обобщается на
@prog_stuff
(rd + 4) % 7 с поправкой на отрицательные числа, и разговаривать вроде не о чем. Бен Джоффе показал, что под капотом это задача про быстрый остаток от деления, и собрал набор функций, которые обгоняют вывод компилятора.Любимая у автора — три инструкции плюс загрузка константы:
imul на (1 << 32) / 7, lea с константой 0x93000000 и сдвиг на 29. Работает на всём знаковом 32-битном диапазоне, а константа в третьей строке по сути задаёт угол поворота: замена одного числа даёт ISO-нумерацию [1..7] вместо [0..6] бесплатно.Всего собрано 280 вариантов под разные цели: пропускная способность против задержки, x86 против ARM. 8-, 16- и 32-битные версии проверены полным перебором заявленного диапазона, 64-битные — четырьмя блоками по миллиарду дат. По сводке автора на Ryzen 9 и M4 Pro новые функции тратят 0,3–0,5 времени алгоритма Нери 2024 года, который до этого считался пределом; на отдельных сочетаниях функции и платформы разрыв доходит до 5,9 раза. Техника обобщается на
x % (2^N − 1), а отдельно выведены быстрые остатки для 24 и 60.@prog_stuff
Автор впервые всерьёз берётся за быстрое преобразование Фурье и по шагам показывает, что видит.
Берёт три секунды синусоиды в 5 герц, снятой сто раз в секунду, получает 300 комплексных чисел — и первым делом удивляется, почему пиков два, на плюс и минус пяти герцах. Дальше по порядку: нулевая частота это среднее значение сигнала, действительная часть отвечает за косинусную составляющую, мнимая за синусную, спектр зеркален, поэтому обычно рисуют половину.
Самое полезное — про грабли. Если оборвать сигнал не на трёх секундах, а на 2,9, чистый пик размазывается по соседним частотам. Оконная функция это лечит, но добавляет собственную размытость. Импульс в данных поднимает шумовой пол на всех частотах сразу, ступенька собирается у низких, прямоугольная волна даёт нечётные гармоники.
В конце он применяет всё это к годовому ряду почасовых данных с придуманной историей про стройку: в спектре видны годовой тренд, суточный и недельный циклы, а рабочая неделя проявляется гармониками с периодами 3,5, 2,4, 1,4 и 1,2 дня.
@prog_stuff
Берёт три секунды синусоиды в 5 герц, снятой сто раз в секунду, получает 300 комплексных чисел — и первым делом удивляется, почему пиков два, на плюс и минус пяти герцах. Дальше по порядку: нулевая частота это среднее значение сигнала, действительная часть отвечает за косинусную составляющую, мнимая за синусную, спектр зеркален, поэтому обычно рисуют половину.
Самое полезное — про грабли. Если оборвать сигнал не на трёх секундах, а на 2,9, чистый пик размазывается по соседним частотам. Оконная функция это лечит, но добавляет собственную размытость. Импульс в данных поднимает шумовой пол на всех частотах сразу, ступенька собирается у низких, прямоугольная волна даёт нечётные гармоники.
В конце он применяет всё это к годовому ряду почасовых данных с придуманной историей про стройку: в спектре видны годовой тренд, суточный и недельный циклы, а рабочая неделя проявляется гармониками с периодами 3,5, 2,4, 1,4 и 1,2 дня.
@prog_stuff