Openresty 10ms API
+ Elasticsearch
Me
-
System Group manager
-
Web Engineer 14y
-
App / Infra / Archi
-
PHP / Perl / Python
-
Docker / Ansible / Packer
Sonicmoov Co.,Ltd.
BtoB & BtcC
Service
Guile - 動画広告配信プラットフォーム
6 Billion req / month
Works
Tonariwa - LINE BC
今日話すこと
1. Openrestyでハイパフォーマンスを維持するための実装の勘所
2. Openresty から Elasticsearch までのデータ保存で工夫した
話さないこと
Elasticsearch
Openresty
ハイパフォーマンスを維持するための
実装の勘所
nginx.location.capture
location /foo {
content_by_lua_block {
local response = ngx.location.capture("/google");
}
}
location /google {
proxy_pass http://www.google.com/;
}Non-Blocking I/Oに対応したサブリクエスト用のAPIを利用
Nginx内部へのリクエストのみ飛ばせる
外部へのリクエストを飛ばすには、proxy_pass を通すことで外部へもリクエストが飛ばせるようになる
nginx.location.capture
location /foo {
content_by_lua_block {
local response = ngx.location.capture(
'/_external/http/',
{
method = ngx.HTTP_POST,
body = Cjson.encode(params),
vars = {
external_host = 'www.google.com',
external_uri = '/search',
}
}
)
}
}
location /_external/http/ {
set_if_empty $external_host '';
set_if_empty $external_uri '';
internal;
resolver 8.8.8.8;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_pass http://$external_host$external_uri;
}nginx.location.capture_multi
location /foo {
content_by_lua_block {
local res1, res2 = ngx.location.capture_multi(
{ "/google" },
{ "/google" }
);
}
}
location /google {
proxy_pass http://www.google.com/;
}Non-Blocking I/Oで並列リクエスト
lua-resty-redis
location /foo {
content_by_lua_block {
local _redis = redis:new()
_redis:set_timeout(10000)
...
_redis:set_keepalive(10000, 2000)
}
}Non-Blocking I/O な redis クライアント
keepalive重要
mset/hmset で極力通信回数を減らす
nginx.shared.DICT
lua_shared_dict cache 64m;
location /foo {
content_by_lua_block {
ngx.shared.cache:set(key, value, 5)
}
}
location /bar {
content_by_lua_block {
local value = ngx.shared.cache:get(key)
}
}Nginx worker共通のメモリ
Openresty から
Elasticsearch までの
データ保存で工夫した
Architecture アーキテクチャ
REST APIで取得したユーザデータをElasticsearchへ

Problem 課題
Openresty → Fluentd
lua-MessagePack

Problem 課題
Openresty → Fluentd
ネットワーク障害がおきたら?

Problem 課題
Openresty → Fluentd
ローカルにFluentdを!

Problem 課題
Openresty → Fluentd
プロセスがダウンしていたら?

Problem 課題
Openresty → Fluentd
io.open():write()

Problem 課題
Openresty → Fluentd
イベントループでのファイルロックは?

Problem 課題
Openresty → Fluentd
lua-resty-lock

Problem 課題
Openresty → Fluentd
デッドロックは?パフォーマンスは?

そこで
Nginx access_log
バッファリングによる効率的なファイル出力
だが、しかし..
access_log への API は提供されていない
リクエストログ残すには?
書込み用にLocationを定義して、
nginx.capture
でリクエスト投げよう!
http {
server {
# API
location = /v1/entrypoint {
content_by_lua_block {
local user = cjson.encode({
index_name = 'sonic', _id = uuid, username = name, time = ngx.time()
})
local res1, res2 = ngx.location.capture{
'/_user', { args = 'doc=' .. urlencode(user) }
}
}
}
# Elasticsearchへの書込み用
location /_user {
access_log /var/log/nginx/source.user.doc.index.log main;
}
}
}ログフォーマットはどうしよう
LTSV?
http {
log_format ltsv 'index_name:$arg_index_name\t'
'id:$arg_id\t'
'username:$arg_username\t'
'time:$arg_time';
server {
# API
location = /v1/entrypoint {
content_by_lua_file /path/to/entrypoint.lua;
}
# Elasticsearchへの書込み用
location /_user {
access_log /var/log/nginx/source.user.doc.index.log ltsv;
}
}
}フィールド固定
↓
フィールド追加
↓
Nginxの設定も変更
フィールド固定
↓
フィールド追加
↓
Nginxの設定も変更
X
変数にしよう!
log_format '$arg_doc'
あれ...
{"foo":"bar"}
↓
\x22foo\x22:\x22bar\x22
Nginx access_log
escaped
" → \x22
ならば!
urlencode
http {
log_format encoded_doc '$arg_doc';
server {
# API
location = /v1/entrypoint {
content_by_lua_block {
local user = cjson.encode({
index_name = 'sonic', _id = uuid, username = name, time = ngx.time()
})
local res1, res2 = ngx.location.capture{
'/_user', { args = 'doc=' .. urlencode(user) }
}
}
}
# Elasticsearchへの書込み用
location /_user {
access_log /var/log/nginx/source.user.doc.index.log encoded_doc;
}
}
}$ tail /var/log/nginx/source.user.doc.index.log
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22foo%22%2C%22time%22%3A1466611078%7D
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22bar%22%2C%22time%22%3A1466611078%7D
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22bar%22%2C%22time%22%3A1466611078%7D
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22bar%22%2C%22time%22%3A1466611078%7D
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22bar%22%2C%22time%22%3A1466611078%7D
%7B%22index_name%22%3A%22sonic%22%2C%22username%22%3A%22bar%22%2C%22time%22%3A1466611078%7DFluentd on AppServer
<source> # format none で生データを取得
type tail
path /var/log/nginx/source.user.doc.index.log
pos_file /var/log/td-agent/source.user.doc.index.log.pos
tag source.user.doc.index
format none
message_key doc
</source>
<match source.**> # URIデコードしてJSONテキストに変換
type uri_decode
key_name doc
add_prefix decoded
</match>
<match decoded.**> # JSONデータ「doc.」プレフィックス付きで fluentd のフィールドとして展開
type forest
subtype parser
remove_prefix decoded
<template>
tag parsed.__TAG__
format json
key_name doc
inject_key_prefix doc.
</template>
</match>
Fluentd on AppServer
<match parsed.**> # インデックス名をタグに追加
type rewrite_tag_filter
remove_tag_prefix parsed
rewriterule1 doc.index_name ^(.+)$ rewrited.${tag}.$1
</match>
<match rewrited.**> # 加工するために付けていた「doc.」プレフィックスを削除
type rename_key
remove_tag_prefix rewrited
add_prefix renamed
rename_rule1 ^doc.(.+) ${md[1]}
deep_rename false
</match>
<match renamed.**> # LogCollector へ送る
type copy
<store>
type forward
<server>
name LogCollector
host xxx.xxx.xxx.xxx
port 24224
</server>
</store>
</match>Fluentd on LogCollector
<source>
type forward
port 24224
</source>
<match renamed.source.user.doc.index.**> # 不要なメタ情報を削除してElasticsearchへ投げる
type copy
<store>
@type elasticsearch_dynamic
@id elasticsearch:user:index
hosts 127.0.0.1:9200
index_name ${tag_parts[5]}
type_name user
write_operation upsert
id_key _id
remove_keys _id, index_name
</store>
</match>
Benchmark 計測
-
jmeter
-
jmeter-ruby
2000 req/sec
thx
Openresty 10ms API + Elasticsearch
By Yuji Iwai
Openresty 10ms API + Elasticsearch
- 2,192