locality-uuid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTlkODUxYzI5ODllNGY3ZDAyZTdmYzVjMjk3NDVjZDkzZjA3ZTVlNg==
5
+ data.tar.gz: !binary |-
6
+ YzU3NmU5YTRlN2NkMTg2YzY5MGEyNDUwMzdhYzY3ZjM5N2FkNmY5Mw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NGJjYzBkMWY2NDUyZDViOGY4NTM1NTA3OWZiYzE2MmMzMWEzMGVkN2VjOTMx
10
+ NDEwYmU1NzYyOTUyZGQwNmIxYjk4MmFiODM0MzEzMTFmZDM5OTg1YjdlZTcy
11
+ ODIwODhhMWUxMTBhZmVmNDNlNjdmOTg4ZGJkZTlhOWQwMzJmZTE=
12
+ data.tar.gz: !binary |-
13
+ NzY0OTRhYTkxN2M5MjIwNWQ2NmFmZmY1ZGUyNTY2Y2FkZTRhNDIwMTdmZDA2
14
+ MGM5YmFmMzFlMjk4YjVlZTQ0ZjI5MTM3ZDNjMzZkOGIwYWYzMzFjY2FkNzEz
15
+ OTkyZTcxZDk2NDU2NmMzNzM3YWUzNjNhY2ZhM2M5M2RjMDE3OTU=
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2013, Groupon, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+
8
+ Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ Neither the name of GROUPON nor the names of its contributors may be
16
+ used to endorse or promote products derived from this software without
17
+ specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ Locality UUID (vB) Ruby
2
+ =======================
3
+
4
+ This is a UUID class intended to help control data locality when inserting into a distributed data
5
+ system, such as MongoDB or HBase. There is also [a Java implementation](groupon/locality-uuid.java).
6
+ This version does not conform to any external standard or spec. Developed at Groupon in Palo Alto
7
+ by Peter Bakkum and Michael Craig.
8
+
9
+ Problems we encountered with other UUID solutions:
10
+
11
+ - Collisions when used with heavy concurrency.
12
+ - Difficult to retrieve useful information, such as the timetamp, from the id.
13
+ - Time-based versions (such as v1) place this component at the front, meaning all generated ids
14
+ start with the same bytes for some time period. This was disadvantageous for us because all
15
+ of these writes were hashed to the same shards of our distributed databases. Thus, only one
16
+ machine was receiving writes at a given time.
17
+
18
+ Solutions:
19
+
20
+ - We have tested thoroughly in a concurrent environment and include both the PID and MAC Address
21
+ in the UUID. In the first block of the UUID we have a counter which we increment with a large
22
+ primary number, ensuring that the counter in a single process takes a long time to wrap around
23
+ to the same value.
24
+ - The UUID layout is very simple and documented below. Retrieving the millisecond-precision timestamp
25
+ is as simple as copying the last segment and converting from hex to decimal.
26
+ - To ensure that generated ids are evenly distributed in terms of the content of their first few bytes,
27
+ we increment this value with a large number. This means that DB writes using these values are
28
+ evenly distributed across the cluster. We ALSO allow toggling into sequential mode, so that the
29
+ first few bytes of the UUID are consistent and writes done with these keys hash to tho same machine
30
+ in the cluster, when this characteristic is desireable. Thus, this library is a tool for managing
31
+ the locality of reads and writes of data inserted with UUID keys.
32
+
33
+ This library has both Java and Ruby implementations, and is also accessible from the command line.
34
+
35
+ Format
36
+ ------
37
+
38
+ This generates UUIDs in the following format:
39
+
40
+ ```
41
+ wwwwwwww-xxxx-byyy-yyyy-zzzzzzzzzzzz
42
+
43
+ w: highly variable counter value
44
+ x: process id
45
+ b: literal hex 'b' representing the UUID version
46
+ y: fragment of machine MAC address
47
+ z: UTC timestamp (milliseconds since epoch)
48
+ ```
49
+
50
+ Example:
51
+
52
+ ```
53
+ 20be0ffc-314a-bd53-7a50-013a65ca76d2
54
+
55
+ counter : 3,488,672,514
56
+ process id : 12,618
57
+ MAC address : __:__:_d:53:7a:50
58
+ timestamp : 1,350,327,498,450 (Mon, 15 Oct 2012 18:58:18.450 UTC)
59
+ ```
60
+
61
+ Example Use
62
+ -----------
63
+
64
+ Install the gem:
65
+
66
+ ```bash
67
+ gem install locality-uuid
68
+ ```
69
+
70
+ Use it in a program:
71
+
72
+ ```ruby
73
+ require 'locality-uuid'
74
+
75
+ uuid = UUID.new
76
+
77
+ puts "UUID : #{uuid.to_s}"
78
+ puts "raw bytes : #{uuid.bytes.unpack("H*")[0]}"
79
+ puts "process id : #{uuid.pid}"
80
+ puts "MAC fragment : #{uuid.mac}"
81
+ puts "timestamp : #{uuid.timestamp}"
82
+ puts "version : #{uuid.version}"
83
+
84
+ copy = UUID.new(uuid.to_s)
85
+
86
+ puts "copied : #{(uuid == copy)}"
87
+ ```
88
+
89
+ Or just run from the command line:
90
+
91
+ ```
92
+ locality-uuid
93
+ ```
94
+
95
+ Notes
96
+ -----
97
+
98
+ This UUID version was designed to have easily readable PID, MAC address, and
99
+ timestamp values, with a regularly incremented count. The motivations for this
100
+ implementation are to reduce the chance of duplicate ids, store more useful
101
+ information in UUIDs, and ensure that the first few characters vary for successively
102
+ generated ids, which can be important for splitting ids over a cluster. The UUID
103
+ generator is also designed to be be thread-safe without locking.
104
+
105
+ Uniqueness is supported by the millisecond precision timestamp, the MAC address
106
+ of the generating machine, the 2 byte process id, and a 4 byte counter. Thus,
107
+ a UUID is guaranteed to be unique in an id space if each machine allows 65,536 processes or less,
108
+ does not share the last 28 bits of its MAC address with another machine in the id
109
+ space, and generates fewer than 4,294,967,295 ids per millisecond in a process.
110
+
111
+ ___Counter___
112
+ The counter value is reversed, such that the least significant 4-bit block is the first
113
+ character of the UUID. This is useful because it makes the earlier bits of the UUID
114
+ change more often. Note that the counter is not incremented by 1 each time, but rather
115
+ by a large prime number, such that its incremental value is significantly different, but
116
+ it takes many iterations to reach the same value.
117
+
118
+ Examples of sequentially generated ids in the default counter mode:
119
+ ```
120
+ c8c9cef9-7a7f-bd53-7a50-013e4e2afbde
121
+ 14951cfa-7a7f-bd53-7a50-013e4e2afbde
122
+ 6f5169fb-7a7f-bd53-7a50-013e4e2afbde
123
+ ba2da6fc-7a7f-bd53-7a50-013e4e2afbde
124
+ 06f8f3fc-7a7f-bd53-7a50-013e4e2afbde
125
+ 51c441fd-7a7f-bd53-7a50-013e4e2afbde
126
+ ac809efe-7a7f-bd53-7a50-013e4e2afbde
127
+ f75cdbff-7a7f-bd53-7a50-013e4e2afbde
128
+ ```
129
+
130
+ Note the high variability of the first few characters.
131
+
132
+ The counter can also be toggled into sequential mode to effectively reverse this logic.
133
+ This is useful because it means you can control the locality of your data as you generate
134
+ ids across a cluster. Sequential mode works by creating an initial value based on a hash
135
+ of the current date and hour. This means it can be discovered independently on distributed
136
+ machines. The value is then incremented by one for each id generated. If you use key-based
137
+ sharding, data inserted with these ids should have some locality.
138
+
139
+
140
+ Examples of sequentially generated ids in sequential counter mode:
141
+ ```
142
+ f5166777-7a7f-bd53-7a50-013e4e2afc26
143
+ f5166778-7a7f-bd53-7a50-013e4e2afc26
144
+ f5166779-7a7f-bd53-7a50-013e4e2afc26
145
+ f516677a-7a7f-bd53-7a50-013e4e2afc26
146
+ f516677b-7a7f-bd53-7a50-013e4e2afc26
147
+ f516677c-7a7f-bd53-7a50-013e4e2afc26
148
+ f516677d-7a7f-bd53-7a50-013e4e2afc26
149
+ f516677e-7a7f-bd53-7a50-013e4e2afc26
150
+ ```
151
+
152
+ ___PID___
153
+ This value is just the current process id modulo 65,536. In my experience, most linux
154
+ machines do not allow PID numbers to go this high, but OSX machines do.
155
+
156
+ ___MAC Address___
157
+ The last 28 bits of the first active MAC address found on the machine. If no active
158
+ MAC address is found, this is filled in with zeroes.
159
+
160
+ ___Timestamp___
161
+ This is the UTC milliseconds since Unix epoch. To convert to a time manually first
162
+ copy the last segment of the UUID, convert to decimal, then use a time library to
163
+ count up from 1970-1-1 0:00:00.000 UTC.
164
+
165
+
166
+ API
167
+ ---
168
+
169
+ __UUID.initialize__
170
+
171
+ Generate a new UUID object.
172
+
173
+ __UUID.initialize (String uuid)__
174
+
175
+ Construct a UUID with the given String, must be of the form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
176
+ where `x` matches `[0-9a-f]`.
177
+
178
+ __UUID.initialize (UUID uuid)__
179
+
180
+ Construct a UUID with the given UUID. This creates a copy.
181
+
182
+ __UUID::use_sequential_ids()__
183
+
184
+ Toggle into sequential mode, so ids are generated in order.
185
+
186
+ __UUID::use_variable_ids()__
187
+
188
+ Toggle into variable mode, so the first few characters of each id vary during generation. This is the default mode.
189
+
190
+ __UUID.bytes() -> String__
191
+
192
+ Get raw byte content of UUID.
193
+
194
+ __UUID.to_s() -> String__
195
+
196
+ Get UUID String in the standard format.
197
+
198
+ __UUID.version() -> String__
199
+
200
+ Return the UUID version character, which is 'b' for ids generated by this library.
201
+
202
+ __UUID.pid() -> Fixnum__
203
+
204
+ Return the PID embedded in the UUID.
205
+
206
+ __UUID.timestamp() -> Time__
207
+
208
+ Return timestamp embedded in UUID, which is set at generation.
209
+
210
+ __UUID.mac() -> String__
211
+
212
+ Get the embedded MAC Address fragment. This will be a String of hex characters.
data/bin/locality-uuid ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2013, Groupon, Inc.
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions
8
+ # are met:
9
+ #
10
+ # Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # Neither the name of GROUPON nor the names of its contributors may be
18
+ # used to endorse or promote products derived from this software without
19
+ # specific prior written permission.
20
+ #
21
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
33
+ require 'locality-uuid'
34
+ puts UUID.new.to_s
35
+
@@ -0,0 +1,184 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'macaddr'
32
+ require 'atomic'
33
+ require 'digest/md5'
34
+
35
+ class UUID
36
+ GEMVERSION = '1.0.0'
37
+ @@VERSION = 'b'
38
+ @@MAC = Mac.addr[7..-1].gsub(/:/, '')
39
+ @@MIDDLE = @@VERSION + @@MAC
40
+ @@PRIME = 198491317
41
+ @@COUNTER_MAX = 4294967295
42
+ @@PID_MAX = 65536
43
+ @@PID = $$ % @@PID_MAX
44
+ @@REGEX = /^[0-9a-zA-Z]{8}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{12}$/
45
+
46
+ @@sequential = false
47
+ @@counter = Atomic.new(Random.rand(@@COUNTER_MAX))
48
+
49
+ def initialize input = nil
50
+ if input == nil
51
+ generate
52
+ elsif input.is_a? String
53
+ parse input
54
+ elsif input.is_a? UUID
55
+ @content = input.bytes
56
+ else
57
+ raise "could not construct UUID with input #{input}"
58
+ end
59
+ end
60
+
61
+ def self.use_sequential_ids
62
+ if !@@sequential
63
+ # get a string that changes every 10 minutes
64
+ String date = Time.now.utc.strftime("%Y%m%d%H%M").slice(0, 11)
65
+
66
+ # run an md5 hash of the string, no reason this needs to be secure
67
+ digest = Digest::MD5.hexdigest date
68
+
69
+ # get first 4 bytes of digest, reverse, and turn into int
70
+ x = [digest[0...8]].pack("H*").unpack("l")[0]
71
+ @@counter.update { |curr| x }
72
+ end
73
+ @@sequential = true
74
+ end
75
+
76
+ def self.use_variable_ids
77
+ @@sequential = false
78
+ end
79
+
80
+ def bytes
81
+ String.new @content
82
+ end
83
+
84
+ def to_s
85
+ s = @content.unpack('H*')[0]
86
+ x = '________-____-____-____-____________'
87
+ x[0 ] = s[0 ]
88
+ x[1 ] = s[1 ]
89
+ x[2 ] = s[2 ]
90
+ x[3 ] = s[3 ]
91
+ x[4 ] = s[4 ]
92
+ x[5 ] = s[5 ]
93
+ x[6 ] = s[6 ]
94
+ x[7 ] = s[7 ]
95
+
96
+ x[9 ] = s[8 ]
97
+ x[10] = s[9 ]
98
+ x[11] = s[10]
99
+ x[12] = s[11]
100
+
101
+ x[14] = s[12]
102
+ x[15] = s[13]
103
+ x[16] = s[14]
104
+ x[17] = s[15]
105
+
106
+ x[19] = s[16]
107
+ x[20] = s[17]
108
+ x[21] = s[18]
109
+ x[22] = s[19]
110
+
111
+ x[24] = s[20]
112
+ x[25] = s[21]
113
+ x[26] = s[22]
114
+ x[27] = s[23]
115
+ x[28] = s[24]
116
+ x[29] = s[25]
117
+ x[30] = s[26]
118
+ x[31] = s[27]
119
+ x[32] = s[28]
120
+ x[33] = s[29]
121
+ x[34] = s[30]
122
+ x[35] = s[31]
123
+ return x
124
+ end
125
+
126
+ def == other
127
+ return false unless other && other.is_a?(UUID)
128
+ return (self.bytes == other.bytes)
129
+ end
130
+
131
+ def version
132
+ @content[6].unpack('H')[0]
133
+ end
134
+
135
+ def pid
136
+ raise "incorrect UUID version: #{version}" unless version == @@VERSION
137
+ @content[4..5].unpack('S>')[0]
138
+ end
139
+
140
+ def timestamp
141
+ i = ("\x00\x00" + @content[10..15]).unpack('Q>')[0]
142
+ Time.at((i / 1000), (i % 1000 * 1000)).utc
143
+ end
144
+
145
+ def mac
146
+ '_____' + @content[6..9].unpack('H*')[0][1..-1]
147
+ end
148
+
149
+ private
150
+
151
+ def generate
152
+ time = (Time.now.utc.to_f * 1000).to_i
153
+ count = 0
154
+
155
+ if !@@sequential
156
+ countn = @@counter.update do |x|
157
+ c = x + @@PRIME
158
+ (c >= @@COUNTER_MAX ? c - @@COUNTER_MAX : c)
159
+ end
160
+
161
+ count = (((countn & 0xF) << 28) | ((countn & 0xF0) << 20))
162
+ count |= (((countn & 0xF00) << 12) | ((countn & 0xF000) << 4))
163
+ count |= (((countn & 0xF0000) >> 4) | ((countn & 0xF00000) >> 12))
164
+ count |= (((countn & 0xF000000) >> 20) | ((countn & 0xF0000000) >> 28))
165
+ else
166
+ countn = @@counter.update do |x|
167
+ c = x + 1
168
+ (c >= @@COUNTER_MAX ? 0 : c)
169
+ end
170
+
171
+ count = countn
172
+ end
173
+
174
+ @content = [count, @@PID, @@MIDDLE, time >> 32, time].pack('NS>H*S>N')
175
+ end
176
+
177
+ def parse input
178
+ raise 'input must be a string' unless input.is_a? String
179
+ raise "invalid uuid: #{input}" unless input =~ @@REGEX
180
+
181
+ input = input.gsub(/\-/, '')
182
+ @content = [input].pack('H*')
183
+ end
184
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright (c) 2013, Groupon, Inc.
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions
8
+ # are met:
9
+ #
10
+ # Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # Neither the name of GROUPON nor the names of its contributors may be
18
+ # used to endorse or promote products derived from this software without
19
+ # specific prior written permission.
20
+ #
21
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
33
+ require File.expand_path('../lib/locality-uuid.rb', __FILE__)
34
+
35
+ Gem::Specification.new do |gem|
36
+ gem.add_development_dependency 'mocha'
37
+ gem.add_development_dependency 'rspec'
38
+ gem.add_development_dependency 'autotest-standalone'
39
+ gem.add_runtime_dependency 'macaddr'
40
+ gem.add_runtime_dependency 'atomic'
41
+
42
+ gem.authors = ["Peter Bakkum"]
43
+ gem.bindir = 'bin'
44
+ gem.description = %q{This is a UUID class intended to help control data locality when inserting into a distributed data system, such as MongoDB or HBase. There is also a Java implementation. This version does not conform to any external standard or spec. Developed at Groupon in Palo Alto by Peter Bakkum and Michael Craig.}
45
+ gem.email = ['pbb7c@virginia.edu']
46
+ gem.executables = ['locality-uuid']
47
+ gem.extra_rdoc_files = ['README.md', 'LICENSE.md']
48
+ gem.files = Dir['README.md', 'LICENSE.md', 'locality-uuid.gemspec', 'Gemfile', '.rspec', 'spec/**/*', 'lib/*', 'bin/*']
49
+ gem.homepage = 'http://github.com/groupon/locality-uuid.rb'
50
+ gem.name = 'locality-uuid'
51
+ gem.rdoc_options = ["--charset=UTF-8"]
52
+ gem.require_paths = ['lib']
53
+ gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.6")
54
+ gem.summary = %q{UUID for data locality in distributed systems.}
55
+ gem.test_files = Dir['spec/**/*']
56
+ gem.version = UUID::GEMVERSION
57
+ gem.license = 'MIT'
58
+ end
59
+
60
+
@@ -0,0 +1,189 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'spec_helper'
32
+ require 'locality-uuid'
33
+
34
+ describe UUID do
35
+ it 'should have the correct structure' do
36
+ r = /^[0-9a-zA-Z]{8}\-[0-9a-zA-Z]{4}\-b[0-9a-zA-Z]{3}\-[0-9a-zA-Z]{4}\-0[0-9a-zA-Z]{11}$/
37
+ UUID.new.to_s.should =~ r
38
+ end
39
+
40
+ it 'should parse uuid values' do
41
+ x1 = UUID.new
42
+ x2 = UUID.new x1.to_s
43
+ x1.should == x2
44
+ x1.timestamp.should == x2.timestamp
45
+ x1.bytes.should == x2.bytes
46
+
47
+ expect {UUID.new 'aoeuaoeu'}.to raise_error
48
+ end
49
+
50
+ it 'should accept uuid in the constructor' do
51
+ x1 = UUID.new
52
+ x2 = UUID.new x1
53
+ x1.should == x2
54
+ x1.bytes.should == x2.bytes
55
+ end
56
+
57
+ it 'should switch modes' do
58
+ n = 1000
59
+ ids = []
60
+
61
+ for i in 0..(n-1)
62
+ ids << UUID.new.to_s
63
+ end
64
+
65
+ for i in 1..(n-1)
66
+ ids[i-1][0].should_not == ids[i][0]
67
+ end
68
+
69
+ UUID.use_sequential_ids
70
+ initial_counter = UUID.new.to_s.split('-')[0]
71
+ UUID.use_variable_ids
72
+ UUID.use_sequential_ids
73
+ second_counter = UUID.new.to_s.split('-')[0]
74
+ initial_counter.should == second_counter
75
+
76
+ ids = []
77
+
78
+ for i in 0..(n-1)
79
+ ids << UUID.new.to_s
80
+ end
81
+
82
+ for i in 1..(n-1)
83
+ ids[i-1][0].should == ids[i][0]
84
+ ids[i-1][1].should == ids[i][1]
85
+ ids[i-1][2].should == ids[i][2]
86
+
87
+ a = ids[i-1][7].to_s
88
+ b = ids[i][7].to_s
89
+
90
+ (((a.ord + 1).chr == b) || (a == '9' && b == 'a') || (a == 'f' && b == '0')).should == true
91
+ end
92
+
93
+ UUID.use_variable_ids
94
+ ids = []
95
+
96
+ for i in 0..(n-1)
97
+ ids << UUID.new.to_s
98
+ end
99
+
100
+ for i in 1..(n-1)
101
+ ids[i-1][0].should_not == ids[i][0]
102
+ end
103
+ end
104
+
105
+ it 'should be immutable' do
106
+ x = UUID.new
107
+ s1 = x.to_s
108
+ b = x.bytes
109
+ b[0] = 'x'
110
+ b[1] = 'x'
111
+ b[2] = 'x'
112
+ b[3] = 'x'
113
+ b[4] = 'x'
114
+
115
+ s2 = x.to_s
116
+ s1.should == s2
117
+ end
118
+
119
+ it 'should be immutable on the construction value' do
120
+ str = '00000000-0000-0000-0000-000000000000'
121
+ x = UUID.new str
122
+ s1 = x.to_s
123
+ str = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
124
+ s1.should == x.to_s
125
+ end
126
+
127
+ it 'should return the UUID version' do
128
+ UUID.new.version.should == 'b'
129
+ UUID.new('20be0ffc-314a-bd53-7a50-013a65ca76d2').version.should == 'b'
130
+ UUID.new('20be0ffc-314a-7d53-7a50-013a65ca76d2').version.should == '7'
131
+ end
132
+
133
+ it 'should return the UUID pid' do
134
+ UUID.new.pid.should == $$ % 65536
135
+ UUID.new('20be0ffc-314a-bd53-7a50-013a65ca76d2').pid.should == 12618
136
+ expect {UUID.new('20be0ffc-314a-7d53-7a50-013a65ca76d2').pid}.to raise_error
137
+ end
138
+
139
+ it 'should return the UUID timestamp' do
140
+ start = Time.now.utc.to_f.round(3)
141
+ time = UUID.new.timestamp
142
+ time.to_f.should be >= start - 0.01
143
+ time.to_f.should be < start + 0.01
144
+ end
145
+
146
+ it 'should return the MAC address fragment' do
147
+ UUID.new.mac.should == '_____' + Mac.addr[7..-1].gsub(/:/, '')
148
+ UUID.new('20be0ffc-314a-bd53-7a50-013a65ca76d2').mac.should == '_____d537a50'
149
+ end
150
+
151
+ it 'should not generate dups' do
152
+ n = 100000
153
+ a = Array.new n
154
+ s = Set.new
155
+
156
+ for i in 0..n
157
+ a[i] = UUID.new
158
+ end
159
+
160
+ a.each { |x| s.add x }
161
+
162
+ a.size.should == s.size
163
+ end
164
+
165
+ it 'should handle concurrent generation' do
166
+ n = 100000
167
+ nthreads = 10
168
+ effective_n = (n / nthreads).to_i * nthreads
169
+ threads = []
170
+
171
+ a = Array.new effective_n
172
+ s = Set.new
173
+
174
+ for i in 0..nthreads
175
+ threads << Thread.new(i) do |i|
176
+ for j in 0..(n / nthreads).to_i
177
+ u = UUID.new
178
+ a[nthreads * j + i] = u
179
+ end
180
+ end
181
+ end
182
+
183
+ threads.each { |thread| thread.join }
184
+ a.each { |x| s.add x }
185
+
186
+ s.size.should == a.size
187
+ end
188
+
189
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
32
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
33
+ require 'rubygems'
34
+ require 'rspec'
35
+ require 'mocha/api'
36
+
37
+ RSpec.configure do |config|
38
+ config.mock_with :mocha
39
+ config.fail_fast = true
40
+ end
41
+
42
+
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: locality-uuid
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Bakkum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mocha
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: autotest-standalone
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: macaddr
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: atomic
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: This is a UUID class intended to help control data locality when inserting
84
+ into a distributed data system, such as MongoDB or HBase. There is also a Java implementation.
85
+ This version does not conform to any external standard or spec. Developed at Groupon
86
+ in Palo Alto by Peter Bakkum and Michael Craig.
87
+ email:
88
+ - pbb7c@virginia.edu
89
+ executables:
90
+ - locality-uuid
91
+ extensions: []
92
+ extra_rdoc_files:
93
+ - README.md
94
+ - LICENSE.md
95
+ files:
96
+ - README.md
97
+ - LICENSE.md
98
+ - locality-uuid.gemspec
99
+ - Gemfile
100
+ - .rspec
101
+ - spec/lib/locality-uuid_spec.rb
102
+ - spec/spec_helper.rb
103
+ - lib/locality-uuid.rb
104
+ - bin/locality-uuid
105
+ homepage: http://github.com/groupon/locality-uuid.rb
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options:
111
+ - --charset=UTF-8
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: 1.3.6
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.0.3
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: UUID for data locality in distributed systems.
130
+ test_files:
131
+ - spec/lib/locality-uuid_spec.rb
132
+ - spec/spec_helper.rb
133
+ has_rdoc: