アドサーバの実装にGo言語を用いるメリット

Pocket

こんにちは。配信・インフラチームの川住です。

先日の記事にもありますが、最近、弊社DSP『Bypass』のRTB入札サーバはGo言語で実装されたものに完全にリプレイスされました。以前の入札サーバはLuaとC言語で実装されていましたが、規模の拡大に伴ってより大量のリクエストを高速に捌く必要が出てきたため、弊社SSP『AdStir』での開発・運用実績があり、Luaより処理が高速で、かつ比較的容易にHTTPサーバを実装できるGo言語へのリプレイスに至りました。

今回は、アドサーバの実装にGo言語を用いるメリットをいくつか紹介します。

リクエスト処理時のリソース消費がNginx+Luaの構成に比べて少ない

従来のLua実装では、ngx_luaモジュールを使用しており、Nginxの各プロセス内でLuaのプログラムを実行する形を取っていました。したがって、比較的大きなサイズのプロセスがforkされてしまうため、メモリ消費量が非常に多くなっていました。それに対して、Go言語を用いたサーバでは、プロセスをforkすることなくリクエストを並行して処理できるため、メモリ消費量がLua実装に比べて非常に少なく済んでいます。

強力なキャッシュモジュールの存在

RTBの入札サーバでは、大量のリクエストを高速に捌く必要があります。そのため、memcachedなどのKVSとの通信にかかるコスト (通信回数, データサイズ etc.) も考慮しなければなりません。Go言語には『go-cache』という強力なインメモリキャッシュのライブラリがあり、こちらを使用することで、KVSから取得したデータをプロセス内に保持でき、KVSとの通信コストを減らせます。Go言語のサーバ自体は1プロセスで動作しているため、キャッシュデータの共有も比較的容易に行えます。

以下にgo-cacheを用いたデータの取得と格納のコードを掲載します。

上記のように、Get関数とSet関数を用いて簡単にデータの取得や格納を行えます。データの格納時にはTTLも設定できます。しかし、go-cacheではデータの取得や格納を行う際にMutexを用いた排他制御を行っています。そのため、RTBの入札サーバのようにデータの読み書きが頻繁な環境で使用すると、go-cacheへのアクセス自体がボトルネックとなるため性能が低下してしまいます。このような環境では、go-cacheのインスタンスを複数生成しておき、キーによってシャーディングするなどして同一資源へのアクセスを分散させる必要があります。以下にシャーディング処理の一例を掲載します。

まとめ

Go言語を用いることで、高速に動作するアドサーバを比較的容易に実装できます。ただし、Go言語のサーバでは1プロセスで処理を行うため、共有資源の排他制御がボトルネックとなる可能性があり、その点を考慮しつつ実装する必要があります。

アドサーバ開発でのLuaの利用 連載(3)

Pocket

こんにちは。ユナイテッドの技術開発部で部長をしている伊良子です。

ユナイテッドでのアドサーバ開発では、Go, Lua, C言語(nginxモジュール)が主に使われる言語ですが、ここ1年ほどで急速にGoを利用するケースが増えています。新規でNginxのCモジュールを開発することはここ数年では皆無ですし、Luaで書かれているアドサーバをGoにリプレースするケースも出てきている状況です。最近ですと、数年前に私がLua+C言語で書いた弊社DSP『Bypass』のRTB入札サーバも、今ではGoに完全にリプレースされており、Luaが大好きだった私としては少々寂しいところです。 しかしながら、保守性の高さからLuaを選択するアドバンテージは高いですし、また速度面でも(弊社内でのベンチマークではありますが)シンプルな処理であるほどLuaが勝るケースが多いため、シンプルなAPIサーバなど、利用の用途はまだまだあると考えています。

今回は、Luaを利用する上で、便利なモジュールを幾つかご紹介したいと思います。

lsyslog

https://github.com/remakeelectric/lsyslog

Luaからsyslogを使うラッパーモジュールです。C言語でコンパイルされたモジュールなので高速に動作しますし、rsyslogの設定と連携することでカスタムされたログを安定的に出力することが可能です。設定も簡単です。

一例として、Luaのサンプルコードと、それに対応したrsyslogの設定を下記します。

rsyslog.confでは、テンプレート設定とルールを設定します。 この例の場合、時間ごとにファイルを切り替え、出力時間を先頭に付加した上で、プログラムから受け取った文字列を展開しています。

lua-cjson

http://www.kyne.com.au/~mark/software/lua-cjson.php

LuaからJSONを扱うモジュールです。C言語でコンパイルされているため非常に高速です。 アドサーバは直接データベースを参照するケースは稀で、memcached等のオンメモリデータを参照するケースが多いのですが、その際データをアドサーバに対して受け渡すのに手軽な方法がJSONです。弊社では他にもバイナリにするケースや、messagepackを用いるケースもありますが、手軽さと視認性の高さからJSONを使うことが最も多いです。JSONを採用する上で問題になるのがデコードにかかるコストなのですが、様々なモジュールや方法を試した結果、最も良好な速度だったのが、このlua-cjsonです。

サンプルコードは下記のような形になります。

まとめ

Luaは自分でC言語のモジュールを作ることが出来るので、Cのライブラリがあれば自分で高速なモジュールを作ることも可能です。弊社では、

  • hiredisライブラリを利用したRedis操作モジュール
  • libmemcachedを利用したmemcached操作モジュール
  • opensslライブラリを利用した、暗号/複合化モジュール

などで利用しています。 C言語と連携しないと複雑な処理を高速に実行することが難しいのがLua言語の特徴ですが、今回ご紹介したような鉄板モジュールが開発チーム内で定まっていれば、C言語を扱えないプログラマでも手軽にLuaでアドサーバを書くことが可能になります。

Hadoop / Spark Conference 2016 参加報告

Pocket

こんにちは。データサイエンスチームの西岡です。

先週月曜におこなわれたHadoop/Spark Conference Japan 2016に参加しましたので、 参加報告をさせていただきます。

聴講した発表は以下です。

  • 午前- 基調講演
  • 13:00- 次世代アーキテクチャから見たHadoop/Sparkの位置づけ (D会場)
  • 13:45- KuduによるHadoopのトランザクションアクセスと分析パフォーマンスのトレードオフ解消 (B会場)
  • 14:30- さくらインターネットが構築した、Apache Sparkによる原価計算システムの仕組みとその背景 (C会場)
  • 15:15- SparkによるGISデータを題材とした時系列データ処理 (D会場)
  • 16:00- Hive On Sparkを活用した高速データ分析 (D会場)

内容を簡単にまとめたスライドも公開しています。

全体を通して受けた印象を一言で言うと、「ますます進化・浸透していくHadoop」です。

開発開始当時(2004年頃)に一般的な構成だったCPU・メモリ・HDDを前提とした作りとなっていましたが、 今やストレージとして普及したSSDや、深層学習などの計算インテンシブなタスクに用いられるGPU・FPGAなど、 新しいハードウェアに対応できるようHadoopの開発を進めていくということでした。

しかしその一方、少数ノードで事足りる環境にはHadoopは不向きであるという指摘もあり、 Hadoopではカバーできない環境を補完するようなプロダクトの登場を予感させる発表もありました。

これからの分散データ処理がどうなっていくのか、1エンジニアとして注目していきたいと思います。

Redisにおけるデータの大量挿入手法

Pocket

こんにちは。Bypass開発チームの川住と申します。
今回はRedisにおけるデータの大量挿入手法について紹介します。

Redisとは?

まず、Redisについて少し紹介します。
Redisとは、メモリ上に Key-Value Store(KVS)を構築できるソフトウェアです(http://redis.io/)。
同系統のソフトウェアにはmemcached (http://memcached.org/) などがあります。
memcachedと比較すると、

  • シングルスレッドでクエリを処理する
  • データを永続化できる
  • ハッシュやリストなど、様々なデータ型を利用できる

といった特徴があります。

これらのソフトウェアを用いると、
メモリ上にデータを保持しており、かつ処理がシンプルであるため、
高速にデータの取得(格納)可能なKVSを構築できます。

データの大量挿入

本題に移ります。
Redisのデータの格納はとても高速ですが、シングルスレッドでクエリを処理するため、
データ数に比例して処理に時間がかかってしまいます。

そこで、大量のデータを挿入する時には、
「1件あたりのデータの挿入にかかる時間」をできるだけ短くする必要があります。

弊社では、Perlスクリプトでデータを加工し、Redisに大量のデータを格納しています。
この時、PerlのRedisモジュールを用いて1件ずつデータを格納してしまうと、

  • Redisとのデータの送受信で発生するRTT (Round-Trip Time)
  • Redisからのレスポンスをパースし、Perlのデータ型に加工する時間

が問題になります。そこで、

  • 複数のコマンドの一括送信
  • Redisからのレスポンスの破棄
    • socketやnc (netcat) でコマンドを送信し、レスポンスを/dev/null等に捨てる

を行い、処理時間を削減します。

処理時間の計測

下記の手法で10万件のデータの格納にかかった時間をそれぞれ計測しました。

  • PerlのRedisモジュールを使う(手法1)

  • socketを用いてデータを流し込む(手法2)

  • ncを用いてデータを流し込む(手法3)

計測結果

同端末で各手法5回ずつ試行し、その平均を処理時間としました。

手法 処理時間 (sec) ロス率
手法1 (module) 59.710145 0%
手法2 (socket) 2.978576 0%
手法3 (netcat) 2.626164 18%

複数のデータの格納コマンドを一括で送信し、
レスポンスのパースを行わないことによって処理時間を大幅に削減できます。
socketを用いる場合は送信(受信)バッファの管理を行わないと、
バッファ溢れによってデータロスが発生してしまいます。

まとめ

今回はRedisにおけるデータの大量insert手法を紹介しました。
目的に合わせて最適な手法を選択することでより効率的にデータを処理できるようになります。
(参考URL: http://redis.io/topics/mass-insert

基本統計量について社内勉強会で発表しました

Pocket

こんにちは。データサイエンティストチームの西岡です。

先日、統計入門ということで基本統計量について社内勉強会で発表しました。

「平均」は恐らく誰もがご存知の統計指標だと思いますが、 平均だけに頼っていてはデータの特徴が見えなくなることもあるよ、ということに伝えるために基本統計量について紹介しました。

基本統計量には大きく分けて

  • 代表値 (representative value)
  • 散布度 (dispersion)

の2種類があります。

代表値は平均値・中央値・最頻値など、文字通りデータを1つの値で示す指標であり、 散布度は分散・標準偏差など、データのバラつき具合を表す指標です。

代表値として平均値ではなく中央値や最頻値を使ったほうが適切な場合もありますし、散布度と合わせるとベターです。

平均値に騙されず、より正確にデータの特徴を掴んでいきましょう。

Pythonでテスト 連載(2) ユニットテストの書き方

Pocket

こんにちは。技術開発部データサイエンティストチームの西岡です。

前回の連載ではなぜユニットテストを書くのかについて述べました。
今回は実際にPythonのunittestモジュールを使用しながら、テストの書き方を説明しようと思います。

Pythonのユニットテストの話ではありますが、ユニットテストの思想自体は他言語でも通用するものでもありますし、Pythonの使用者以外の方も自身のよく使われる言語のユニットテストフレームワークと照らし合わせながら読んでいただければと思います。

なお、ここではPython3を使用しています。

テストケース

calc.pyはテスト対象のスクリプト、test_calc.pyはテストスクリプトです。

calc.py

tests/test_calc.py

ディレクトリ構造は以下のようであるとします。

test_calc.pyでは、calc.pyのadd_three関数にある入力を渡し、その出力が期待通りであるかをテストしています。 これをテストケースと呼びます。

unittestには

  • unittest.TestCaseクラスを継承したクラスにテストメソッドを記述
  • テストメソッドはtest_*で始める

というルールがありますが、これさえ守ればコマンドラインからテストを実行することができます。

この例ではもちろん5+3≠7なので、

上のようにテストが失敗します。 テストが通ったときは下のような表示になります。

テストフィクスチャ

ここで、Rectangleという長方形を表すクラスがあるとします。 Rectangleクラスには、

  • 長方形の幅・高さを取得するgetメソッド
  • 長方形の幅・高さを設定するsetメソッド
  • 長方形の面積を取得するareaメソッド

があり、インスタンスの使用が終わるとfinalizeメソッドで幅と高さをNoneにしなければならないものとします。

階層構造

rect.py

tests/test_rect.py

という感じになるかと思います。

しかし、これではrec = rect.Rectangle(5, 3)rec.finalize()を全テストメソッドで書かなければならず、非常に手間です。

unittestでは、このようなテストに伴って発生する初期化処理・終了処理をまとめるためにsetUp・tearDownメソッドを使用します。

tests/test_rect.py

これにより初期化・終了処理をまとめることができ、すっきりしたテストコードになります。

ただし、setUpとtearDownによってインスタンスの生成が1回にまとまったわけではないことに注意が必要です(以下の実行結果を参照)。

これはテストが以前におこなわれた何らかの実行に依存しないようにするためです。

テストの書き方

テスト対象の関数が複数のことをおこなう場合

テストの書き方は、テスト対象のコードにより大きく書き方が変わります。

以下の関数をどうテストすればよいかを考えてみましょう。

このcalc_average_bmi関数では、全員のBMIの平均を返します(BMIが何かについては、こちらのサイトを参照)。

この関数をテストする際に問題が起こりうる箇所は2つあります。

  • 返される平均の値が正しくないとき、BMI計算が間違っているのか平均計算が間違っているのか特定できない

  • 引数に空の配列が渡された時、平均計算においてZeroDivisionErrorになる

  • person.height == 0のときにBMI計算においてZeroDivisionErrorになる

下2つの「引数に空の配列が渡される」「身長が0である」といった例外はcatchすれば済みますが、そもそもcalc_average_bmi関数は

  • 平均計算
  • BMI計算

の2つのことをやっているため、関数自身はどんどん読みづらく、肥大化していきます。

そこで、BMI計算と平均計算を分離しましょう。

bmi.py

test_bmi.py

と分離すれば、bmi計算と平均計算は各々のメソッドで完結し、calc_average_bmiはほぼユニットテストを書く必要もないほど単純なコードになりました。

ここでは省きましたが、身長の単位がセンチメートルかメートルかもこのメソッドの中でテストすることができます。

メソッドに1つのことだけをさせるように変更すれば、テストもその1つのことだけを確認すればよくなるため、テストが非常に書きやすくなり、またテスト漏れも防ぐことができます。

プログラムをテスタブルにするため、テストが書きやすいようにプロダクションコードの設計から見直すことが重要です。

外部サービスのテスト

ここでいう外部サービスとは、データベースやWeb APIなどです。

git commit時のテストで毎回データベースを丸々スキャンするようなクエリを流すわけにもいきませんし、外部APIは(あまり無いかとは思いますが)落ちている可能性もあるので、外部サービスとの連携テストにはモックオブジェクトを使用するのが一般的です。

今回はモックを使用したテストについては述べませんが、いずれ取り上げてみたいと思います。

まとめ

テスタブルなコードを書くことでバグを減らし、より高速な開発をおこなっていきましょう。

Pythonでテスト 連載(1) なぜユニットテストを書くのか?

Pocket

こんにちは。技術開発部データサイエンティストチームの西岡と申します。

データサイエンスチームでは、CTR・CVR予測や、ユーザターゲティングの精度向上に日々取り組んでいます。

前回の伊良子の連載でも述べてある通り広告配信をおこなうアドサーバにはLuaやGoなどが使用されていますが、
データサイエンスチームでは分析がメインとなるためPythonを使用し始めました。 Pythonにはscikit-learnpandasなどの分析用ライブラリが充実しており、モデリング等を迅速におこなえるためです。

もちろん、分析のみならず製品への機能追加もPythonでおこなっています。 そこで、この連載ではPythonのユニットテストの書き方・テストフレームワークを紹介します。
まず第1回となる今回は、そもそもなぜユニットテストを書くのかについて説明します。

そもそも「テスト」とは?

「テスト」が何なのかということについて、wikipediaには

ソフトウェアテスト(software test)は、コンピュータのプログラムを実行し、 正しく動作するか、目標とした品質に到達しているか、 意図しない動作をしないかどうかを確認する作業のことである。

とあります。

要はデバッグのための工程です。 そもそも、テストという大仰な名前をつけずとも

プログラムを書く -> 動作確認する -> 正しく動かなければ修正 -> 再度動作確認する -> …

という流れはエンジニアであれば意識せずとも普段からやっていることです。

「ユニットテスト」とは?

ユニットテストが何なのかについてはこちらのサイトが参考になります。

単体テスト(ユニットテストと呼ばれることもあります)は、プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。通常、関数やメソッドが単体テストの単位(ユニット)となります。

例をあげると、

という関数があったとして、

というように、ユニットテストでは外部のプログラムからテスト対象の関数やメソッドを呼び出し、期待した結果が得られるかを検証します。

なぜユニットテストを書くのか?

ユニットテストを書く利点について、いろいろあるかと思いますがここでは以下の3点をあげておきます。

  • プログラムの理解が容易になる

    ユニットテストが存在することで、テスト対象のコードの振る舞いが理解しやすくなります。

    get_nested_arrayは名前からは配列を返すのか?と予想してしまいますが、このテストがあることで辞書型を返すということがわかります。

    多人数が関わる開発では、自分が書いたプログラムを他の人が触る、あるいは他の人が書いたプログラムを自分が触ることは日常茶飯事のため、ユニットテストはプログラムの理解を助けることにつながります。

  • プログラムのリファクタも兼ねる

    複雑な関数やメソッドではテストすべき項目が多く、テストもどんどん複雑になっていきます。

    関数・メソッドに多くのことをさせすぎず役割を1つに絞っておくことでテストも簡潔で分かりやすいものになります。 はじめからユニットテストを書くことを念頭においておけば、設計を意識しながらコードを書くことにつながり、プログラムもすっきりしたものになります(参考: テスト駆動開発)。

  • プログラムの変更が容易になる

    プログラムに変更を加えると予期せぬ箇所にも影響が出て動作がおかしくなる、ということはままあります。 プログラムにユニットテストが付属していれば他の部分に影響が出てもテストが失敗するため、バグの早期発見につながります。

ユニットテストは「銀の弾丸」ではない

前項ではユニットテストはいいことずくめであるかのような書き方をしましたが、ユニットテストにも「限界」があります。

  • 工数がかかる

    製品のプログラムとは別にテスト用のコードを書かなければならないため、当然ながら工数がかかります。 プロジェクトの規模や人数を考えてどの程度まで網羅するべきかをその都度決めておく必要があるでしょう。

  • 全てをユニットテストでテストできるわけではない

    ユニットテストでのテストが向いているのは、関数やメソッドなどのプログラムを構成する最小単位です。

    例えばWebアプリケーションを開発していると、画面に期待した通りHTML要素が配置されるかもテストしなければなりません。 しかしユニットテストでは画面のテストをおこなうのは難しく、しかも画面はデザインの変更などにより頻繁に変わる部分であるため、テストコードを書くには向いていません。

ユニットテストを書くのは「手段」に過ぎない

前述した通りユニットテストは「銀の弾丸」ではありませんが、それでもメンテナンスコストの低下、開発速度の向上など、ユニットテストを書くことで多大な恩恵が受けられると思います。

しかし、ユニットテストを書く際には気をつけておくべきことがあります。
あくまでユニットテストを書くのは「手段」にすぎず「目的」ではないということです。

最大の目的はバグをなくすことです。

そのために必要なのは適切なテストケースを考えることです。たとえどれだけ変更がそのプログラムに加えられても、このプログラムはこう動いてほしいと明示化することです。

終わりに

本連載の第一回では、なぜユニットテストを書くべきかを中心に書きました。

次回の連載では、実際にPythonのユニットテストフレームワークを使ってどうテストを記述するのかを解説します。

アドサーバ開発でのLuaの利用 連載(2)

Pocket

こんにちは。技術開発部で部長をしている伊良子と申します。

前回は、Luaを採用した経緯/理由について書くことで、Lua及びngx_luaの紹介をさせて頂きました。連載第二回の今回はngx_luaをより高速に動作させるためのTIPSをご紹介したいと思います。

LuaJITを利用する

Luaの最新バージョンは5.3ですが、ngx_luaがサポートしているのは5.1までです。ngx_luaは、このLua5.1を使用するよりもJITコンパイラ版であるLuaJITの使用を推奨しています。LuaJITはLua5.1系に対応した本家Luaとは別のプロジェクトで、本家Luaと比べて高速に動作します。
弊社でもngx_luaを利用し始めた当初は本家Luaを使用していたのですが、LuaJITに切り替えた際には1.3倍以上のパフォーマンス向上となりました。実際のアプリケーションでの速度比較なため、速度低下の要因は様々あるにも関わらずLuaJITに変えただけで1.3倍高速になったのには驚きました。

ngx_luaでの利用ではありませんが、本家LuaとLuaJITでベンチマーク比較してみましょう。

フィボナッチ数列の40番目の数を求めるプログラムで比較してみます。このような処理だと上記したような実際のWebアプリケーションと違い、処理系のみの比較となります。

LuaJITが10倍の速度差で圧勝しました。

ngx.shared.DICTの利用

ngx.shared.DICTは、子プロセス間で共通に使える記憶領域です。

のような形で使えます。memcachedと似たような利用方法になりますが、特筆すべきはその速度です。memcachedとngx.shared.DICTを、ApacheBencheを使った比較です。

memcachedでSET/GETを30回づつ行った場合:400〜500req/sec
同様のSET/GETをngx.shared.DICTで行った場合: 1200〜1400/sec
何もしないでレスポンスだけ返す場合: 1300〜1400req/sec

ngx.shared.DICTを使った場合は、何もしない場合と変わらない速度で動作しました。直接メモリーを読み書きするため非常に高速です。弊社では、memcachedのキャッシュとして利用しています(キャッシュのキャッシュ)

ngx.varやngx.req.XXXを何度も呼ばない

ngx.varやngx.reqは、参照するたびに、ngx.var.全XXX、ngx.req.全XXXを展開する仕様であるらしく、参照のコストが高いです。

のように一度だけ参照して、以後はその変数(テーブル)を使いまわすようにします。

os.timeではなくngx.timeを使う

luaの標準ライブラリであるos.timeを使うと、使う度にシステムコールが発生してしまいますが、ngx.timeを使えばnginxがキャッシュした時間を返すため高速です。

今回はこの辺で。
ネタもなくなってきたので次回でLuaは最終回とさせて頂きます。

アドサーバ開発でのLuaの利用 連載(1)

Pocket

こんにちは。技術開発部で部長をしている伊良子と申します。

ユナイテッド株式会社(以下、弊社)では「高速・大量配信」を実現するための技術の一つとして、Lua言語やGo言語によるシステム開発を行っています。 サーバのスケールアウト(スケールアップ)のみで大量配信を実現するのではなく、同じサーバスペックでより高速に動作するプログラムを書くことは、売上に占めるサーバインフラの費用が高額になりがちな広告プラットフォームにとって非常に重要なことだと考えています。 当ブログでは、弊社のRTB広告プラットフォームであるAdStir及びBypassで、これらの言語がどのように使われているかについて連載していきます。 今回はLuaを採用した経緯、理由について書くことで、Luaの紹介をさせて頂きます。

Lua言語を採用するまでの経緯

Lua言語そのものの紹介はWikipediaにお任せするとして、弊社でこの言語が利用されるに至った経緯から書かせて頂きます。 弊社では、RTB広告を始める以前から、アドネットワーク、アフィリエイトネットワークなどの各種アドサーバを開発・運用していましたが、これら広告システムで培ってきた技術を元に、RTB広告プラットフォームであるAdStir及びBypassを開発し、2011年9月にリリース致しました。 その当時の各種アドサーバは、nginxの拡張モジュールをC言語で開発したものでした。C言語で書かれたアドサーバプログラムは非常に高速に動作し、信頼性の高いものでしたので、長らく弊社でのアドサーバ開発のスタンダードとなっていました。 しかし、

  • RTB広告プラットフォームに求められる機能が徐々に複雑化していくにつれ、C言語での追加開発は保守性が悪く、開発コストが増大してきたこと。
  • WEBプログラマーの集団であるエンジニアチームで、C言語は誰にでも扱える言語ではなかったこと。 などの理由から代替の言語を考えるようになり、 同じnginxの拡張モジュールであるngx_luaを試すに至りました。ngx_luaは、nginxのモジュールをLua言語で記述出来るようにする拡張モジュールです。 そして、2012年後半以降のアドサーバ開発ではngx_luaをアドサーバ開発のメインに採用するようになったのですが、その採用理由を下記する形で、そのメリットをご紹介したいと思います。

Luaを採用したポイントその一「高速に動作する」

当時実際にプロダクトとして動いていたC言語で書かれたnginx拡張モジュールと、まったく同じ機能をLuaモジュールで実装したものでベンチマークしてみた所、70%ほどの性能比でした。 単純に既存システムをリプレースした場合は、約1.5倍のインフラ費用がかかることになりますが、今後開発するプロダクトで採用する場合、新規開発コストやその後の保守コストも考えれば十分なパフォーマンスであると判断しました。 PHPで書いたプログラムと比較することで、どれくらいLua言語が高速かをご紹介します。

比較環境 SakuraVPS 2G(仮想3core) PHP: httpd-2.4 + php5_module + APC Lua: nginx-1.7.1 + LuaJIT + ngx_lua httpdはサーバスペックに合わせた設定を、nginxはworker数を4にだけ変更しました。 処理内容: memcachedからJSONをgetし、配列の要素(文字列)を連結して表示する ApacheBenchでlocalhost宛に計測 ( ab -c 100 -n 10000 ‘http://127.0.0.1:8000/test.php’ )

PHP

ベンチマーク結果
Lua Requests per second: 4127.80 [#/sec] (mean)
PHP Requests per second: 2430.98 [#/sec] (mean)

このような単純な処理でも、1.7倍ほどの速度差がつきました。 勿論、行う処理や設定、動作方法によって速度差は変わってくると思います。 (基本的にPerl使いなので、PHPに詳しくないため、動作方法やチューニングによってもっとPHPは高速になると思います)

Luaを採用したポイントその二「C言語でブリッジライブラリを書ける」

Lua言語は、非常に軽量な言語(例えば、splitもない)であるため、複雑な処理を実装するのが難しい場合があり、仮に実装したとしても、その高速性が失われるかもしれないという問題があります。それを解決する手段として「C言語でブリッジライブラリを書く」ことが出来ます。 弊社では元々C言語でアドサーバを書いていたため、既存のコードを流用出来るという点は大きなメリットでした。また、C言語で書いた部分は高速かつ省メモリで動作するため、元々高速なLuaをより高速に動作させることも可能となります(ボトルネックになっている処理をCライブラリ化するなど) 例えば、弊社でブリッジライブラリを書いている部分としては

  • OpenSSLライブラリを利用した暗号/複合処理
  • libmemcachedhiredisなどを利用したKVSの利用 などが挙げられます。 (サンプル) libuuidライブラリを利用して、UUIDを生成するコード

Luaからの利用方法

C言語が得意な人でもC言語のみであらゆる処理を実装するのは大変だと思いますが、改修が多い部分はLuaで開発し、要所要所をC言語で記述することで、保守性の高いプログラムを開発出来ると思います。 次回は、nginxで動作させる方法と、Luaをより高速に利用するためのテクニックをご紹介する予定です。

ダイナミックリターゲティングについて

Pocket

こんにちは。伊東です。

今回は弊社Bypassで使用されているダイナミックリターゲティングという広告手法についてご紹介します。

ダイナミックリターゲティングとは?
そもそもリターゲティング広告はサイト訪問の際にブラウザ内のクッキーを活用し、ユーザが訪れたことのあるサイトの広告を表示させる手法です。
ダイナミックリターゲティングはその表示する広告の中身を動的に(ダイナミックに)変更して表示します。
何を変更するかというと
①商品内容
②広告枠自体のデザイン(テンプレート)
です。

商品内容についてはどんな商品を見ていたのかというデータを基に解析し、その商品を閲覧した人たちがよく見ている(又は購入している)商品が表示されます。
広告枠自体のデザイン(テンプレート)が変わる要因としてはダイナミックリターゲティング広告を載せるサイトが指定している枠サイズであったり、各枠のデザインの違いによる効果(CTR,CVR)の違いがあり、これらの要因を加味して効果が高いテンプレートが採用されやすくなります。

item_different

template_select

タグ設置のメリット
ダイナミックリターゲティングを行うために以下のタグが用意されています。

  • サイトトップページ用
  • 商品詳細ページ用
  • カートページ用
  • CVページ用

この中で、設置が必須であるタグは商品詳細ページ用だけです。
その他のタグは必須ではないですが、設置した方が効果が良い傾向にあります。
例えばカートページ用タグを踏んだがCV(商品購入)せず、サイトから離脱した場合、買い忘れであったり、商品購入を迷ったとみなすことができるので、後一歩で買う可能性が高いと判断できます。
また、以前にCVしたユーザが商品詳細ページ用タグを踏んだ場合、その商品(もしくは類似商品)を買う可能性が、一度もCVしていないユーザと比較して高い(傾向にある)ため、以前CVしたユーザに積極的に広告表示を行うことができます。

おわりに
ダイナミックリターゲティングについてと、タグ設置の必要性について紹介しましたが、これによって少しでもダイナミックリターゲティングについての理解が深められれば幸いです。