12/08/2018, 16:41

Optimistic Locking trong Rails

Optimistic Locking là gì ? Trong quá trình vận hành hệ thống, mình đã gặp phải một tình huống khá trớ trêu là 1 bản ghi bị ghi đè nhiều lần và dữ liệu trước bị ghi đè bởi dữ liệu sau khiến dữ liệu update trước đó bị mất hoàn toàn. Sau khi điều tra thì vấn đề là do hệ thống của mình có nhiều người ...

Optimistic Locking là gì ?

Trong quá trình vận hành hệ thống, mình đã gặp phải một tình huống khá trớ trêu là 1 bản ghi bị ghi đè nhiều lần và dữ liệu trước bị ghi đè bởi dữ liệu sau khiến dữ liệu update trước đó bị mất hoàn toàn. Sau khi điều tra thì vấn đề là do hệ thống của mình có nhiều người dùng cùng quản lý một bản ghi, và người sau đã vô tình mở form edit trước khi người trước ấn submit vì vậy form edit của người sau chỉ chứa thông tin đã cũ.

Để dễ hình dung ta có thể liên hệ tới hệ thống Redmine, chắc hẳn nhiều bạn sử dụng redmine đã từng gặp việc khi update thì redmine hiện ra thông báo The issue has been updated by an other user while you were editing it. Thông báo này chính là để hạn chế trường hợp mà mình đã nói. Nhưng làm thế nào mà Redmine lại làm được việc này.

Đơn giản thôi, nếu bạn Inspect form của redmine thì sẽ thấy một field đặc biệt

Đó là field lock_version. Và nó là Optimistic Locking, dịch ra có nghĩa là khóa lạc quan, vì nó cho phép nhiều người dùng cùng truy cập vào một bản ghi để chỉnh sửa với việc conflict data là thấp nhất.

Cách thức làm việc của Optimistic Locking

Cách thức khá là đơn giản: mỗi khi update bản ghi thì chúng ta sẽ kiểm tra giá trị của Optimistic Locking trong database, nếu đã bị thay đổi thì sẽ không cho phép update.

Optimistic Locking có thể là một field tự tăng hoặc lưu thời điểm update cuối cùng.

Optimistic Locking trong Rails

Thật may là Rails đã hỗ trợ Optimistic Locking, có thể tìm hiểu thêm ở đây

Optimistic Locking trong Rails sử dụng lock_version Chúng ta có thể thay đổi Optimistic Locking bằng cách sau

class Person < ActiveRecord::Base
  self.locking_column = :lock_person
end

Để kích hoạt Optimistic Locking trong Rails, chúng ta chỉ cần tạo 1 field lock_version trong table, (lưu ý data type phải là integer và có default: 0) Mỗi lần update thì Rails sẽ tự tăng giá trị của lock_version lên 1 đơn vị, vì vậy nếu update bị conflict thì giá trị lock_version sẽ không khớp Rails sẽ raise exception StaleObjectError

Ví dụ:

p1 = Person.find(1)
p2 = Person.find(1)

p1.first_name = "Michael"
p1.save

p2.first_name = "should fail"
p2.save # Raises an ActiveRecord::StaleObjectError

Các bước triển khai Optimistic Locking trong Rails

Thêm migrate field lock_version

# migrations/011_add_products_lock_version.rb
add_column :products, :lock_version, :integer, :default => 0, :null => false

Chúng ta có controller như sau:

# products_controller.rb
def update
  @product = Product.find(params[:id])
  if @product.update_attributes(params[:product])
    flash[:notice] = "Successfully updated product."
    redirect_to product_path(@product)
  else
    render :action => 'edit'
  end
rescue ActiveRecord::StaleObjectError
  @product.reload
  render :action => 'conflict'
end

Xử lý view

# _form.rhtml
<%= f.hidden_field :lock_version %>

# conflict.rhtml
<% title "Edit Product Conflict" %>

Someone edited the product the same time you did. Please re-apply your changes to the product.

<h2>Your Submission:</h2>
<pre>
<% params[:product].each do |name, value| %>
  <%=h name.humanize %>: <%=h value %>
<% end %>
</pre>

<h2>Edit Product:</h2>
<% form_for :product, :url => product_path(@product), :html => { :method => :put } do |f| %>
  <%= render :partial => 'form', :locals => { :f => f } %>
  <%= submit_tag 'Resolve' %>
<% end %>

0