Skip to content

Instantly share code, notes, and snippets.

@VeerpalBrar
Last active May 11, 2022 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VeerpalBrar/9481931b396d89767e6b4aeca97715ec to your computer and use it in GitHub Desktop.
Save VeerpalBrar/9481931b396d89767e6b4aeca97715ec to your computer and use it in GitHub Desktop.
Simple Quorum example in ruby
# Node is an abstraction of the db
class Node
attr_reader :id
def initialize(id)
@id = id
@data = {}
end
def read(key)
@data[key]
end
def write(key, value, timestamp)
@data[key] = {value: value, time: timestamp}
end
end
class Quorum
attr_reader :nodes
def initialize(nodes)
@nodes = nodes
end
def write(key, value)
wait_for_result(:write, key, value, Time.now)
end
def read(key)
results = wait_for_result(:read, key)
if read_conflicts?(results)
puts "Conflicting reads: #{results.map{|r| r ? r[:value] : nil}.uniq}"
latest_value = latest_value(results)
wait_for_result(:write, key, latest_value[:value], latest_value[:time])
return latest_value[:value]
end
puts "No conflicts"
results.first[:value]
end
private
def quorum_size
@size ||= (@nodes.length / 2.to_f).ceil
end
def wait_for_result(action, *args)
responses = []
tasklist = []
# Use threads to represent making calls to all the db replica's
puts "STARTING #: #{action} #{args}"
nodes.each do |node|
task = Thread.new do
sleep(rand(3));
# puts "node #{node.id} is waking up"
result = node.send(action, *args)
responses.push(result)
end
tasklist << task
end
# Wait for enough db's to response to establish a majority
sleep 0.1 while responses.length < quorum_size
# thread clean up
tasklist.each { |task|
task.kill if task.alive?
}
puts "FINISHED #: #{action} #{args}"
responses
end
def read_conflicts?(results)
results.map { |result| result ? result[:value] : nil }.uniq.size > 1
end
def latest_value(results)
results.reduce(nil) do |latest, result|
if result && (!latest || result[:time] > latest[:time])
result
else
latest
end
end
end
end
require 'test/unit/assertions'
include Test::Unit::Assertions
nodes = [Node.new(1), Node.new(2), Node.new(3), Node.new(4), Node.new(5)]
q = Quorum.new(nodes)
q.write(:foo, "bar")
puts assert_equal(q.read(:foo), "bar")
q.write(:foo, "baz")
q.write(:foo, "baz 2")
puts assert_equal(q.read(:foo), "baz 2")
puts assert_equal(q.read(:foo), "baz 2")
puts assert_equal(q.read(:foo), "baz 2")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment