juno 0.1.0
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.
- data/.gitignore +4 -0
- data/.travis.yml +25 -0
- data/Gemfile +19 -0
- data/LICENSE +20 -0
- data/README.md +83 -0
- data/Rakefile +15 -0
- data/SPEC.md +95 -0
- data/juno.gemspec +22 -0
- data/lib/juno.rb +31 -0
- data/lib/juno/activerecord.rb +58 -0
- data/lib/juno/base.rb +113 -0
- data/lib/juno/couch.rb +43 -0
- data/lib/juno/datamapper.rb +63 -0
- data/lib/juno/dbm.rb +15 -0
- data/lib/juno/expires.rb +45 -0
- data/lib/juno/file.rb +62 -0
- data/lib/juno/gdbm.rb +15 -0
- data/lib/juno/hashfile.rb +12 -0
- data/lib/juno/localmemcache.rb +16 -0
- data/lib/juno/memcached.rb +7 -0
- data/lib/juno/memcached_dalli.rb +55 -0
- data/lib/juno/memcached_native.rb +56 -0
- data/lib/juno/memory.rb +7 -0
- data/lib/juno/mongodb.rb +43 -0
- data/lib/juno/proxy.rb +5 -0
- data/lib/juno/pstore.rb +49 -0
- data/lib/juno/redis.rb +34 -0
- data/lib/juno/riak.rb +45 -0
- data/lib/juno/sdbm.rb +15 -0
- data/lib/juno/sequel.rb +51 -0
- data/lib/juno/sqlite.rb +50 -0
- data/lib/juno/tokyocabinet.rb +36 -0
- data/lib/juno/version.rb +3 -0
- data/lib/juno/yaml.rb +9 -0
- data/test/helper.rb +182 -0
- data/test/test_active_record.rb +36 -0
- data/test/test_couch.rb +13 -0
- data/test/test_datamapper.rb +64 -0
- data/test/test_dbm.rb +13 -0
- data/test/test_expires.rb +10 -0
- data/test/test_file.rb +9 -0
- data/test/test_gdbm.rb +13 -0
- data/test/test_hashfile.rb +9 -0
- data/test/test_localmemcache.rb +13 -0
- data/test/test_memcached.rb +15 -0
- data/test/test_memcached_dalli.rb +15 -0
- data/test/test_memcached_native.rb +15 -0
- data/test/test_memory.rb +9 -0
- data/test/test_mongodb.rb +13 -0
- data/test/test_proxy.rb +9 -0
- data/test/test_pstore.rb +9 -0
- data/test/test_redis.rb +13 -0
- data/test/test_riak.rb +13 -0
- data/test/test_sdbm.rb +13 -0
- data/test/test_sequel.rb +13 -0
- data/test/test_sqlite.rb +13 -0
- data/test/test_tokyocabinet.rb +13 -0
- data/test/test_yaml.rb +9 -0
- data/unsupported/benchmarks.rb +234 -0
- data/unsupported/cassandra.rb +45 -0
- data/unsupported/fog.rb +60 -0
- data/unsupported/test_cassandra.rb +13 -0
- data/unsupported/test_rackspace.rb +15 -0
- data/unsupported/test_s3.rb +15 -0
- data/unsupported/test_tokyotyrant.rb +13 -0
- data/unsupported/tokyotyrant.rb +29 -0
- metadata +165 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::ActiveRecord do
|
5
|
+
describe 'with connection option set' do
|
6
|
+
def new_store
|
7
|
+
store = Juno::ActiveRecord.new(:connection => { :adapter => 'sqlite3', :database => File.join(make_tempdir, 'db.sqlite3')})
|
8
|
+
store.migrate
|
9
|
+
store
|
10
|
+
end
|
11
|
+
|
12
|
+
class_eval(&JunoSpecification)
|
13
|
+
|
14
|
+
it 'updates an existing key/value' do
|
15
|
+
@store['foo/bar'] = 4
|
16
|
+
@store['foo/bar'] += 4
|
17
|
+
records = @store.table.find :all, :conditions => { :key => 'foo/bar' }
|
18
|
+
records.count.must_equal 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'using preexisting ActiveRecord connection' do
|
23
|
+
include Helper
|
24
|
+
|
25
|
+
it 'uses an existing connection' do
|
26
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => File.join(make_tempdir, 'db.sqlite3')
|
27
|
+
|
28
|
+
store = Juno::ActiveRecord.new
|
29
|
+
store.migrate
|
30
|
+
store.table.table_exists?.must_equal true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue LoadError => ex
|
35
|
+
puts "Juno::ActiveRecord not tested: #{ex.message}"
|
36
|
+
end
|
data/test/test_couch.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::DataMapper do
|
5
|
+
before do
|
6
|
+
DataMapper.setup(:default, :adapter => :in_memory)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'with the default repository' do
|
10
|
+
def new_store
|
11
|
+
Juno::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper.sqlite3")
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
Juno::DataMapper::Store.auto_migrate!(:juno)
|
16
|
+
end
|
17
|
+
|
18
|
+
class_eval(&JunoSpecification)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'when :repository specified' do
|
22
|
+
def new_store
|
23
|
+
Juno::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper.sqlite3")
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
Juno::DataMapper::Store.auto_migrate!(:sample)
|
28
|
+
end
|
29
|
+
|
30
|
+
class_eval(&JunoSpecification)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'with multiple stores' do
|
34
|
+
include Helper
|
35
|
+
|
36
|
+
before do
|
37
|
+
@first_store = Juno::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/first.sqlite3")
|
38
|
+
@first_store.clear
|
39
|
+
|
40
|
+
@second_store = Juno::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/second.sqlite3")
|
41
|
+
@second_store.clear
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not cross contaminate when storing' do
|
45
|
+
@first_store['key'] = 'value'
|
46
|
+
@second_store['key'] = 'value2'
|
47
|
+
|
48
|
+
@first_store['key'].must_equal 'value'
|
49
|
+
@second_store['key'].must_equal 'value2'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not cross contaminate when deleting' do
|
53
|
+
@first_store['key'] = 'value'
|
54
|
+
@second_store['key'] = 'value2'
|
55
|
+
|
56
|
+
@first_store.delete('key').must_equal 'value'
|
57
|
+
@first_store.key?('key').must_equal false
|
58
|
+
@second_store['key'].must_equal 'value2'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue LoadError => ex
|
63
|
+
puts "Juno::Datamapper not tested: #{ex.message}"
|
64
|
+
end
|
data/test/test_dbm.rb
ADDED
data/test/test_file.rb
ADDED
data/test/test_gdbm.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::GDBM do
|
5
|
+
def new_store
|
6
|
+
Juno::GDBM.new(:file => File.join(make_tempdir, 'gdbm.db'))
|
7
|
+
end
|
8
|
+
|
9
|
+
class_eval(&JunoSpecification)
|
10
|
+
end
|
11
|
+
rescue LoadError => ex
|
12
|
+
puts "Juno::GDBM not tested: #{ex.message}"
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::LocalMemCache do
|
5
|
+
def new_store
|
6
|
+
Juno::LocalMemCache.new(:file => File.join(make_tempdir, 'lmc'))
|
7
|
+
end
|
8
|
+
|
9
|
+
class_eval(&JunoSpecification)
|
10
|
+
end
|
11
|
+
rescue LoadError => ex
|
12
|
+
puts "Juno::LocalMemCache not tested: #{ex.message}"
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::Memcached do
|
5
|
+
def new_store
|
6
|
+
# HACK: memcached is running on 221122 because of travis-ci
|
7
|
+
Juno::Memcached.new(:server => 'localhost:22122', :namespace => 'juno')
|
8
|
+
end
|
9
|
+
|
10
|
+
class_eval(&JunoSpecification)
|
11
|
+
class_eval(&JunoExpiresSpecification)
|
12
|
+
end
|
13
|
+
rescue LoadError => ex
|
14
|
+
puts "Juno::Memcached not tested: #{ex.message}"
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::MemcachedDalli do
|
5
|
+
def new_store
|
6
|
+
# HACK: memcached is running on 221122 because of travis-ci
|
7
|
+
Juno::MemcachedDalli.new(:server => 'localhost:22122', :namespace => 'juno')
|
8
|
+
end
|
9
|
+
|
10
|
+
class_eval(&JunoSpecification)
|
11
|
+
class_eval(&JunoExpiresSpecification)
|
12
|
+
end
|
13
|
+
rescue LoadError => ex
|
14
|
+
puts "Juno::MemcachedDalli not tested: #{ex.message}"
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::MemcachedNative do
|
5
|
+
def new_store
|
6
|
+
# HACK: memcached is running on 221122 because of travis-ci
|
7
|
+
Juno::MemcachedNative.new(:server => 'localhost:22122', :namespace => 'juno')
|
8
|
+
end
|
9
|
+
|
10
|
+
class_eval(&JunoSpecification)
|
11
|
+
class_eval(&JunoExpiresSpecification)
|
12
|
+
end
|
13
|
+
rescue LoadError => ex
|
14
|
+
puts "Juno::MemcachedNative not tested: #{ex.message}"
|
15
|
+
end
|
data/test/test_memory.rb
ADDED
data/test/test_proxy.rb
ADDED
data/test/test_pstore.rb
ADDED
data/test/test_redis.rb
ADDED
data/test/test_riak.rb
ADDED
data/test/test_sdbm.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::SDBM do
|
5
|
+
def new_store
|
6
|
+
Juno::GDBM.new(:file => File.join(make_tempdir, 'gdbm.db'))
|
7
|
+
end
|
8
|
+
|
9
|
+
class_eval(&JunoSpecification)
|
10
|
+
end
|
11
|
+
rescue LoadError => ex
|
12
|
+
puts "Juno::SDBM not tested: #{ex.message}"
|
13
|
+
end
|
data/test/test_sequel.rb
ADDED
data/test/test_sqlite.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Juno::TokyoCabinet do
|
5
|
+
def new_store
|
6
|
+
Juno::TokyoCabinet.new(:file => File.join(make_tempdir, 'tokyocabinet.db'))
|
7
|
+
end
|
8
|
+
|
9
|
+
class_eval(&JunoSpecification)
|
10
|
+
end
|
11
|
+
rescue LoadError => ex
|
12
|
+
puts "Juno::TokyoCabinet not tested: #{ex.message}"
|
13
|
+
end
|
data/test/test_yaml.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'benchmark'
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
# Hacked arrays
|
6
|
+
# Array modifications
|
7
|
+
class HackedArray < Array
|
8
|
+
# Random keys/values
|
9
|
+
attr_reader :keys_used
|
10
|
+
def random_key(no_repeat = true, clean_keys_used = false)
|
11
|
+
@keys_used = [] if clean_keys_used or @keys_used.nil? or @keys_used.size == self.size
|
12
|
+
begin key = rand(self.size) end while no_repeat and @keys_used.include?(key)
|
13
|
+
@keys_used << key
|
14
|
+
return key
|
15
|
+
end
|
16
|
+
|
17
|
+
def random_value(no_repeat = true, clean_keys_used = false)
|
18
|
+
values_at(random_key(no_repeat, clean_keys_used)).first
|
19
|
+
end
|
20
|
+
alias_method :random, :random_value
|
21
|
+
|
22
|
+
def random_subset(n, no_repeat = true, clean_keys_used = true)
|
23
|
+
(1..n).map{|x| random_value(no_repeat, (clean_keys_used && x == 1))}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new_from_string(str)
|
27
|
+
res = new
|
28
|
+
str.split('').each{|x| res << x}
|
29
|
+
res
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require "../lib/juno"
|
34
|
+
require "juno/memcache"
|
35
|
+
require "juno/tyrant"
|
36
|
+
require "juno/berkeley"
|
37
|
+
|
38
|
+
|
39
|
+
stores = {
|
40
|
+
'Redis' => { },
|
41
|
+
'Memcached' => { :class_name => "Memcache", :server => "localhost:11211", :namespace => 'juno_bench' },
|
42
|
+
'Tyrant' => { :host => 'localhost', :port => 1978 }, # Breaks for any n > 50 on my machine
|
43
|
+
'MongoDB' => { :host => 'localhost', :port => 27017, :db => 'juno_bench' },
|
44
|
+
'LMC' => { :filename => "bench.lmc" },
|
45
|
+
'Berkeley' => { :file => "bench.bdb" },
|
46
|
+
'Rufus' => {:file => "bench.rufus"},
|
47
|
+
'Memory' => { },
|
48
|
+
'DataMapper' => { :setup => "sqlite3::memory:" },
|
49
|
+
# 'Couch' => {:db => "couch_test"},
|
50
|
+
'TC (Tyrant)' =>
|
51
|
+
{:name => "test.tieredtyrant", :backup => Juno::Tyrant.new(:host => "localhost", :port => 1978), :class_name => "TieredCache"},
|
52
|
+
'TC (Memcached)' =>
|
53
|
+
{:name => "test.tieredmc", :backup => Juno::Memcache.new(:server => "localhost:11211", :namespace => "various"), :class_name => "TieredCache"}
|
54
|
+
}
|
55
|
+
|
56
|
+
stats, keys, data, errors, summary = {}, [], HackedArray.new, HackedArray.new, HackedArray.new
|
57
|
+
dict = HackedArray.new_from_string 'abcdefghijklnopq123456789'
|
58
|
+
n = ARGV[0] ? ARGV[0].to_i : 100
|
59
|
+
m = ARGV[1] ? ARGV[1].to_i : 10
|
60
|
+
c = ARGV[2] ? ARGV[2].to_i : 3
|
61
|
+
vlen_min, vlen_max, vlen_ttl, vlen_avg = 99999, 0, 0, 0
|
62
|
+
ds = dict.size
|
63
|
+
|
64
|
+
puts "======================================================================"
|
65
|
+
puts "Comparison of write/read between Juno Stores"
|
66
|
+
puts "======================================================================"
|
67
|
+
|
68
|
+
puts "Data loading..."
|
69
|
+
n.times do |x|
|
70
|
+
klen = 6 + rand(3)
|
71
|
+
vlen = (rand(m) + 1) * (rand(m) + rand(m) + 1)
|
72
|
+
key = dict.random_subset(klen).join
|
73
|
+
keys << key
|
74
|
+
value = key * vlen
|
75
|
+
data << [key, value]
|
76
|
+
vs = value.size
|
77
|
+
vlen_min = vs if vs < vlen_min
|
78
|
+
vlen_max = vs if vs > vlen_max
|
79
|
+
vlen_ttl = vlen_ttl + vs
|
80
|
+
end
|
81
|
+
vlen_avg = vlen_ttl / n
|
82
|
+
|
83
|
+
puts "----------------------------------------------------------------------"
|
84
|
+
#puts data.inspect
|
85
|
+
puts "Total keys: #{keys.size}, unique: #{keys.uniq.size}"
|
86
|
+
#puts keys.sort.inspect
|
87
|
+
|
88
|
+
puts "----------------------------------------------------------------------"
|
89
|
+
puts " Minimum Maximum Total Average xps "
|
90
|
+
puts "----------------------------------------------------------------------"
|
91
|
+
puts "Lenght Stats % 10i % 10i % 10i % 10i " % [vlen_min, vlen_max, vlen_ttl, vlen_avg]
|
92
|
+
|
93
|
+
module Juno
|
94
|
+
class TieredCache
|
95
|
+
include Juno::Defaults
|
96
|
+
|
97
|
+
def initialize(options)
|
98
|
+
@bdb = Juno::Berkeley.new(:file => File.join(File.dirname(__FILE__), options[:name]))
|
99
|
+
@mc = options[:backup]
|
100
|
+
# @mc = Juno::Tyrant.new(:host => "localhost", :port => 1978)
|
101
|
+
# @mc = Juno::Memcache.new(:server => "localhost:11211", :namespace => options[:name])
|
102
|
+
end
|
103
|
+
|
104
|
+
def [](key)
|
105
|
+
val = @bdb[key]
|
106
|
+
unless val
|
107
|
+
@bdb[key] = val if val = @mc[key]
|
108
|
+
end
|
109
|
+
val
|
110
|
+
end
|
111
|
+
|
112
|
+
def []=(key, val)
|
113
|
+
@bdb[key] = val
|
114
|
+
@mc[key] = val
|
115
|
+
end
|
116
|
+
|
117
|
+
def store(key, value, options = {})
|
118
|
+
@bdb.store(key, value, options)
|
119
|
+
@mc.store(key, value, options)
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete(key)
|
123
|
+
bdb_val = @bdb.delete(key)
|
124
|
+
mc_val = @mc.delete(key)
|
125
|
+
bdb_val || mc_val
|
126
|
+
end
|
127
|
+
|
128
|
+
def clear
|
129
|
+
@mc.clear
|
130
|
+
@bdb.clear
|
131
|
+
end
|
132
|
+
|
133
|
+
def update_key(name, options)
|
134
|
+
@mc.update_key(name, options)
|
135
|
+
@bdb.update_key(name, options)
|
136
|
+
end
|
137
|
+
|
138
|
+
def key?(key)
|
139
|
+
@bdb.key?(key) || @mc.key?(key)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
stores.each do |name, options|
|
145
|
+
cname = options.delete(:class_name) || name
|
146
|
+
puts "======================================================================"
|
147
|
+
puts name
|
148
|
+
puts "----------------------------------------------------------------------"
|
149
|
+
begin
|
150
|
+
require "../lib/juno/#{cname.downcase}"
|
151
|
+
rescue LoadError
|
152
|
+
end
|
153
|
+
klass = Juno.const_get(cname)
|
154
|
+
@cache = klass.new(options)
|
155
|
+
stats[name] = {
|
156
|
+
:writes => [],
|
157
|
+
:reads => [],
|
158
|
+
:totals => [],
|
159
|
+
:avgs => [],
|
160
|
+
}
|
161
|
+
c.times do |round|
|
162
|
+
@cache.clear
|
163
|
+
print "[#{round + 1}] R"
|
164
|
+
m1 = Benchmark.measure do
|
165
|
+
n.times do
|
166
|
+
key, value = data.random
|
167
|
+
|
168
|
+
@cache[key] = value
|
169
|
+
end
|
170
|
+
end
|
171
|
+
stats[name][:writes] << m1.real
|
172
|
+
print "W "
|
173
|
+
m2 = Benchmark.measure do
|
174
|
+
n.times do
|
175
|
+
key, value = data.random
|
176
|
+
res = @cache[key]
|
177
|
+
errors << [name, key, value, res] unless res == value
|
178
|
+
end
|
179
|
+
end
|
180
|
+
stats[name][:reads] << m2.real
|
181
|
+
stats[name][:totals] << (m1.real + m2.real)
|
182
|
+
stats[name][:avgs] << (m1.real + m2.real)
|
183
|
+
end
|
184
|
+
print "\n"
|
185
|
+
puts "----------------------------------------------------------------------"
|
186
|
+
puts " Minimum Maximum Total Average xps "
|
187
|
+
puts "----------------------------------------------------------------------"
|
188
|
+
tcmin, tcmax, tctot, tcavg = 99999, 0, 0, 0
|
189
|
+
[:writes, :reads].each do |sname|
|
190
|
+
cmin, cmax, ctot, cavg = 99999, 0, 0, 0
|
191
|
+
stats[name][sname].each do |val|
|
192
|
+
cmin = val if val < cmin
|
193
|
+
tcmin = val if val < tcmin
|
194
|
+
cmax = val if val > cmax
|
195
|
+
tcmax = val if val > tcmax
|
196
|
+
ctot = ctot + val
|
197
|
+
tctot = tctot + val
|
198
|
+
end
|
199
|
+
cavg = ctot / c
|
200
|
+
puts "%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f " % ["#{name} #{sname}", cmin, cmax, ctot, cavg, n / cavg]
|
201
|
+
end
|
202
|
+
tcavg = tctot / (c * 2)
|
203
|
+
puts "%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f " % ["#{name} avgs", tcmin, tcmax, tctot, tcavg, n / tcavg]
|
204
|
+
summary << [name, tcmin, tcmax, tctot, tcavg, n / tcavg]
|
205
|
+
end
|
206
|
+
puts "----------------------------------------------------------------------"
|
207
|
+
if errors.size > 0
|
208
|
+
puts "Errors : #{errors.size}"
|
209
|
+
# puts errors.inspect
|
210
|
+
else
|
211
|
+
puts "No errors in reading!"
|
212
|
+
end
|
213
|
+
puts "======================================================================"
|
214
|
+
puts "Summary :: #{c} runs, #{n} keys"
|
215
|
+
puts "======================================================================"
|
216
|
+
puts " Minimum Maximum Total Average xps "
|
217
|
+
puts "----------------------------------------------------------------------"
|
218
|
+
summary.each do |sry|
|
219
|
+
puts "%-14.14s % 10.4f % 10.4f % 10.4f % 10.4f % 10.4f " % sry
|
220
|
+
end
|
221
|
+
puts "======================================================================"
|
222
|
+
puts "THE END"
|
223
|
+
puts "======================================================================"
|
224
|
+
|
225
|
+
#======================================================================
|
226
|
+
#Summary :: 3 runs, 1000 keys
|
227
|
+
#======================================================================
|
228
|
+
# Minimum Maximum Total Average xps
|
229
|
+
#----------------------------------------------------------------------
|
230
|
+
#MemcacheDB 0.6202 2.7850 7.0099 1.1683 855.9366
|
231
|
+
#Memcached 0.4483 0.6563 3.3251 0.5542 1804.4385
|
232
|
+
#Redis 0.3282 0.5221 2.2965 0.3828 2612.6444
|
233
|
+
#MongoDB 0.6660 1.0539 5.1667 0.8611 1161.2745
|
234
|
+
#======================================================================
|