🤖

🤖

:gijutsu_burogu:

🍺Redisより早いNoSQL DBのAerospikeをRubyから使ってみた

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

以前、アドテクについて学ぶ機会があったので知見を残しておく。

アドテク界隈ではAerospikeがよく使われるようだ。 https://www.aerospike.jp/

Aerospikeとは NoSQL(キー・バリュー・ストア)データベース インメモリ、もしくはフラッシュメモリSSD)にデータを保持 ノードの追加により、処理能力とデータ容量をスケールアウト可能 99%以上のトランザクションを1ms以下のレイテンシで処理可能 僅か数台で百万TPS超のスループットを実現 AppNexusやBlueKaiで稼働実績あり みたいな感じです。要約すると超絶速いKVSってことでいいと思います。

http://tech.im-dmp.net/entry/archives/421

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とかこねくりまわさないといけないのでめんどくさい。バグなのかなんなのか調べる必要がありそう。