クリックからデータベースまで:1つのリクエストの旅
1回のクリックは、住所の検索、接続、暗号化、リクエスト、アプリ、データベース、返信の7つの区間を通ります。荷物のたとえ、curlで区間ごとに測る方法、答え付きの演習、遅いページをAIに直してもらうときの取り違えを解説します。
プログラミングを学び始めた人、プロダクトを作る人、遅いWebページをAIに直してもらいたいけれど原因がどこか分からない人のための記事です。ネットワークの知識は要りません。むずかしい言葉にはどれも身近なたとえを添え、計算の部分は印をつけてあるので、不要なら読み飛ばせます。
数字と定義は公開されている文書から取っており、末尾に一覧があります。「例」や「模擬」と書いたものは作った例で、実在の人物や会社には触れていません。実際のシステムにはこの記事より多くの細部があるので、以下のたとえは覚えやすくするための補助にすぎません。
**最初にできなかったことを書きます。**私がこの記事を書いた環境は、ローカルのネットワークポートを開くことと、外部へのhttpsアクセスを禁止していました。そのため、どのサーバーに対してもcurlを実際に動かせていません。「実際に動かした例」の節は、私が書いて実際に実行した模擬ですが、遅延は私が決めた値で、ネットワークから測ったものではありません。記事中のcurlコマンドには「ここでは未実行」と明記しました。実際の数字は、ご自身のマシンで動かして確かめてください。
まず要点#
- ページを開くと、リクエストは住所の検索、接続、暗号化の握手、リクエストの送信、アプリの処理、データベースの応答、返信という複数の区間を通ります。どの区間も、それぞれ単独で遅くなったり失敗したりします。
- どこが遅いかは、推測せずに測ります。
curlの-wオプションで時間の目印を出せます。 - curlの目印は開始からの累計です。1つの区間の時間は、その目印から直前の目印を引いて求めます。
- この記事の模擬では、合計が近い2つのケース(0.973秒と1.383秒)でも犯人がまったく違いました。一方はデータベース待ち(約83%)、もう一方はDNSの検索(約87%)です。
200は、プロトコルの上でリクエストが成功したことしか示しません。内容が正しいとは言っていません。- 遅いページをAIに直してもらうときは、先に測って、数字をAIに渡します。AIが書いたコードは、目に見えにくい小さなクエリをたくさん隠すことがありますが、その頻度を測った研究は見つかりませんでした。
この記事を読むと説明できるようになること#
- Webリクエストにはどんな区間があり、なぜ区間ごとに遅くなったり失敗したりするのか。
- なぜcurlの時間の目印は引き算が必要で、各区間をどう計算するのか。
- AIに直してもらう前に、問題がネットワーク、アプリ、データベースのどこにあるかを絞り込む方法。
むずかしい言葉をやさしく#
| 用語 | やさしい意味 |
|---|---|
| DNS(ドメイン名システム) | インターネットの電話帳です。「example.com」という名前を渡すと、サーバーの数字の住所を返します。 |
| TCP(接続) | 「これから荷物を送ります」と伝える電話です。相手が出て確認してから送ります。 |
| TLS(暗号化の握手) | 荷物を送る前に、鍵つきの箱を使うと両者で決めることです。外の人が中を見られません。httpsで始まる住所で使われます。 |
| HTTP | ブラウザとサーバーの間でやりとりする、注文書と返品書の言葉です。 |
| ヘッダー | 書類に添えるメモの行です。内容の種類、言語、長さなど。 |
| ステータスコード | 返信書にある短い数字です。200は完了、404は見つからない、500はサーバーのエラー。 |
| データベース | 情報を長く保管する倉庫です。アプリは倉庫に問い合わせないと、答えるデータがありません。 |
| レイテンシ(遅延) | 待ち時間です。秒やミリ秒で表します(1ミリ秒は1000分の1秒)。 |
1. まずイメージ:荷物を送る#
別の街の店に注文するところを思い浮かべてください。荷物が届くまでに、7つのことが起きます。
- 店の住所を電話帳で調べます(DNS)。
- 注文があると電話で知らせ、相手が出るのを待ちます(TCP接続)。
- 両者で鍵つきの箱を使うと決めます(TLS)。
- 何がほしいかを書いた注文書を送ります(HTTPリクエスト)。
- 受付が注文書を受け取って処理します(サーバー上のアプリ)。
- 受付が倉庫へ行き、商品を探します(データベース)。
- 商品と書類が戻ってきます(返信)。
荷物が遅れても、古い電話帳か、話し中の回線か、受付か、倉庫か、すぐには分かりません。一番信頼できるのは、1つずつ時間を測る人です。コンピューターも同じです。
MDNは、道のたとえでもっと短く説明しています。あなたの端末は家、サーバーは店、DNSは住所録、HTTPは注文に使う言葉です。MDNが挙げる段階はDNSの検索、リクエスト、レスポンス、組み立ての4つです。この記事は、測れる場所が見えるようにもう少し細かく分けています。
ここから言えること:「ページが遅い」は1つの故障ではありません。7つの区間の合計で、たいてい1つの区間が時間の大半を占めます。
2. 7つの区間を1つずつ#
下の表は簡略化した地図です。右端の列は、各区間がどう失敗しやすいかを示すので、エラーメッセージで見分けられます。
| # | 区間 | たとえ | 遅くなりやすいとき | 失敗のしかた |
|---|---|---|---|---|
| 1 | DNS | 電話帳で調べる | DNSサーバーが遠い、混んでいる、新しいドメイン | 名前が解決できず、ブラウザが「サーバーが見つからない」と表示 |
| 2 | TCP接続 | 電話で知らせる | サーバーが遠い、パケットが失われる | 接続拒否、またはタイムアウト |
| 3 | TLS | 鍵つきの箱 | ネットワークが遠い、やりとりが何往復も必要 | 証明書の期限切れや名前の不一致で、ブラウザが「安全ではない」と警告 |
| 4 | リクエスト送信 | 注文書 | 送るデータが大きい | 権限がない(401、403)、ヘッダーが間違い |
| 5 | アプリ | 受付の処理 | コードが遅い、大きなループ | コードが対処できない場面で500エラー |
| 6 | データベース | 倉庫へ行く | インデックスがない、小さなクエリが多すぎる | 接続の枯渇、クエリのタイムアウト |
| 7 | 返信 | 商品の配達 | 内容が大きい、圧縮されていない | 途中で切れる、内容が足りない |
2.1 区間1:DNS、電話帳#
**たとえるなら:**店の番号を案内に聞く電話です。名前は分かっていますが、番号は分かりません。
コンピューター同士は数字の住所で話します。ブラウザはDNSサーバーに聞いて、「example.com」を数字の住所に変えます。結果はたいていしばらく**覚えておかれる(キャッシュ)**ので、次回は速くなります。最初の測定がその後より遅くなりやすいのはこのためです。
2.2 区間2と3:接続と暗号化の握手#
**たとえるなら:**電話で知らせてから、両者で鍵つきの箱を使うと決めることです。
MDNによると、HTTPはTCPの上で動き、たいていTLSで暗号化されます。HTTPには信頼できる通り道が必要なので、まず接続を開きます。httpsの住所では、鍵を決めるTLSの握手が加わります。古いHTTP/1.0はリクエストと返信ごとに新しい接続を開きましたが、その後のバージョンは接続を再利用するので、同じサーバーへの2回目のリクエストは区間1から3を省けることが多くなります。
2.3 区間4:HTTPリクエストを送る#
**たとえるなら:**注文書です。品物の名前(パス)、注文の方法(メソッド)、いくつかのメモ(ヘッダー)が書かれています。
MDNは、リクエストを、メソッド(取得ならGET、送るならPOST)、リソースのパス、HTTPのバージョン、任意のヘッダー、場合によっては本文からなるものと説明しています。MDNの形に倣った例です。
GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: fr
返信には、バージョン、短い説明つきのステータスコード、ヘッダー、場合によっては本文(ページの内容)があります。
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 29769
MDNによるステータスコードの5つの分類です。1xxは情報、2xxは成功、3xxは転送、4xxはリクエストを送った側の問題、5xxはサーバー側の問題です。よく見るコードは、200が成功、301が別の住所へ恒久的に移動、304が変更なしなので保存済みのコピーを使う、404が見つからない、500がサーバーの一般的なエラー、503が一時的に応答できない、です。
2.4 区間5と6:アプリとデータベース#
**たとえるなら:**受付が書類を読み、倉庫へ行きます。倉庫へ行くたびに、往復の時間がかかります。
動的なページでは、時間がこの2つの区間に集まりやすくなります。アプリがあなたのコードを実行し、データが必要ならデータベースに聞きます。聞くたびに1往復で、データベースが別のマシンにあるときは特に長くなります。小さく100回聞くのは、大きく1回聞くより遅くなりがちです。100回分の往復代がかかるからです。節4.1に計算できる例があります。
2.5 区間7:返信が戻る#
内容が大きいほど時間がかかります。圧縮されていなければなおさらです。文字のページでは、たいてい一番短い区間ですが、大きな画像やダウンロードでは長くなることがあります。
**ここから言えること:**区間ごとに「失敗の特徴」があります。エラーメッセージは手がかりなので、AIに推測させる前によく読みましょう。
3. curlで区間ごとに測る#
**たとえるなら:**荷物の通り道の関所ごとに、ストップウォッチを置くことです。
curlは、HTTPリクエストを送るコマンドラインのツールです。この記事で大切なオプションは2つです。
-v(verbose、おしゃべり):全過程を表示します。curlの文書によると、*で始まる行はcurl自身の情報(名前の検索、接続)、>で始まる行はcurlが送ったもの、<で始まる行はcurlが受け取ったものです。- **
-w(write-out、終了後に数値を出す):**決めた書式で計測値を出します。
知っておく5つの目印です。libcurlの文書によると、すべて秒単位で、すべて開始から数えます。
| 目印 | 意味 |
|---|---|
time_namelookup |
DNSの検索が終わった |
time_connect |
TCP接続が終わった |
time_appconnect |
TLSの握手が終わった(TLSを使わないときの値はlibcurlの文書に明記がなく、私も確かめていないので、httpの住所ではこの目印を無視してください) |
time_starttransfer |
返信の最初のバイトを受け取った。サーバーの処理時間を含む |
time_total |
すべて終わった |
コマンドの例です(ここでは未実行です。私の環境が外部へのhttpsを禁止しているためで、ご自身のマシンで動かしてください)。
curl -s -o /dev/null -w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' https://example.com/
この形のコマンドをcurl 8.7.1で実行したところ、変数名は受け付けられましたが、接続が遮断されたのでステータスコードは000になり、数字には意味がありません。使っていません。
3.1 引き算の式#
目印は累計なので、各区間の時間は次のとおりです。
DNS = time_namelookup
TCP接続 = time_connect - time_namelookup
TLSの握手 = time_appconnect - time_connect
サーバーの待ち = time_starttransfer - time_appconnect
ダウンロード = time_total - time_starttransfer
http(TLSなし)の住所では、TLSの行を省き、「サーバーの待ち」の行でtime_appconnectの代わりにtime_connectを使います(近似です)。
大事な限界:「サーバーの待ち」の間には、アプリ(区間5)とデータベース(区間6)の両方が含まれます。curlはこの2つを分けられません。分けるにはクエリのログか専用の監視ツールが必要です。
4. 実際に動かした例:7つの区間の模擬(計算は読み飛ばせます)#
これは、私がPython 3.14.8で書き、自分の環境で実際に動かした模擬です。各区間は、私が先に決めた待ち時間です。プログラムはcurlと同じ形で累計の目印を出し、引き算で各区間を計算します。遅延は私が選んだ値で、ネットワークから測ったものではありません。確かめているのは、引き算が決めた値どおりに返るかどうかです。
ケースA:データベースが遅い(DNS 0.040秒、接続0.030、TLS 0.060、サーバーの待ち0.800、ダウンロード0.020)。ケースB:DNSが遅い(DNS 1.200、接続0.030、TLS 0.060、サーバーの待ち0.050、ダウンロード0.020)。
実行の実際の出力です。
A: slow database
time_namelookup 0.045 leg = 0.045
time_connect 0.080 leg = 0.035
time_appconnect 0.144 leg = 0.064
time_starttransfer 0.949 leg = 0.805
time_total 0.973 leg = 0.024
longest leg: time_starttransfer (0.805 s)
B: slow DNS, fast database
time_namelookup 1.205 leg = 1.205
time_connect 1.240 leg = 0.035
time_appconnect 1.305 leg = 0.065
time_starttransfer 1.360 leg = 0.055
time_total 1.383 leg = 0.023
longest leg: time_namelookup (1.205 s)
結果の読み方です。
- ケースA:サーバーの待ちの区間は、合計0.973秒のうち0.805秒で、約83%です。0.805が決めた0.800よりわずかに大きいのは、実際の待機が必ず数ミリ秒余分にかかるためです。
- ケースB:DNSの区間は、合計1.383秒のうち1.205秒で、約87%です。2つのケースの合計は近い(1.4秒未満)のに、直すべき場所はまったく違います。
time_totalだけを見ていると、2つのケースは区別できません。ケースBでデータベースを直しても効果はありません。
はっきりさせておくと、これは私が遅延を決めた模擬です。証明しているのは計算の方法であり、本物のネットワークの姿ではありません。あなたのマシンの数字は違います。
4.1 余計なクエリ:数えたのは本物、時間は仮定#
2つ目の例も、PythonとメモリのSQLite 3.53.4で実際に動かしました。顧客が50人、注文が200件あります。方法1:顧客の一覧を取り、顧客ごとに別のクエリを出す。方法2:結合(join)と集計を使った1つのクエリ。
same answer: True
queries, one per row: 51
queries, one join : 1
waiting for the database at 5 ms per query: 255 ms vs 5 ms
どちらも同じ答えですが、片方は51クエリ、もう片方は1クエリです。数えた値は本物です。1往復5ミリ秒は、別のマシンにあるデータベースを想定した私の仮定です(メモリ上のSQLiteはネットワーク遅延がほぼありません)。その仮定では、51クエリの待ちは255ミリ秒、1クエリは5ミリ秒です。このパターンはN+1(一覧を取る1回のクエリと、行ごとのN回のクエリ)と呼ばれます。
ここから言えること:「サーバーの待ち」が長いとき、最初の問いは「どのクエリが遅いか」ではなく、「このページはクエリを何回出しているか」です。
5. 答え付きの演習#
この演習は、自分のマシンで動く偽のサーバーを使います。私はこのサーバーを動かせていません。私の環境がネットワークポートを開くことを禁止していたためです(ポートを開くコマンドは「Operation not permitted」と報告しました)。確認できたのはコードがコンパイルできることだけです。下の答えは、私のコードの書き方から導いたものなので、実際に動かして答えと比べてください。
5.1 偽のサーバー#
slow_app.pyとして保存します。
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/slow":
time.sleep(0.8) # 遅いクエリを模擬する
body = b"orders: 3\n"
elif self.path == "/missing":
self.send_response(404)
self.send_header("Content-Length", "0")
self.end_headers()
return
else:
body = b"hello\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *a):
pass
HTTPServer(("127.0.0.1", 8765), Handler).serve_forever()
1つ目のターミナルでpython3 slow_app.pyを動かし、別のウィンドウで以下のコマンドを使います。
5.2 演習1:遅い区間を見つける#
curl -s -o /dev/null -w 'tcp=%{time_connect} first_byte=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' http://127.0.0.1:8765/slow
curl -s -o /dev/null -w 'tcp=%{time_connect} first_byte=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' http://127.0.0.1:8765/
問い:それぞれのコマンドで、ほぼすべての時間を占める区間はどれですか。引き算で求めてください。
答え(コードから導いたものなので、自分の数字と比べてください):1つ目のコマンドでは、first_byteからtcpを引いた値が約0.8秒(数ミリ秒の誤差つき)になります。コードが返信の前に0.8秒待つからです。残りは、両端が同じマシンなのでほぼゼロです。2つ目のコマンドは待ちがないので、どの数字もとても小さくなります。住所はhttpでTLSがないので、time_appconnectの代わりにtime_connectを使います。
5.3 演習2:404と時間#
curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://127.0.0.1:8765/missing
答え:code=404で、時間はとても小さくなります。教訓:エラーの返信でも速いことがあります。時間が短いことは、正しいことを意味しません。
5.4 演習3:1回の測定では足りない#
演習1のコマンド(/slowのパス)を5回動かし、first_byteの値を書き留めます。小さい順に並べたときの真ん中の値(中央値)を取ります。
**答え:**数字は0.8秒に近くなりますが、数ミリ秒ずつずれます。本物のネットワークではずれがずっと大きいので、1回の測定で結論を出さないでください。
5.5 httpsでも試す(ここでは未実行)#
curl -v https://example.com/ -o /dev/null
curl -s -o /dev/null -w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' https://example.com/
-vのコマンドでは、*の行(接続と握手)、>の行(curlが送った注文書)、<の行(返信書)を探します。-wのコマンドでは、節3.1の式を当てはめます。最初の実行はDNSが遅くなりやすく、2回目はキャッシュのおかげで速くなりやすくなります。
6. 取り違えやすいところ#
荷物の確認にたとえると、遅れて届いたと知るだけでは足りず、どの関所で遅れたかを知る必要があります。
| 状況 | なぜ取り違えるか | 見抜き方 |
|---|---|---|
| ページが遅いのでデータベースをすぐ直す | DNSやTLSが長い区間かもしれない(上のケースBのように) | 先に測り、目印を引き算し、一番大きい区間を見る |
ステータス200だけを見る |
200はプロトコルの上で成功したことしか示さず、本文が空だったり、アプリが書いたエラーメッセージが入っていたりする | コードだけでなく、返ってきた内容も確認する |
| 2回目が速かったので直ったと思う | キャッシュ(DNS、ブラウザ、サーバー)で後の実行が速くなる。304は保存済みのコピーを使わせるためのもの | 最初と後の両方を測り、どちらか明記する |
time_totalを1区間の時間として使う |
目印は累計 | 必ず後の目印から前の目印を引く |
| 1回だけの測定 | ネットワークとサーバーは揺らぐ | 何回も測り、中央値を取る |
| 転送(3xx)がある | 追いかけた各リクエストも、すべての区間を再び消費する。libcurlの文書によると、追った各リクエストの時間は足し合わされる | -vで各リクエストを見て、不要な転送を避ける |
| 自分のマシンで測ってユーザーの状況を推測する | ユーザーは別の場所、別のネットワークにいる | 可能なら複数の場所から測り、どこで測ったか明記する |
7. 例:遅いレポートページと過ごす朝#
これは想定の例で、実在の人物や製品には触れていません。名前、数、時刻は作ったものです。
- **09:30。**ユーザーが、レポートのページが表示されるまで約3秒かかると報告します。
- **09:40。**開発者がエージェントに頼みます。「クエリを速くして。」エージェントはインデックスを1つ追加し、「最適化しました」と報告します。
- **10:05。**ページはまだ遅いままです。開発者が
-wつきでcurlを動かすと、tcp=0.02、first_byte=3.05、total=3.08でした。サーバーの待ちがほぼ3秒を占めます。DNS、接続、ダウンロードは無視できる大きさです。 - 10:15。開発者がクエリのログをつけると、1回のページ読み込みで601クエリ(一覧の1クエリと、行ごとの600クエリ)が出ています。データベースが別のマシンにあるので、各クエリの待ちは約5ミリ秒です。計算は、601かける5ミリ秒で3,005ミリ秒となり、約3秒と合います。
- **10:50。**開発者はエージェントに、結合を使った1つのクエリにまとめ、前後のクエリ数を表示するよう頼みます。
first_byteは約0.1秒まで下がります(この例の仮定の数字です)。
取るべき行動:先に測り、その数字をAIに渡します。「速くして」とだけ言うと、AIは違う場所を直し、たとえば犯人でないクエリにインデックスを足して、それでも完了と報告することがあります。記事AIの結果に証拠を添えさせるのように証拠を求めましょう。直す前後のクエリ数とfirst_byteです。
この例は頻度については何も証明しません。証拠は別にあります。節4.1で、同じ答えに対して51クエリと1クエリを実際に数えました。AIが書いたコードがN+1を起こしやすいかどうかは、直接測った研究を見つけられませんでした。2025年の研究(Unveiling Inefficiencies in LLM-Generated Code)は、3つのオープンソースモデルがHumanEval+で書いた492個の小さなコードを分析し、ロジックと性能の問題が一般的な非効率に含まれると報告していますが、これは小さなアルゴリズム課題で、データベースを使うWebアプリではありません。N+1は起こりうるリスクとして扱い、自分で測ってください。確実とは考えないでください。AIが書いたコードのセキュリティについては、AIを使うアプリのOWASP Top 10も参照してください。
8. 使った数字と文書#
| 出典 | 内容 | 読み方 | 注意 |
|---|---|---|---|
| MDN、How the web works | 4段階:DNSの検索、リクエスト、レスポンス、組み立て | 最も基本の地図 | とても簡略で、TLSには触れない |
| MDN、HTTPの概要 | リクエストとレスポンスのモデル、状態を持たない、TCPの上で動く(たいていTLSつき)、接続の再利用 | 区間2、3、4の根拠 | 参考文書で、測定値はない |
| MDN、ステータスコード | 1xxから5xxの5分類と、200、301、304、404、500、503の意味 | ステータスコードの部分の根拠 | プロトコル上の意味 |
| libcurl、STARTTRANSFER_TIMEとAPPCONNECT_TIME | 時間は開始から数え、サーバーの処理を含み、転送をまたいで足し合わされる | 引き算の式の根拠 | TLSなしのときの値は文書に明記がない |
| Everything curl、verbose | *、>、<の意味 |
-vの読み方の根拠 |
手引きの文書 |
| 私の模擬 | 引き算で各区間を計算する方法 | 方法の説明 | 遅延は私が決めた値で、ネットワークの測定ではない |
| 私のクエリ数 | 同じ答えに51クエリと1クエリ | N+1の説明 | 1往復5ミリ秒は仮定 |
| モデルが生成したコードの非効率に関する2025年の研究 | 492個のコードでロジックと性能の問題が多い | 測る必要性を示唆するもので、結論ではない | 小さなアルゴリズムのコード、3つのオープンソースモデルで、Webアプリではない |
正しく読むために:これらの文書は、区間への分け方と計算の方法を支持します。私は本物のネットワークでは測っておらず、AIが書いたコードのN+1を測った研究も見つけられませんでした。
9. チェックリスト:ページが遅いときの8つの質問#
- 数字で測ったか、それとも推測しているか。
- 目印を取って引き算したか、それとも
time_totalを使っているか。 - どの区間が一番大きいか。DNS、接続、TLS、サーバーの待ち、ダウンロードのどれか。
- これは最初の実行か、後の実行(キャッシュあり)か。何回も測ったか。
- ステータスコードは何で、返ってきた内容は正しいか。
- 「サーバーの待ち」が長いなら、このページはクエリを何回出しているか。
- AIに直してもらうとき、測定値を渡し、直した後の測定値も求めたか。
- どこから測ったか。ユーザーも似た場所にいるか。
10. バランスよく見る:この地図が簡略化していること#
**できること:**問題を分ける方法、測るための式、AIに頼む前に確かめる習慣を与えます。
できないこと:
- これは、TLSつきのTCPの上のHTTP/1.1を簡略化した地図です。実際にはCDN(コンテンツ配信ネットワーク)、プロキシ、ロードバランサーもあります。それぞれが区間を1つ足したり、減らしたりします。
- HTTP/3は、RFC 9114によると、UDPの上に作られたQUICというプロトコルの上で動き、接続の部分と暗号化の握手を一体にします。私が読んだ二次的な情報源によると、これは接続を開くときの往復回数を減らします。その場合、
time_connectとtime_appconnectは、節3の表ほどきれいに分かれないことがあります。正確さが必要なら、RFC本文を読んでください。 - curlは1つの場所から、1回だけ測ります。1回の測定はノイズです。専用の監視ツール(APM)は、アプリとデータベースを分けるなど、より多くを見られます。
- 節4の模擬は計算を説明するだけで、節5の演習は私が実際には動かしていません。
**大げさに言わない:**7つの区間を知っても、インフラの専門家にはなれませんが、正しい問いを立てるには十分です。**軽く見ない:**違う場所を直しても時間はかかりますし、それでも誰かが「完了しました」と報告します。
11. 結論#
「ページが遅い」は、いくつかの区間の合計で、たいてい1つの区間がほとんどを占めます。curl -wで測り、隣り合う目印を引き算して、一番大きい区間を見ましょう。それがサーバーの待ちなら、推測する前にクエリを数えます。そしてAIに直してもらうときは、測定値を渡し、直した後の測定値も求め、その数字を自分の手で開いて確かめてください。
参考資料#
定義は2026年10月時点の公開文書によります。HTTP/3の部分は二次的な情報源に基づくので、引用する前に原文を開いて確認してください。
- MDN、How the web works
- MDN、HTTPの概要とHTTPレスポンスのステータスコード
- curl、CURLINFO_STARTTRANSFER_TIMEとCURLINFO_APPCONNECT_TIME
- Everything curl、Verboseとcurlのマニュアル(このページの-wの部分は読めておらず、変数名はcurl 8.7.1を動かして確認しました)
- IETF、RFC 9114(HTTP/3)とRFC 9000(QUIC)
- Unveiling Inefficiencies in LLM-Generated Code: Toward a Comprehensive Taxonomy、arXiv 2503.06327(2025年)
よくある質問
- Webリクエストはどんな区間を通りますか?
- この記事の簡略化した見取り図では、DNSでサーバーの住所を調べ、TCP接続を開き、(httpsなら)TLSで暗号化の握手をし、HTTPリクエストを送り、アプリが処理し、アプリがデータベースに問い合わせ、返信が戻ります。MDNは、DNSの検索、リクエスト、レスポンス、組み立ての4段階で簡潔に説明しています。実際にはCDNやプロキシが入ったりHTTP/3が使われたりするので、方向をつかむための地図と考えてください。
- ページが遅いのが、ネットワークのせいか、アプリのせいか、データベースのせいかを、どう見分けますか?
- curlに-wをつけてtime_namelookup、time_connect、time_appconnect、time_starttransfer、time_totalを出し、隣り合う目印を引き算します。一番大きい区間が見る場所を示します。time_appconnectからtime_starttransferまでの待ち時間にはアプリとデータベースの両方が含まれ、curlはこの2つを分けられません。分けるにはクエリのログや監視ツールが必要です。
- なぜcurlの時間の目印は引き算が必要なのですか?
- 開始からの累計だからです。libcurlの文書は、time_starttransferを、開始から応答の最初のバイトを受け取るまでの時間と説明しており、前の区間とサーバーの処理時間がすでに含まれています。time_totalを1つの区間の時間として読むのは誤解です。
- ステータスコード200なら、すべて正しいということですか?
- いいえ。MDNによると、200はプロトコルの上でリクエストが成功したという意味です。本文が空だったり、アプリが書いたエラーメッセージが入っていたりすることがあるので、ステータスコードだけでなく内容も確認します。
- AIが書いたコードは、余計なデータベースクエリを生みやすいですか?
- その可能性はありますが、AIが書いたコードでN+1パターン(一覧を取る1回のクエリと、行ごとのクエリ)を直接測った研究は見つかりませんでした。2025年の研究は、3つのオープンソースモデルが書いた492個の小さなコードで、ロジックと性能の問題が一般的な非効率に含まれると報告していますが、これはアルゴリズムの問題であり、Webアプリではありません。確実なのは、自分のページのクエリ数を数えることです。