Skip to content

Instantly share code, notes, and snippets.

@victorhazbun
Created September 19, 2023 19:15
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 victorhazbun/41209fc114551f31eeb4d17e429d947a to your computer and use it in GitHub Desktop.
Save victorhazbun/41209fc114551f31eeb4d17e429d947a to your computer and use it in GitHub Desktop.
URL shortener
class IntToBase
def self.convert(int, base)
digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
result = ""
while int > 0
result += digits[int % base]
int /= base
end
result.reverse
end
end
require 'snow_flake'
require 'int_to_base'
service_epoch = Time.new(2023, 9, 19, 0, 0, 0).strftime('%s%L').to_i
datacenter_id = 3
node_id = 2
snow_flake = SnowFlake.new(service_epoch, datacenter_id, node_id)
next_id = snow_flake.next_id
base62 = IntToBase.convert(next_id, 62)
pp "Next ID: #{next_id}"
pp "Parsed ID: #{snow_flake.parse(next_id)}"
pp "Base62 ID: #{base62}"
pp "Short URL: https://short.com/#{base62}"
# Pure ruby independent ID generator like the Snowflake, ChiliFlake.
# @see https://github.com/twitter/snowflake
# @see https://github.com/ma2shita/chiliflake
class SnowFlake
TIMESTAMP_BITS = 41
NODE_ID_BITS = 5
DATACENTER_ID_BITS = 5
SEQUENCE_BITS = 12
MAX_NODE_ID = (1 << NODE_ID_BITS) # 1024
MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) # 1024
MAX_SEQUENCE = (1 << SEQUENCE_BITS) # 4096
def initialize(target_epoch, datacenter_id, node_id, sequence = 0)
raise OverflowError, "invalid node_id (#{node_id} >= #{MAX_NODE_ID})" if node_id >= MAX_NODE_ID
raise OverflowError, "invalid datacenter_id (#{datacenter_id} >= #{MAX_DATACENTER_ID})" if datacenter_id >= MAX_DATACENTER_ID
raise OverflowError, "invalid sequence (#{sequence} >= #{MAX_SEQUENCE})" if sequence >= MAX_SEQUENCE
@target_epoch = target_epoch
@node_id = node_id % MAX_NODE_ID
@datacenter_id = datacenter_id % MAX_DATACENTER_ID
@sequence = sequence % MAX_SEQUENCE
@last_time = current_time
end
def next_id
time = current_time
raise InvalidSystemClockError, "(#{time} < #{@last_time})" if time < @last_time
if time == @last_time
@sequence = (@sequence + 1) % MAX_SEQUENCE
# NOTE: Distributed in node_id so if more than MAX_SEQUENCE, or wait until the next time.
# time = till_next_time if @sequence == 0
else
@sequence = 0
end
@last_time = time
compose(@last_time, @datacenter_id, @node_id, @sequence)
end
def parse(flake_id)
SnowFlake.parse(flake_id, @target_epoch)
end
def self.parse(flake_id, target_epoch)
hash = {}
hash[:epoch_time] = flake_id >> (SEQUENCE_BITS + NODE_ID_BITS + DATACENTER_ID_BITS)
hash[:time] = Time.at((hash[:epoch_time] + target_epoch) / 1000.0)
hash[:datacenter_id] = (flake_id >> (SEQUENCE_BITS + DATACENTER_ID_BITS)).to_s(2)[-DATACENTER_ID_BITS, DATACENTER_ID_BITS].to_i(2)
hash[:node_id] = (flake_id >> SEQUENCE_BITS).to_s(2)[-NODE_ID_BITS, NODE_ID_BITS].to_i(2)
hash[:sequence] = flake_id.to_s(2)[-SEQUENCE_BITS, SEQUENCE_BITS].to_i(2)
hash
end
private
def compose(last_time, datacenter_id, node_id, sequence)
((last_time - @target_epoch) << (SEQUENCE_BITS + NODE_ID_BITS + DATACENTER_ID_BITS)) +
(datacenter_id << (SEQUENCE_BITS + DATACENTER_ID_BITS)) +
(node_id << SEQUENCE_BITS) +
sequence
end
def current_time
Time.now.strftime('%s%L').to_i
end
end
class SnowFlake::OverflowError < StandardError; end
class SnowFlake::InvalidSystemClockError < StandardError; end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment