Skip to content

Commit

Permalink
Add optimistic lock for concurrent insertion
Browse files Browse the repository at this point in the history
  • Loading branch information
jasl committed Dec 4, 2024
1 parent 921babf commit a7b2ef3
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 80 deletions.
47 changes: 24 additions & 23 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/rails/rails.git
revision: 2ab0094ab6e94d30a5a424dc93c2aeb1066cc76b
revision: 95deab7b439abba23fdc4bd659116dab5dbe2606
branch: main
specs:
actioncable (8.1.0.alpha)
Expand Down Expand Up @@ -127,7 +127,7 @@ GEM
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crass (1.0.6)
date (3.4.0)
date (3.4.1)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
Expand All @@ -144,11 +144,11 @@ GEM
activesupport (>= 6.1)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
io-console (0.7.2)
io-console (0.8.0)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.8.2)
json (2.9.0)
kamal (2.3.0)
activesupport (>= 7.0)
base64 (~> 0.2)
Expand All @@ -162,7 +162,7 @@ GEM
zeitwerk (>= 2.6.18, < 3.0)
keccak (1.3.1)
language_server-protocol (3.17.0.3)
logger (1.6.1)
logger (1.6.2)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand All @@ -175,7 +175,7 @@ GEM
matrix (0.4.2)
mini_mime (1.1.5)
mini_portile2 (2.8.8)
minitest (5.25.2)
minitest (5.25.4)
msgpack (1.7.5)
net-imap (0.5.1)
date
Expand All @@ -192,27 +192,28 @@ GEM
net-protocol
net-ssh (7.3.0)
nio4r (2.7.4)
nokogiri (1.16.7-aarch64-linux)
nokogiri (1.16.8-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.7-arm-linux)
nokogiri (1.16.8-arm-linux)
racc (~> 1.4)
nokogiri (1.16.7-arm64-darwin)
nokogiri (1.16.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86-linux)
nokogiri (1.16.8-x86-linux)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
nokogiri (1.16.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
nokogiri (1.16.8-x86_64-linux)
racc (~> 1.4)
ostruct (0.6.1)
pagy (9.3.1)
pagy (9.3.2)
parallel (1.26.3)
parser (3.3.6.0)
ast (~> 2.4.1)
racc
pg (1.5.9)
pkg-config (1.5.8)
psych (5.2.0)
psych (5.2.1)
date
stringio
public_suffix (6.0.1)
puma (6.5.0)
Expand All @@ -230,9 +231,9 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rainbow (3.1.1)
rake (13.2.1)
rbsecp256k1 (6.0.0)
Expand All @@ -241,18 +242,18 @@ GEM
rubyzip (~> 2.3)
rdoc (6.8.1)
psych (>= 4.0.0)
regexp_parser (2.9.2)
reline (0.5.11)
regexp_parser (2.9.3)
reline (0.5.12)
io-console (~> 0.5)
rexml (3.3.9)
rubocop (1.69.0)
rubocop (1.69.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rubocop-ast (>= 1.36.1, < 2.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.36.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.36.2)
Expand All @@ -275,7 +276,7 @@ GEM
rubocop-rails
ruby-progressbar (1.13.0)
rubyzip (2.3.2)
securerandom (0.3.2)
securerandom (0.4.0)
selenium-webdriver (4.27.0)
base64 (~> 0.2)
logger (~> 1.4)
Expand Down Expand Up @@ -318,7 +319,7 @@ GEM
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.0.2)
useragent (0.16.10)
useragent (0.16.11)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down
10 changes: 10 additions & 0 deletions app/controllers/api/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ def create
}
}, status: :unprocessable_content
end
rescue ActiveRecord::StaleObjectError
render json: {
status: "error",
error: {
message: "Event not saved",
data: "Too fast requests."
}
}, status: :unprocessable_content
end

def batch_create
Expand All @@ -60,6 +68,8 @@ def batch_create
errored = event_params
break
end
rescue ActiveRecord::StaleObjectError
errored = event_params
end

render json: {
Expand Down
14 changes: 14 additions & 0 deletions app/models/conversation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class Conversation < ApplicationRecord
self.primary_key = %i[pubkey session]
self.lock_optimistically = true

has_many :events,
foreign_key: %i[pubkey session],
dependent: :restrict_with_exception

def latest_event
events.order(id: :desc).first
end
end
36 changes: 15 additions & 21 deletions app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ class Event < ApplicationRecord
scope :of_topic, ->(topic) { where(topic:) }
scope :of_recipient, ->(recipient) { where(recipient:) }

belongs_to :conversation, foreign_key: %i[pubkey session], required: true, autosave: true
has_one :merkle_node, dependent: :restrict_with_exception

after_create :add_to_merkle_tree
# after_create :add_to_merkle_tree

# A publisher must not send events in the same time which makes harder to sort them.
validates :created_at,
uniqueness: {
scope: :pubkey
},
presence: true,
comparison: {
greater_than_or_equal_to: ->(current) {
current.latest&.created_at || 0
current.conversation&.latest_event_created_at || 0
}
}

Expand All @@ -37,14 +36,20 @@ class Event < ApplicationRecord
format: { with: /\A\h+\z/ },
allow_nil: true

before_validation do
before_validation on: :create do
self.session = tags.find { |tag| tag[0] == "s" }&.[](1)
self.topic = tags.find { |tag| tag[0] == "t" }&.[](1)
self.recipient = tags.find { |tag| tag[0] == "p" }&.[](1)

self.conversation ||= Conversation.find_or_create_by(pubkey: pubkey, session: session) do |c|
c.latest_event_created_at = created_at
c.events_count = 0
end
end

def readonly?
persisted?
after_validation on: :create do
self.conversation.latest_event_created_at = created_at
self.conversation.events_count += 1
end

def merkle_tree_hash
Expand All @@ -63,19 +68,8 @@ def inclusion_proof
merkle_node&.inclusion_proof
end

def latest
@latest ||=
Event
.of_topic(topic)
.of_pubkey(pubkey)
.of_session(session)
.order(id: :desc)
.first
end

def reload(options = nil)
@latest = nil
super
def readonly?
persisted?
end

class << self
Expand Down
7 changes: 7 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,12 @@ class Application < Rails::Application
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")

config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

config.generators do |g|
g.helper false
g.assets false
end
end
end
16 changes: 16 additions & 0 deletions db/migrate/20240325200100_create_conversations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class CreateConversations < ActiveRecord::Migration[8.1]
def change
create_table :conversations, primary_key: %i[pubkey session] do |t|
t.string :pubkey, null: false
t.string :session, null: false

t.integer :lock_version
t.datetime :latest_event_created_at
t.integer :events_count

t.timestamps
end
end
end
6 changes: 2 additions & 4 deletions db/migrate/20240325200101_create_events.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class CreateEvents < ActiveRecord::Migration[8.0]
class CreateEvents < ActiveRecord::Migration[8.1]
def change
create_table :events do |t|
t.string :eid, null: false, index: { unique: true }
Expand All @@ -10,9 +10,7 @@ def change
t.string :content, null: false
t.string :sig, null: false

t.datetime :created_at, null: false

t.index %i[pubkey created_at], unique: true
t.datetime :created_at, null: false, index: true
end
end
end
4 changes: 2 additions & 2 deletions db/migrate/20240325200102_add_extended_fields_to_events.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

class AddExtendedFieldsToEvents < ActiveRecord::Migration[8.0]
class AddExtendedFieldsToEvents < ActiveRecord::Migration[8.1]
def change
change_table :events, id: :string do |t|
change_table :events do |t|
t.string :session, null: false

t.string :topic, null: true, index: true
Expand Down
2 changes: 1 addition & 1 deletion db/migrate/20240325200103_create_merkle_nodes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class CreateMerkleNodes < ActiveRecord::Migration[8.0]
class CreateMerkleNodes < ActiveRecord::Migration[8.1]
def change
# 创建顺序:parent to children
create_table :merkle_nodes do |t|
Expand Down
12 changes: 11 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a7b2ef3

Please sign in to comment.