moneta 0.7.0 → 0.7.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.
Files changed (179) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +39 -6
  3. data/CHANGES +18 -0
  4. data/README.md +69 -31
  5. data/Rakefile +1 -1
  6. data/SPEC.md +6 -0
  7. data/benchmarks/run.rb +235 -133
  8. data/lib/active_support/cache/moneta_store.rb +1 -0
  9. data/lib/moneta.rb +29 -19
  10. data/lib/moneta/adapters/activerecord.rb +18 -26
  11. data/lib/moneta/adapters/cassandra.rb +5 -7
  12. data/lib/moneta/adapters/client.rb +3 -5
  13. data/lib/moneta/adapters/cookie.rb +2 -0
  14. data/lib/moneta/adapters/couch.rb +1 -3
  15. data/lib/moneta/adapters/datamapper.rb +4 -6
  16. data/lib/moneta/adapters/dbm.rb +1 -3
  17. data/lib/moneta/adapters/file.rb +4 -0
  18. data/lib/moneta/adapters/fog.rb +2 -4
  19. data/lib/moneta/adapters/gdbm.rb +1 -3
  20. data/lib/moneta/adapters/hbase.rb +5 -7
  21. data/lib/moneta/adapters/leveldb.rb +2 -4
  22. data/lib/moneta/adapters/localmemcache.rb +1 -3
  23. data/lib/moneta/adapters/lruhash.rb +1 -3
  24. data/lib/moneta/adapters/memcached.rb +2 -2
  25. data/lib/moneta/adapters/{memcached_dalli.rb → memcached/dalli.rb} +4 -6
  26. data/lib/moneta/adapters/{memcached_native.rb → memcached/native.rb} +8 -7
  27. data/lib/moneta/adapters/mongo.rb +8 -9
  28. data/lib/moneta/adapters/pstore.rb +1 -3
  29. data/lib/moneta/adapters/redis.rb +2 -4
  30. data/lib/moneta/adapters/riak.rb +3 -5
  31. data/lib/moneta/adapters/sdbm.rb +1 -13
  32. data/lib/moneta/adapters/sequel.rb +3 -5
  33. data/lib/moneta/adapters/sqlite.rb +2 -4
  34. data/lib/moneta/adapters/tokyocabinet.rb +2 -4
  35. data/lib/moneta/base.rb +22 -2
  36. data/lib/moneta/builder.rb +3 -3
  37. data/lib/moneta/cache.rb +1 -1
  38. data/lib/moneta/expires.rb +27 -35
  39. data/lib/moneta/lock.rb +1 -3
  40. data/lib/moneta/logger.rb +3 -5
  41. data/lib/moneta/net.rb +7 -3
  42. data/lib/moneta/proxy.rb +2 -1
  43. data/lib/moneta/server.rb +33 -21
  44. data/lib/moneta/shared.rb +11 -7
  45. data/lib/moneta/stack.rb +1 -1
  46. data/lib/moneta/transformer.rb +15 -13
  47. data/lib/moneta/transformer/config.rb +33 -31
  48. data/lib/moneta/version.rb +1 -1
  49. data/lib/rack/cache/moneta.rb +6 -0
  50. data/lib/rack/moneta_cookies.rb +1 -0
  51. data/lib/rack/session/moneta.rb +1 -0
  52. data/moneta.gemspec +1 -1
  53. data/spec/generate.rb +403 -232
  54. data/spec/helper.rb +23 -2
  55. data/spec/moneta/adapter_activerecord_spec.rb +3 -2
  56. data/spec/moneta/adapter_cassandra_spec.rb +4 -3
  57. data/spec/moneta/adapter_client_spec.rb +4 -3
  58. data/spec/moneta/adapter_cookie_spec.rb +3 -2
  59. data/spec/moneta/adapter_couch_spec.rb +3 -2
  60. data/spec/moneta/adapter_datamapper_spec.rb +3 -2
  61. data/spec/moneta/adapter_dbm_spec.rb +3 -2
  62. data/spec/moneta/adapter_file_spec.rb +3 -2
  63. data/spec/moneta/adapter_fog_spec.rb +3 -2
  64. data/spec/moneta/adapter_gdbm_spec.rb +3 -2
  65. data/spec/moneta/adapter_hbase_spec.rb +3 -2
  66. data/spec/moneta/adapter_leveldb_spec.rb +3 -2
  67. data/spec/moneta/adapter_localmemcache_spec.rb +3 -2
  68. data/spec/moneta/adapter_lruhash_spec.rb +3 -2
  69. data/spec/moneta/adapter_memcached_dalli_spec.rb +4 -3
  70. data/spec/moneta/adapter_memcached_native_spec.rb +4 -3
  71. data/spec/moneta/adapter_memcached_spec.rb +4 -3
  72. data/spec/moneta/adapter_memory_spec.rb +50 -1
  73. data/spec/moneta/adapter_mongo_spec.rb +3 -2
  74. data/spec/moneta/adapter_pstore_spec.rb +124 -4
  75. data/spec/moneta/adapter_redis_spec.rb +4 -3
  76. data/spec/moneta/adapter_riak_spec.rb +3 -2
  77. data/spec/moneta/adapter_sdbm_spec.rb +3 -2
  78. data/spec/moneta/adapter_sequel_spec.rb +3 -2
  79. data/spec/moneta/adapter_sqlite_spec.rb +3 -2
  80. data/spec/moneta/adapter_tokyocabinet_bdb_spec.rb +3 -2
  81. data/spec/moneta/adapter_tokyocabinet_hdb_spec.rb +3 -2
  82. data/spec/moneta/adapter_yaml_spec.rb +32 -5
  83. data/spec/moneta/cache_file_memory_spec.rb +3 -2
  84. data/spec/moneta/cache_memory_null_spec.rb +3 -2
  85. data/spec/moneta/expires_file_spec.rb +23 -41
  86. data/spec/moneta/expires_memory_spec.rb +69 -13
  87. data/spec/moneta/lock_spec.rb +50 -1
  88. data/spec/moneta/null_adapter_spec.rb +26 -0
  89. data/spec/moneta/proxy_expires_memory_spec.rb +51 -12
  90. data/spec/moneta/proxy_redis_spec.rb +4 -3
  91. data/spec/moneta/shared_spec.rb +4 -3
  92. data/spec/moneta/simple_activerecord_spec.rb +105 -12
  93. data/spec/moneta/simple_activerecord_with_expires_spec.rb +106 -13
  94. data/spec/moneta/simple_cassandra_spec.rb +106 -13
  95. data/spec/moneta/simple_client_tcp_spec.rb +106 -13
  96. data/spec/moneta/simple_client_unix_spec.rb +106 -14
  97. data/spec/moneta/simple_couch_spec.rb +105 -12
  98. data/spec/moneta/simple_couch_with_expires_spec.rb +106 -13
  99. data/spec/moneta/simple_datamapper_spec.rb +105 -12
  100. data/spec/moneta/simple_datamapper_with_expires_spec.rb +106 -13
  101. data/spec/moneta/simple_datamapper_with_repository_spec.rb +105 -12
  102. data/spec/moneta/simple_dbm_spec.rb +105 -12
  103. data/spec/moneta/simple_dbm_with_expires_spec.rb +106 -13
  104. data/spec/moneta/simple_file_spec.rb +105 -12
  105. data/spec/moneta/simple_file_with_expires_spec.rb +106 -13
  106. data/spec/moneta/simple_fog_spec.rb +105 -12
  107. data/spec/moneta/simple_fog_with_expires_spec.rb +106 -13
  108. data/spec/moneta/simple_gdbm_spec.rb +105 -12
  109. data/spec/moneta/simple_gdbm_with_expires_spec.rb +106 -13
  110. data/spec/moneta/simple_hashfile_spec.rb +105 -12
  111. data/spec/moneta/simple_hashfile_with_expires_spec.rb +106 -13
  112. data/spec/moneta/simple_hbase_spec.rb +105 -12
  113. data/spec/moneta/simple_hbase_with_expires_spec.rb +106 -13
  114. data/spec/moneta/simple_leveldb_spec.rb +105 -12
  115. data/spec/moneta/simple_leveldb_with_expires_spec.rb +106 -13
  116. data/spec/moneta/simple_localmemcache_spec.rb +105 -12
  117. data/spec/moneta/simple_localmemcache_with_expires_spec.rb +106 -13
  118. data/spec/moneta/simple_lruhash_spec.rb +70 -12
  119. data/spec/moneta/simple_lruhash_with_expires_spec.rb +71 -13
  120. data/spec/moneta/simple_memcached_dalli_spec.rb +106 -13
  121. data/spec/moneta/simple_memcached_native_spec.rb +106 -13
  122. data/spec/moneta/simple_memcached_spec.rb +106 -13
  123. data/spec/moneta/simple_memory_spec.rb +70 -12
  124. data/spec/moneta/simple_memory_with_compress_spec.rb +70 -12
  125. data/spec/moneta/simple_memory_with_expires_spec.rb +71 -13
  126. data/spec/moneta/simple_memory_with_json_key_serializer_spec.rb +41 -15
  127. data/spec/moneta/simple_memory_with_json_serializer_spec.rb +24 -11
  128. data/spec/moneta/simple_memory_with_json_value_serializer_spec.rb +45 -14
  129. data/spec/moneta/simple_memory_with_prefix_spec.rb +70 -12
  130. data/spec/moneta/simple_memory_with_snappy_compress_spec.rb +70 -12
  131. data/spec/moneta/simple_mongo_spec.rb +105 -12
  132. data/spec/moneta/simple_mongo_with_expires_spec.rb +106 -13
  133. data/spec/moneta/simple_null_spec.rb +36 -1
  134. data/spec/moneta/simple_pstore_spec.rb +105 -12
  135. data/spec/moneta/simple_pstore_with_expires_spec.rb +106 -13
  136. data/spec/moneta/simple_redis_spec.rb +106 -13
  137. data/spec/moneta/simple_riak_spec.rb +105 -12
  138. data/spec/moneta/simple_riak_with_expires_spec.rb +106 -13
  139. data/spec/moneta/simple_sdbm_spec.rb +105 -12
  140. data/spec/moneta/simple_sdbm_with_expires_spec.rb +106 -13
  141. data/spec/moneta/simple_sequel_spec.rb +105 -12
  142. data/spec/moneta/simple_sequel_with_expires_spec.rb +106 -13
  143. data/spec/moneta/simple_sqlite_spec.rb +105 -12
  144. data/spec/moneta/simple_sqlite_with_expires_spec.rb +106 -13
  145. data/spec/moneta/simple_tokyocabinet_spec.rb +105 -12
  146. data/spec/moneta/simple_tokyocabinet_with_expires_spec.rb +106 -13
  147. data/spec/moneta/simple_yaml_spec.rb +104 -11
  148. data/spec/moneta/simple_yaml_with_expires_spec.rb +105 -12
  149. data/spec/moneta/stack_file_memory_spec.rb +3 -2
  150. data/spec/moneta/stack_memory_file_spec.rb +3 -1
  151. data/spec/moneta/transformer_bencode_spec.rb +23 -10
  152. data/spec/moneta/transformer_bert_spec.rb +23 -10
  153. data/spec/moneta/transformer_bson_spec.rb +23 -10
  154. data/spec/moneta/transformer_bzip2_spec.rb +13 -3
  155. data/spec/moneta/transformer_json_spec.rb +23 -10
  156. data/spec/moneta/transformer_lzma_spec.rb +13 -3
  157. data/spec/moneta/transformer_lzo_spec.rb +13 -3
  158. data/spec/moneta/transformer_marshal_base64_spec.rb +70 -12
  159. data/spec/moneta/transformer_marshal_escape_spec.rb +70 -12
  160. data/spec/moneta/transformer_marshal_hmac_spec.rb +70 -12
  161. data/spec/moneta/transformer_marshal_md5_spec.rb +70 -12
  162. data/spec/moneta/transformer_marshal_md5_spread_spec.rb +70 -12
  163. data/spec/moneta/transformer_marshal_prefix_spec.rb +70 -12
  164. data/spec/moneta/transformer_marshal_rmd160_spec.rb +70 -12
  165. data/spec/moneta/transformer_marshal_sha1_spec.rb +70 -12
  166. data/spec/moneta/transformer_marshal_sha256_spec.rb +70 -12
  167. data/spec/moneta/transformer_marshal_sha384_spec.rb +70 -12
  168. data/spec/moneta/transformer_marshal_sha512_spec.rb +70 -12
  169. data/spec/moneta/transformer_marshal_truncate_spec.rb +70 -12
  170. data/spec/moneta/transformer_marshal_uuencode_spec.rb +70 -12
  171. data/spec/moneta/transformer_msgpack_spec.rb +23 -10
  172. data/spec/moneta/transformer_ox_spec.rb +67 -9
  173. data/spec/moneta/transformer_quicklz_spec.rb +13 -3
  174. data/spec/moneta/transformer_snappy_spec.rb +13 -3
  175. data/spec/moneta/transformer_tnet_spec.rb +23 -10
  176. data/spec/moneta/transformer_yaml_spec.rb +67 -9
  177. data/spec/moneta/transformer_zlib_spec.rb +13 -3
  178. data/spec/monetaspecs.rb +4649 -1096
  179. metadata +8 -5
data/.gitignore CHANGED
@@ -1,7 +1,11 @@
1
+ .yardoc
2
+ doc
1
3
  attic
2
4
  spec/tmp
3
5
  *~
4
6
  *.swp
5
7
  *.rdb
8
+ .#*
9
+ benchmarks/*.histogram
6
10
  benchmarks/bench.*
7
11
  Gemfile.lock
data/.travis.yml CHANGED
@@ -17,7 +17,12 @@ before_install:
17
17
  env:
18
18
  - "TASK=test:parallel"
19
19
  - "TASK=test:non_parallel"
20
- - "TASK=benchmarks"
20
+ - "TASK=benchmarks CONFIG=uniform_small"
21
+ - "TASK=benchmarks CONFIG=uniform_medium"
22
+ - "TASK=benchmarks CONFIG=uniform_large"
23
+ - "TASK=benchmarks CONFIG=normal_small"
24
+ - "TASK=benchmarks CONFIG=normal_medium"
25
+ - "TASK=benchmarks CONFIG=normal_large"
21
26
  matrix:
22
27
  allow_failures:
23
28
  # - rvm: ruby-head
@@ -28,12 +33,40 @@ matrix:
28
33
  exclude:
29
34
  - rvm: jruby
30
35
  env: "TASK=test:parallel"
31
- - rvm: 1.8.7
32
- env: "TASK=benchmarks"
33
36
  - rvm: jruby
34
- env: "TASK=benchmarks"
37
+ env: "TASK=benchmarks CONFIG=uniform_small"
38
+ - rvm: jruby
39
+ env: "TASK=benchmarks CONFIG=uniform_medium"
40
+ - rvm: jruby
41
+ env: "TASK=benchmarks CONFIG=uniform_large"
42
+ - rvm: jruby
43
+ env: "TASK=benchmarks CONFIG=normal_small"
44
+ - rvm: jruby
45
+ env: "TASK=benchmarks CONFIG=normal_medium"
46
+ - rvm: jruby
47
+ env: "TASK=benchmarks CONFIG=normal_large"
48
+ - rvm: rbx-18mode
49
+ env: "TASK=benchmarks CONFIG=uniform_small"
50
+ - rvm: rbx-18mode
51
+ env: "TASK=benchmarks CONFIG=uniform_medium"
52
+ - rvm: rbx-18mode
53
+ env: "TASK=benchmarks CONFIG=uniform_large"
35
54
  - rvm: rbx-18mode
36
- env: "TASK=benchmarks"
55
+ env: "TASK=benchmarks CONFIG=normal_small"
56
+ - rvm: rbx-18mode
57
+ env: "TASK=benchmarks CONFIG=normal_medium"
58
+ - rvm: rbx-18mode
59
+ env: "TASK=benchmarks CONFIG=normal_large"
60
+ - rvm: rbx-19mode
61
+ env: "TASK=benchmarks CONFIG=uniform_small"
62
+ - rvm: rbx-19mode
63
+ env: "TASK=benchmarks CONFIG=uniform_medium"
64
+ - rvm: rbx-19mode
65
+ env: "TASK=benchmarks CONFIG=uniform_large"
66
+ - rvm: rbx-19mode
67
+ env: "TASK=benchmarks CONFIG=normal_small"
68
+ - rvm: rbx-19mode
69
+ env: "TASK=benchmarks CONFIG=normal_medium"
37
70
  - rvm: rbx-19mode
38
- env: "TASK=benchmarks"
71
+ env: "TASK=benchmarks CONFIG=normal_large"
39
72
  script: "bundle exec rake $TASK"
data/CHANGES ADDED
@@ -0,0 +1,18 @@
1
+ 0.7.1
2
+
3
+ * Memcached: Use binary protocol and no base64 encoding of the keys
4
+ * Transformer: Remove newlines from base64 encodes values
5
+ * Server: Add method #run which will block and #running? to allow forking
6
+ * SDBM: #store might raise errors (Don't use SDBM, it is unstable!)
7
+ * Add #decrement method
8
+ * Fix #fetch to handle false correctly
9
+ * Fix Expires middleware to handle boolean and nil values correctly
10
+ * Base64 encode Riak keys since Riak needs valid UTF-8 for the REST interface
11
+
12
+ 0.7.0
13
+
14
+ * Major rewrite by Daniel Mendler
15
+
16
+ 0.6.0
17
+
18
+ * First public release by Yehuda Katz
data/README.md CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/minad/moneta.png?branch=master)](http://travis-ci.org/minad/moneta) [![Dependency Status](https://gemnasium.com/minad/moneta.png?travis)](https://gemnasium.com/minad/moneta) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/minad/moneta)
4
4
 
5
- Moneta provides a standard interface for interacting with various kinds of key/value stores. Moneta is very feature rich:
5
+ Moneta provides a standard interface for interacting with various kinds of key/value stores. A short overview of the features:
6
6
 
7
7
  * Supports a lot of backends (See below)
8
- * Supports proxies (Similar to [Rack middlewares](http://rack.github.com/))
9
- * Custom serialization via `Moneta::Transformer` proxy (Marshal/JSON/YAML and many more)
10
- * Custom key transformation via `Moneta::Transformer` proxy
11
- * Value compression via `Moneta::Transformer` proxy (Zlib, Snappy, LZMA, ...)
8
+ * Allows a full configuration of the serialization -> compression -> adapter stack using proxies (Similar to [Rack middlewares](http://rack.github.com/))
9
+ * Configurable serialization via `Moneta::Transformer` proxy (Marshal/JSON/YAML and many more)
10
+ * Configurable value compression via `Moneta::Transformer` proxy (Zlib, Snappy, LZMA, ...)
11
+ * Configurable key transformation via `Moneta::Transformer` proxy
12
12
  * Expiration for all stores (Added via proxy `Moneta::Expires` if not supported natively)
13
- * Atomic incrementation and decrementation for most stores (Method `#increment`)
13
+ * Atomic incrementation and decrementation for most stores (Method `#increment` and `#decrement`)
14
14
  * Includes a very simple key/value server (`Moneta::Server`) and client (`Moneta::Adapters::Client`)
15
15
  * Integration with [Rails](http://rubyonrails.org/), [Rack](http://rack.github.com/) as cookie and session store and [Rack-Cache](https://github.com/rtomayko/rack-cache)
16
16
 
@@ -110,6 +110,10 @@ Special transformers:
110
110
 
111
111
  ## Moneta API
112
112
 
113
+ The Moneta API is purposely extremely similar to the Hash API with a few minor additions.
114
+ There are the additional methods `#load`, `#increment`, `#decrement` and `#close`. Every method takes also a optional
115
+ option hash. In order so support an identical API across stores, Moneta does not support iteration or partial matches.
116
+
113
117
  ~~~
114
118
  #initialize(options) options differs per-store, and is used to set up the store.
115
119
 
@@ -134,14 +138,15 @@ Special transformers:
134
138
  #increment(key, amount = 1, options = {}) increment numeric value. This is a atomic operation
135
139
  which is not supported by all stores. Returns current value.
136
140
 
141
+ #decrement(key, amount = 1, options = {}) increment numeric value. This is a atomic operation
142
+ which is not supported by all stores. Returns current value.
143
+ This is just syntactic sugar for incrementing with a negative value.
144
+
137
145
  #clear(options = {}) clear all keys in this store.
138
146
 
139
147
  #close close database connection.
140
148
  ~~~
141
149
 
142
- The Moneta API is purposely extremely similar to the Hash API. In order so support an
143
- identical API across stores, it does not support iteration or partial matches.
144
-
145
150
  ### Creating a Store
146
151
 
147
152
  There is a simple interface to create a store using `Moneta.new`:
@@ -205,35 +210,36 @@ The stores support the `#increment` which allows atomic increments of unsigned i
205
210
  a non existing value, it will be created. If you increment a non integer value an exception will be raised.
206
211
 
207
212
  ~~~ ruby
208
- store.increment('counter') => 1 # counter created
209
- store.increment('counter') => 2
210
- store.increment('counter', -1) => 1
211
- store.increment('counter', 13) => 14
212
- store.increment('counter', 0) => 14
213
+ store.increment('counter') # returns 1, counter created
214
+ store.increment('counter') # returns 2
215
+ store.increment('counter', -1) # returns 1
216
+ store.increment('counter', 13) # returns 14
217
+ store.increment('counter', 0) # returns 14
218
+ store.decrement('counter') # returns 13
213
219
  store['name'] = 'Moneta'
214
- store.increment('name') => Exception
220
+ store.increment('name') # raises an Exception
215
221
  ~~~
216
222
 
217
223
  If you want to access the counter value you have to use raw access to the datastore. This is only important
218
- if you have a `Moneta::Transformer` somewhere in your proxy chain which transforms the values e.g. with `Marshal`.
224
+ if you have a `Moneta::Transformer` somewhere in your proxy stack which transforms the values e.g. with `Marshal`.
219
225
 
220
226
  ~~~ ruby
221
- store.increment('counter') => 1 # counter created
222
- store.load('counter', :raw => true) => '1'
227
+ store.increment('counter') # returns 1, counter created
228
+ store.load('counter', :raw => true) # returns 1
223
229
 
224
230
  store.store('counter', '10', :raw => true)
225
- store.increment('counter') => 11
231
+ store.increment('counter') # returns 11
226
232
  ~~~
227
233
 
228
234
  Fortunately there is a nicer way to do this using some syntactic sugar!
229
235
 
230
236
  ~~~ ruby
231
- store.increment('counter') => 1 # counter created
232
- store.raw['counter'] => '1'
233
- store.raw.load('counter') => '1'
237
+ store.increment('counter') # returns 1, counter created
238
+ store.raw['counter'] # returns 1
239
+ store.raw.load('counter') # returns 1
234
240
 
235
241
  store.raw['counter'] = '10'
236
- store.increment('counter') => 11
242
+ store.increment('counter') # returns 11
237
243
  ~~~
238
244
 
239
245
  You can also keep the `raw` store in a variable and use it like this:
@@ -241,12 +247,12 @@ You can also keep the `raw` store in a variable and use it like this:
241
247
  ~~~ ruby
242
248
  counters = store.raw
243
249
 
244
- counters.increment('counter') => 1 # counter created
245
- counters['counter'] => '1'
246
- counters.load('counter') => '1'
250
+ counters.increment('counter') # returns 1, counter created
251
+ counters['counter'] # returns 1
252
+ counters.load('counter') # returns 1
247
253
 
248
254
  counters['counter'] = '10'
249
- counters.increment('counter') => 11
255
+ counters.increment('counter') # returns 11
250
256
  ~~~
251
257
 
252
258
  Stores which support incrementation (you have to use `Moneta::Lock` if you want to use the store in a multithreading environment.)
@@ -280,7 +286,7 @@ Stores which don't support incrementation:
280
286
  For raw data access as described before the class `Moneta::OptionMerger` is used. It works like this:
281
287
 
282
288
  ~~~ ruby
283
- # All methods after `with` get the options passed
289
+ # All methods after 'with' get the options passed
284
290
  store.with(:raw => true).load('key')
285
291
 
286
292
  # You can also specify the methods
@@ -293,8 +299,8 @@ store.raw.load('key')
293
299
  # Access substore where all keys get a prefix
294
300
  substore = store.prefix('sub')
295
301
  substore['key'] = 'value'
296
- store['key'] => nil
297
- store['subkey'] => 'value'
302
+ store['key'] # returns nil
303
+ store['subkey'] # returns 'value'
298
304
 
299
305
  # Set expiration time for all keys
300
306
  short_lived_store = long_lived_store.expires(60)
@@ -408,7 +414,9 @@ config.cache_store :moneta_store, :store => Moneta.build do
408
414
  end
409
415
  ~~~
410
416
 
411
- ## Advanced - Build your own key value server
417
+ ## Advanced
418
+
419
+ ### Build your own key value server
412
420
 
413
421
  You can use Moneta to build your own key/value server which is shared between
414
422
  multiple processes. If you run the following code in two different processes,
@@ -432,6 +440,36 @@ store = Moneta.build do
432
440
  end
433
441
  ~~~
434
442
 
443
+ If you want to go further, you might want to take a look at `Moneta::Server` and `Moneta::Adapters::Client` which
444
+ are used by `Moneta::Shared` and provide the networking communication. But be aware that they are experimental
445
+ and subjected to change. They provide an acceptable performance (for being ruby only), but don't have a stable protocol yet.
446
+
447
+ You might wonder why I didn't use [DRb](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/drb/rdoc/DRb.html) to implement server and client -
448
+ in fact my first versions used it, but with much worse performance and it was real fun to implement the networking directly :)
449
+ There is still much room for improvement and experiments, try [EventMachine](http://eventmachine.rubyforge.org/),
450
+ try [Kgio](http://bogomips.org/kgio/), ...
451
+
452
+ ### ToyStore ORM
453
+
454
+ If you want something more advanced to handle your objects and relations,
455
+ use John Nunemaker's [ToyStore](https://github.com/jnunemaker/toystore) which works
456
+ together with Moneta. Assuming that `Person` is a `ToyStore::Object` you can
457
+ add persistence using Moneta as follows:
458
+
459
+ ~~~ ruby
460
+ # Use the Moneta Redis backend
461
+ Person.adapter :memory, Moneta.new(:Redis)
462
+ ~~~
463
+
464
+ ## Testing and Benchmarks
465
+
466
+ Testing is done using [Travis-CI](http://travis-ci.org/minad/moneta). Currently we support Ruby 1.8.7 and 1.9.3.
467
+
468
+ Benchmarks for each store are done on [Travis-CI](http://travis-ci.org/minad/moneta) for each build. Take a look there
469
+ to compare the speed of the different key value stores for different key/value sizes and size distributions.
470
+ Feel free to add your own configurations! The impact of Moneta should be minimal since it is only a thin layer
471
+ on top of the different stores.
472
+
435
473
  ## More information
436
474
 
437
475
  * http://yehudakatz.com/2009/02/12/whats-the-point/
data/Rakefile CHANGED
@@ -32,7 +32,7 @@ end
32
32
 
33
33
  task :benchmarks do
34
34
  Dir.chdir('benchmarks')
35
- ruby('run.rb')
35
+ ruby("run.rb #{ENV['CONFIG']}")
36
36
  end
37
37
 
38
38
  task :default => :test
data/SPEC.md CHANGED
@@ -51,6 +51,12 @@ Behaves the same as <code>[]=</code>, but allows the client to send additional o
51
51
  ### <code>increment(key[Object], amount[Integer] = 1, options[Hash] => {}) => Integer(value)</code>
52
52
 
53
53
  Increments a value atomically. This method is not supported by all stores and might raise a <code>NotImplementedError</code>.
54
+ This method MUST accept negative values, but the result MUST be unsigned.
55
+
56
+ ### <code>decrement(key[Object], amount[Integer] = 1, options[Hash] => {}) => Integer(value)</code>
57
+
58
+ Decrements a value atomically. This method is not supported by all stores and might raise a <code>NotImplementedError</code>.
59
+ This method MUST accept negative values, but the result MUST be unsigned.
54
60
 
55
61
  ### <code>clear(options[Hash] => {})</code>
56
62
 
data/benchmarks/run.rb CHANGED
@@ -4,35 +4,8 @@ $: << File.join(File.dirname(__FILE__), '..', 'lib')
4
4
  require 'benchmark'
5
5
  require 'moneta'
6
6
 
7
- begin
8
- require 'dm-core'
9
- DataMapper.setup(:default, :adapter => :in_memory)
10
- rescue LoadError => ex
11
- puts "Failed to load DataMapper - #{ex.message}"
12
- end
13
-
14
- begin
15
- server = Moneta::Server.new(Moneta.new(:Memory))
16
- rescue
17
- puts "Failed to start Moneta server - #{ex.message}"
18
- end
19
-
20
- class Array
21
- def random_index
22
- rand(size)
23
- end
24
-
25
- def random_value
26
- self[random_index]
27
- end
28
-
29
- def random_subset(n)
30
- (1..n).map{|x| random_value }
31
- end
32
- end
33
-
34
- stores = {
35
- :ActiveRecord => { :connection => { :adapter => 'sqlite3', :database => 'bench.activerecord' } },
7
+ STORES = {
8
+ :ActiveRecord => { :connection => { :adapter => 'sqlite3', :database => ':memory:' } },
36
9
  :Cassandra => {},
37
10
  :Client => {},
38
11
  :Couch => {},
@@ -41,7 +14,6 @@ stores = {
41
14
  :File => { :dir => 'bench.file' },
42
15
  :GDBM => { :file => 'bench.gdbm' },
43
16
  :HBase => {},
44
- :HBase => {},
45
17
  :HashFile => { :dir => 'bench.hashfile' },
46
18
  :LRUHash => {},
47
19
  :LevelDB => { :dir => 'bench.leveldb' },
@@ -53,143 +25,273 @@ stores = {
53
25
  :PStore => { :file => 'bench.pstore' },
54
26
  :Redis => {},
55
27
  :Riak => {},
56
- :SDBM => { :file => 'bench.sdbm' },
28
+ # SDBM is unstable
29
+ # :SDBM => { :file => 'bench.sdbm' },
57
30
  :Sequel => { :db => 'sqlite:/' },
58
- :Sqlite => { :file => 'bench.sqlite' },
59
- :YAML => { :file => 'bench.yaml' },
31
+ :Sqlite => { :file => ':memory:' },
32
+ # YAML is so fucking slow
33
+ # :YAML => { :file => 'bench.yaml' },
34
+ }
35
+
36
+ CONFIGS = {
37
+ :uniform_small => {
38
+ :runs => 3,
39
+ :keys => 1000,
40
+ :min_key_length => 1,
41
+ :max_key_length => 32,
42
+ :key_dist => :uniform,
43
+ :min_val_length => 0,
44
+ :max_val_length => 256,
45
+ :val_dist => :uniform
46
+ },
47
+ :uniform_medium => {
48
+ :runs => 3,
49
+ :keys => 100,
50
+ :min_key_length => 3,
51
+ :max_key_length => 200,
52
+ :key_dist => :uniform,
53
+ :min_val_length => 0,
54
+ :max_val_length => 1024,
55
+ :val_dist => :uniform
56
+ },
57
+ :uniform_large => {
58
+ :runs => 3,
59
+ :keys => 100,
60
+ :min_key_length => 3,
61
+ :max_key_length => 200,
62
+ :key_dist => :uniform,
63
+ :min_val_length => 0,
64
+ :max_val_length => 10240,
65
+ :val_dist => :uniform
66
+ },
67
+ :normal_small => {
68
+ :runs => 3,
69
+ :keys => 1000,
70
+ :min_key_length => 1,
71
+ :max_key_length => 32,
72
+ :key_dist => :normal,
73
+ :min_val_length => 0,
74
+ :max_val_length => 256,
75
+ :val_dist => :normal
76
+ },
77
+ :normal_medium => {
78
+ :runs => 3,
79
+ :keys => 100,
80
+ :min_key_length => 3,
81
+ :max_key_length => 200,
82
+ :key_dist => :normal,
83
+ :min_val_length => 0,
84
+ :max_val_length => 1024,
85
+ :val_dist => :normal
86
+ },
87
+ :normal_large => {
88
+ :runs => 3,
89
+ :keys => 100,
90
+ :min_key_length => 3,
91
+ :max_key_length => 200,
92
+ :key_dist => :normal,
93
+ :min_val_length => 0,
94
+ :max_val_length => 10240,
95
+ :val_dist => :normal
96
+ },
60
97
  }
61
98
 
62
- stats, keys, data, errors, summary = {}, [], [], [], []
63
- dict = 'ABCDEFGHIJKLNOPQRSTUVWXYZabcdefghijklnopqrstuvwxyz123456789'.split('')
64
- vlen_min, vlen_max, vlen_total, vlen_average = 99999, 0, 0, 0
65
- klen_min, klen_max, klen_total, klen_average = 99999, 0, 0, 0
99
+ config_name = ARGV.size == 1 ? ARGV.first.to_sym : :uniform_medium
100
+ unless config = CONFIGS[config_name]
101
+ puts "Configuration #{config_name} not found"
102
+ exit
103
+ end
104
+
105
+ DICT = 'ABCDEFGHIJKLNOPQRSTUVWXYZabcdefghijklnopqrstuvwxyz123456789'.freeze
106
+
107
+ class String
108
+ def random(n)
109
+ (1..n).map { self[rand(size),1] }.join
110
+ end
111
+ end
112
+
113
+ class Array
114
+ def sum
115
+ inject(0, &:+)
116
+ end
66
117
 
67
- RUNS = 3
68
- KEYS = 100
69
- MIN_KEY_SIZE = 3
70
- MAX_KEY_SIZE = 64
71
- MIN_VALUE_SIZE = 1
72
- MAX_VALUE_SIZE = 1024 * 10
118
+ def randomize
119
+ rest, result = dup, []
120
+ result << rest.slice!(rand(rest.size)) until result.size == size
121
+ result
122
+ end
123
+ end
73
124
 
74
- puts '======================================================================'
75
- puts 'Comparison of write/read between Moneta Stores'
76
- puts '======================================================================'
125
+ Process.fork do
126
+ begin
127
+ Moneta::Server.new(Moneta.new(:Memory)).run
128
+ rescue Exception => ex
129
+ puts "\e[31mFailed to start Moneta server - #{ex.message}\e[0m"
130
+ end
131
+ end
132
+ sleep 1 # Wait for server
77
133
 
78
- stores.each do |name, options|
134
+ STORES.each do |name, options|
79
135
  begin
136
+ if name == :DataMapper
137
+ begin
138
+ require 'dm-core'
139
+ DataMapper.setup(:default, :adapter => :in_memory)
140
+ rescue LoadError => ex
141
+ puts "\e[31mFailed to load DataMapper - #{ex.message}\e[0m"
142
+ end
143
+ elsif name == :Riak
144
+ require 'riak'
145
+ Riak.disable_list_keys_warnings = true
146
+ end
147
+
80
148
  cache = Moneta.new(name, options.dup)
81
149
  cache['test'] = 'test'
82
150
  rescue Exception => ex
83
- puts "#{name} not benchmarked - #{ex.message}"
84
- stores.delete(name)
151
+ puts "\e[31m#{name} not benchmarked - #{ex.message}\e[0m"
152
+ STORES.delete(name)
85
153
  ensure
86
154
  cache.close if cache
87
155
  end
88
156
  end
89
157
 
90
- puts 'Data loading...'
91
- KEYS.times do |x|
92
- klen = rand(MAX_KEY_SIZE - MIN_KEY_SIZE) + MIN_KEY_SIZE
93
- vlen = rand(MAX_VALUE_SIZE - MIN_VALUE_SIZE) + MIN_VALUE_SIZE
158
+ HEADER = "\n Minimum Maximum Total Average Ops/s"
159
+ SEPARATOR = '=' * 68
94
160
 
95
- key = dict.random_subset(klen).join
96
- value = dict.random_subset(vlen).join
97
- keys << key
98
- data << [key, value]
161
+ puts "\e[1m\e[36m#{SEPARATOR}\n\e[36mConfig #{config_name}\n\e[36m#{SEPARATOR}\e[0m"
162
+ config.each do |k,v|
163
+ puts '%-16s = %-10s' % [k,v]
164
+ end
165
+
166
+ module Rand
167
+ extend self
168
+
169
+ def normal_rand(mean, stddev)
170
+ # Box-Muller transform
171
+ theta = 2 * Math::PI * (rand(1e10) / 1e10)
172
+ scale = stddev * Math.sqrt(-2 * Math.log(1 - (rand(1e10) / 1e10)))
173
+ [mean + scale * Math.cos(theta),
174
+ mean + scale * Math.sin(theta)]
175
+ end
176
+
177
+ def uniform(min, max)
178
+ rand(max - min) + min
179
+ end
180
+
181
+ def normal(min, max)
182
+ mean = (min + max) / 2
183
+ stddev = (max - min) / 4
184
+ loop do
185
+ val = normal_rand(mean, stddev)
186
+ return val.first if val.first >= min && val.first <= max
187
+ return val.last if val.last >= min && val.last <= max
188
+ end
189
+ end
190
+ end
191
+
192
+ stats, data, summary = {}, {}, []
193
+
194
+ until data.size == config[:keys]
195
+ key = DICT.random(Rand.send(config[:key_dist], config[:min_key_length], config[:max_key_length]))
196
+ data[key] = DICT.random(Rand.send(config[:val_dist], config[:min_val_length], config[:max_val_length]))
197
+ end
99
198
 
100
- vlen_min = value.size if value.size < vlen_min
101
- vlen_max = value.size if value.size > vlen_max
102
- vlen_total = vlen_total + value.size
199
+ key_lengths, val_lengths = data.keys.map(&:size), data.values.map(&:size)
200
+ data = data.to_a
103
201
 
104
- klen_min = key.size if key.size < klen_min
105
- klen_max = key.size if key.size > klen_max
106
- klen_total = klen_total + key.size
202
+ def write_histogram(file, sizes)
203
+ min = sizes.min
204
+ delta = sizes.max - min
205
+ histogram = []
206
+ sizes.each do |s|
207
+ s = 10 * (s - min) / delta
208
+ histogram[s] ||= 0
209
+ histogram[s] += 1
210
+ end
211
+ File.open(file, 'w') do |f|
212
+ histogram.each_with_index { |n,i| f.puts "#{i*delta/10+min} #{n}" }
213
+ end
107
214
  end
108
- vlen_average = vlen_total / KEYS
109
215
 
110
- puts '----------------------------------------------------------------------'
111
- puts "Total keys: #{keys.size}, unique: #{keys.uniq.size}"
112
- puts '----------------------------------------------------------------------'
113
- puts ' Minimum Maximum Total Average xps '
114
- puts '----------------------------------------------------------------------'
115
- puts 'Key Length % 10i % 10i % 10i % 10i ' % [klen_min, klen_max, klen_total, klen_average]
116
- puts 'Value Length % 10i % 10i % 10i % 10i ' % [vlen_min, vlen_max, vlen_total, vlen_average]
216
+ write_histogram('key.histogram', key_lengths)
217
+ write_histogram('value.histogram', val_lengths)
218
+
219
+ puts "\n\e[1m\e[34m#{SEPARATOR}\n\e[34mComputing keys and values...\n\e[34m#{SEPARATOR}\e[0m"
220
+ puts %{ Minimum Maximum Total Average}
221
+ puts 'Key Length % 8d % 8d % 8d % 8d ' % [key_lengths.min, key_lengths.max, key_lengths.sum, key_lengths.sum / data.size]
222
+ puts 'Value Length % 8d % 8d % 8d % 8d ' % [val_lengths.min, val_lengths.max, val_lengths.sum, val_lengths.sum / data.size]
117
223
 
118
- stores.each do |name, options|
224
+ STORES.each do |name, options|
119
225
  begin
120
- puts '======================================================================'
121
- puts name
122
- puts '----------------------------------------------------------------------'
226
+ puts "\n\e[1m\e[34m#{SEPARATOR}\n\e[34m#{name}\n\e[34m#{SEPARATOR}\e[0m"
227
+
123
228
  cache = Moneta.new(name, options.dup)
124
229
 
125
230
  stats[name] = {
126
- :writes => [],
127
- :reads => [],
128
- :totals => [],
129
- :averages => [],
231
+ :write => [],
232
+ :read => [],
233
+ :sum => [],
234
+ :error => []
130
235
  }
131
236
 
132
- RUNS.times do |round|
133
- cache.clear
134
- print "[#{round + 1}] W"
135
- m1 = Benchmark.measure do
136
- KEYS.times do
137
- key, value = data.random_value
138
- cache[key] = value
237
+ %w(Rehearse Measure).each do |type|
238
+ state = ''
239
+ print "%s [%#{2 * config[:runs]}s] " % [type, state]
240
+
241
+ config[:runs].times do |run|
242
+ cache.clear
243
+ print "%s[%-#{2 * config[:runs]}s] " % ["\b" * (2 * config[:runs] + 3), state << 'W']
244
+
245
+ data = data.randomize
246
+ m1 = Benchmark.measure do
247
+ data.each {|k,v| cache[k] = v }
139
248
  end
140
- end
141
- stats[name][:writes] << m1.real
142
- print 'R '
143
- m2 = Benchmark.measure do
144
- KEYS.times do
145
- key, value = data.random_value
146
- res = cache[key]
147
- errors << [name, key, value, res] unless res == value
249
+
250
+ print "%s[%-#{2 * config[:runs]}s] " % ["\b" * (2 * config[:runs] + 3), state << 'R']
251
+
252
+ data = data.randomize
253
+ error = 0
254
+ m2 = Benchmark.measure do
255
+ data.each do |k, v|
256
+ error += 1 if v != cache[k]
257
+ end
258
+ end
259
+
260
+ if type == 'Measure'
261
+ stats[name][:write] << m1.real
262
+ stats[name][:error] << error
263
+ stats[name][:read] << m2.real
264
+ stats[name][:sum] << (m1.real + m2.real)
148
265
  end
149
266
  end
150
- stats[name][:reads] << m2.real
151
- stats[name][:totals] << (m1.real + m2.real)
152
- stats[name][:averages] << (m1.real + m2.real)
153
267
  end
154
- puts ''
155
- puts '----------------------------------------------------------------------'
156
- puts ' Minimum Maximum Total Average xps '
157
- puts '----------------------------------------------------------------------'
158
- tcmin, tcmax, tctot, tcavg = 99999, 0, 0, 0
159
- [:writes, :reads].each do |sname|
160
- cmin, cmax, ctot, cavg = 99999, 0, 0, 0
161
- stats[name][sname].each do |val|
162
- cmin = val if val < cmin
163
- tcmin = val if val < tcmin
164
- cmax = val if val > cmax
165
- tcmax = val if val > tcmax
166
- ctot = ctot + val
167
- tctot = tctot + val
168
- end
169
- cavg = ctot / RUNS
170
- puts '%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f ' % ["#{name} #{sname}", cmin, cmax, ctot, cavg, KEYS / cavg]
268
+
269
+ puts HEADER
270
+ [:write, :read, :sum].each do |i|
271
+ total = stats[name][i].sum
272
+ ops = (config[:runs] * data.size) / total
273
+ line = '%-17.17s %-5s % 8d % 8d % 8d % 8d % 8d' %
274
+ [name, i, stats[name][i].min * 1000, stats[name][i].max * 1000,
275
+ total * 1000, total * 1000 / config[:runs], ops]
276
+ summary << [-ops, line << "\n"] if i == :sum
277
+ puts line
171
278
  end
172
- tcavg = tctot / (RUNS * 2)
173
- puts '%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f ' % ["#{name} avgs", tcmin, tcmax, tctot, tcavg, KEYS / tcavg]
174
- summary << [name, tcmin, tcmax, tctot, tcavg, KEYS / tcavg]
175
- rescue Exception => ex
176
- puts "Failed to benchmark #{name} - #{ex.message}"
279
+
280
+ errors = stats[name][:error].sum
281
+ if errors > 0
282
+ puts "\e[31m%-23.23s % 8d % 8d % 8d % 8d\e[0m" %
283
+ ['Read errors', stats[name][:error].min, stats[name][:error].max, errors, errors / config[:runs]]
284
+ else
285
+ puts "\e[32mNo read errors"
286
+ end
287
+ rescue StandardError => ex
288
+ puts "\n\e[31mFailed to benchmark #{name} - #{ex.message}\e[0m\n"
177
289
  ensure
178
290
  cache.close if cache
179
291
  end
180
292
  end
181
- puts '----------------------------------------------------------------------'
182
- if errors.size > 0
183
- puts "Errors : #{errors.size}"
184
- # puts errors.inspect
185
- else
186
- puts 'No errors in reading!'
187
- end
188
- puts '======================================================================'
189
- puts "Summary: #{RUNS} runs, #{KEYS} keys"
190
- puts '======================================================================'
191
- puts ' Minimum Maximum Total Average xps '
192
- puts '----------------------------------------------------------------------'
193
- summary.each do |sry|
194
- puts '%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f ' % sry
293
+
294
+ puts "\n\e[1m\e[36m#{SEPARATOR}\n\e[36mSummary #{config_name}: #{config[:runs]} runs, #{data.size} keys\n\e[36m#{SEPARATOR}\e[0m#{HEADER}\n"
295
+ summary.sort_by(&:first).each do |entry|
296
+ puts entry.last
195
297
  end