以前、アドテクについて学ぶ機会があったので知見を残しておく。
アドテク界隈ではAerospikeがよく使われるようだ。
https://www.aerospike.jp/
Aerospikeとは
NoSQL(キー・バリュー・ストア)データベース
インメモリ、もしくはフラッシュメモリ(SSD)にデータを保持
ノードの追加により、処理能力とデータ容量をスケールアウト可能
99%以上のトランザクションを1ms以下のレイテンシで処理可能
僅か数台で百万TPS超のスループットを実現
AppNexusやBlueKaiで稼働実績あり
みたいな感じです。要約すると超絶速いKVSってことでいいと思います。
Rubyから使用した。
Aerospike Ruby Client
というGemを使用した。
https://github.com/aerospike/aerospike-client-ruby
ぶっちゃけ、Rubyから使うのはオススメしない。Aerospikeで速さを求めているのに、わざわざ遅いRubyを使うのはナンセンスだと思う。
自分は短時間で成果が求められる環境で使用したため慣れているRubyを使用した。
RubyからAerospikeの使い方
RDBとAerospikeの名前の対応関係は以下のようになっている。
Aerospikeでnamespaceを新しく作るのはそこそこめんどくさいので、初期からあるtest namespaceをとりあえず使っておけばOK
RDB | Aerospike |
---|---|
db | namespace |
table | set |
column | bin |
module Shared
attr_accessor :write_policy, :policy, :client, :logger
def init
host = '33.222.111.00'
@@options = {
:host => host,
:port => 3000, # Aerospikeはデフォルトで3000番
:namespace => 'test', # デフォルトでtestというnamespaceがある
:set => 'advertisers', # 操作したいsetをする(メソッドの引数にしてもよさそう)
}
@write_policy = WritePolicy.new
@policy = Policy.new(connection_queue_size: 10000, timeout: 0.005)
@logger = Logger.new(STDOUT, Logger::INFO)
@client = host ? Client.new(Host.new(host, port)) : Client.new
end
def host
@@options[:host]
end
def port
@@options[:port]
end
def namespace
@@options[:namespace]
end
def set_name
@@options[:set]
end
end
shared/shared.rb
を利用することで、Aerospikeとの処理を行う。
require 'sinatra'
require 'aerospike'
require './shared/shared'
include Aerospike
include Shared
class MyApp < Sinatra::Base
# ~~~~いろいろ省略~~~~
# binを更新する
# 引数
# pk: primary key
# client: Aerospikeクライアント(ex: Shared.client)
# add_bin1: bin1の増加量(ex: 5)
# add_bin2: bin2の増加量(ex: 10)
def update_bins(pk, client, add_bin1, add_bin2)
key = Key.new(Shared.namespace, Shared.set_name, pk)
record = client.get(key)
bin1 = record.bins['bin1']
bin2 = record.bins['bin2']
bin1 += add_bin1
bin2 += add_bin2
client.put(key, record.bins)
end
# bin1, bin2を取得する
# 引数
# pk: primary key
# client: Aerospikeクライアント(ex: Shared.client)
# return: Hash{ :bin1 => Int, :bin2 => Int }
def get_bins(pk, client)
key = Key.new(Shared.namespace, Shared.set_name, pk)
bins_hash = client.get(key, ['bin1', 'bin2'], Shared.policy).bins
bins_hash
end
# update_bins(1, Shared.client, 5, 10) のように呼び出して使用
end
ハマったところ(バグ?)
def get_records(client)
stmt = Statement.new(Shared.namespace, Shared.set_name, ['bin1'])
records = client.query(stmt)
end
このように書くとSQLでいうところのselect bin1 from Shared.set_name
が動くはずなのだが、全件ひっぱてくることができなかった。なぜか、4割ほどのレコードだけ引っ張ってくることができた。対策として、一つのレコードで全件分のデータを保存するために、Integer型でなくList型でbinを持つことが考えられる。Listとかこねくりまわさないといけないのでめんどくさい。バグなのかなんなのか調べる必要がありそう。