Что делать, если два воркера одновременно обращаются к одним данным?

Защиту выбирают по типу операции и месту хранения данных. Mutex не согласует разные процессы, а одна очередь не исключает повторы или параллельную обработку.

Короткий ответ

Что ответить на собеседовании

Сначала различают чтение и изменение данных: два чтения сами по себе не создают потерянного обновления. Для потоков одного процесса подходит общий Mutex; для разных процессов с общей БД — атомарный SQL, блокировка строки в транзакции либо optimistic locking с обработкой конфликта. В Rails можно использовать with_lock. Повторы задач требуют идемпотентности; очередь сама по себе не гарантирует эксклюзивный доступ.

Подробный разбор

Ответ с пояснениями

Сначала уточняют, что значит «воркер»: поток одного процесса, отдельный процесс или экземпляр на другом сервере. Затем определяют операцию. Два чтения сами по себе не вызывают потерянного обновления, хотя требования к согласованному снимку могут потребовать транзакции. Опасна последовательность «прочитать значение — вычислить новое — записать», если другой исполнитель делает то же самое.

Для общего объекта в памяти потокам одного процесса помогает один и тот же Mutex. Это не межпроцессная блокировка: отдельные экземпляры mutex в разных процессах не согласуют доступ к БД. Thread::Mutex

Для простого увеличения счётчика предпочтителен атомарный SQL вида SET counter = counter + 1, а не вычисление нового значения из устаревшего Ruby-объекта. Rails предоставляет, например, update_counters; такие операции обходят обычные callbacks и validations, что нужно учитывать. Атомарное изменение счётчиков

Если проверка и изменение образуют бизнес-операцию, можно блокировать строку в транзакции:

# stock — неотрицательное число единиц товара
product = Product.find(product_id)
product.with_lock do
  raise 'Нет остатка' if product.stock < quantity

  product.update!(stock: product.stock - quantity)
end

Здесь quantity заранее проверено как положительное целое. with_lock перечитывает объект с блокировкой до выполнения блока. Все конкурирующие пути изменения должны соблюдать тот же инвариант; произвольная запись устаревшего значения вне протокола всё ещё опасна. Pessimistic locking

При редких конфликтах подходит optimistic locking через lock_version: StaleObjectError обрабатывают, перечитывают запись и заново проверяют бизнес-условия. Повторы ограничивают, а не запускают бесконечно. Optimistic locking

Наконец, блокировка не предотвращает повторное списание при повторной доставке задачи. Нужны идемпотентность и, например, уникальный идентификатор операции с ограничением уникальности в БД. Очередь сама по себе не гарантирует ни единственного исполнителя для этих данных, ни выполнения ровно один раз. Рекомендации Sidekiq

Практика в реальном времени

Подготовьтесь к следующему собеседованию

Interview Boost учитывает вакансию, резюме и технологии и помогает сформулировать ответ прямо во время интервью.

Начать подготовку