DEV Community

Cover image for Advisory locks (рекомендательные блокировки)
Ivan Shamatov
Ivan Shamatov

Posted on • Edited on

3

Advisory locks (рекомендательные блокировки)

не путать с другими более оптимистичными или менее пессимистичными блокировками

Возможно у вас в проекте уже используются эти локи, но вы никогда не задумывались, что это за локи, как они работают и, самое важное, как НЕ работают.
Эта блокировка в качестве аргумента принимает строку, которая будет служить уникальным ключом.

Начнем с того, что лок делать НЕ умеет

1. Этот лок не блокирует данные, записи, таблицы, которые вы модифицируете внутри блока кода
Если у вас есть вот такой кусочек кода, который выполняется в потоке А

User.with_advisory_lock("updating_users_#{users.ids.join}") do
  users.where(role: "admin").update(role: "user")
end
Enter fullscreen mode Exit fullscreen mode

и вот такой кусочек кода, который выполняется в потоке Б,

User.join(:permisson).where(permissions: { update: true }).update(role: "admin")
Enter fullscreen mode Exit fullscreen mode

то поток Б может модифицировать данные, которые модифицирует поток А, и наоборот.

2. Нельзя заменить транзакцию локом.
Рекомендательные блокировки (в постгресе) так вообще игнорируют границы транзакции если запрос на уровне сессии. Чтобы получить эффект "все или ничего" нужно использовать транзакцию, даже если используешь и лок. Например, вот так:

User.transaction do
  User.with_advisory_lock("updating_user_#{user.id}") do
    user.update(role: "admin")
    user.user_organization.first.update_periods
  end
end
Enter fullscreen mode Exit fullscreen mode

3. В общем случае, ключ для блокировок разных частей кода должен быть разным.
Вот два метода, и оба используют блокировку с одним и тем же ключом. Да, смотрится очень эффектно, но смысла в этом мало, потому как методы абсолютно независимые. Нет никакого резона, чтобы метод "update" ждал, пока метод "check" закончит свою работу, даже если он выполняется параллельно. И наоборот.

def update(users)
  with_user_lock do
    users.where(role: "admin").update(role: "user")
  end
end

def check(users)
  with_user_lock do
    users.all? { _1.task_completed? }
  end
end

def with_user_lock(user, &block)
  User.with_advisory_lock("updating_user_#{user.id}", &block)
end
Enter fullscreen mode Exit fullscreen mode

Конечно, есть исключения и можно придумать сценарий, когда один и тот же лок в разных методах имеет смысл, но вы должны четко понимать, что вы делаете и зачем.

Так а зачем же тогда нужен этот лок?

Эта блокировка отлично подходит для контроля конкурентных запросов на уровне приложения (Application-level Concurrency Control). Это мьютекс, который используется для того, чтобы гарантировать, что два разных процесса, которые исполняют ОДИН и ТОТ ЖЕ код, не выполняют этот код одновременно.

class UpdateRoleJob < ApplicationJob
  def perform(user)
    User.with_advisory_lock("updating_user_#{user.id}") do
      user.update(role: "admin")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Если по какой-то причине у вас в очереди появилось две задачи для одного и того же пользователя, и если случилось так, что 2 разных воркера начнут обрабатывать эти две задачи, то вы можете быть уверены, что только один из них будет выполняться в данный момент, а второй будет ждать, пока первый закончит свою работу.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (2)

Collapse
 
glebson1988 profile image
Gleb

с одним и тем же ключОм
😉

Collapse
 
abuhtoyarov profile image
Buhtoyarov Artem

Ухты, спасибо. Раньше использовал громоздкую конструкцию с мьютексом и сохранением ключика в редис. Этот метод выглядит более лаконично.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay