Module: CouchbaseId::Generator

Defined in:
lib/couchbase-id/generator.rb

Overview

NOTE

incr, decr, append, prepend == atomic

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/couchbase-id/generator.rb', line 80

def self.included(base)
    class << base
        attr_accessor :__overflow__
        attr_accessor :__class_id_generator__
    end


    base.class_eval do
        #
        # Best case we have 18446744073709551615 * 18446744073709551615 model entries for each database cluster
        #  and we can always change the cluster id if this limit is reached
        #
        define_model_callbacks :save, :create
        before_save :generate_id
        before_create :generate_id

        def self.default_class_id_generator(overflow, count)
            id = Radix.convert(overflow, B10, B64) + Radix.convert(count, B10, B64)
            "#{self.design_document}_#{CLUSTER_ID}-#{id}"
        end

        #
        # Override the default hashing function
        #
        def self.set_class_id_generator(callback = nil, &block)
            callback ||= block
            self.__class_id_generator__ = callback
        end

        #
        # Configure class level variables
        base.__overflow__ = nil
        base.__class_id_generator__ = method(:default_class_id_generator)
    end
end

Instance Method Details

#generate_idObject

instance method



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/couchbase-id/generator.rb', line 36

def generate_id
    if self.id.nil?                
        #
        # Generate the id (incrementing values as required)
        #
        overflow = self.class.__overflow__ ||= self.class.bucket.get("#{self.class.design_document}:#{CLUSTER_ID}:overflow", :quiet => true) # Don't error if not there
        count = self.class.bucket.incr("#{self.class.design_document}:#{CLUSTER_ID}:count", :create => true)     # This models current id count
        if count == 0 || overflow.nil?
            overflow ||= 0
            overflow += 1
            # We shouldn't need to worry about concurrency here due to the size of count
            # Would require ~18446744073709551615 concurrent writes
            self.class.bucket.set("#{self.class.design_document}:#{CLUSTER_ID}:overflow", overflow)
            self.class.__overflow__ = overflow
        end
        
        self.id = self.class.__class_id_generator__.call(overflow, count)
        
        
        #
        # So an existing id would only be present if:
        # => something crashed before incrementing the overflow
        # => this is another request was occurring before the overflow is incremented
        #
        # Basically only the overflow should be able to cause issues, we'll increment the count just to be sure
        # One would hope this code only ever runs under high load during an overflow event
        #
        while self.class.bucket.get(self.id, :quiet => true).present?
            # Set in-case we are here due to a crash (concurrency is not an issue)
            # Note we are not incrementing the @__overflow__ variable
            self.class.bucket.set("#{self.class.design_document}:#{CLUSTER_ID}:overflow", overflow + 1)
            count = self.class.bucket.incr("#{self.class.design_document}:#{CLUSTER_ID}:count")               # Increment just in case (attempt to avoid infinite loops)
            
            # Reset the overflow
            if self.class.__overflow__ == overflow
                self.class.__overflow__ = nil
            end

            # Generate the new id
            self.id = self.class.__class_id_generator__.call(overflow + 1, count)
        end
    end
end