slugdb 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2395e205029cd093867c8593abab596f7565a4429c4aad35e5382f15d06fd32a
4
- data.tar.gz: a7ba3d30db63551af9ccdaa602662071102a0cdc974e48a9943663396d6b044c
3
+ metadata.gz: f61f717430a8d97efaee087d193660aa6424a4f6743d9a0c4123a191d428a335
4
+ data.tar.gz: 8f8427c80d1d8a78e02863cea58d1938c319cbc1b2919169c822e64090175826
5
5
  SHA512:
6
- metadata.gz: 9ddf6f107a4a90c2e355330927e755804805b1c5a3b817a6423a5476ab54fe66af104a6d9e2255b32089618dca653285c9a7080a3fd79314fcdcb40478104599
7
- data.tar.gz: 9c3009a21fff95f7d3bd472d55db792c6a2188ebf654826b8d74b573057edfa6f9e56c1e63fc3eede79973cf6e8eff54b55df106af4da27439a2a110b491c60f
6
+ metadata.gz: 507cc4a08fd33abb68c0003ea71bf61867ffc64eb9b5b2930df0c6fd8d1c31952256d6b074f4adbe4c206dd5d7ebaf630da066b3dad012336533594ca0ebfa24
7
+ data.tar.gz: 6c7660bad22e4df6fcb043b06d86555b026f4d5142e3cec96ef9c30a1500fa45d97f7764565afc6d1583c7254c3649e14ccd39e782797542ef0188a83bd606f0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Slugdb
1
+ # SlugDB
2
2
 
3
- A completely Ruby, lightweight, zero dependency, NoSQL, file based databased.
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/[USERNAME]/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/[USERNAME]/slugdb/blob/master/CODE_OF_CONDUCT.md).
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 Slugdb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/slugdb/blob/master/CODE_OF_CONDUCT.md).
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: { pk: pk, sk: sk } }
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:) # rubocop:disable Naming/MethodParameterName
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
- item = attributes.merge(pk: pk, sk: sk)
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] = item
65
- indexes.each { |name, schema| index_item(db, item, name, schema) }
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
- item
71
+ new_item
69
72
  end
70
73
 
71
- # rubocop:disable Naming/MethodParameterName,Metrics/AbcSize
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 do |db|
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,Metrics/AbcSize
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SlugDB
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
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.0
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-04 00:00:00.000000000 Z
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