У разработчиков есть привычка: если задача требует очереди — сразу тянуть RabbitMQ или Redis. И забывают, что для маленького сайта с парой тысяч задач в день есть решение проще.
Когда очередь на самом деле не нужна
Маленький сайт, которому нужно раз в час отправить email-рассылку. Вы ставите RabbitMQ, Redis, докер-контейнер с воркером. И вот уже три сервиса вместо одного. А что, если можно обойтись SQLite?
Не в качестве временного решения. В качестве постоянного. Звучит дико? Давайте разберёмся.
Как это работает
Берём таблицу tasks. Поля: id, payload (JSON с данными задачи), status, created_at, processed_at. Одновременно с этим создаём механизм, который забирает одну задачу, выполняет её и помечает как выполненную.
На первый взгляд всё просто. Но есть нюанс: если два воркера стартуют одновременно, они могут забрать одну и ту же задачу. Решение — блокировка на уровне записи.
SQLite поддерживает SELECT ... FOR UPDATE SKIP LOCKED. Это означает: выбрать строку, заблокировать её так, чтобы другие запросы её пропустили. Идеально для одиночного захвата задачи.
Пример кода
Создаём таблицу:
CREATE TABLE tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payload TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
processed_at DATETIME
);Забираем задачу:
db.transaction(() => {
const task = db.prepare(`
SELECT * FROM tasks
WHERE status = 'pending'
ORDER BY created_at ASC
LIMIT 1
`).get();
if (task) {
db.prepare(`
UPDATE tasks
SET status = 'processing'
WHERE id = ?
`).run(task.id);
return task;
}
})();Обработали — пометили выполненной:
db.prepare(`
UPDATE tasks
SET status = 'done', processed_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(task.id);Весь процесс — внутри транзакции. Два параллельных воркера? Второй просто не найдёт задачу в статусе pending и уйдёт на следующую итерацию.
Что это даёт
Во-первых, ноль внешних зависимостей. Один файл базы данных, который вы и так ведёте. Не нужно поднимать отдельный сервис, следить за его uptime, настраивать reconnect логику.
Во-вторых, ACID. Если воркер упадёт посреди обработки — задача останется в статусе processing. При следующем запуске выбираете задачи не по pending, а по processing, где processed_at старше часа. Ручной recovery без дополнительного инструмента.
В-третьих, простота отладки. Посмотреть очередь — обычный SELECT. Добавить задачу вручную — INSERT. Всё через тот же интерфейс, которым вы уже пользуетесь.
Ограничения
Это неEnterprise-решение. Если вам нужны сотни задач в секунду — Redis и RabbitMQ всё ещё правильный выбор. SQLite работает на файловых блокировках, и на высокой конкурентности это станет узким местом.
Но для подавляющего большинства проектов — ежедневные email-рассылки, генерация отчётов, пересчёт аналитики — возможностей SQLite хватит с запасом.
Главное
Мы часто мыслим двумя крайностями: «возьму самое простое» или «возьму самое мощное». И забываем про середину. SQLite как очередь — это пример середины, которая закроет 90% потребностей, а стоить будет ноль дополнительных сервисов.
Знать возможности инструмента важнее, чем следовать списку «лучших практик».
Комментарии
Пока нет комментариев. Стань первым!