アドサーバ開発」カテゴリーアーカイブ

lua-resty-httpを利用したCookieの操作

こんにちは。配信/インフラチームの佐々木です。

弊社では広告配信にLuaを利用しておりますが、LuaでCookieを扱う際lua-resty-httpというモジュールを利用すると、Cookieが複数の場合などに非常に便利です。 そこで今回はresty-httpを利用したCookieSyncの流れを、サンプルコードと共に説明したいと思います。 一言にCookieSyncと言っても色々な方式がありますが、今回はSSP(united.jp)側からDSP(example.net)のCookieを自ドメインで保存する形式で記載します。

resty-httpでCookieを付与する処理は上記のようなコードになります。 "http://example.net"というURLにアクセスする際、クライアントが保持しているCookieから該当するプレフィックス_example_が付いている物だけ抽出し、プレフィックスを取り除いた上でヘッダに付与してリクエストします。プレフィックスを入れる処理は後述します。

上記の処理では"http://example.net"から付与されたCookieをTableに保存しています。resty-httpの仕様として、SetされるCookieが単数の場合は文字列、複数の場合はTableとして扱われますので、両方のケースが想定される場合は上記のような処理になります。

CookieSyncをする場合、どのドメインのCookieなのか区別する必要がありますので、プレフィックスに_example_を付与した上で、ドメインを書き換えます。 こうして書き換えたCookieを、クライアントに保存します。 再度クライアントがアクセスした際は、一番上の処理に戻り、保存されたCookieを付与してリクエストをします。

今回はCookieの処理のみになりますので非常に短いコードになりましたが、lua-resty-httpは非常にシンプルで扱いやすく、LuaでAPIを実装する際には非常に有効なモジュールとなっております。

今回は以上となります。

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

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

先日の記事にもありますが、最近、弊社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)

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

ユナイテッドでのアドサーバ開発では、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でアドサーバを書くことが可能になります。

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

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

前回は、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)

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

ユナイテッド株式会社(以下、弊社)では「高速・大量配信」を実現するための技術の一つとして、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をより高速に利用するためのテクニックをご紹介する予定です。