moneta 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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