こんにちは。技術開発部で部長をしている伊良子と申します。
ユナイテッド株式会社(以下、弊社)では「高速・大量配信」を実現するための技術の一つとして、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' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
local memcached = require "resty.memcached" local json = require "cjson" local memc, memd_err = memcached:new( {key_transform = { ngx.unescape_uri, ngx.escape_uri } } ) if not memc then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end memc:set_timeout(40) local memc_ok, memc_err = memc:connect('127.0.0.1', 11211) if not memc_ok then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end local memcached_key = 'TESTKEY' local json_str, flags, memc_err = memc:get(memcached_key) if not json_str or not type(json_str) == 'string' then ngx.exit(ngx.HTTP_NOT_FOUND) end local flag, responce_data = pcall(json.decode, json_str) if not flag then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if not responce_data or not responce_data.content then ngx.exit(ngx.HTTP_NOT_FOUND) end local content = "" for i, val in ipairs(responce_data.content) do content = content .. val end ngx.print(content) ngx.exit(ngx.HTTP_OK) |
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php $memcache = new Memcached(); $memcache->addServer("localhost", 11211); $memcached_key = 'TESTKEY'; $json_str = $memcache->get($memcached_key); $responce_data = json_decode($json_str); $content = ""; foreach ($responce_data->{'content'} as $value) { $content .= $value; } print $content; ?> |
ベンチマーク結果
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ライブラリを利用した暗号/複合処理
- libmemcachedやhiredisなどを利用したKVSの利用 などが挙げられます。 (サンプル) libuuidライブラリを利用して、UUIDを生成するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <lua.h> #include <lualib.h> #include <lauxlib.h> #include <uuid/uuid.h> static int luuid(lua_State *L) { uuid_t u; char buf[37]; uuid_generate_random(u); uuid_unparse(u, buf); lua_pushstring(L, buf); return 1; } static const struct luaL_Reg luuidlib[] = { {"uuid", luuid}, {NULL, NULL}, }; int luaopen_luuid(lua_State *L) { luaL_openlib(L, "luuid", luuidlib, 0); return 1; } |
Luaからの利用方法
1 2 3 4 |
local luuid = require "luuid" local uuid = luuid.uuid() print(uuid) |
C言語が得意な人でもC言語のみであらゆる処理を実装するのは大変だと思いますが、改修が多い部分はLuaで開発し、要所要所をC言語で記述することで、保守性の高いプログラムを開発出来ると思います。 次回は、nginxで動作させる方法と、Luaをより高速に利用するためのテクニックをご紹介する予定です。