locality-uuid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/LICENSE.md +29 -0
- data/README.md +212 -0
- data/bin/locality-uuid +35 -0
- data/lib/locality-uuid.rb +184 -0
- data/locality-uuid.gemspec +60 -0
- data/spec/lib/locality-uuid_spec.rb +189 -0
- data/spec/spec_helper.rb +42 -0
- metadata +133 -0
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
data/Gemfile
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|