制御と限界
ゲートウェイが存在する理由は「開く」ではなく、**「開いても傷つかない」**ことです。このページでは、何がブロックされ、何が制限され、何が記録されるのかを、値とともに説明します。
スキーマを変更する文はブロックします
最初のトークンが以下の6つのいずれかであれば、400 QUERY_SYNTAX です。
DROP · TRUNCATE · ALTER · CREATE · GRANT · REVOKE
{"result":"ERROR","error_code":"QUERY_SYNTAX",
"message":"DDL statements are not allowed: DROP"}
コメントで回避することはできません。 ブロックコメントと行コメントを先に取り除いてから、最初のトークンを確認します。
/* 안녕 */ DROP TABLE pp.mm_tag -- 막힙니다
-- 한 줄
DROP TABLE pp.mm_tag -- 막힙니다
コメントを取り除いた結果、何も残らない場合も、これも400です。
セミコロンがあれば拒否します
{"result":"ERROR","error_code":"QUERY_SYNTAX",
"message":"Multiple statements are not allowed"}
最初のトークンだけを検査する方法は、1つのリクエストに文が1つだけの場合にのみ成立します。 SELECT 1; DROP TABLE x を許可した場合、最初のトークンは SELECT となり、検査をそのまま通過します。そのため、セミコロンが見えたら、内容を見る前に拒否します。
結果行の上限
| 設定 | デフォルト値 |
|---|---|
data.gateway.query.max.rows | 100,000 (0 = 無制限) |
上限に達すると、ゲートウェイはそこで読み取りを止めます — 全体を最後まで読み込んでから切り捨てるのではありません。そのため、巨大な結果でも無限ストリームは発生しません。
POST /api/v1/query→ レスポンスのtruncatedフィールドPOST /api/v1/query/stream→end行のtruncatedフィールド
サーバーログにも記録されます。
Query result truncated to max_rows=[100000], lang=[PQL]
HTTPは200で、行も正常です。 truncated を見ないと、切り詰められた結果を全体だと思い込んだまま集計してしまいます。 それがこのフラグを常に載せる理由です。
レートリミット
| 設定 | デフォルト値 | 内容 |
|---|---|---|
data.gateway.rate.limit | 1000 | クライアントごとの1秒あたりのリクエスト数 |
data.gateway.rate.limit.global | 0 (無制限) | 全クライアント合算の上限 |
data.gateway.trusted.proxies | 空 | X-Forwarded-For を信頼する直接のpeerリスト |
バケットキーは信頼できるクライアントIPです。
X-Forwarded-For はデフォルトで無視されます直接のpeerが trusted.proxies にある場合のみ、XFFの最初のホップをクライアントIPとして信頼します。空のままにすると、常に実際の接続アドレスを使用します — そうしないと、ヘッダーを1つ偽造して制限を回避できます。
リバースプロキシの背後に置いた場合、そのプロキシのアドレスを必ずこのリストに追加してください。追加しないと、全リクエストがプロキシ1つのバケットに集中します。
グローバル上限は「バケットの急増」に対する防御です — 異なるIPをどれだけ作っても、全体の処理量はこの値を超えません。
/admin/* も同じ制限を「共有」します/api/* と /admin/* は同じカウンターを使用します。別々にすると、グローバル上限が実質的に2倍になり、負荷を2つの入口に分散して回避できます。
リクエストサイズ
| 設定 | デフォルト値 |
|---|---|
data.gateway.max.body.bytes | 1,048,576 (1 MB) |
data.gateway.max.params.bytes | 65,536 (64 KB) |
超過すると413で早期拒否します — 読み込んだ後ではなく、読み込む前に切断します。
タイムアウトとスローログ
| 設定 | デフォルト値 | 内容 |
|---|---|---|
data.gateway.async.timeout.ms | 60,000 | 非同期リクエストのタイムアウト |
data.gateway.slow.query.ms | 10,000 | これより遅い読み取りは警告 |
data.gateway.slow.update.ms | 2,000 | これより遅い書き込みは警告 |
リクエストは仮想スレッドで処理します。同時実行の上限は data.gateway.async.concurrency.limit で、デフォルト値は256です(0 以下なら無制限)。上限に達すると、送信側は空きが出るまで待機します — 破棄せずにバックプレッシャーをかけます。起動ログが実際の値を出力します。
Async executor: virtual threads, concurrency limit=[256]
以前の固定プールは、キューが500件溜まるまでスレッドを20個以上増やさず、実質的な同時クエリ数は20でした。そして、キューで待機していたリクエストが60秒のタイムアウトにかかり、503が発生していました。実際のボトルネックはDBコネクションプールなので、現在はプールサイズを調整せず、同時実行の上限1つだけを設定しています。
クエリログ — すべてのリクエストが残ります
| 項目 | 値 |
|---|---|
| テーブル | tm_data_gateway_log (Aster TSDB, timeuuid PK) |
| 残るもの | 言語 · クエリ文 · 行数 · 実行時間 · 失敗の有無 · エラーメッセージ |
| 書き込み | キューに入れてバッチでフラッシュ — クエリ経路をブロックしません |
キューの状態は GET /admin/metrics の gateway.querylog.* ゲージが示します。
/api/health はlivenessではなくreadinessですコンテキストが生きているというだけでUPを報告するのは無意味です — クエリを受け付ける準備ができたときだけUPです。チェックセット(database · inmemory · query_log)がすべて真である必要があり、そうでなければ STARTING または DEGRADED です。クエリログが初期化されていないゲートウェイはUPではありません — 監査記録が抜けたまま「正常」に見えると、後で「誰が何を照会したか」に答えられなくなります。
認証 · コンソールアカウント
- 認証はSpring Securityチェーン一つが決定します。リストにないパスは401であり、匿名ではありません。
- セッションもクッキーも作成しません(STATELESS)。資格情報はリクエストごとにヘッダーで送られます。
- コンソールのログインアカウント(
data.gateway.web.user·data.gateway.web.password)は、両方とも入力されている必要があります。片方でも空だと、画面が「設定されていません」と表示し、503LOGIN_NOT_CONFIGUREDですべて拒否します。
デフォルトアカウントを入れると、すべての顧客インストールに同じ資格情報が含まれ、誰も変更しません。 値はレンダリングされる設定(plantpulse-datalake-cli のテンプレート)に入れてください。
ユーザー名とパスワードは常に両方とも比較し(片方だけ間違っていてももう片方をスキップしません)、失敗レスポンスはどちらが間違っているかは示しません。
値を変更するには
正本は plantpulse-datalake-cli の config/templates/ です。コンテナ内で修正したものは、再起動で消えます。
docker exec plantpulse-datalake pd config diff --templates
docker exec plantpulse-datalake pd restart data-gateway
手順は設定変更方法、キーローテーションはパスワード · APIキー変更を参照してください。