large_object_store 0.0.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.
- data/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/Rakefile +6 -0
- data/Readme.md +32 -0
- data/gem-public_cert.pem +20 -0
- data/large_object_store.gemspec +17 -0
- data/lib/large_object_store.rb +58 -0
- data/lib/large_object_store/version.rb +3 -0
- data/spec/large_object_store_spec.rb +107 -0
- data/spec/spec_helper.rb +1 -0
- metadata +56 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
large_object_store (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
bump (0.3.9)
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
rake (10.0.3)
|
12
|
+
rspec (2.12.0)
|
13
|
+
rspec-core (~> 2.12.0)
|
14
|
+
rspec-expectations (~> 2.12.0)
|
15
|
+
rspec-mocks (~> 2.12.0)
|
16
|
+
rspec-core (2.12.2)
|
17
|
+
rspec-expectations (2.12.1)
|
18
|
+
diff-lcs (~> 1.1.3)
|
19
|
+
rspec-mocks (2.12.2)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bump
|
26
|
+
large_object_store!
|
27
|
+
rake
|
28
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Store large objects in memcache or others by slicing them.
|
2
|
+
- uses read_multi for fast access
|
3
|
+
- returns nil if one slice is missing
|
4
|
+
|
5
|
+
Install
|
6
|
+
=======
|
7
|
+
|
8
|
+
```Bash
|
9
|
+
gem install large_object_store
|
10
|
+
```
|
11
|
+
|
12
|
+
Usage
|
13
|
+
=====
|
14
|
+
|
15
|
+
```Ruby
|
16
|
+
Rails.cache.write("a", "a"*10_000_000) # => false -> oops too large
|
17
|
+
|
18
|
+
store = LargeObjectStore.wrap(Rails.cache)
|
19
|
+
store.write("a", "a"*10_000_000) # => true -> always!
|
20
|
+
store.read("a").size # => 10_000_000 using multi_get
|
21
|
+
store.read("b") # => nil
|
22
|
+
store.fetch("a"){ "something" } # => "something" executes block on miss
|
23
|
+
```
|
24
|
+
|
25
|
+
Author
|
26
|
+
======
|
27
|
+
[Ana Martinez](https://github.com/anamartinez)<br/>
|
28
|
+
acemacu@gmail.com<br/>
|
29
|
+
[Michael Grosser](https://github.com/grosser)<br/>
|
30
|
+
michael@grosser.it<br/>
|
31
|
+
License: MIT<br/>
|
32
|
+
[](https://travis-ci.org/anamartinez/large_object_store)
|
data/gem-public_cert.pem
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDMjCCAhqgAwIBAgIBADANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdtaWNo
|
3
|
+
YWVsMRcwFQYKCZImiZPyLGQBGRYHZ3Jvc3NlcjESMBAGCgmSJomT8ixkARkWAml0
|
4
|
+
MB4XDTEzMDIwMzE4MTMxMVoXDTE0MDIwMzE4MTMxMVowPzEQMA4GA1UEAwwHbWlj
|
5
|
+
aGFlbDEXMBUGCgmSJomT8ixkARkWB2dyb3NzZXIxEjAQBgoJkiaJk/IsZAEZFgJp
|
6
|
+
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorXo/hgbUq97+kII9H
|
7
|
+
MsQcLdC/7wQ1ZP2OshVHPkeP0qH8MBHGg6eYisOX2ubNagF9YTCZWnhrdKrwpLOO
|
8
|
+
cPLaZbjUjljJ3cQR3B8Yn1veV5IhG86QseTBjymzJWsLpqJ1UZGpfB9tXcsFtuxO
|
9
|
+
6vHvcIHdzvc/OUkICttLbH+1qb6rsHUceqh+JrH4GrsJ5H4hAfIdyS2XMK7YRKbh
|
10
|
+
h+IBu6dFWJJByzFsYmV1PDXln3UBmgAt65cmCu4qPfThioCGDzbSJrGDGLmw/pFX
|
11
|
+
FPpVCm1zgYSb1v6Qnf3cgXa2f2wYGm17+zAVyIDpwryFru9yF/jJxE38z/DRsd9R
|
12
|
+
/88CAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUsiNnXHtKeMYYcr4yJVmQ
|
13
|
+
WONL+IwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQAlyN7kKo/NQCQ0
|
14
|
+
AOzZLZ3WAePvStkCFIJ53tsv5Kyo4pMAllv+BgPzzBt7qi605mFSL6zBd9uLou+W
|
15
|
+
Co3s48p1dy7CjjAfVQdmVNHF3MwXtfC2OEyvSQPi4xKR8iba8wa3xp9LVo1PuLpw
|
16
|
+
/6DsrChWw74HfsJN6qJOK684hJeT8lBYAUfiC3wD0owoPSg+XtyAAddisR+KV5Y1
|
17
|
+
NmVHuLtQcNTZy+gRht3ahJRMuC6QyLmkTsf+6MaenwAMkAgHdswGsJztOnNnBa3F
|
18
|
+
y0kCSWmK6D+x/SbfS6r7Ke07MRqziJdB9GuE1+0cIRuFh8EQ+LN6HXCKM5pon/GU
|
19
|
+
ycwMXfl0
|
20
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
name = "large_object_store"
|
3
|
+
require "#{name.gsub("-","/")}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, LargeObjectStore::VERSION do |s|
|
6
|
+
s.summary = "Store large objects in memcache or others"
|
7
|
+
s.authors = ["Ana Martinez"]
|
8
|
+
s.email = "acemacu@gmail.com"
|
9
|
+
s.homepage = "http://github.com/anamartinez/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = "MIT"
|
12
|
+
cert = File.expand_path("~/.ssh/gem-private_key.pem")
|
13
|
+
if File.exist?(cert)
|
14
|
+
s.signing_key = cert
|
15
|
+
s.cert_chain = ["gem-public_cert.pem"]
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "large_object_store/version"
|
2
|
+
|
3
|
+
module LargeObjectStore
|
4
|
+
|
5
|
+
def self.wrap(store)
|
6
|
+
RailsWrapper.new(store)
|
7
|
+
end
|
8
|
+
|
9
|
+
class RailsWrapper
|
10
|
+
attr_reader :store
|
11
|
+
|
12
|
+
LIMIT = 1024**2 - 100
|
13
|
+
|
14
|
+
def initialize(store)
|
15
|
+
@store = store
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(key, value, options = {})
|
19
|
+
value = Marshal.dump(value)
|
20
|
+
|
21
|
+
# store number of pages
|
22
|
+
pages = (value.size / LIMIT.to_f).ceil
|
23
|
+
@store.write("#{key}_0", pages, options)
|
24
|
+
|
25
|
+
# store object
|
26
|
+
page = 1
|
27
|
+
loop do
|
28
|
+
slice = value.slice!(0, LIMIT)
|
29
|
+
break if slice.size == 0
|
30
|
+
|
31
|
+
@store.write("#{key}_#{page}", slice, options)
|
32
|
+
page += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def read(key)
|
39
|
+
# read pages
|
40
|
+
pages = @store.read("#{key}_0")
|
41
|
+
return if pages.nil?
|
42
|
+
|
43
|
+
# read sliced data
|
44
|
+
keys = Array.new(pages).each_with_index.map{|_,i| "#{key}_#{i+1}" }
|
45
|
+
slices = @store.read_multi(*keys).values
|
46
|
+
return nil if slices.compact.size < pages
|
47
|
+
Marshal.load(slices.join(""))
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch(key, options={})
|
51
|
+
value = read(key)
|
52
|
+
return value unless value.nil?
|
53
|
+
value = yield
|
54
|
+
write(key, value, options)
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
class TestCache
|
5
|
+
def initialize
|
6
|
+
@data = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(k,v, options={})
|
10
|
+
v = Marshal.dump(v)
|
11
|
+
return false if v.bytesize > 1024**2
|
12
|
+
@data[k] = v
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(k)
|
17
|
+
real_read(k)
|
18
|
+
end
|
19
|
+
|
20
|
+
def real_read(k)
|
21
|
+
v = @data[k]
|
22
|
+
v.nil? ? nil : Marshal.load(v)
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_multi(*keys)
|
26
|
+
Hash[keys.map{|k| [k, real_read(k)] }]
|
27
|
+
end
|
28
|
+
|
29
|
+
def keys
|
30
|
+
@data.keys
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe LargeObjectStore do
|
35
|
+
let(:store) { LargeObjectStore.wrap(TestCache.new) }
|
36
|
+
|
37
|
+
it "has a VERSION" do
|
38
|
+
LargeObjectStore::VERSION.should =~ /^[\.\da-z]+$/
|
39
|
+
end
|
40
|
+
|
41
|
+
it "wraps and returns a wrapper" do
|
42
|
+
store.class.should == LargeObjectStore::RailsWrapper
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can write/read big objects" do
|
46
|
+
store.write("a", "a"*10_000_000).should == true
|
47
|
+
store.read("a").size.should == 10_000_000
|
48
|
+
end
|
49
|
+
|
50
|
+
it "passes options" do
|
51
|
+
store.store.should_receive(:write).with(anything, anything, :expires_in => 111).twice
|
52
|
+
store.write("a", "a", :expires_in => 111)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "cannot read corrupted objects" do
|
56
|
+
store.write("a", ["a"*10_000_000]).should == true
|
57
|
+
store.store.write("a_4", nil)
|
58
|
+
store.read("a").should == nil
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can write/read big non-string objects" do
|
62
|
+
store.write("a", ["a"*10_000_000]).should == true
|
63
|
+
store.read("a").first.size.should == 10_000_000
|
64
|
+
end
|
65
|
+
|
66
|
+
it "can read/write objects with encoding" do
|
67
|
+
store.write("a", "ß"*10_000_000).should == true
|
68
|
+
store.read("a").size.should == 10_000_000
|
69
|
+
end
|
70
|
+
|
71
|
+
it "can write/read giant objects" do
|
72
|
+
store.write("a", "a"*100_000_000).should == true
|
73
|
+
store.read("a").size.should == 100_000_000
|
74
|
+
end
|
75
|
+
|
76
|
+
it "uses necessary keys" do
|
77
|
+
store.write("a", "a"*5_000_000)
|
78
|
+
store.store.keys.should == ["a_0", "a_1", "a_2", "a_3", "a_4", "a_5"]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "uses read_multi" do
|
82
|
+
store.write("a", "a"*5_000_000)
|
83
|
+
store.store.should_receive(:read).with("a_0").and_return 5
|
84
|
+
store.read("a").size.should == 5_000_000
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#fetch" do
|
88
|
+
it "executes the block on miss" do
|
89
|
+
store.fetch("a"){ 1 }.should == 1
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does not execute the block on hit" do
|
93
|
+
store.fetch("a"){ 1 }
|
94
|
+
store.fetch("a"){ 2 }.should == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
it "passes the options" do
|
98
|
+
store.should_receive(:write).with(anything, anything, :expires_in => 111)
|
99
|
+
store.fetch("a", :expires_in => 111){ 2 }
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can fetch false" do
|
103
|
+
store.fetch("a"){ false }.should == false
|
104
|
+
store.read("a").should == false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "large_object_store"
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: large_object_store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ana Martinez
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: acemacu@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- .travis.yml
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- Rakefile
|
24
|
+
- Readme.md
|
25
|
+
- gem-public_cert.pem
|
26
|
+
- large_object_store.gemspec
|
27
|
+
- lib/large_object_store.rb
|
28
|
+
- lib/large_object_store/version.rb
|
29
|
+
- spec/large_object_store_spec.rb
|
30
|
+
- spec/spec_helper.rb
|
31
|
+
homepage: http://github.com/anamartinez/large_object_store
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.25
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Store large objects in memcache or others
|
56
|
+
test_files: []
|