Когда вы публикуете техническую статью, особенно о внутреннем устройстве инструментов, внимательные читатели могут найти уязвимости или недочёты, которые вы пропустили. Именно так произошло с проектом Pulse после статьи на Хабре. Комментарии указали на три конкретные проблемы: длительный запрос истории может блокировать очистку WAL в SQLite, успешное выполнение команды docker run ещё не означает, что контейнер действительно запустился и готов к работе, а разрешения модуля сами по себе не изолируют его от файловой системы сервера. В этой статье я расскажу, как проверял эти замечания, что изменил и какие ограничения остались.
Проблема с SQLite WAL: почему долгий запрос мешает очистке
SQLite использует механизм WAL (Write-Ahead Logging) для повышения производительности и конкурентного доступа. Однако если один запрос выполняется слишком долго, он может удерживать блокировку и препятствовать очистке WAL-файла. В результате WAL растёт, занимает дисковое пространство и может замедлить работу базы. Читатели Хабра обратили внимание, что в Pulse запрос истории мог выполняться неограниченно долго, особенно при больших объёмах данных.
Чтобы проверить это, я написал тест, который одновременно запускает чтение истории и запись отчётов. Оказалось, что при определённых условиях чтение действительно блокировало очистку WAL. Решение: установить таймаут для запросов и добавить мониторинг размера WAL. Конкретные шаги:
- Использовать PRAGMA busy_timeout для управления временем ожидания блокировок.
- Ограничить время выполнения запросов истории через таймаут на уровне приложения (например, с помощью контекста с отменой).
- Регулярно проверять размер WAL-файла и при превышении порога выполнять PRAGMA wal_checkpoint(TRUNCATE).
Эти меры не устраняют проблему полностью, но снижают риск неконтролируемого роста WAL. Важно понимать, что даже с таймаутами интенсивная запись может временно увеличивать WAL — это нормально, если размер остаётся в разумных пределах.
Проверка состояния Docker-контейнера после команды
Многие разработчики ошибочно полагают, что если команда docker run завершилась с кодом 0, контейнер уже работает и готов принимать запросы. На самом деле контейнер может запуститься, но приложение внутри него ещё не инициализировалось или даже упасть сразу после старта. Читатели указали, что в Pulse проверка ограничивалась только кодом возврата команды.
Я добавил механизм проверки состояния контейнера: после выполнения команды запускается опрос docker inspect или docker ps с фильтром по имени, чтобы убедиться, что контейнер находится в состоянии running. Кроме того, полезно проверять healthcheck, если он определён в Dockerfile. Типичные ошибки:
- Игнорирование кода возврата docker run — он может быть нулевым, даже если контейнер сразу завершился.
- Отсутствие таймаута на ожидание готовности — можно зависнуть навсегда.
- Проверка только наличия контейнера, но не его работоспособности (например, не отвечает порт).
Теперь модуль явно проверяет, что контейнер запущен и, если возможно, что приложение внутри отвечает. Но даже после этих изменений нельзя гарантировать, что контейнер готов к обработке запросов — это зависит от самого приложения. Поэтому в документации я отметил, что успешный запуск контейнера — лишь первый шаг.
Переход на WASI: изоляция через ограниченный API-посредник
Изначально модуль выполнялся в среде с определёнными разрешениями, но, как верно заметили читатели, разрешения сами по себе не мешают модулю читать файлы сервера, если он получил доступ к файловой системе. Например, если модулю разрешено читать /data, он может прочитать любой файл в этой директории, включая конфиденциальные.
Решение — перевести модуль в среду WASI (WebAssembly System Interface), которая обеспечивает более строгую изоляцию. WASI позволяет запускать WebAssembly-модули с явно определёнными возможностями: доступ к файлам, сетевым сокетам и т.д. предоставляется через хоста, и модуль не может выйти за пределы разрешённого. В нашем случае модуль должен взаимодействовать с Docker, но не иметь прямого доступа к файловой системе сервера. Поэтому я реализовал API-посредник: модуль в WASI обращается к Docker через ограниченный HTTP-интерфейс, который предоставляет только необходимые методы (запуск контейнера, проверка статуса).
Что это дало:
- Модуль больше не может напрямую читать или записывать файлы на хосте.
- Все взаимодействия с Docker проходят через контролируемый посредник, который можно аудировать.
- Среда выполнения WASI обеспечивает дополнительную изоляцию памяти и системных вызовов.
Однако переход на WASI потребовал переписать часть кода и настроить хост-среду. Также появились ограничения: например, модуль не может использовать некоторые библиотеки, не скомпилированные под WASI. Пришлось искать альтернативы.
Что теперь проверяется и какие ограничения остались
После доработок модуль проверяет:
- Таймауты SQL-запросов и размер WAL.
- Состояние контейнера после запуска (running, healthcheck).
- Изоляцию через WASI и API-посредник.
Но остаются принципиальные ограничения. Во-первых, нельзя гарантировать выполнение команд «ровно один раз»: если модуль отправил команду на запуск контейнера, но не получил ответ (например, из-за сбоя сети), повторная попытка может создать дубликат. Во-вторых, запущенный контейнер не обязательно готов к работе — это зависит от приложения. В-третьих, WASI не является панацеей: если посредник скомпрометирован, изоляция нарушится. Поэтому важно тщательно проектировать API-посредник и ограничивать его возможности.
Главный вывод: обратная связь от читателей помогла выявить слабые места и сделать систему надёжнее. Но не стоит обещать больше, чем можно гарантировать. Честно документируйте ограничения — это вызывает доверие и помогает пользователям правильно строить свои ожидания.