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