daybreak 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/test/test.rb CHANGED
@@ -1,21 +1,16 @@
1
- require 'set'
1
+ require 'minitest/autorun'
2
+ require 'minitest/benchmark'
2
3
 
3
- begin
4
- require 'simplecov'
5
- SimpleCov.start
6
- SimpleCov.command_name "Unit tests"
7
- rescue Exception => ex
8
- puts "No coverage report generated: #{ex.message}"
9
- end
4
+ require 'set'
10
5
 
11
6
  require File.expand_path(File.dirname(__FILE__)) + '/test_helper.rb'
12
7
 
13
- describe "database functions" do
8
+ describe Daybreak::DB do
14
9
  before do
15
10
  @db = Daybreak::DB.new DB_PATH
16
11
  end
17
12
 
18
- it "should insert" do
13
+ it 'should insert' do
19
14
  @db[1] = 1
20
15
  assert_equal @db[1], 1
21
16
  assert @db.has_key?(1)
@@ -24,98 +19,141 @@ describe "database functions" do
24
19
  assert_equal @db.length, 1
25
20
  end
26
21
 
27
- it "should persist values" do
28
- @db.set('1', '4', true)
29
- @db.set('4', '1', true)
22
+ it 'should persist values' do
23
+ @db['1'] = '4'
24
+ @db['4'] = '1'
25
+ @db.sync
30
26
 
31
27
  assert_equal @db['1'], '4'
32
28
  db2 = Daybreak::DB.new DB_PATH
33
29
  assert_equal db2['1'], '4'
34
30
  assert_equal db2['4'], '1'
35
- db2.close!
31
+ db2.close
32
+ end
33
+
34
+ it 'should persist after clear' do
35
+ @db['1'] = 'xy'
36
+ @db.clear
37
+ @db['1'] = '4'
38
+ @db['4'] = '1'
39
+ @db.close
40
+
41
+ @db = Daybreak::DB.new DB_PATH
42
+ assert_equal @db['1'], '4'
43
+ assert_equal @db['4'], '1'
44
+ end
45
+
46
+ it 'should persist after compact' do
47
+ @db['1'] = 'xy'
48
+ @db['1'] = 'z'
49
+ @db.compact
50
+ @db['1'] = '4'
51
+ @db['4'] = '1'
52
+ @db.close
53
+
54
+ @db = Daybreak::DB.new DB_PATH
55
+ assert_equal @db['1'], '4'
56
+ assert_equal @db['4'], '1'
57
+ end
58
+
59
+ it 'should reload database file in sync after compact' do
60
+ db = Daybreak::DB.new DB_PATH
61
+
62
+ @db['1'] = 'xy'
63
+ @db['1'] = 'z'
64
+ @db.compact
65
+ @db['1'] = '4'
66
+ @db['4'] = '1'
67
+ @db.flush
68
+
69
+ db.sync
70
+ assert_equal db['1'], '4'
71
+ assert_equal db['4'], '1'
72
+ db.close
36
73
  end
37
74
 
38
- it "should compact cleanly" do
75
+ it 'should reload database file in sync after clear' do
76
+ db = Daybreak::DB.new DB_PATH
77
+
78
+ @db['1'] = 'xy'
79
+ @db['1'] = 'z'
80
+ @db.clear
81
+ @db['1'] = '4'
82
+ @db['4'] = '1'
83
+ @db.flush
84
+
85
+ db.sync
86
+ assert_equal db['1'], '4'
87
+ assert_equal db['4'], '1'
88
+ db.close
89
+ end
90
+
91
+ it 'should compact cleanly' do
39
92
  @db[1] = 1
40
93
  @db[1] = 1
41
- @db.flush!
94
+ @db.sync
95
+
42
96
  size = File.stat(DB_PATH).size
43
- @db.compact!
97
+ @db.compact
44
98
  assert_equal @db[1], 1
45
99
  assert size > File.stat(DB_PATH).size
46
100
  end
47
101
 
48
- it "should allow for default values" do
49
- default_db = Daybreak::DB.new(DB_PATH, 0)
102
+ it 'should allow for default values' do
103
+ default_db = Daybreak::DB.new(DB_PATH, :default => 0)
50
104
  assert_equal default_db[1], 0
51
105
  default_db[1] = 1
52
106
  assert_equal default_db[1], 1
107
+ default_db.close
53
108
  end
54
109
 
55
- it "should handle default values that are procs" do
110
+ it 'should handle default values that are procs' do
56
111
  db = Daybreak::DB.new(DB_PATH) {|key| Set.new }
57
112
  assert db['foo'].is_a? Set
113
+ db.close
58
114
  end
59
115
 
60
- it "should be able to sync competing writes" do
116
+ it 'should be able to sync competing writes' do
61
117
  @db.set! '1', 4
62
118
  db2 = Daybreak::DB.new DB_PATH
63
119
  db2.set! '1', 5
64
- @db.read!
120
+ @db.sync
65
121
  assert_equal @db['1'], 5
122
+ db2.close
66
123
  end
67
124
 
68
- it "should be able to handle another process's call to compact" do
69
- 20.times {|i| @db.set i, i, true }
125
+ it 'should be able to handle another process\'s call to compact' do
126
+ @db.lock { 20.times {|i| @db[i] = i } }
70
127
  db2 = Daybreak::DB.new DB_PATH
71
- 20.times {|i| @db.set i, i + 1, true }
72
- @db.compact!
73
- db2.read!
74
- assert_equal 20, db2['19']
128
+ @db.lock { 20.times {|i| @db[i] = i } }
129
+ @db.compact
130
+ db2.sync
131
+ assert_equal 19, db2['19']
132
+ db2.close
75
133
  end
76
134
 
77
- it "can empty the database" do
135
+ it 'can empty the database' do
78
136
  20.times {|i| @db[i] = i }
79
- @db.empty!
137
+ @db.clear
80
138
  db2 = Daybreak::DB.new DB_PATH
81
139
  assert_equal nil, db2['19']
140
+ db2.close
82
141
  end
83
142
 
84
- it "should compact subclassed dbs" do
85
- class StringDB < Daybreak::DB
86
- def serialize(it)
87
- it.to_s
88
- end
89
-
90
- def parse(it)
91
- it
92
- end
93
- end
94
-
95
- db = StringDB.new 'string.db'
96
- db[1] = 'one'
97
- db[2] = 'two'
98
- db.delete 2
99
- db.compact!
100
- assert_equal db[1], 'one'
101
- assert_equal db[2], nil
102
- db.empty!
103
- db.close!
104
- end
105
-
106
- it "should handle deletions" do
143
+ it 'should handle deletions' do
107
144
  @db[1] = 'one'
108
145
  @db[2] = 'two'
109
- @db.delete 'two'
146
+ @db.delete! 'two'
110
147
  assert !@db.has_key?('two')
111
148
  assert_equal @db['two'], nil
112
149
 
113
150
  db2 = Daybreak::DB.new DB_PATH
114
151
  assert !db2.has_key?('two')
115
152
  assert_equal db2['two'], nil
153
+ db2.close
116
154
  end
117
155
 
118
- it "should close and reopen the file when clearing the database" do
156
+ it 'should close and reopen the file when clearing the database' do
119
157
  begin
120
158
  1000.times {@db.clear}
121
159
  rescue
@@ -123,8 +161,164 @@ describe "database functions" do
123
161
  end
124
162
  end
125
163
 
164
+ it 'should have threadsafe lock' do
165
+ @db[1] = 0
166
+ inc = proc { 1000.times { @db.lock { @db[1] += 1 } } }
167
+ a = Thread.new &inc
168
+ b = Thread.new &inc
169
+ a.join
170
+ b.join
171
+ assert_equal @db[1], 2000
172
+ end
173
+
174
+ it 'should synchronize across processes' do
175
+ @db[1] = 0
176
+ @db.flush
177
+ @db.close
178
+ begin
179
+ a = fork do
180
+ db = Daybreak::DB.new DB_PATH
181
+ 1000.times do |i|
182
+ db.lock { db[1] += 1 }
183
+ db["a#{i}"] = i
184
+ sleep 0.01 if i % 100 == 0
185
+ end
186
+ db.close
187
+ end
188
+ b = fork do
189
+ db = Daybreak::DB.new DB_PATH
190
+ 1000.times do |i|
191
+ db.lock { db[1] += 1 }
192
+ db["b#{i}"] = i
193
+ sleep 0.01 if i % 100 == 0
194
+ end
195
+ db.close
196
+ end
197
+ Process.wait a
198
+ Process.wait b
199
+ @db = Daybreak::DB.new DB_PATH
200
+ 1000.times do |i|
201
+ assert_equal @db["a#{i}"], i
202
+ assert_equal @db["b#{i}"], i
203
+ end
204
+ assert_equal @db[1], 2000
205
+ rescue NotImplementedError
206
+ warn 'fork is not available: skipping multiprocess test'
207
+ @db = Daybreak::DB.new DB_PATH
208
+ end
209
+ end
210
+
211
+ it 'should synchronize across threads' do
212
+ @db[1] = 0
213
+ @db.flush
214
+ @db.close
215
+ a = Thread.new do
216
+ db = Daybreak::DB.new DB_PATH
217
+ 1000.times do |i|
218
+ db.lock { db[1] += 1 }
219
+ db["a#{i}"] = i
220
+ sleep 0.01 if i % 100 == 0
221
+ end
222
+ db.close
223
+ end
224
+ b = Thread.new do
225
+ db = Daybreak::DB.new DB_PATH
226
+ 1000.times do |i|
227
+ db.lock { db[1] += 1 }
228
+ db["b#{i}"] = i
229
+ sleep 0.01 if i % 100 == 0
230
+ end
231
+ db.close
232
+ end
233
+ a.join
234
+ b.join
235
+ @db = Daybreak::DB.new DB_PATH
236
+ 1000.times do |i|
237
+ assert_equal @db["a#{i}"], i
238
+ assert_equal @db["b#{i}"], i
239
+ end
240
+ assert_equal @db[1], 2000
241
+ end
242
+
243
+ it 'should support background compaction' do
244
+ @db[1] = 0
245
+ @db.flush
246
+ @db.close
247
+ stop = false
248
+ a = Thread.new do
249
+ db = Daybreak::DB.new DB_PATH
250
+ 1000.times do |i|
251
+ db.lock { db[1] += 1 }
252
+ db["a#{i}"] = i
253
+ sleep 0.01 if i % 100 == 0
254
+ end
255
+ db.close
256
+ end
257
+ b = Thread.new do
258
+ db = Daybreak::DB.new DB_PATH
259
+ 1000.times do |i|
260
+ db.lock { db[1] += 1 }
261
+ db["b#{i}"] = i
262
+ sleep 0.01 if i % 100 == 0
263
+ end
264
+ db.close
265
+ end
266
+ c = Thread.new do
267
+ db = Daybreak::DB.new DB_PATH
268
+ db.compact until stop
269
+ db.close
270
+ end
271
+ d = Thread.new do
272
+ db = Daybreak::DB.new DB_PATH
273
+ db.compact until stop
274
+ db.close
275
+ end
276
+ stop = true
277
+ a.join
278
+ b.join
279
+ c.join
280
+ d.join
281
+ @db = Daybreak::DB.new DB_PATH
282
+ 1000.times do |i|
283
+ assert_equal @db["a#{i}"], i
284
+ assert_equal @db["b#{i}"], i
285
+ end
286
+ assert_equal @db[1], 2000
287
+ end
288
+
289
+ it 'should support compact in lock' do
290
+ @db[1] = 2
291
+ @db.lock do
292
+ @db[1] = 2
293
+ @db.compact
294
+ end
295
+ end
296
+
297
+ it 'should support clear in lock' do
298
+ @db[1] = 2
299
+ @db.lock do
300
+ @db[1] = 2
301
+ @db.clear
302
+ end
303
+ end
304
+
305
+ it 'should support flush in lock' do
306
+ @db[1] = 2
307
+ @db.lock do
308
+ @db[1] = 2
309
+ @db.flush
310
+ end
311
+ end
312
+
313
+ it 'should support set! in lock' do
314
+ @db[1] = 2
315
+ @db.lock do
316
+ @db.set!(1, 2)
317
+ end
318
+ end
319
+
126
320
  after do
127
- @db.empty!
128
- @db.close!
321
+ @db.clear
322
+ @db.close
129
323
  end
130
324
  end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'minitest/autorun'
2
- require 'minitest/benchmark'
3
-
4
1
  HERE = File.expand_path(File.dirname(__FILE__))
5
2
  DB_PATH = File.join HERE, "test.db"
6
3
 
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daybreak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jeff Larson
9
+ - Daniel Mendler
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2013-01-08 00:00:00.000000000 Z
13
+ date: 2013-01-14 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: rake
@@ -43,9 +44,10 @@ dependencies:
43
44
  - - ! '>='
44
45
  - !ruby/object:Gem::Version
45
46
  version: '0'
46
- description: A simple dimple key-value store for ruby.
47
+ description: Incredibly fast pure-ruby key-value store
47
48
  email:
48
49
  - thejefflarson@gmail.com
50
+ - mail@daniel-mendler.de
49
51
  executables: []
50
52
  extensions: []
51
53
  extra_rdoc_files: []
@@ -61,11 +63,12 @@ files:
61
63
  - daybreak.gemspec
62
64
  - lib/daybreak.rb
63
65
  - lib/daybreak/db.rb
64
- - lib/daybreak/record.rb
66
+ - lib/daybreak/format.rb
67
+ - lib/daybreak/queue.rb
68
+ - lib/daybreak/serializer.rb
65
69
  - lib/daybreak/version.rb
66
- - lib/daybreak/writer.rb
67
- - test/bench.rb
68
- - test/compare.rb
70
+ - script/bench
71
+ - script/converter
69
72
  - test/prof.rb
70
73
  - test/test.rb
71
74
  - test/test_helper.rb
@@ -93,11 +96,9 @@ rubyforge_project:
93
96
  rubygems_version: 1.8.24
94
97
  signing_key:
95
98
  specification_version: 3
96
- summary: Daybreak provides an in memory key-value store that is easily enumerable
97
- in ruby.
99
+ summary: Daybreak provides an incredibly fast pure-ruby in memory key-value store,
100
+ which is multi-process safe and uses a journal log to store the data.
98
101
  test_files:
99
- - test/bench.rb
100
- - test/compare.rb
101
102
  - test/prof.rb
102
103
  - test/test.rb
103
104
  - test/test_helper.rb
@@ -1,62 +0,0 @@
1
- module Daybreak
2
- # Records define how data is serialized and read from disk.
3
- module Record
4
- # Thrown when either key or data is missing
5
- class UnnacceptableDataError < Exception; end
6
-
7
- # Thrown when there is a CRC mismatch between the data from the disk
8
- # and what was written to disk previously.
9
- class CorruptDataError < Exception; end
10
-
11
- extend self
12
-
13
- # The mask a record uses to check for deletion.
14
- DELETION_MASK = 1 << 31
15
-
16
- # Read a record from an open io source, check the CRC, and set <tt>@key</tt>
17
- # and <tt>@data</tt>.
18
- # @param [#read] io an IO instance to read from
19
-
20
- # The serialized representation of the key value pair plus the CRC.
21
- # @return [String]
22
- def serialize(record)
23
- raise UnnacceptableDataError, 'key and data must be defined' unless record[0] && record[1]
24
- s = key_data_string(record)
25
- s << crc_string(s)
26
- end
27
-
28
- # Create a new record to read from IO.
29
- # @param [#read] io an IO instance to read from
30
- def deserialize(buf)
31
- record = []
32
- masked = read32(buf)
33
- # Read the record's key bytes
34
- record << buf.slice!(0, masked & (DELETION_MASK - 1)) <<
35
- # Read the record's value bytes
36
- buf.slice!(0, read32(buf)) <<
37
- # Set the deletion flag
38
- ((masked & DELETION_MASK) != 0)
39
- raise CorruptDataError, 'CRC mismatch' unless buf.slice!(0, 4) == crc_string(key_data_string(record))
40
- record
41
- end
42
-
43
- private
44
-
45
- # Return the deletion flag plus two length prefixed cells
46
- def key_data_string(record)
47
- part(record[0], record[0].bytesize + (record[2] ? DELETION_MASK : 0)) << part(record[1], record[1].bytesize)
48
- end
49
-
50
- def crc_string(s)
51
- [Zlib.crc32(s, 0)].pack('N')
52
- end
53
-
54
- def part(data, length)
55
- [length].pack('N') << data
56
- end
57
-
58
- def read32(buf)
59
- buf.slice!(0, 4).unpack('N')[0]
60
- end
61
- end
62
- end