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 разных воркера начнут обрабатывать эти две задачи, то вы можете быть уверены, что только один из них будет выполняться в данный момент, а второй будет ждать, пока первый закончит свою работу.

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (2)

Collapse
 
glebson1988 profile image
Gleb

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

Collapse
 
abuhtoyarov profile image
Buhtoyarov Artem

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

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay