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 +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:
|