fluentdとfluent-plugin-bigqueryを使ってBigQueryにデータを投入する場合の設定について。
BigQueryの制限
2015/04/16に制限の変更がありました(公式発表, 日本語のまとめ情報)。これにより、本記事の内容も影響を受けますので、新しい制限値を参考の上、適宜調整してください。
- 1行20KBまで
- 1リクエスト500行まで
- 1リクエスト1MBまで
- 1秒10,000行まで (申請により100,000まで)
- 1秒10MBまで
参照: https://cloud.google.com/bigquery/quota-policy#streaminginserts
1リクエスト1MBを10リクエストで10MB、1リクエスト500行を20リクエストで1万行。
このどちらかが1秒あたりのリクエスト数の上限となる。
例えばサーバのアクセスログであれば1レコードのサイズは小さいため、10MB制限より先に1万レコードの制限に達すると思われる。よって、秒間のリクエストが20を超えないようにしておくとよい。
バッファの設定
- buffer_chunk_records_limit
- buffer_chunk_limit
- buffer_queue_limit
fluent-plugin-bigqueryはチャンクを1リクエストとしてBigQueryのInsert APIを呼び出す。よって、BigQuery側の制限に違反しないようにプラグイン側を適切に設定する必要がある。
buffer_chunk_records_limit
チャンクごとの最大レコード数。
buffer_chunk_records_limit 500
BigQuery側で「1リクエスト500行まで」という制限があるので500とする。
500ちょうどにしておくとエラーになることがあるという情報があるので、300程度にしておくとよいかもしれない(コメント欄参照)。
buffer_chunk_limit
チャンクごとの最大バイト数。
buffer_chunk_limit 1000000
BigQuery側で「1リクエスト1MBまで」という制限があるので1000000とする。
1MBちょうどにしておくとエラーになることがあるという情報があるので、768k程度にしておくとよいかもしれない(コメント欄参照)。
buffer_queue_limit
プラグイン側でいくつのチャンクをメモリに保持しておくか設定できる。
デフォルト値は1024となっている。
小さすぎるとキューが溢れてエラーとなってしまうので、メモリ量と相談して大きめの値にしておくとよい。
1チャンクの最大サイズが1MBなので、デフォルトの1024だと最大1GBを使うことになる。ただ、buffer_chunk_records_limit
が500と小さいので、実際には1チャンクが1MBになるわけではない。
例えばnginxなどのアクセスログで1レコードが 0.2 - 0.4KB 程度になる場合だと、1チャンクは 0.4KB * 500 = 200KB
となる。1GBをバッファに使えるのであれば5000チャンクほどにできる。
送信の設定
- flush_interval
- try_flush_interval
- queued_chunk_flush_interval
- num_threads
flush_interval
バッファに溜まったレコードをflushする間隔。
デフォルト値は0.25となっている。
これはbuffer_chunk_records_limit
やbuffer_chunk_limit
に達していないバッファ中のレコードをflushする間隔となる。
高レートの場合はbuffer_chunk_records_limit
やbuffer_chunk_limit
の制限に先に達してflushされるので、この値の影響は小さい。
低レートの場合だと、これが送信の間隔になる。レコードをBigQuery側で即座にクエリしたい用途では短くする意味がある。
BigQueryは、リアルタイムにクエリというよりはバッチ処理の用途で用いられるため、1秒あるいはもっと長い値でも多くのケースで問題ない。
try_flush_interval
送信待ちとなったチャンクを、実際に送信する間隔。
言い換えると、送信待ちチャンクが存在するかどうかを確認する間隔。
デフォルト値は0.05となっている。
flush_interval
は「バッファに溜まったレコードを送信待ちに移す間隔」、try_flush_interval
は「送信待ちのチャンクの有無を確認する間隔」となる。
buffer_chunk_records_limit
やbuffer_chunk_limit
に達したチャンクは送信待ちになるが、これらのチャンクが送信されるまでに最大でtry_flush_interval
秒かかることになる。
ただ、実際にはBigQueryへのHTTP POSTのほうが時間がかかることで待たされるため、try_flush_interval
の影響を受けないケースも多い。
例えばBigQueryで0.5秒かかるとすると、その処理が終わった時はtry_flush_interval
の0.05秒はすでに過ぎているため、即座に次の送信が始まることになる。
queued_chunk_flush_interval
複数のチャンクが存在する場合に、あるチャンクの送信の後に次のチャンクの送信までの待ち時間。
デフォルト値は1となっている。
num_threads
バッファを処理するスレッド数。
デフォルト値は1となっている。
BigQueryのAPIの呼び出しは、外部サービスへのHTTP POSTのため一定の時間がかかる。このため複数のスレッドで実行すると効率がよい。
設定例
BigQueryにレコードを送信する量より、レコードを受信する量が多いと、バッファが溢れてしまうので、これが発生しないようにする。
ピーク時で秒間1000行が送られてくる場合を考えてみる。
1秒で1000行なので、1秒で2チャンクが溜まることになる。
HTTP POSTに0.5秒かかるとして、queued_chunk_flush_interval
がデフォルトの1のままだと、2チャンクの送信には2秒かかる。2チャンクだとHTTP POSTだけで1秒以上かかることもあるので、queued_chunk_flush_interval
が0でもバッファ溢れの可能性は残る。
そこでnum_threads
を2以上にする。
例えばnum_threads
が2で、2チャンクがキューに溜まっている場合、1スレッドが1チャンクを処理すれば0.5秒で送信できる。これであればバッファは溢れない。
BigQuery APIが遅い場合も考慮して、大きめの値にしておく。
buffer_chunk_records_limit 500 # BigQuery上限
buffer_chunk_limit 1000000 # BigQuery上限
buffer_queue_limit 5000 # 1GBくらい
flush_interval 1 # 暇な時間帯は1秒おき
try_flush_interval 0.05 # チャンクが溜まったら早めに送信
num_threads 4 # HTTP POSTが遅いので複数スレッド
queued_chunk_flush_interval 0.01 # チャンクが溜まった場合は待ち時間短めで連続送信
エラーとリトライ
buffer_chunk_records_limit
とbuffer_chunk_limit
をBigQuery側の制限を超えない値にしていても413 Request Too Large
が発生することがある(らしい)。
また、リクエストが正当であっても、一時的に500系のエラーも発生する。
fluentdのアウトプットプラグインはリトライ機能を備えているので、エラー時は送信がリトライされる。
insertId
リトライ時にレコードが重複してBigQueryに登録されるのを防ぐためにはinsertId
を使う。BigQueryはinsertId
を少なくとも1分間は覚えていて、ベストエフォートで重複を防いでくれる。
fluent-plugin-bigqueryはinsert_id_field
で指定されたフィールドをinsertId
としてBigQueryに送信するので、レコード中にユニークな値があるのであれば、そのフィールド名をinsert_id_field
に設定する。
nginxやApacheのアクセスログだと難しいが、アプリケーションからレコードを送信する場合など自分でコントロール出来るのであればinsertId
を入れるようにしておくと良い。