slugdb 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +42 -4
- data/bin/benchmark +159 -0
- data/lib/slugdb.rb +28 -21
- data/lib/slugdb/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f61f717430a8d97efaee087d193660aa6424a4f6743d9a0c4123a191d428a335
|
4
|
+
data.tar.gz: 8f8427c80d1d8a78e02863cea58d1938c319cbc1b2919169c822e64090175826
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 507cc4a08fd33abb68c0003ea71bf61867ffc64eb9b5b2930df0c6fd8d1c31952256d6b074f4adbe4c206dd5d7ebaf630da066b3dad012336533594ca0ebfa24
|
7
|
+
data.tar.gz: 6c7660bad22e4df6fcb043b06d86555b026f4d5142e3cec96ef9c30a1500fa45d97f7764565afc6d1583c7254c3649e14ccd39e782797542ef0188a83bd606f0
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# SlugDB
|
2
2
|
|
3
|
-
A completely Ruby, lightweight, zero dependency, NoSQL, file based
|
3
|
+
A completely native Ruby, lightweight, zero dependency, NoSQL, file based database.
|
4
4
|
|
5
5
|
I wanted a tiny database for an embedded project that could follow the advanced data modelling techniques for NoSQL. This is basically a very slimmed down SQLite but for NoSQL without any C bindings.
|
6
6
|
|
@@ -647,6 +647,44 @@ sdb.query(
|
|
647
647
|
# :sk=>"2021-03-04T10:30:29Z"}]
|
648
648
|
```
|
649
649
|
|
650
|
+
## Performance
|
651
|
+
|
652
|
+
Using Ubuntu 20.04 on an i7-10710U with Ruby `3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]` I got the following:
|
653
|
+
|
654
|
+
```
|
655
|
+
john@devbox:~/work/ruby-slugdb$ bundle exec bin/benchmark
|
656
|
+
Rehearsal -------------------------------------------------------------------------------
|
657
|
+
put_item 5 partitions, 1000 items 124.536929 0.864719 125.401648 (125.411962)
|
658
|
+
put_item 50 partitions, 100 items 114.684999 0.332007 115.017006 (115.025352)
|
659
|
+
put_item 500 partitions, 10 items 118.303101 0.288004 118.591105 (118.601603)
|
660
|
+
put_item 5000 partitions, 1 items 160.530284 0.883942 161.414226 (161.427220)
|
661
|
+
2 indexes put_item 5 partitions, 1000 items 251.300281 1.031866 252.332147 (252.364579)
|
662
|
+
2 indexes put_item 50 partitions, 100 items 235.390541 0.963895 236.354436 (236.383481)
|
663
|
+
2 indexes put_item 500 partitions, 10 items 241.453317 0.999958 242.453275 (242.479636)
|
664
|
+
2 indexes put_item 5000 partitions, 1 items 310.027647 1.291865 311.319512 (311.353699)
|
665
|
+
put_item, get_item 5 partitions, 1000 items 201.732843 0.499968 202.232811 (202.251035)
|
666
|
+
put_item, get_item 50 partitions, 100 items 191.160082 0.427998 191.588080 (191.604090)
|
667
|
+
put_item, get_item 500 partitions, 10 items 197.149766 0.419959 197.569725 (197.586483)
|
668
|
+
put_item, get_item 5000 partitions, 1 items 266.727467 0.575973 267.303440 (267.323639)
|
669
|
+
------------------------------------------------------------------- total: 2421.577411sec
|
670
|
+
|
671
|
+
user system total real
|
672
|
+
put_item 5 partitions, 1000 items 120.888413 0.347965 121.236378 (121.245881)
|
673
|
+
put_item 50 partitions, 100 items 113.471668 0.268009 113.739677 (113.747636)
|
674
|
+
put_item 500 partitions, 10 items 117.166616 0.319974 117.486590 (117.492926)
|
675
|
+
put_item 5000 partitions, 1 items 158.001298 0.427986 158.429284 (158.434901)
|
676
|
+
2 indexes put_item 5 partitions, 1000 items 251.204792 0.719985 251.924777 (251.937582)
|
677
|
+
2 indexes put_item 50 partitions, 100 items 235.023671 0.715972 235.739643 (235.755838)
|
678
|
+
2 indexes put_item 500 partitions, 10 items 241.072163 0.575991 241.648154 (241.662752)
|
679
|
+
2 indexes put_item 5000 partitions, 1 items 310.193882 1.339952 311.533834 (311.556014)
|
680
|
+
put_item, get_item 5 partitions, 1000 items 201.417776 0.467966 201.885742 (201.898954)
|
681
|
+
put_item, get_item 50 partitions, 100 items 190.772848 0.523983 191.296831 (191.308661)
|
682
|
+
put_item, get_item 500 partitions, 10 items 197.940498 0.475973 198.416471 (198.428618)
|
683
|
+
put_item, get_item 5000 partitions, 1 items 267.390145 0.715933 268.106078 (268.127310)
|
684
|
+
```
|
685
|
+
|
686
|
+
A key take away is that PStore kinda sucks with large data sets. My initial use case is hosting maybe a few hundred items. I wanted NoSQL funcitonality that didn't have a third part dep. Making this performant could be a thing but I'm not interested in that right now. PStore serializes the entire hash to disk on every write, keep that in mind.
|
687
|
+
|
650
688
|
## Development
|
651
689
|
|
652
690
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -655,7 +693,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
655
693
|
|
656
694
|
## Contributing
|
657
695
|
|
658
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
696
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/watmin/Ruby-slugdb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/watmin/Ruby-slugdb/blob/master/CODE_OF_CONDUCT.md).
|
659
697
|
|
660
698
|
## License
|
661
699
|
|
@@ -663,4 +701,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
663
701
|
|
664
702
|
## Code of Conduct
|
665
703
|
|
666
|
-
Everyone interacting in the
|
704
|
+
Everyone interacting in the SlugDB project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/watmin/Ruby-slugdb/blob/master/CODE_OF_CONDUCT.md).
|
data/bin/benchmark
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'benchmark'
|
6
|
+
require 'slugdb'
|
7
|
+
require 'pry'
|
8
|
+
require 'tempfile'
|
9
|
+
|
10
|
+
##
|
11
|
+
# Factory to make benchmark inputs
|
12
|
+
module BenchmarkFactory
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def partitions(count, size)
|
16
|
+
Array.new(count) { |p| items("p#{p}#", size) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def items(partition, size)
|
20
|
+
Array.new(size) do |x|
|
21
|
+
{ pk: partition, sk: "s#{x}#", ipk: partition, isk: "s#{x}#" }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
five_partitions = BenchmarkFactory.partitions(5, 1000)
|
27
|
+
fifty_partitions = BenchmarkFactory.partitions(50, 100)
|
28
|
+
five_hundred_partitions = BenchmarkFactory.partitions(500, 10)
|
29
|
+
five_thousand_partitions = BenchmarkFactory.partitions(5000, 1)
|
30
|
+
|
31
|
+
Benchmark.bmbm do |reporter| # rubocop:disable Metrics/BlockLength
|
32
|
+
reporter.report('put_item 5 partitions, 1000 items') do
|
33
|
+
Tempfile.create do |db_file|
|
34
|
+
db = SlugDB.new(db_file)
|
35
|
+
five_partitions.each do |partition|
|
36
|
+
partition.each { |item| db.put_item(**item) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
reporter.report('put_item 50 partitions, 100 items') do
|
42
|
+
Tempfile.create do |db_file|
|
43
|
+
db = SlugDB.new(db_file)
|
44
|
+
fifty_partitions.each do |partition|
|
45
|
+
partition.each { |item| db.put_item(**item) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
reporter.report('put_item 500 partitions, 10 items') do
|
51
|
+
Tempfile.create do |db_file|
|
52
|
+
db = SlugDB.new(db_file)
|
53
|
+
five_hundred_partitions.each do |partition|
|
54
|
+
partition.each { |item| db.put_item(**item) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
reporter.report('put_item 5000 partitions, 1 items') do
|
60
|
+
Tempfile.create do |db_file|
|
61
|
+
db = SlugDB.new(db_file)
|
62
|
+
five_thousand_partitions.each do |partition|
|
63
|
+
partition.each { |item| db.put_item(**item) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
reporter.report('2 indexes put_item 5 partitions, 1000 items') do
|
69
|
+
Tempfile.create do |db_file|
|
70
|
+
db = SlugDB.new(db_file)
|
71
|
+
db.add_index(name: :ski, pk: :sk, sk: :pk)
|
72
|
+
db.add_index(name: :i, pk: :ipk, sk: :isk)
|
73
|
+
five_partitions.each do |partition|
|
74
|
+
partition.each { |item| db.put_item(**item) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
reporter.report('2 indexes put_item 50 partitions, 100 items') do
|
80
|
+
Tempfile.create do |db_file|
|
81
|
+
db = SlugDB.new(db_file)
|
82
|
+
db.add_index(name: :ski, pk: :sk, sk: :pk)
|
83
|
+
db.add_index(name: :i, pk: :ipk, sk: :isk)
|
84
|
+
fifty_partitions.each do |partition|
|
85
|
+
partition.each { |item| db.put_item(**item) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
reporter.report('2 indexes put_item 500 partitions, 10 items') do
|
91
|
+
Tempfile.create do |db_file|
|
92
|
+
db = SlugDB.new(db_file)
|
93
|
+
db.add_index(name: :ski, pk: :sk, sk: :pk)
|
94
|
+
db.add_index(name: :i, pk: :ipk, sk: :isk)
|
95
|
+
five_hundred_partitions.each do |partition|
|
96
|
+
partition.each { |item| db.put_item(**item) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
reporter.report('2 indexes put_item 5000 partitions, 1 items') do
|
102
|
+
Tempfile.create do |db_file|
|
103
|
+
db = SlugDB.new(db_file)
|
104
|
+
db.add_index(name: :ski, pk: :sk, sk: :pk)
|
105
|
+
db.add_index(name: :i, pk: :ipk, sk: :isk)
|
106
|
+
five_thousand_partitions.each do |partition|
|
107
|
+
partition.each { |item| db.put_item(**item) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
reporter.report('put_item, get_item 5 partitions, 1000 items') do
|
113
|
+
Tempfile.create do |db_file|
|
114
|
+
db = SlugDB.new(db_file)
|
115
|
+
five_partitions.each do |partition|
|
116
|
+
partition.each { |item| db.put_item(**item) }
|
117
|
+
end
|
118
|
+
five_partitions.each do |partition| # rubocop:disable Style/CombinableLoops
|
119
|
+
partition.each { |item| db.get_item(**item) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
reporter.report('put_item, get_item 50 partitions, 100 items') do
|
125
|
+
Tempfile.create do |db_file|
|
126
|
+
db = SlugDB.new(db_file)
|
127
|
+
fifty_partitions.each do |partition|
|
128
|
+
partition.each { |item| db.put_item(**item) }
|
129
|
+
end
|
130
|
+
fifty_partitions.each do |partition| # rubocop:disable Style/CombinableLoops
|
131
|
+
partition.each { |item| db.get_item(**item) }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
reporter.report('put_item, get_item 500 partitions, 10 items') do
|
137
|
+
Tempfile.create do |db_file|
|
138
|
+
db = SlugDB.new(db_file)
|
139
|
+
five_hundred_partitions.each do |partition|
|
140
|
+
partition.each { |item| db.put_item(**item) }
|
141
|
+
end
|
142
|
+
five_hundred_partitions.each do |partition| # rubocop:disable Style/CombinableLoops
|
143
|
+
partition.each { |item| db.get_item(**item) }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
reporter.report('put_item, get_item 5000 partitions, 1 items') do
|
149
|
+
Tempfile.create do |db_file|
|
150
|
+
db = SlugDB.new(db_file)
|
151
|
+
five_thousand_partitions.each do |partition|
|
152
|
+
partition.each { |item| db.put_item(**item) }
|
153
|
+
end
|
154
|
+
five_thousand_partitions.each do |partition| # rubocop:disable Style/CombinableLoops
|
155
|
+
partition.each { |item| db.get_item(**item) }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/lib/slugdb.rb
CHANGED
@@ -36,7 +36,7 @@ class SlugDB
|
|
36
36
|
end
|
37
37
|
reindex!
|
38
38
|
|
39
|
-
{ name
|
39
|
+
{ name => { pk: pk, sk: sk } }
|
40
40
|
end
|
41
41
|
|
42
42
|
def list_indexes
|
@@ -47,7 +47,7 @@ class SlugDB
|
|
47
47
|
@pstore.transaction { |db| db[:main].keys }
|
48
48
|
end
|
49
49
|
|
50
|
-
def get_item(pk:, sk
|
50
|
+
def get_item(pk:, sk:, **_) # rubocop:disable Naming/MethodParameterName
|
51
51
|
@pstore.transaction do |db|
|
52
52
|
next if db[:main][pk].nil? || db[:main][pk][sk].nil?
|
53
53
|
|
@@ -56,41 +56,32 @@ class SlugDB
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def put_item(pk:, sk:, **attributes) # rubocop:disable Naming/MethodParameterName
|
59
|
-
|
59
|
+
old_item = get_item(pk: pk, sk: sk)
|
60
|
+
new_item = attributes.merge(pk: pk, sk: sk)
|
60
61
|
indexes = list_indexes
|
61
62
|
|
62
63
|
@pstore.transaction do |db|
|
64
|
+
perform_delete(db, indexes, pk, sk, old_item)
|
65
|
+
|
63
66
|
db[:main][pk] ||= {}
|
64
|
-
db[:main][pk][sk] =
|
65
|
-
indexes.each { |name, schema| index_item(db,
|
67
|
+
db[:main][pk][sk] = new_item
|
68
|
+
indexes.each { |name, schema| index_item(db, new_item, name, schema) }
|
66
69
|
end
|
67
70
|
|
68
|
-
|
71
|
+
new_item
|
69
72
|
end
|
70
73
|
|
71
|
-
# rubocop:disable Naming/MethodParameterName
|
74
|
+
# rubocop:disable Naming/MethodParameterName
|
72
75
|
def delete_item(pk:, sk:, **_)
|
73
76
|
item = get_item(pk: pk, sk: sk)
|
74
77
|
return if item.nil?
|
75
78
|
|
76
79
|
indexes = list_indexes
|
77
|
-
@pstore.transaction
|
78
|
-
db[:main][pk].delete(sk)
|
79
|
-
db[:main].delete(pk) if db[:main][pk].empty?
|
80
|
-
|
81
|
-
indexes.each do |name, schema|
|
82
|
-
next unless item.key?(schema[:pk]) && item.key?(schema[:sk])
|
83
|
-
|
84
|
-
db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]].delete(item[:sk])
|
85
|
-
delete_if_empty?(db[name][item[schema[:pk]]][item[schema[:sk]]], item[:pk])
|
86
|
-
delete_if_empty?(db[name][item[schema[:pk]]], item[schema[:sk]])
|
87
|
-
delete_if_empty?(db[name], item[schema[:pk]])
|
88
|
-
end
|
89
|
-
end
|
80
|
+
@pstore.transaction { |db| perform_delete(db, indexes, pk, sk, item) }
|
90
81
|
|
91
82
|
item
|
92
83
|
end
|
93
|
-
# rubocop:enable Naming/MethodParameterName
|
84
|
+
# rubocop:enable Naming/MethodParameterName
|
94
85
|
|
95
86
|
# rubocop:disable Lint/UnusedBlockArgument,Naming/MethodParameterName
|
96
87
|
# rubocop:disable Metrics/PerceivedComplexity,Metrics/MethodLength
|
@@ -136,6 +127,22 @@ class SlugDB
|
|
136
127
|
db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]][item[:sk]] = item
|
137
128
|
end
|
138
129
|
|
130
|
+
def perform_delete(db, indexes, pk, sk, item) # rubocop:disable Naming/MethodParameterName,Metrics/AbcSize
|
131
|
+
return if item.nil?
|
132
|
+
|
133
|
+
db[:main][pk].delete(sk)
|
134
|
+
db[:main].delete(pk) if db[:main][pk].empty?
|
135
|
+
|
136
|
+
indexes.each do |name, schema|
|
137
|
+
next unless item.key?(schema[:pk]) && item.key?(schema[:sk])
|
138
|
+
|
139
|
+
db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]].delete(item[:sk])
|
140
|
+
delete_if_empty?(db[name][item[schema[:pk]]][item[schema[:sk]]], item[:pk])
|
141
|
+
delete_if_empty?(db[name][item[schema[:pk]]], item[schema[:sk]])
|
142
|
+
delete_if_empty?(db[name], item[schema[:pk]])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
139
146
|
def delete_if_empty?(hash, key)
|
140
147
|
hash.delete(key) if hash[key].empty?
|
141
148
|
end
|
data/lib/slugdb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slugdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Shields <watmin>
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem-release
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- LICENSE.txt
|
42
42
|
- README.md
|
43
43
|
- Rakefile
|
44
|
+
- bin/benchmark
|
44
45
|
- bin/console
|
45
46
|
- bin/setup
|
46
47
|
- lib/slugdb.rb
|