0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ClickHouse 26.8 の CREATE HANDLER だけで HTTP API を作ってみた

0
Posted at

1. はじめに

ClickHouse が 2026 年 9 月 5 日に公開したブログ「ClickHouse as a streaming HTTP API」1は、26.8 で追加された CREATE HANDLER 文を使って、アプリケーションサーバーを置かずに ClickHouse そのものを HTTP API にする手順を示しています。ブログの主張を要約すると、認可ロジックや業務ロジックを持たない API なら「クエリを SQL で固定した HTTP エンドポイントを DB 側に置けば、間のアプリケーション層を 1 つ減らせる」というものです。この文で作る名前付きの HTTP ハンドラーを、以下では handler と書きます。

同じ構成の層が、6 月に書いた 3D グローブの記事2にありました。前作では Cloud Run 上の Express が /api/towers を受け、クエリ文字列を SQL のパラメータとして渡し、返ってきた JSON を別の構造に変換して返していました。中身は変換だけの層です。ブログが減らせるとしているのはこの層です。

そこで、同じ cell_towers データと同じクエリで、Express 経由と handler 直接の 2 経路を並べてみました。行数が一致するか、どれだけ速くなるか、そして DB が直接 HTTP を受けるときに権限と監査証跡がどう見えるかの 3 点を確かめます。

ClickHouse Cloud で SELECT version() を実行したところ、この記事の執筆時点では 26.4.1 が返り、26.8 の CREATE HANDLER はまだ使えませんでした。検証はすべてローカルの Docker(26.8.2.7)で行っています。

1.1. 結論(先出し)

  • Express の /api/towers(集約モード)は CREATE HANDLER 1 文で置き換えられ、解像度 2〜6 のすべてで返却行数が一致した
  • 同じマシン・同じクエリで、解像度 6(返却 30 万行・23.8 MB)の完了時間は Express 経由 0.738 秒に対し handler 直接 0.143 秒。サーバー側の読み取り行数と結果バイト数は両経路で同一なので、差は Express 側の JSON 解析と再シリアライズによると考えられる
  • filter / sort / page などの URL パラメータは handler のクエリ結果に対する後処理で、サーバーの読み取り行数を減らさない
  • 認可は handler オブジェクトではなく、handler が実行する SQL に対する通常の GRANT で決まる。元の表の SELECT を持たないユーザーは SQL SECURITY DEFINER のビュー経由の handler だけ呼べ、query_log には呼び出しユーザー名義で記録される
  • handler が受け持たないのは静的ファイル配信・CORS・エンドユーザーの識別で、前作の Cloud Run が受け持つのはこの 3 つだけになる

1.2. 検証ゴール

# 確かめること 確認できれば OK の条件
1 Express の /api/towersCREATE HANDLER だけで置き換えられるか 同じ JSON 構造の行が返り、解像度 2〜6 の返却行数とサーバー側の読み取り行数が Express 経由と一致する
2 大きい応答で Express 経由と handler 直接のレイテンシ・転送量がどう違うか 同一マシンで複数回測り、先頭バイト到達と完了の時間、バイト数を経路ごとに表にできる
3 handler を呼ぶ専用ユーザーの権限と監査証跡がどう見えるか 元の表の SELECT を持たないユーザーがビュー経由の handler は呼べ、ビュー外の列や元の表は読めない。query_log に呼び出しユーザーと経路が記録される

2. 検証環境

項目 内容
マシン Windows 11 Pro、Docker Desktop
ClickHouse clickhouse/clickhouse-server:26.8.2.7(コンテナ 1 台、HTTP ポート 8124)
比較対象 Node.js v24.12.0 + Express + @clickhouse/client 1.23.1(前作の server.js を接続先だけ変えて起動)
データ cell_towers 43,276,150 行3、H3(六角形グリッド)で事前集約した表 cell_towers_h3 1,852,135 行(解像度 2〜6)
計測 curl の time_starttransfer / time_total / size_downloadsystem.query_log

cell_towers_h3 は前作と同じ SQL で作った集約表で、解像度ごとの行数は 2: 5,477 / 3: 23,834 / 4: 100,624 / 5: 389,259 / 6: 1,332,941 です。Express と handler の両方がこの表を読みます。計測はすべて localhost で、ネットワークの往復は測っていません。測るのは「間に Express があるかどうか」の差だけです。

Docker の ClickHouse にホストから接続するときに 1 点つまずきました。イメージが生成する default-user.xmldefault ユーザーの接続元を 127.0.0.1::1 に絞っており、ポート転送越しのアクセスは Code: 194 REQUIRED_PASSWORD で 401 になります。users.d/<networks><ip>::/0</ip></networks> を書いた XML を置いて解消しました。ただしファイル名はアルファベット順で default-user.xml より後(zzz-allow-network.xml)にする必要があります。先だと default-user.xml に上書きされます。


3. 何を置き換えるか

前作の Express の役割は 3 つでした。/api/towers でクエリ文字列を受けて ClickHouse に問い合わせる役割、返ってきた JSON を { mode, res, radios, stats, data } という構造に変換する役割、そしてビルド済みのフロントエンド(React + deck.gl)を静的ファイルとして配る役割です。

// 前作 server.js の中核部分(抜粋)
const sql =
  'SELECT h3ToString(h3) AS h3, sum(cnt) AS cnt,' +
  ' any(center_lon) AS lon, any(center_lat) AS lat' +
  ' FROM default.cell_towers_h3' +
  ' WHERE h3_res = {res:UInt8} AND radio IN {radios:Array(String)}' +
  ' GROUP BY h3 ORDER BY cnt DESC LIMIT {limit:UInt32}';

const rs = await client.query({ query: sql, query_params, format: 'JSON' });
const json = await rs.json();
res.json({ mode: 'agg', res: resolution, radios, stats: {...}, data: json.data });

CREATE HANDLER で置き換えられるのは最初の 1 つです。SQL は型付きプレースホルダのまま handler に移せます。2 つ目の JSON の変換は、フロントエンドが data 配列しか使っていないので、handler の出力を直接読むように変えれば不要になります。3 つ目の静的配信は handler ではできないので、配信の仕組みは別に必要です。

Express あり/なしの構成対比

図の左が前作、右が今回の構成です。右では Cloud Run が静的配信だけになり、ブラウザは /api/towers を ClickHouse に直接問い合わせます。


4. CREATE HANDLER で作る

4.1. handler の定義

前作の SQL をそのまま AS の後ろに置きます。クエリ文字列で受ける版と、URL パスから解像度を受ける版の 2 つを作りました。

-- クエリ文字列版: /api/towers?res=3&radios=['LTE','UMTS']&limit=300000
CREATE HANDLER towers URL '/api/towers' METHODS (GET) AS
SELECT h3ToString(h3) AS h3, sum(cnt) AS cnt, any(center_lon) AS lon, any(center_lat) AS lat
FROM default.cell_towers_h3
WHERE h3_res = {res:UInt8} AND radio IN {radios:Array(String)}
GROUP BY h3 ORDER BY cnt DESC LIMIT {limit:UInt32};

-- パス版: /api/towers/3
CREATE HANDLER towers_path URL REGEXP '/api/towers/(?P<res>[0-9]+)' AS
SELECT h3ToString(h3) AS h3, sum(cnt) AS cnt, any(center_lon) AS lon, any(center_lat) AS lat
FROM default.cell_towers_h3
WHERE h3_res = {res:UInt8}
GROUP BY h3 ORDER BY cnt DESC LIMIT 300000;

プレースホルダの {res:UInt8} には、クエリ文字列の res=3 と、正規表現の名前付きグループ (?P<res>...) のどちらからでも値が入ります。Array(String) の引数も、クエリ文字列に radios=['LTE','UMTS'] と書けばそのまま受け付けられました。curl から渡すときは -g を付けて角括弧のグロブ展開を止めるか、%5B %5D に URL エンコードします。

定義した handler は system.handlers に登録されます4。列は name / url_match_type(exact / prefix / regexp)/ url / methods / query / create_query です。DROP HANDLERALTER HANDLER(URL だけ付け替え)もその場で反映されました。

ブログには http_allow_path_requests を有効にしてサーバーを再起動する手順があります。この設定は system.server_settings の説明文によれば、URL パスでテーブルを直接指す別機能(/my_db/my_table.csv のような呼び出し)を HTTP インターフェースに通すためのものです。今回は http_allow_path_requests も、それに続く 4 つのユーザー設定(http_allow_database_as_path / http_allow_table_as_file / http_allow_filters_as_path / http_allow_filters_as_unrecognized_url_parameters)もデフォルトの 0 のまま、CREATE HANDLER と後述の filter / sort / page が動きました。named handler にはこれらの設定は不要でした。

4.2. Express と同じ行が返るか

解像度 2〜6 について、Express の data 配列の要素数と、handler が返した行数を並べました。handler 側はデフォルトの TSV で受け、行数を数えています。

解像度 Express(data 要素数) handler クエリ文字列版 handler パス版 サーバー読み取り行数
2 1,922 1,922 1,922 16,384
3 9,062 9,062 9,062 40,960
4 40,811 40,811 40,811 106,496
5 171,604 171,604 171,604 409,600
6 300,000 300,000 300,000 1,344,231

解像度 6 は前作と同じ LIMIT 300000 の上限に達しています。サーバー側の読み取り行数(system.query_logread_rows)も 3 経路で同じ値でした。

query_log には 26.8 で http_handler_name 列が増えており4、Express 経由の行は空、handler 経由の行は towers / towers_path が入ります。http_user_agent も Express 経由は clickhouse-js/1.23.1、直接は curl/8.2.1 と分かれるので、経路の突き合わせはこの 2 列で足りました。


5. 速さと形式

5.1. 経路ごとのレイテンシ

同じマシンで、解像度ごとに 6 経路を順番に 1 回ずつ呼ぶのを 1 ラウンドとして 5 ラウンド実行し、中央値を取りました。ラウンド前に各 URL を 1 回ウォームアップしています。同じホスト上の別コンテナ(Langfuse)は計測中だけ止めました。表の SSE は Server-Sent Events で、5.3 章で扱います。

解像度 Express(gzip なし) Express(gzip) handler JSONEachRow handler JSONEachRow + gzip handler ArrowStream handler SSE
2 0.030 秒 0.026 秒 0.017 秒 0.014 秒 0.008 秒 0.014 秒
3 0.047 秒 0.092 秒 0.014 秒 0.023 秒 0.010 秒 0.022 秒
4 0.144 秒 0.267 秒 0.039 秒 0.075 秒 0.029 秒 0.059 秒
5 0.410 秒 0.890 秒 0.080 秒 0.167 秒 0.069 秒 0.210 秒
6 0.738 秒 1.612 秒 0.143 秒 0.353 秒 0.128 秒 0.379 秒

値は curl の time_total(完了時間)の 5 ラウンド中央値です。転送量は解像度 6 で Express 23.8 MB、handler JSONEachRow 23.8 MB、gzip 付き 7.2〜7.4 MB、ArrowStream 7.7 MB、SSE 31.8 MB でした。

Express と handler JSONEachRow は転送量が同じ 23.8 MB で、サーバー側の read_rowsresult_bytes も同じです。先頭バイトの到達時刻(time_starttransfer)を見ると、Express は解像度 6 で 0.729 秒とほぼ完了時刻と同じで、handler は 0.125 秒で先頭バイトが届きます。前作の Express は ClickHouse の JSON を rs.json() で全件パースし、res.json() で再シリアライズしてから返す作りなので、応答は全件を受け取ってから始まっていると考えられます。

ClickHouse の JSON 形式(default_format=JSON)でも取りました。こちらはタブで字下げした整形出力で meta と statistics を含むため、解像度 6 で 31.3 MB と 1.3倍のサイズになります。前作の Express が返していた data 配列と同じ密度で比べるなら JSONEachRow が相当します。

5.2. gzip はどちらも遅くなる

Accept-Encoding: gzip を付けると、転送量は 23.8 MB から 7 MB 台に減ります。一方で完了時間は Express で 0.74 秒から 1.61 秒、handler で 0.14 秒から 0.35 秒に長くなりました。localhost なので帯域を使わず、圧縮の CPU 時間だけが加わったことになります。前作は Cloud Run の応答サイズ上限(HTTP/1 で 32 MiB)を避けるために gzip を入れていました。この上限は chunked 転送やストリーミングを使わない応答に適用されるもので5、Express が JSON を全件組み立ててから返していた前作はこれに当たります。handler 直接では Cloud Run を経由しないので、圧縮するかは回線次第で選べます。

5.3. 形式で差が出る

ADBC ドライバの記事6で、速さを決めるのは接続方式でなく FORMAT だと書きました。handler でも同じで、?default_format=ArrowStream を付けるだけで解像度 6 が 7.7 MB / 0.128 秒と、gzip 無しの経路で最小・最速でした。ArrowArrowStream の両方が URL パラメータだけで切り替わります。Arrow の応答は形式識別用の ARROW1 という先頭バイト列で始まります。handler の定義側で形式を固定する必要はありませんでした。

ブログが取り上げている SSE は、設定 framing_output_format=EventStream で有効になります4。応答は event: data / event: profile_events / event: progress の 3 種類のイベントです。デフォルトでは data のペイロードが base64 になるため、解像度 6 で 31.8 MB と framing なしの JSONEachRow より 3 割増えます。JSONEachPacketString を選ぶと base64 化されない 1 行 1 パケットの ndjson になります。

event: progress
data: {"read_rows":"1344231","read_bytes":"44359623","total_rows_to_read":"1344231","elapsed_ns":"100480384"}

読むときに 1 つ注意があります。応答ヘッダの X-ClickHouse-Summaryresult_rows が 0 のままです。ヘッダはストリームの先頭で送られ、その時点では値が確定していないためと考えられます。最終的な値は最後の progress イベントに result_rowsmemory_usage 付きで含まれます。進捗イベントの頻度は interactive_delay(マイクロ秒)で変わり、解像度 6 の約 0.15 秒のクエリではデフォルトの 1 秒で 3件、10 ミリ秒にすると 9件でした。解像度 3 のように 10 ミリ秒未満で終わるクエリは、設定に関わらず 1件です。

5.4. filter と page は後処理

26.8 の HTTP インターフェースには filter / sort / select / limit / page のパラメータが追加されました4。これは handler 固有ではなく HTTP インターフェース全体の機能で、handler の URL にそのまま付けられます。

呼び出し 返り方
/api/towers/3?limit=3 上位 3 行
/api/towers/3?limit=3&page=2 4〜6 位の 3 行
/api/towers/3?sort=cnt&limit=3 handler の ORDER BY cnt DESC を上書きして昇順 3 行
/api/towers/3?filter=cnt > 400000 該当する 3 行

どれも期待どおりの行が返ります。一方で system.query_logread_rows は、解像度 3 で常に 40,960 でした。解像度 6 の limit=1000 では page=1 でも page=300 でも 1,344,231 です。エラー文に出る書き換え後の SQL を見ると SELECT * FROM (handler の SQL) WHERE (filter) という構造になっており、handler のクエリを毎回全部実行したうえで外側で絞っています。後ろのページほど読み取り量が減ることはありませんでした。Docs も filter は「アクセス制御の仕組みではない」と明記しています7


6. 権限と監査

DB が直接 HTTP を受けるとき確かめたいのは、誰の権限で動き、何が記録されるかです。Oracle Backend with Firebase APIs の記事8で「SDK 経由の読み書きに DB 側の権限と監査は適用されるか」を確かめたのと同じ問いを、ClickHouse でも確かめました。

6.1. 専用ユーザーと DEFINER ビュー

パスワード無しの専用ユーザー api_user を作り、元の表 cell_towers_h3 への GRANT は与えません。代わりに default ユーザーを定義者とする SQL SECURITY DEFINER のビュー(以下 DEFINER ビュー)を作り、そのビューだけを api_user に許可します。ビューは radio を LTE に固定し、列も h3 / cnt / lon / lat / h3_res に絞っています。

CREATE USER api_user IDENTIFIED WITH no_password HOST ANY;

CREATE VIEW default.towers_lte_v
SQL SECURITY DEFINER DEFINER = default
AS SELECT h3ToString(h3) AS h3, cnt, center_lon AS lon, center_lat AS lat, h3_res
FROM default.cell_towers_h3
WHERE radio = 'LTE';

CREATE HANDLER towers_lte URL REGEXP '/api/lte/(?P<res>[0-9]+)' METHODS (GET) AS
SELECT h3, sum(cnt) AS cnt, any(lon) AS lon, any(lat) AS lat
FROM default.towers_lte_v
WHERE h3_res = {res:UInt8}
GROUP BY h3 ORDER BY cnt DESC LIMIT 300000;

GRANT SELECT ON default.towers_lte_v TO api_user;

api_user で 8 通りの呼び出しを試しました。

呼び出し 結果
/api/lte/3(ビュー経由の handler) 200、LTE の集約 6,022 行
/?query=SELECT count() FROM cell_towers_h3(元の表を直接) 403 Code: 497 ACCESS_DENIED
/?query=SELECT count() FROM towers_lte_v(ビューを直接) 200、446,495
/api/lte/3?filter=radio = 'GSM'(ビューに無い列で絞る) 404 Code: 47 UNKNOWN_IDENTIFIER
/api/lte/3?select=radio(ビューに無い列を選ぶ) 404 Code: 47 UNKNOWN_IDENTIFIER
/api/lte/3?select=h3_res(ビューにはあるが handler の SELECT に無い列) 404 Code: 47 UNKNOWN_IDENTIFIER
/api/lte/3?filter=cnt > 100000&limit=3(正当な後処理) 200、3 行
/api/towers/3(GRANT の無い元の表を読む既存 handler) 403 Code: 497 ACCESS_DENIED

分かったことは 3 つです。1 つ目は、handler そのものに実行権限のような単位が無いことです。system.privileges には CREATE HANDLER / ALTER HANDLER / DROP HANDLER / SHOW HANDLERS はありますが、呼び出しの可否は handler が内部で実行する SQL に対する通常の GRANT だけで決まります。最後の行のとおり、元の表を読む既存 handler を api_user が呼ぶと元の表の権限不足で拒否されます。2 つ目は、filterselect で外側から追加できるのは handler の SELECT リストに出ている列だけで、ビューにあっても handler が出していない列(h3_res)は指定できないことです。3 つ目は、エラー応答に handler の SQL 全文がテーブル名込みで返ることです。上の UNKNOWN_IDENTIFIER の本文には SELECT * FROM (SELECT h3, sum(cnt) ... FROM default.towers_lte_v WHERE h3_res = _CAST(3, 'UInt8') ...) WHERE (radio = 'GSM') がそのまま入っていました。公開 API にするなら、エラー本文を書き換える前段のプロキシが必要です。

6.2. query_log に何が記録されるか

呼び出しユーザーは api_user、ビューの定義者は default です。system.query_log を見ると、userinitial_userapi_user のままで、DEFINER ビューを通っても記録上のユーザーは変わりませんでした。http_handler_nametowers_ltehttp_request_url/api/lte/3 が入るので、どの handler が誰に呼ばれたかは 1 行で分かります。

一方で記録されないものもあります。query 列に入るのはパラメータ展開済みの handler の SQL(WHERE h3_res = _CAST(3, 'UInt8'))で、URL の filterlimit の後処理は含まれません。http_request_url にもクエリ文字列は入らず、パスだけです。また、HTTP 層で拒否された呼び出しは query_log に 1 行も記録されませんでした。?query=SELECT 1 を handler の URL に付けると Code: 115 UNKNOWN_SETTING になります。パス引数と同名の param_res=2 を付けると Code: 36 BAD_ARGUMENTS です。どちらも ExceptionBeforeStart としてすら記録されていません。監査を query_log だけに頼るなら、この 2 種類は見えない前提で設計することになります。


7. 考察

Express を経由しない経路が速いのは、クエリの差ではありません。5.1 章のとおりサーバー側の読み取り行数と結果バイト数は両経路で同じで、差はクライアントに届くまでの経路の分だけでした。前作の Express は JSON を全件パースして変換していたので、その処理時間が差になっていると考えられます。逆に言うと、前作の Express のように「型を付けて渡し、結果を変換する」だけの層は、handler の型付きパラメータと出力形式の指定で置き換えられます。業務ロジックや複数クエリの合成がある層は、この検証の範囲外です。

handler が受け持たないのは 3 つです。静的ファイルの配信、ブラウザから直接呼ぶときの CORS、そしてエンドユーザーの識別です。前作の構成なら Cloud Run は React のビルド成果物を配るだけになり、/api は ClickHouse を指します。配るだけなら Cloud Run である必要はなく、Cloud Storage の静的ウェブサイト配信(カスタムドメインで HTTPS にするなら外部アプリケーションロードバランサが必要)9や、SSL と CDN が組み込まれた Firebase Hosting10 でも足ります。今回 Cloud Run を残したのは前作との対比のためで、これらへの置き換えは試していません。CORS は今回試していません。エンドユーザーの識別は 6.2 章のとおり、query_log に記録されるのは handler を呼んだ DB ユーザーで、その先にいる人は記録されません。これは Firebase APIs の記事で ORDS 経由の監査に接続プールのユーザーしか記録されなかったのと同じ結果で、DB が直接 HTTP を受けても、HTTP リクエストを送った人を DB 側で識別できることにはならないと考えられます。

認可を handler ではなく SQL の GRANT に任せる設計は、ブログも「認可は handler ではなく実行ユーザーの権限で決まる」と説明しており1、Docs の filter の注記「アクセス制御の仕組みではない」7とも整合します。今回のように DEFINER ビューで列と行を絞ってから handler に渡す構成なら、filter でビュー外の列を参照できないことは 6.1 章で確かめられました。一方、エラー本文に SQL が出る点は、公開 API として外に出すときには前段のプロキシで本文を差し替える必要があると考えられます。今回はその設定までは試していません。

page は handler のクエリを毎回全件実行したうえでの後処理で、解像度 6 の limit=1000 では page=300 でも読み取りは 1,344,231 行のままでした。上位 N 件を数ページ見るダッシュボードなら差は出にくいと考えられますが、大きな結果を分割して取り出す用途では、30 万行を 300 ページに分けると handler のクエリが 300 回実行され、読み取りは 300 × 1,344,231 行になります。分割して取りたいなら、handler の SQL 側に {offset:UInt32} のようなプレースホルダを持たせるほうが読み取り行数を抑えられます。


8. まとめ

# 確かめること 結果
1 Express の /api/towersCREATE HANDLER で置き換えられるか 置き換えられた。解像度 2〜6 で返却行数と読み取り行数が一致
2 大きい応答でのレイテンシ・転送量 解像度 6 で Express 0.738 秒に対し handler 0.143 秒。ArrowStream なら 7.7 MB / 0.128 秒
3 権限と監査 認可は SQL への GRANT で決まり、DEFINER ビューで列と行を絞れる。query_log は呼び出しユーザー名義で http_handler_name 付き。HTTP 層のエラーは記録されない

ClickHouse 26.8 の CREATE HANDLER で、3D グローブの API サーバーが担っていた「クエリを受けて JSON を返す」部分を DB 側に移しました。静的配信と CORS とエンドユーザーの識別は 7 章のとおり別の仕組みが必要です。ClickHouse Cloud に 26.8 が配信されたら、同じ handler が Cloud でも作れるかを追記します。

参考

  1. ClickHouse as a streaming HTTP API(ClickHouse 公式ブログ、2026 年 9 月 5 日。CREATE HANDLER の使い方と、認可は実行ユーザーの権限で決まるという説明) 2

  2. Cloud Run と ClickHouse で、4,327万件の基地局を3Dグローブに可視化してみた(前作。Express の /api/towers と H3 集約表 cell_towers_h3 の作り方)

  3. Cell Towers データセット(ClickHouse 公式のサンプルデータ。DDL とロード手順)

  4. ClickHouse Changelog(26.8 の New Feature 節。CREATE HANDLERsystem.handlers、query_log の http_handler_name 列、HTTP の filter / sort / page パラメータ、framing_output_format の追加) 2 3 4

  5. Cloud Run の割り当てと上限(Networking limits の表。HTTP/1 の最大応答サイズ 32 MiB は chunked 転送やストリーミングを使わない場合に適用)

  6. ClickHouse 公式の ADBC ドライバを実測比較してみたFORMAT Arrow / ArrowStream の違いと、速さは接続方式でなく形式で決まるという実測)

  7. ClickHouse HTTP Interface(HTTP インターフェースの仕様。filter パラメータはアクセス制御の仕組みではないという注記) 2

  8. Oracle Backend with Firebase APIs で作ったアプリをデータベース側から確認してみた(SDK 経由の読み書きに対する DB 側の監査で、エンドユーザーの識別子が DB に届かなかった実測)

  9. Cloud Storage で静的ウェブサイトをホストする(Google Cloud ドキュメント。HTML / CSS / JavaScript の配信手順と、HTTPS には外部アプリケーションロードバランサと SSL 証明書が必要という記載)

  10. Firebase Hosting(Firebase ドキュメント。SSL が組み込みで、CDN エッジにキャッシュして配信する旨)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?