Блог
[симуляция: ревьюер, параллелизм, остановка ходов, диспетчер; замер: хаос связи узлов и ожидания на PostgreSQL 16, повтор решений, шлюз симуляции; ПЛИС: только синтез; в production ничего нет] На одном входе с фиксированным зерном рантайм акторов Queen сокращает худшее восстановление с 1181 с до 27 с и при перегрузке завершает на 18.7% больше рецензий, но выигрывает не везде. 2026-10-09 за флагами легли семь изменений по итогам изучения конкурентов, у каждого спецификация на t27 и бенчмарк против кода, который оно заменяет: журнал, повторяющий 73 435 решений с 0 расхождений, ходы, которые действительно останавливаются, ожидания как строки Postgres (чтений GitHub 24 вместо 180), связь узлов с фенсингом (потерянных писем 0 вместо 21) и шлюз симуляции с зерном, который проходит за 33.5 с и первым нашёл настоящую ошибку рантайма. Большую часть выигрыша диспетчера дала 60-минутная граница хода, а не акторы, а адаптивный параллелизм проигрывает при 3 полосах. На XC7A200T карта как она написана стоит 26 737 LUT, и 13 функций не синтезируются; предел около 37 000 акторов на плату ставит память, а не логика.

На одном и том же входе с фиксированным зерном рантайм акторов Queen обходит старый цикл опроса там, где работа ломается: худшее восстановление после падения или зависания сокращается с 1181 с до 27 с, а при перегрузке рецензий выходит на 18.7% больше. Обходит не везде. 2026-10-09 на интеграционную ветку actors-next легли семь изменений по итогам изучения конкурентов, у каждого — спецификация на t27, тесты и бенчмарк против кода, который оно должно заменить. Два из них проигрывают в случаях, которые мы можем назвать, одно сознательно отдаёт пропускную способность, а большая часть выигрыша диспетчера оказалась заслугой простого лимита времени, а не акторов. Новый шлюз симуляции на первых же прогонах нашёл настоящую ошибку рантайма. Каждое новое поведение стоит за флагом, выключенным по умолчанию, а исправлениям ошибок флаг не нужен. В production пока ничего из этого не работает.
Ниже встречаются числа двух видов, и у каждого есть пометка. Симуляция — прогон с фиксированным зерном на виртуальных часах, где частоты сбоев и длительности задач — допущения. Замер — настоящие процессы ОС, настоящий PostgreSQL 16 или настоящий инструмент синтеза.
Queen — планировщик роя агентов t27. Она раздаёт issue на GitHub рабочим агентам — пчёлам, рецензирует их pull request и запускает релизные задания. С 2026-10-08 действует правило владельца: каждая параллельная часть Queen становится актором с pid, почтовым ящиком и супервизором, и ни один цикл не заменяется, пока не опубликованы MVP, его тесты и бенчмарк на том же входе (t27#7851). Каждое решение рантайм получает вызовом скомпилированной спецификации t27 — «карты». Хост на TypeScript только держит состояние и выполняет ввод-вывод.
Первым доменом акторов стал ревьюер pull request (trios#1698). Нагрузка: пуассоновские прибытия, 5% попыток падают, 2% зависают на 30 минут, горизонт 7 часов симуляции. Без сбоев цикл и акторы идут вровень: done p95 319 с против 309 с. Со сбоями акторы сокращают done p95 с 553 с до 384 с, а худшее восстановление — с 1181 с до 27 с. При 120 прибытиях в час они завершают 565 строк из 660 против 476. Сообщение стоит 3.7 мкс на кольце из 1000 акторов. [симуляция]
Production показал то, чего симуляция не моделировала. Ревьюер на акторах заработал в 19:00Z 2026-10-08. К 22 строкам, ветку которых нельзя прочитать, он ходил примерно 184 раза в минуту, а цикл — около 20 раз. Backoff исправил это в течение часа (trios#1702). За первые 10.5 минуты после него ревьюер был занят 23% своих воркер-секунд (584 из 2520). Через день живая Queen показывала около 11 рецензий в час против примерно 58 завершённых диспетчеризаций (reviewer.t27). Ничто в рантайме не могло сказать, какой из замеров верен. [замер, production]
Исследование сравнило рантайм с BEAM/OTP, Akka, Orleans, движками долговременного исполнения — DBOS, Temporal, Restate, Hatchet — и агентными фреймворками на акторах. Оно нашло три пробела, и ни один из них не является недостающей функцией BEAM. Akka и Orleans из коробки меряют глубину ящика, время в ящике и долгие ходы; у нас были строки логов. OTP, Orleans и Effect останавливают работу по протоколу: сигнал, дедлайн, финализаторы, затем kill. Мы же бросали ход и давали ему работать дальше. DBOS, Hatchet и Cloudflare делают из долгого ожидания строку базы, которая не держит процесс.
Нашлось и то, в чём рантайм уже впереди. Аренда узла в 20 с при пульсе 5 с замечает мёртвый узел раньше, чем net_ticktime BEAM по умолчанию (от 45 до 75 с), чем split-brain resolver Akka по умолчанию (около 45 с) и чем Orleans 9 (90 с). Наша контрольная полоса, которая обслуживает сигналы раньше данных, — та же идея, что OTP 28 выпустил как priority messages. Зависший ход мы убиваем, а Orleans о нём только сообщает (MaxRequestProcessingTime). Это задокументированные значения по умолчанию самих вендоров, а не наши замеры.
Семь пунктов — это эпик trios#1712. Каждая спецификация сначала влита в t27, а каждый рантайм лёг на actors-next отдельной PR. В таблице — главное число каждого пункта; цена — в разделах после неё.
| # | Изменение (спецификация, рантайм, флаг) | Что сдвинулось | Основание |
|---|---|---|---|
| 1 | Телеметрия и воспроизводимый журнал решений (t27#8285, trios#1725, TRIOS_QUEEN_ACTORS_TELEMETRY=on) | 73 435 решений карт воспроизведены с 0 расхождений; с телеметрией и без неё расписание одно и то же | замер повтора; цена — оценка |
| 2 | Ходы, которые действительно останавливаются (t27#8281, trios#1720, TRIOS_QUEEN_TURN_STOP=on) | одновременных рецензий во время зависаний: до 7-10, теперь не больше 4; живых процессов-внуков после kill: 10 из 10, теперь 0 | симуляция; тест процессов — замер |
| 3 | Параллелизм по очереди (t27#8275, trios#1716, TRIOS_QUEEN_REVIEWER_ADAPTIVE=1) | перегрузка: 565 из 660 строк при 80.7/ч, теперь 660 из 660 при 94.3/ч | симуляция |
| 4 | Долгие ожидания как строки (t27#8272, t27#8282, trios#1722, TRIOS_QUEEN_WAITS=rows) | одно 90-минутное ожидание CI: 180 чтений GitHub, теперь 24; 180 записей в журнал задания, теперь 0 | замер на PostgreSQL 16, виртуальные часы |
| 5 | Симуляция с зерном как шлюз CI (t27#8292, t27#8306, trios#1726) | 18 случаев по 10 000 шагов, каждый дважды, за 33.5 с в CI и с 0 расхождений; 4 воссозданных дефекта пойманы, и одна настоящая ошибка найдена первой | замер (собственные прогоны шлюза) |
| 6 | Фенсинг связи узлов и инкарнации (t27#8278, trios#1724, вызывающих пока нет) | потеряно писем: 21, теперь 0; pid переиспользован: 5 из 5, теперь 0; писем от замёрзшего узла: 40, теперь 0 | замер на PostgreSQL 16, процессы ОС |
| 7 | Ключевые акторы и диспетчер пчёл (t27#8286, trios#1723, TRIOS_QUEEN_DISPATCH=actors) | восстановление после падения, p50 при 6-минутных раундах: 701 с, теперь 149 с; двойных захватов на двух узлах: 0 | симуляция |
Пункт 1 считает события по видам акторов, ведёт гистограммы времени хода и времени в ящике и поднимает пороговые события с зазором, как монитор long_message_queue в OTP 27. Тревога по глубине ящика включается на 192 и выключается на 64 из 256 мест. Долгий ход — половина границы kill, 150 с для рецензии. Шторм рестартов отмечается за один рестарт до того, как супервизор сдастся. Записи берутся выборочно: 8 сообщений на вид актора и 16 вызовов на функцию карты в секунду, в кольцо на 4096 записей.
Каждое решение уже является вызовом скомпилированной спецификации, поэтому журнал этих вызовов делает рантайм воспроизводимым. Прогон ревьюера с зерном записал 73 435 решений от 38 функций трёх карт; повтор на закреплённых картах дал 0 расхождений. Испорченный, пустой или чужой журнал, как и журнал с записями вне выборки, проверку не проходит. Из 13 впрыснутых падений счётчики увидели 12: тринадцатое закончилось после того, как рестарт rest_for_one уже заменил его воркер. [замер]
На спокойной машине (нагрузка около 4; 1000 акторов передают 100 000 сообщений, 21 прогон) телеметрия добавляет +3.3% CPU на сообщение по медиане, от +1.6 до +5.2% между p25 и p75. Это в пределах цели +10%. Тот же прогон нашёл цену, которой мы не хотели: без телеметрии сообщение стоило 4.5 мкс против 3.7 мкс, опубликованных до семи лейнов. Профиль (trios#1746) показал, что эта цена старше лейнов: 44% времени уходило на повторный поиск карты при каждом вызове по ключу длиной больше 100 символов, а слот, известный с момента запуска, запрашивался трижды на сообщение. После обоих исправлений сообщение стоит 1.55 мкс — в 2.4 раза меньше прежней базы; это исправление ушло в production со второй партией (trios#1754). Телеметрия стоит столько же в абсолютных числах, поэтому на подешевевшей базе добавляет 8–13% — на границе цели +10%. Следующее изменение (trios#1755) сократило собственные инструкции телеметрии на сообщение на 27–33%; отношение на спокойной машине ещё предстоит измерить. Production-замера ещё нет, поэтому вопрос «23% занятости против 11 рецензий в час» остаётся открытым.
Пункт 2 даёт каждому ходу AbortSignal. Вызов модели, команды git и проверки критериев ему подчиняются, остановленная рецензия не пишет вердикт, а эскалация убивает всю группу процессов, никогда не один pid. Раньше рецензия, убитая на своей границе, держала полосу z.ai до собственного 120-секундного таймаута. В симуляции одновременных рецензий во время зависаний становится не больше 4 вместо 7-10, и полоса освобождается через 0 с виртуального времени после kill вместо 48-74 с (p50); настоящая отмена вызова модели заняла 2.6-27.5 мс. [симуляция]
Тест с настоящими процессами: 10 ходов, каждый оставляет внука sleep, половина глуха к SIGTERM, kill на границе в 1 с. При старом proc.kill(9) одному pid все 10 внуков были живы через 10 с. При убийстве группы не выжил ни один, а глухой к SIGTERM ход освобождался за 3012 мс. [замер]
Цена видна при перегрузке. С глухими зависаниями 78.4 рецензии в час падают до 60.7; с зависаниями, которые слышат отмену, 76.6 падают до 68.4. Старый код не был быстрее: он работал за своей границей. Во втором случае он отработал за границей 12 216 рецензия-секунд, это около 68 рецензий медианной длины при разнице в 57. В симуляции нет лимита полос на ключ, поэтому рецензия за границей там бесплатна. В production это третий одновременный запрос на ключ, и z.ai отвечает на него ошибкой 1302. При постоянной нагрузке пропускная способность одинакова. [симуляция]
Пункт 3 заменяет фиксированный пул из 4 ревьюеров пулом, размер которого выводится из очереди, свободных полос модели и памяти (reviewer_sizing.t27). При 47 свободных полосах — столько показывал /queen/status 2026-10-09 — он разгребает перегрузку: 660 из 660 строк при 94.3 в час против 565 из 660 при 80.7. Всплеск из 120 строк закрывается за 2895 с вместо 11 690 с. Цена — больше одновременных вызовов модели: до 16 против 7. [симуляция]
Проигрывает он в двух случаях. При всего 3 полосах фиксированный пул завершает 429 из 660, а адаптивный — 360. Рецензия держит полосу только на время вызова модели, поэтому пул, ограниченный числом полос, оставляет каждую полосу простаивать до конца рецензии. В контейнере с 1.5 ГБ свободной памяти и принятыми 512 МБ на рецензию пул ограничен тремя и завершает 399 против 565. Флаг остаётся выключенным, пока телеметрия не измерит свободные полосы и память на рецензию. [симуляция]
Пункт 4 паркует долгое ожидание строкой в Postgres, queen_wait, а будит её актор-планировщик. Начинается он с поправки к исследованию. 90-минутное ожидание CI в релизном задании не жило в памяти процесса: задание уже было строкой, и рестарт ничего не терял. Стоило оно чтения GitHub и записи в журнал задания примерно каждые 30 с.
За это ожидание, на PostgreSQL 16 и виртуальных часах, чтений GitHub становится 24 вместо 180, а записей в журнал задания — 0 вместо 180. Ни одно ожидание не теряется при принудительном рестарте ни в старой, ни в новой версии. Заметить, что прогон CI закончился, теперь занимает 83 с вместо 23 с при границе 240 с вместо 30 с, потому что раунд заменён опросом раз в 15 с. Время процесса без задержки GitHub растёт с 1108 мс до 1463 мс, около половины — этот общий опрос. С учётом измеренных 51 мс на чтение GitHub старый способ ждал GitHub 9.2 с, а новый — 1.2 с. Разрыв в обнаружении закрыл бы вебхук; он не подключён. [замер]
Пункт 5 превращает виртуальные часы в шлюз CI (simulation.t27, t27#8292 и t27#8306; харнесс trios#1726). Он гоняет настоящий рантайм на трёх узлах: акторы ревьюера, удалённых детей, сеть в памяти, а на двух зёрнах — настоящую связь через Postgres поверх симулированного хранилища. Зёрна и смесь сбоев выбирает карта: потеря узла, зависший ход, переполнение ящика, нечитаемая строка, 429 от провайдера, устаревшая аренда, падение рецензии. Случаев 18: 16 в сети в памяти и 2 на хранилище, по 10 000 шагов каждый. Каждый случай прогоняется дважды, два лога обязаны совпасть, а все случаи вместе обязаны достичь восьми редких состояний, которых требует карта. В CI шлюз занимает 33.5 с, всё задание — 52 с. Четыре прогона шлюза подряд на итоговом коде дали 0 прогонов с разными логами. [замер]
Чтобы проверить, что он ловит то, что должен, каждый известный дефект возвращали в настоящий исходный код и снова запускали шлюз:
| Возвращённый дефект | Провалено случаев | Первый провал |
|---|---|---|
| Нет backoff ожидания (горячий цикл при запуске) | 18 из 18 | зерно 3600507402, шаг 421: одну строку посетили 5 раз за виртуальную минуту |
| Связь узлов подтверждает письмо до его обработки | 2 из 2 случаев на хранилище | зерно 3600507402, шаг 2637 |
| pid без инкарнации | 18 из 18 | зерно 3600507402, шаг 2543 |
| Ход выполняется после остановки своего процесса | 2 из 18 | зерно 3600507402, шаг 7690 |
О дефекте из последней строки мы не знали: его нашёл шлюз. На первых прогонах шлюз провалил 5 из 64 зёрен: ход выполнялся для процесса, который уже завершился. Рантайм забирает письмо и выполняет ход на микрозадачу позже, а между этими моментами супервизор может остановить процесс. Теперь queen-actors.ts выполняет ход, только пока его процесс актуален, и на это есть регрессионный тест. На нагруженном Mac проверка ничего не стоила сверх шума (медиана 11.4 мкс на сообщение с ней и 11.65 мкс без неё). А на коде до пункта 6 симулированное хранилище воспроизвело оба дефекта связи узлов без единого изменения исходного кода.
У шлюза два ограничения. Ключевых акторов и адаптивного пула ревьюеров в его симулированном мире пока нет. И каждую ночь его никто не запускает: GitHub выполняет расписания только на ветке по умолчанию, а кода Queen там нет.
Пункт 6 чинит связь узлов через Postgres раньше, чем ею кто-то воспользуется; в production у неё нет вызывающих. netlink.t27 кладёт номер инкарнации в каждый pid и проверяет эпоху аренды при каждой записи. Пульс переезжает в свой поток, а узел отключает себя сам через 15 с без продления — на один пульс раньше, чем любой сосед сможет счесть его мёртвым по аренде в 20 с. Один и тот же сценарий хаоса прогнан до и после, на настоящем PostgreSQL 16, где каждый узел — отдельный процесс ОС. [замер]
| Случай хаоса | До | После |
|---|---|---|
| Потеряно писем, 10 ответов на чтение сброшены посреди доставки (из 200) | 21 | 0 |
| Потеряно писем за одной недекодируемой строкой (из 20) | 10 | 0 |
| pid переиспользован за 5 рестартов | 5 из 5 | 0 |
| Письма прошлой инкарнации доставлены новой | 25 из 25 | 0 |
| Писем от узла, замёрзшего на 30 с, после того как сосед счёл его мёртвым | 40 | 0 |
| Ложный node-down, пока один ход держал цикл до 25 с | 1 | 0 |
Каждый из шести дефектов сначала воспроизведён красным. Цена — подтверждение: 6.16 SQL-операторов почты на круг вместо 4.11. CPU сдвинулся на 7-8%, в пределах разброса между прогонами на Mac с нагрузкой от 21 до 42. Запись ниже прогоняет само правило фенсинга: спецификация проходит, фенсинг сдвигается с 15 с на 20 с аренды, падает ровно этот тест, и git возвращает файл.
t27c test-report specs/queen/netlink.t27 · фенсинг раньше решения соседей
Пункт 7 запускает диспетчер пчёл как набор акторов, по одному на issue, со связями, вызовом и упорядоченной остановкой. В его бенчмарк добавлен контроль: старый цикл, у которого раннеры ограничены 60-минутной границей хода, как у акторов. При 16 полосах со сбоями цикл завершает 262 issue, ограниченный цикл — 349, акторы — 352. Акторы добавляют 3, то есть 0.9%; остальное дал лимит. [симуляция]
Акторы выигрывают там, где раунды медленные и где пчёлы падают. При 6-минутных раундах они завершают 330 против 307 (+7.5%), а p50 восстановления после падения снижается с 701 с до 149 с. Повтор при нехватке полос они проигрывают: 20-27 с у цикла против 60-98 с. На всплеске они завершают 612 против 616, потому что бросают issue после 3 засчитанных провалов. Двойных захватов 0 у каждого рантайма на одном узле. На двух узлах диспетчера акторы держат 0, когда держатель захвата — pid, а в негативном контроле их 273. Предложение в t27#7851 — не заменять цикл по этим числам: 60-минутный лимит для нынешнего цикла сразу забирает большую часть выигрыша. [симуляция]
Карта решений — чистая спецификация t27, поэтому t27c может опустить её в Verilog. Мы синтезировали её под XC7A200T нашей стендовой платы, чтобы увидеть, сколько стоил бы рантайм акторов в железе. Это только синтез, yosys 0.63: ни размещения и трассировки, ни тактовой частоты, на плату ничего не загружалось. Происхождение чисел — в trinity#1582.
while, выход из которого зависит от данных, 4 — в имя, которое сгенерированный Verilog не разрешает, и 1 — в ширину, которую yosys не может определить.actors-next влита в production-ветку один раз, с согласия владельца: trios#1730, 2026-10-10 в 13:38Z, а в 16:03Z за ней ушла вторая партия (trios#1754): очередь слотов ревью, ограниченный drain, укреплённые ключевые акторы, события акторов и исправление цены. Каждое новое поведение в них стоит за выключенным флагом, поэтому production ведёт себя как прежде, пока флаги не включат по одному, по данным. До тех пор каждое число здесь получено из бенчмарка.runRound, а не сама функция.Поработаем вместе
Работаю по контракту и part-time с hardware-AI, FPGA/RTL и ML-системами — от спецификации и открытого тулчейна до воспроизводимых измерений.