Зміст дописунатисність на посилання, щоб перейти до потрібного місця
Якщо ви працюєте з Rails і вам потрібно уникнути одночасної зміни одного запису декількома процесами, варто звернути увагу на механізм блокування записів у базі даних. Один із найнадійніших способів зробити це — pessimistic locking.
Що таке pessimistic lock?
Pessimistic lock (песимістичне блокування) означає, що запис у базі даних фізично блокується для змін з інших потоків або процесів, поки поточна операція не завершиться. Тобто якщо один процес отримав блокування на запис, інші мусять чекати, поки він його звільнить.
У ActiveRecord песимістичне блокування реалізується через метод lock, який додає SELECT ... FOR UPDATE, що утримує блокування до кінця транзакції.
Використання в Rails
Ось простий приклад використання lock у Rails:
ActiveRecord::Base.transaction do order = Order.lock.find(order_id) order.status = "processed" order.save! end
У цьому випадку, якщо інший процес спробує отримати Order із тим самим order_id, він змушений буде чекати, поки поточний процес завершить транзакцію.
Навіщо це потрібно?
Уявімо, що ми обробляємо замовлення. Коли користувач натискає кнопку "Оплатити", нам потрібно зменшити кількість товару на складі. Якщо кілька людей одночасно намагаються купити останню одиницю товару, без блокування можливий race condition:
- Процес 1 читає кількість товару (1 одиниця).
- Процес 2 читає ту ж саму кількість (1 одиниця).
- Обидва процеси намагаються записати нове значення (0 одиниць).
- Один з процесів втрачає оновлення іншого.
Якщо ж використати lock, другий процес буде змушений дочекатися завершення першого і вже потім вирішувати, що робити.
Альтернативи pessimistic lock
Pessimistic locking не завжди найкращий вибір, оскільки він може блокувати інші процеси, уповільнюючи роботу застосунку. Іноді краще використовувати:
- Optimistic Locking — перевіряє, чи змінився запис між читанням і записом.
- Atomic операції — використовує UPDATE ... WHERE або increment! без читання значень у Ruby.
- Queue-based підхід — розподіляє оновлення через чергу (Sidekiq, RabbitMQ).
Optimistic Locking у Rails
Optimistic locking працює через колонку lock_version. Якщо два процеси читають запис, то при спробі записати зміни система перевіряє, чи не змінився запис за цей час:
class Order < ApplicationRecord attr_accessor :lock_version end
Тоді, якщо один процес внесе зміни, а інший спробує записати застарілу версію, він отримає ActiveRecord::StaleObjectError.
Якщо простими словами:
- Pessimistic locking блокує запис, щоб уникнути конфліктів.
- Optimistic locking дозволяє працювати без блокування, але потребує обробки конфліктів.
- Atomic операції зменшують ризик race condition без блокувань.
Обирайте правильний підхід залежно від задачі. Якщо критично уникнути конфліктів за будь-яку ціну — використовуйте lock. Якщо важлива продуктивність і можна дозволити конфлікти — краще lock_version або atomic updates.