リトライを入れて帰宅したら、翌朝キューが詰まっていた
目標
Lokiが入ってくるリクエストを拒否する4つの理由を自分で作ってみて、400と429を別々に扱うクライアントを作り、1行も失わずに保存します。
なぜ重要なのか
書き込み側の拒否は2種類です。ラベルの文法・ラベルの個数・行の長さの違反は400で、このような行は、何度送り直しても永遠に拒否されます。ストリームあたりの速度超過は429で、こちらは、待ってから再送すれば成功します。両者を同じコードで扱うと、片方はキューを埋め、もう片方はデータを失います。レート制限がストリーム単位であることも重要です。カーディナリティを減らそうとしてラベルをまとめると、1つのストリームにトラフィックが集中して429が出て、429を避けようとして分けると、ストリーム数が増えます。2つの力が反対に引っ張る場所で、バランスを取ることが、このコースが教えようとしている判断です。
ステップ
/root/lk-ingest-limitsでLokiを起動し、date +%sを/root/lk-ingest-limits/anchor.txtに書いてください。そしてhttp://localhost:3100/configから5つの上限を読み取って、/root/lk-ingest-limits/limits.tsvに、ヘッダーなしで5行、タブで区切った2つの欄<설정이름><탭><값>(プレースホルダーは設定名、タブ、値です)で書きます。順序はper_stream_rate_limit、per_stream_rate_limit_burst、max_line_size、max_label_names_per_series、unordered_writesです。- 4種類のpushを投げて、ステータスコードを
/root/lk-ingest-limits/labels.tsvに、ヘッダーなしで4行、タブで区切った2つの欄<이름><탭><코드>(プレースホルダーは名前、タブ、コードです)で書いてください。名前と内容は順に、ok(ラベルapp="probe")、digit(ラベル名が1bad)、dash(ラベル名がbad-name)、toomany(ラベル16個、名前はl0からl15)です。タイムスタンプはすべて基準時刻を使い、本文は何か短い文字列で構いません。 - ステップ1の表の
max_line_sizeを超える行を1つ作って送ってください。コマンドラインでは渡せないので、本体をファイルにして--data-binary @파일(プレースホルダーはファイル名です)で送ります。結果を/root/lk-ingest-limits/03-size.txtに3行で書いてください。code=<정수>、limit_bytes=<본문에 적힌 한도 바이트>、sent_bytes=<보낸 줄의 길이>です(プレースホルダーは順に、整数、本文に書かれた上限のバイト数、送った行の長さです)。 /root/lk-ingest-limits/loki-low.yamlを書いて、3300番ポート(gRPC 9097)にLokiをもう1つ起動してください。保存パスは/tmp/lokilowの下で、limits_config.per_stream_rate_limitは4KB、per_stream_rate_limit_burstは8KBです。そのサーバーの/configから2つの値を読み取って、/root/lk-ingest-limits/04-low.txtにrate=<값>とburst=<값>の2行で書きます(プレースホルダーは値です)。- 3300番ポートのLokiに、900バイトの行を1つのストリーム(
{"app":"flood"})で連続して送り、429を受け取ってください。/root/lk-ingest-limits/05-429.txtに2行を書きます。first_429=<처음 429 가 난 순번, 1부터>とlimit_in_body=<본문에 적힌 한도 문자열>です(プレースホルダーは順に、最初に429が出た順番(1から)と、本文に書かれた上限の文字列です)。そして、429レスポンスの本文を丸ごと/root/lk-ingest-limits/429.bodyに保存してください。 - ステップ1・4で読んだ2つの値と、ステップ5の順番で、バーストを説明してください。
/root/lk-ingest-limits/06-burst.txtに4行を書きます。rate_bytes=<초당 허용 바이트 정수>、burst_bytes=<버스트 바이트 정수>、sent_bytes=<429 가 나기까지 보낸 본문 바이트 합>、why=<공백 뺀 40자 이상 한 문장>です(プレースホルダーは順に、毎秒の許容バイト数(整数)、バーストのバイト数(整数)、429が出るまでに送った本文のバイト数の合計、空白を除いて40文字以上の1文です)。 /root/lk-ingest-limits/send.shを作成して、{"app":"keep"}ストリームに、900バイトの行20個を3300番ポートに送り、429を受けたら指数バックオフで再試行して、すべて保存されるようにしてください。タイムスタンプは、基準時刻から過去20秒の間に、1行につき1秒ずつ使います。そして/root/lk-ingest-limits/07-send.txtに、sent=20、stored=<저장된 줄 수>、retries=<재시도 횟수>の3行を書いてください(プレースホルダーは保存された行数と、リトライ回数です)。保存された行数は、クエリして確認します。- 毎秒12,000バイトを出力するサービスがあるとして、ステップ4の上限の下に収まるには、ストリームを最低いくつに分ける必要があるかを計算して、
/root/lk-ingest-limits/08-design.txtに4行で書いてください。throughput_bytes=12000、limit_bytes=<4단계 한도의 바이트>、streams_needed=<올림한 정수>、tradeoff=<공백 뺀 60자 이상>です(プレースホルダーは順に、ステップ4の上限のバイト数、切り上げた整数、空白を除いて60文字以上の文です)。最後の行には、ストリームを増やすことの代償を一緒に書きます。
参考
- 作業ディレクトリは
/root/lk-ingest-limitsです。デフォルトの上限のLokiはステップ1で、低い上限の2つ目はステップ4で起動します。 - 非常に長い行は、コマンドライン引数では渡せません(
Argument list too long)。本体のJSONをファイルとして作り、--data-binary @파일(プレースホルダーはファイル名です)で送ってください。 - 同じストリームに、同じタイムスタンプで同じ内容を送ると、重複としてまとめられます。複数の行を送るときは、タイムスタンプを1つずつ変えてください。
- 2つ目のLokiは、
http_listen_portだけを変えると、gRPCが重なって落ちます。保存パスも重なってはいけません。 - よくある間違い: 400にリトライをかけてしまいます。直せる拒否と、待つべき拒否を、コードで分ける必要があります。
- リクエスト検証とレート制限・設定ドキュメント・HTTP API・ラベル
サーバーが持っている上限を読む
/root/lk-ingest-limitsでLokiを起動し、date +%sを/root/lk-ingest-limits/anchor.txtに書いてください。そしてhttp://localhost:3100/configから5つの上限を読み取って、/root/lk-ingest-limits/limits.tsvに、ヘッダーなしで5行、タブで区切った2つの欄<설정이름><탭><값>(プレースホルダーは設定名、タブ、値です)で書きます。順序はper_stream_rate_limit、per_stream_rate_limit_burst、max_line_size、max_label_names_per_series、unordered_writesです。
すべてlimits_configブロックにあります。値は、サーバーが出力した文字列のまま書いてください。3MBのように単位が付いて出ることも、数字だけが出ることもあります。この5つが、後のステップですべて使われます。
ラベル名のルールを破ると400になる
4種類のpushを投げて、ステータスコードを/root/lk-ingest-limits/labels.tsvに、ヘッダーなしで4行、タブで区切った2つの欄<이름><탭><코드>(プレースホルダーは名前、タブ、コードです)で書いてください。名前と内容は順に、ok(ラベルapp="probe")、digit(ラベル名が1bad)、dash(ラベル名がbad-name)、toomany(ラベル16個、名前はl0からl15)です。タイムスタンプはすべて基準時刻を使い、本文は何か短い文字列で構いません。
curl -o /dev/null -w '%{http_code}'で、コードだけを取り出せます。400の本文を一度は自分で読んでください。どのルールを破ったかを、位置まで教えてくれます。ラベルの個数の上限は、ステップ1の表にあります。
行が1つでも長すぎると、リクエスト全体が落ちる
ステップ1の表のmax_line_sizeを超える行を1つ作って送ってください。コマンドラインでは渡せないので、本体をファイルにして--data-binary @파일(プレースホルダーはファイル名です)で送ります。結果を/root/lk-ingest-limits/03-size.txtに3行で書いてください。code=<정수>、limit_bytes=<본문에 적힌 한도 바이트>、sent_bytes=<보낸 줄의 길이>です(プレースホルダーは順に、整数、本文に書かれた上限のバイト数、送った行の長さです)。
/opt/lab/d5/gen.pyとは別に、本体のJSONを作る小さなPythonを自分で書いてもかまいません。400の本文に、上限と送った長さが、バイトで並んで書かれて出てきます。この拒否が危険な理由は、同じリクエストに載っていたまともな行まで、一緒に落ちるからです。
低い上限をかけた2つ目のLokiを起動する
/root/lk-ingest-limits/loki-low.yamlを書いて、3300番ポート(gRPC 9097)にLokiをもう1つ起動してください。保存パスは/tmp/lokilowの下で、limits_config.per_stream_rate_limitは4KB、per_stream_rate_limit_burstは8KBです。そのサーバーの/configから2つの値を読み取って、/root/lk-ingest-limits/04-low.txtにrate=<값>とburst=<값>の2行で書きます(プレースホルダーは値です)。
HTTPポートだけを変えると、gRPCが重なって落ちます。ログファイルの最後の行に、そのまま書かれて出てきます。保存パスも、1つ目と重なってはいけません。デフォルトの上限(3MB/秒)では、このラボで429を作りにくいので、わざと低くします。
本物の429を受け取ってみる
3300番ポートのLokiに、900バイトの行を1つのストリーム({"app":"flood"})で連続して送り、429を受け取ってください。/root/lk-ingest-limits/05-429.txtに2行を書きます。first_429=<처음 429 가 난 순번, 1부터>とlimit_in_body=<본문에 적힌 한도 문자열>です(プレースホルダーは順に、最初に429が出た順番(1から)と、本文に書かれた上限の文字列です)。そして、429レスポンスの本文を丸ごと/root/lk-ingest-limits/429.bodyに保存してください。
タイムスタンプは毎回異なる必要があります(同じタイムスタンプ・同じ内容は、重複としてまとめられます)。本文には、どのストリームがどの上限を超えたかが、文章で書かれて出てきます。何回目で出るかは、バーストのために、一度ではわかりにくいです。それが次のステップです。
最初の数回は、なぜ通ったのか
ステップ1・4で読んだ2つの値と、ステップ5の順番で、バーストを説明してください。/root/lk-ingest-limits/06-burst.txtに4行を書きます。rate_bytes=<초당 허용 바이트 정수>、burst_bytes=<버스트 바이트 정수>、sent_bytes=<429 가 나기까지 보낸 본문 바이트 합>、why=<공백 뺀 40자 이상 한 문장>です(プレースホルダーは順に、毎秒の許容バイト数(整数)、バーストのバイト数(整数)、429が出るまでに送った本文のバイト数の合計、空白を除いて40文字以上の1文です)。
4KBは4096バイトです。送ったバイト数の合計は、行の長さ×順番で見積もります(ヘッダーとラベルは除き、本文だけを数えます)。バーストがあると、静かだったあと一気に押し寄せるトラフィックが、最初のしばらくは通過することを、数字で説明してください。
応用①: バックオフリトライで1行も失わない
/root/lk-ingest-limits/send.shを作成して、{"app":"keep"}ストリームに、900バイトの行20個を3300番ポートに送り、429を受けたら指数バックオフで再試行して、すべて保存されるようにしてください。タイムスタンプは、基準時刻から過去20秒の間に、1行につき1秒ずつ使います。そして/root/lk-ingest-limits/07-send.txtに、sent=20、stored=<저장된 줄 수>、retries=<재시도 횟수>の3行を書いてください(プレースホルダーは保存された行数と、リトライ回数です)。保存された行数は、クエリして確認します。
バックオフは、失敗するたびに待つ時間を延ばすことです(例: 1秒、2秒、4秒)。上限を設けて、永遠に回らないようにしてください。400を429と取り違えたときの安全装置です。保存の確認は、そのストリームを基準時刻の前後でクエリして、行数を数えれば済みます。
応用②: 上限の下に収まる設計
毎秒12,000バイトを出力するサービスがあるとして、ステップ4の上限の下に収まるには、ストリームを最低いくつに分ける必要があるかを計算して、/root/lk-ingest-limits/08-design.txtに4行で書いてください。throughput_bytes=12000、limit_bytes=<4단계 한도의 바이트>、streams_needed=<올림한 정수>、tradeoff=<공백 뺀 60자 이상>です(プレースホルダーは順に、ステップ4の上限のバイト数、切り上げた整数、空白を除いて60文字以上の文です)。最後の行には、ストリームを増やすことの代償を一緒に書きます。
レート制限はストリーム単位なので、分ければ、上限がその分増えます。しかし、ストリームを増やすことは、ラベルの組み合わせを増やすことで、そちらにはカーディナリティのコストがあります。2つの力が互いに反対に引っ張ることを、最後の行に書いてください。