kyototycoon 0.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,3 +1,12 @@
1
+ KyotoTycoon client for Ruby.
2
+
3
+ # Feature / Fixture
4
+
5
+ * Always Keep-Alive connect (v0.5.0+)
6
+ * You can choise key/value encoding from URI or Base64
7
+ * You can use MessagePack tranparency
8
+ * Benchmark scripts appended(they are connect to localhost:19999)
9
+
1
10
  # Install
2
11
 
3
12
  $ gem install kyototycoon
@@ -6,7 +15,7 @@
6
15
 
7
16
  ## Simple case
8
17
 
9
- @kt = KyotoTycoon.new
18
+ @kt = KyotoTycoon.new('localhost', 1978)
10
19
 
11
20
  # traditional style
12
21
  @kt.set('foo', 123)
@@ -21,9 +30,18 @@
21
30
 
22
31
 
23
32
  ## Complex case
33
+ # KT#configure for instance setting store.
34
+
35
+ KyotoTycoon.configure(:generic) do |kt|
36
+ kt.db = '*' # on memory
37
+ end
24
38
 
25
- @kt = KyotoTycoon.new('remotehost', 12345) # connect any host, any port
26
- @kt.db = '*' # on memory, or DB file as KT known
39
+ # connect any host, any port
40
+ KyotoTycoon.configure(:favicon, 'remotehost', 12345) do |kt|
41
+ kt.db = 'favicons.kch' # DB file as KT known
42
+ end
43
+
44
+ @kt = KyotoTycoon.create(:generic) # got KT instance by KT#configure(:generic) rules
27
45
 
28
46
  # set/bulk_set/get/bulk_get uses msgpack. default as :default
29
47
  @kt.serializer = :msgpack
@@ -37,14 +55,14 @@
37
55
  # @kt.logger = STDOUT
38
56
  # @kt.logger = Logger.new(STDOUT)
39
57
 
40
- # HTTP agent
41
- @kt.agent = :skinny # low-level socket communicate. a bit of faster than :nethttp(default). try benchmark/agent.rb
42
-
43
58
  # standby server
44
59
  @kt.connect_timeout = 0.5 # => wait 0.5 sec for connection open
45
60
  @kt.servers << ['server2', 1978] # standby server that will use when primary server (a.k.a. KT#new(host, port)) is dead.
46
61
  @kt.servers << ['server3', 1978] # same as above
47
62
 
63
+ # key/value encoding from :U or :B(default). default as base64 because it seems better than URL encode for me.
64
+ @kt.colenc = :U
65
+
48
66
  # get/set
49
67
  @kt.set('foo', 42, 100) # => expire at 100 seconds after
50
68
  @kt['foo'] # => 42. it is interger by msgpack serializer works
@@ -61,7 +79,7 @@
61
79
  @kt.remove_bulk([:foo, :bar])
62
80
 
63
81
  # it can store when msgpack using.
64
- @kt['baz'] = {'a' => 'a', 'b' => 'b}
82
+ @kt['baz'] = {'a' => 'a', 'b' => 'b'}
65
83
  @kt['baz'] # => {'a' => 'a', 'b' => 'b}
66
84
 
67
85
  # increment
@@ -70,6 +88,10 @@
70
88
  @kt.increment('bar', 10) # => 12
71
89
  @kt.increment('bar', -5) # => 7
72
90
 
91
+ # shorthand
92
+ @kt.incr('foo') # => 1
93
+ @kt.decr('foo') # => 0
94
+
73
95
  # delete keys
74
96
  @kt.delete(:foo, :bar, :baz)
75
97
 
data/benchmark/bulk.rb CHANGED
@@ -1,11 +1,7 @@
1
1
  # -- coding: utf-8
2
2
 
3
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- require "rubygems"
5
- require "benchmark"
6
- require 'kyototycoon.rb'
3
+ require File.expand_path("#{File.dirname(__FILE__)}/helper.rb")
7
4
 
8
- kt = KyotoTycoon.new
9
5
  bulk={}
10
6
  50000.times.map{|n|
11
7
  bulk[n.to_s] = "#{n}-#{rand}"
@@ -15,23 +11,5 @@ job = lambda {|kt|
15
11
  kt.get_bulk(bulk.keys)
16
12
  kt.clear
17
13
  }
18
- Benchmark.bm do |x|
19
- x.report('default') {
20
- kt.serializer=:default
21
- job.call(kt)
22
- }
23
- x.report('msgpack') {
24
- kt.serializer=:msgpack
25
- job.call(kt)
26
- }
27
- x.report('default(skinny)') {
28
- kt.agent = :skinny
29
- kt.serializer=:default
30
- job.call(kt)
31
- }
32
- x.report('msgpack(skinny)') {
33
- kt.agent = :skinny
34
- kt.serializer=:msgpack
35
- job.call(kt)
36
- }
37
- end
14
+
15
+ benchmark(job)
@@ -1,11 +1,7 @@
1
1
  # -- coding: utf-8
2
2
 
3
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- require "rubygems"
5
- require "benchmark"
6
- require 'kyototycoon.rb'
3
+ require File.expand_path("#{File.dirname(__FILE__)}/helper.rb")
7
4
 
8
- kt = KyotoTycoon.new
9
5
  bulk={}
10
6
  str = "string ああ" * 10000
11
7
  100.times.map{|n|
@@ -16,13 +12,4 @@ job = lambda {|kt|
16
12
  kt.get_bulk(bulk.keys)
17
13
  kt.clear
18
14
  }
19
- Benchmark.bm do |x|
20
- x.report('default') {
21
- kt.serializer=:default
22
- job.call(kt)
23
- }
24
- x.report('msgpack') {
25
- kt.serializer=:msgpack
26
- job.call(kt)
27
- }
28
- end
15
+ benchmark(job)
data/benchmark/getset.rb CHANGED
@@ -1,11 +1,7 @@
1
1
  # -- coding: utf-8
2
2
 
3
- require "rubygems"
4
- require "benchmark"
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- require 'kyoto_tycoon.rb'
3
+ require File.expand_path("#{File.dirname(__FILE__)}/helper.rb")
7
4
 
8
- kt = KyotoTycoon.new
9
5
  job = lambda {|kt|
10
6
  1000.times{|n|
11
7
  kt.set(n.to_s, n)
@@ -13,25 +9,4 @@ job = lambda {|kt|
13
9
  }
14
10
  kt.clear
15
11
  }
16
- Benchmark.bm do |x|
17
- x.report('default') {
18
- kt.agent = :nethttp
19
- kt.serializer=:default
20
- job.call(kt)
21
- }
22
- x.report('msgpack') {
23
- kt.agent = :nethttp
24
- kt.serializer=:msgpack
25
- job.call(kt)
26
- }
27
- x.report('default(skinny)') {
28
- kt.agent = :skinny
29
- kt.serializer=:default
30
- job.call(kt)
31
- }
32
- x.report('msgpack(skinny)') {
33
- kt.agent = :skinny
34
- kt.serializer=:msgpack
35
- job.call(kt)
36
- }
37
- end
12
+ benchmark(job)
@@ -1,10 +1,6 @@
1
1
  # -- coding: utf-8
2
2
 
3
- require "rubygems"
4
- require "benchmark"
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- require "msgpack"
7
- require 'kyototycoon.rb'
3
+ require File.expand_path("#{File.dirname(__FILE__)}/helper.rb")
8
4
 
9
5
  job = lambda {|kt|
10
6
  cnt = 0
@@ -22,12 +18,4 @@ job = lambda {|kt|
22
18
  cnt
23
19
  }
24
20
 
25
- kt = KyotoTycoon.new
26
-
27
- %w"nethttp skinny".each{|agent|
28
- %w!default msgpack!.each{|serializer|
29
- kt.agent = agent.to_sym
30
- kt.serializer = serializer.to_sym
31
- puts "#{agent}/#{serializer}: #{job.call(kt)}"
32
- }
33
- }
21
+ benchmark(job)
@@ -0,0 +1,22 @@
1
+ # -- coding: utf-8
2
+
3
+ require "benchmark"
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'kyototycoon.rb'
7
+
8
+ def benchmark(job)
9
+ kt = KyotoTycoon.new('0.0.0.0', 19999)
10
+ Benchmark.bm do |x|
11
+ %w!B U!.each{|colenc|
12
+ %w!default msgpack!.each{|serializer|
13
+ x.report("#{serializer}, colenc=#{colenc}") {
14
+ kt.serializer=serializer.to_sym
15
+ kt.colenc = colenc.to_sym
16
+ job.call(kt)
17
+ }
18
+ }
19
+ }
20
+ end
21
+ end
22
+
data/kyototycoon.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{kyototycoon}
8
- s.version = "0.1.2"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["uu59"]
12
- s.date = %q{2010-12-09}
12
+ s.date = %q{2010-12-22}
13
13
  s.description = %q{KyotoTycoon client for Ruby}
14
14
  s.email = %q{a@tt25.org}
15
15
  s.extra_rdoc_files = [
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
  "README.markdown",
23
23
  "Rakefile",
24
24
  "VERSION",
25
- "benchmark/agent.rb",
25
+ "benchmark/helper.rb",
26
26
  "benchmark/bulk.rb",
27
27
  "benchmark/bulk_bigdata.rb",
28
28
  "benchmark/getset.rb",
@@ -33,7 +33,6 @@ Gem::Specification.new do |s|
33
33
  "lib/kyototycoon/serializer/default.rb",
34
34
  "lib/kyototycoon/serializer/msgpack.rb",
35
35
  "lib/kyototycoon/tsvrpc.rb",
36
- "lib/kyototycoon/tsvrpc/nethttp.rb",
37
36
  "lib/kyototycoon/tsvrpc/skinny.rb",
38
37
  "spec/helper.rb"
39
38
  ]
data/lib/kyototycoon.rb CHANGED
@@ -10,9 +10,10 @@ require "kyototycoon/serializer/default.rb"
10
10
  require "kyototycoon/serializer/msgpack.rb"
11
11
  require "kyototycoon/tsvrpc.rb"
12
12
  require "kyototycoon/tsvrpc/skinny.rb"
13
- require "kyototycoon/tsvrpc/nethttp.rb"
14
13
 
15
14
  class KyotoTycoon
15
+ VERSION = '0.5.0'
16
+
16
17
  attr_accessor :colenc, :connect_timeout, :servers
17
18
  attr_reader :serializer, :logger, :db
18
19
 
@@ -42,8 +43,7 @@ class KyotoTycoon
42
43
  @servers = [[host, port]]
43
44
  @serializer = KyotoTycoon::Serializer::Default
44
45
  @logger = Logger.new(nil)
45
- @agent = :nethttp
46
- @colenc = :U
46
+ @colenc = :B
47
47
  @connect_timeout = 0.5
48
48
  end
49
49
 
@@ -63,10 +63,6 @@ class KyotoTycoon
63
63
  @logger = logger
64
64
  end
65
65
 
66
- def agent=(agent)
67
- @agent = agent
68
- end
69
-
70
66
  def get(key)
71
67
  res = request('/rpc/get', {:key => key})
72
68
  @serializer.decode(Tsvrpc.parse(res[:body])['value'])
@@ -218,35 +214,39 @@ class KyotoTycoon
218
214
  params ||= {}
219
215
  params[:DB] = @db
220
216
  end
221
- if @servers.length > 1
222
- @servers.each{|s|
223
- host,port = *s
224
- if ping(host, port)
225
- @servers = [[host, port]]
226
- break
227
- end
228
- }
229
- end
230
- if @servers.length == 0
231
- msg = "alived server not exists"
232
- @logger.crit(msg)
233
- raise msg
234
- end
235
- tsvrpc ||= begin
236
- host, port = *@servers.first
237
- Tsvrpc.new(host, port)
217
+
218
+ status,body = client.request(path, params, @colenc)
219
+ if ![200, 450].include?(status.to_i)
220
+ raise body
238
221
  end
239
- res = tsvrpc.request(path, params, @agent, @colenc)
222
+ res = {:status => status, :body => body}
240
223
  @logger.info("#{path}: #{res[:status]} with query parameters #{params.inspect}")
241
224
  res
242
225
  end
243
226
 
227
+ def client
228
+ host, port = *choice_server
229
+ @client ||= begin
230
+ Tsvrpc::Skinny.new(host, port)
231
+ end
232
+ end
233
+
234
+ def start
235
+ client.start
236
+ end
237
+
238
+ def finish
239
+ client.finish
240
+ end
241
+
242
+ private
243
+
244
244
  def ping(host, port)
245
245
  begin
246
+ rpc = Tsvrpc::Skinny.new(host, port)
246
247
  timeout(@connect_timeout){
247
248
  @logger.debug("connect check #{host}:#{port}")
248
- rpc = Tsvrpc.new(host, port)
249
- res = rpc.request('/rpc/echo', {'0' => '0'}, :skinny, :U)
249
+ res = rpc.request('/rpc/echo', {'0' => '0'}, :U)
250
250
  @logger.debug(res)
251
251
  }
252
252
  true
@@ -254,9 +254,38 @@ class KyotoTycoon
254
254
  # Ruby 1.8.7 compatible
255
255
  @logger.warn("connect failed at #{host}:#{port}")
256
256
  false
257
+ rescue SystemCallError
258
+ @logger.warn("connect failed at #{host}:#{port}")
259
+ false
257
260
  rescue => ex
258
261
  @logger.warn("connect failed at #{host}:#{port}")
259
262
  false
263
+ ensure
264
+ rpc.finish
260
265
  end
261
266
  end
267
+
268
+ def choice_server
269
+ current = @servers.first
270
+ if @servers.length > 1
271
+ @servers.each{|s|
272
+ host,port = *s
273
+ if ping(host, port)
274
+ @servers = [[host, port]]
275
+ break
276
+ end
277
+ }
278
+ end
279
+ if @servers.length == 0
280
+ msg = "alived server not exists"
281
+ @logger.crit(msg)
282
+ raise msg
283
+ end
284
+ result = @servers.first
285
+ if current != result
286
+ @client = nil
287
+ end
288
+ result
289
+ end
290
+
262
291
  end
@@ -2,29 +2,7 @@
2
2
 
3
3
 
4
4
  class KyotoTycoon
5
- class Tsvrpc
6
- def initialize(host, port)
7
- @host = host
8
- @port = port
9
- end
10
-
11
- def http(agent)
12
- case agent
13
- when :skinny
14
- Skinny.new(@host, @port)
15
- else
16
- Nethttp.new(@host, @port)
17
- end
18
- end
19
-
20
- def request(path, params, agent, colenc)
21
- status,body = *http(agent).request(path, params, colenc)
22
- if ![200, 450].include?(status)
23
- raise body
24
- end
25
- {:status => status, :body => body}
26
- end
27
-
5
+ module Tsvrpc
28
6
  def self.parse(body)
29
7
  body.split("\n").inject({}){|r, line|
30
8
  k,v = *line.split("\t", 2).map{|v| CGI.unescape(v)}
@@ -1,28 +1,29 @@
1
1
  # -- coding: utf-8
2
2
 
3
3
  class KyotoTycoon
4
- class Tsvrpc
4
+ module Tsvrpc
5
5
  class Skinny
6
6
  def initialize(host, port)
7
7
  @host = host
8
8
  @port = port
9
9
  @tpl = ""
10
- @tpl << "POST %s HTTP/1.0\r\n"
10
+ @tpl << "POST %s HTTP/1.1\r\n"
11
11
  @tpl << "Content-Length: %d\r\n"
12
12
  @tpl << "Content-Type: text/tab-separated-values; colenc=%s\r\n"
13
13
  @tpl << "\r\n%s"
14
14
  end
15
15
 
16
16
  def request(path, params, colenc)
17
- sock ||= ::TCPSocket.new(@host, @port)
17
+ start
18
18
  query = KyotoTycoon::Tsvrpc.build_query(params, colenc)
19
19
  request = @tpl % [path, query.bytesize, colenc, query]
20
- sock.write(request)
21
- status = sock.gets[9, 3]
20
+ @sock.write(request)
21
+ first_line = @sock.gets
22
+ status = first_line[9, 3]
22
23
  bodylen = 0
23
24
  body = ""
24
25
  loop do
25
- line = sock.gets
26
+ line = @sock.gets
26
27
  if line['Content-Length']
27
28
  bodylen = line.match(/[0-9]+/)[0].to_i
28
29
  next
@@ -31,10 +32,17 @@ class KyotoTycoon
31
32
  break
32
33
  end
33
34
  end
34
- body = sock.read(bodylen)
35
- sock.close
35
+ body = @sock.read(bodylen)
36
36
  [status.to_i, body]
37
37
  end
38
+
39
+ def start
40
+ @sock ||= ::TCPSocket.new(@host, @port)
41
+ end
42
+
43
+ def finish
44
+ @sock.close if @sock
45
+ end
38
46
  end
39
47
  end
40
48
  end
data/spec/helper.rb CHANGED
@@ -23,6 +23,9 @@ describe do
23
23
  end
24
24
 
25
25
  before(:each) do
26
+ end
27
+
28
+ after(:each) do
26
29
  @kt.clear
27
30
  end
28
31
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 2
9
- version: 0.1.2
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - uu59
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-09 00:00:00 +09:00
17
+ date: 2010-12-22 00:00:00 +09:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -103,7 +103,7 @@ files:
103
103
  - README.markdown
104
104
  - Rakefile
105
105
  - VERSION
106
- - benchmark/agent.rb
106
+ - benchmark/helper.rb
107
107
  - benchmark/bulk.rb
108
108
  - benchmark/bulk_bigdata.rb
109
109
  - benchmark/getset.rb
@@ -114,7 +114,6 @@ files:
114
114
  - lib/kyototycoon/serializer/default.rb
115
115
  - lib/kyototycoon/serializer/msgpack.rb
116
116
  - lib/kyototycoon/tsvrpc.rb
117
- - lib/kyototycoon/tsvrpc/nethttp.rb
118
117
  - lib/kyototycoon/tsvrpc/skinny.rb
119
118
  - spec/helper.rb
120
119
  has_rdoc: true
data/benchmark/agent.rb DELETED
@@ -1,27 +0,0 @@
1
- # -- coding: utf-8
2
-
3
- require "rubygems"
4
- require "benchmark"
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- require 'kyototycoon.rb'
7
-
8
- kt = KyotoTycoon.new
9
- job = lambda {|kt|
10
- 10000.times{|n|
11
- kt.set(n.to_s, n)
12
- kt.get(n)
13
- }
14
- kt.clear
15
- }
16
- Benchmark.bm do |x|
17
- x.report('skinny') {
18
- kt.agent = :skinny
19
- kt.serializer=:default
20
- job.call(kt)
21
- }
22
- x.report('nethttp') {
23
- kt.agent = :nethttp
24
- kt.serializer=:default
25
- job.call(kt)
26
- }
27
- end
@@ -1,28 +0,0 @@
1
- # -- coding: utf-8
2
-
3
- require "rubygems"
4
-
5
- class KyotoTycoon
6
- class Tsvrpc
7
- class Nethttp
8
- def initialize(host, port)
9
- @host = host
10
- @port = port
11
- @http ||= ::Net::HTTP.new(@host, @port)
12
- end
13
-
14
- def request(path, params, colenc)
15
- query = KyotoTycoon::Tsvrpc.build_query(params, colenc)
16
- req = Net::HTTP::Post.new(path)
17
- if query.length > 0
18
- req.body = query
19
- req['Content-Length'] = query.size
20
- end
21
- req['Content-Type'] = "text/tab-separated-values; colenc=#{colenc}"
22
- req['Connection'] = "close"
23
- res = @http.request(req)
24
- [res.code.to_i, res.body]
25
- end
26
- end
27
- end
28
- end