locality-uuid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: