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%7D

Fluentd 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

  • 1,954