
JavaScript
イベント
マガジン
技術ブログ
本記事は、Claude Code + さくらのAI EngineではじめるAgentic Codingを加筆修正したものです。 いまやAIは仕事に欠かせない存在になりました。数あるAIサービスの中でもプログラミングにおい […]
ウェブサイトのパフォーマンス問題はよくあることですが、根本原因の特定は困難な作業となります。この投稿では、 Server-Timing ヘッダー の潜在能力を引き出すことで、パフォーマンスに関するトラブルシューティングのプロセスをシンプルにする方法を学びます。 Server-Timing ヘッダーは、バックエンドのコンポーネントがユーザーリクエストへのレスポンスにおいて、タイミングメトリクスやパフォーマンスモニタリングに関するインサイトを伝達できるようにします。 ウェブサイトのアクセスでは、画像変換などのコンテンツ最適化やデータベースからの動的なデータ取得を含んだ、複雑なサーバーサイドのプロセスが関与しています。遅いリクエストの原因となるサーバーやプロセスを特定するには、複数のログを突き合わせて分析する必要があり、時間がかかってしまいます。このプロセスをシンプルにすることで迅速に問題を解決できます。具体的には、ユーザー体験の品質シグナルとサーバーサイドのパフォーマンス指標とを直接関連付けて、単一のログ行内にカプセル化することで実現します。この方法は、広範なデータクエリや相関分析が不要であり、パフォーマンス問題を素早く特定し、原因となるサーバーコンポーネントまで追跡することを可能にします。このアプローチの実例として Common Media Client Data(CMCD) が挙げられます。 CMCD は動画ストリーミング業界で最近生まれた革新的な技術で、クライアントおよびサーバー両方のオブサーバビリティデータを単一のリクエストログ行にシームレスに統合するものです。ウェブサイトにおいては、 Server-Timing ヘッダーを実装することで同様の方式を採用できます。サーバーサイドのメトリクスとクライアントサイドで利用可能なメトリクスとを効果的に統合し、特定のリクエスト-レスポンスサイクルのパフォーマンスを包括的に把握するのです。 私たちが提案するソリューションは 2 つのパートで構成されます。第一に、エンドユーザーのレイテンシを測定してパフォーマンス問題を特定すること、第二に、そうした問題が発生した際にサーバーのインサイトに即座にアクセスすることです。 まず前者を取り上げてから、 Server-Timing の実装について掘り下げていきましょう。 パフォーマンス問題の検出 ウェブサイトのパフォーマンスはレイテンシに大きく依存します。レイテンシとは、ユーザーアクション(リンクのクリックやフォームの送信など)とサーバーからのレスポンスとの間の時間遅延を指します。ウェブサイトにおけるレイテンシは、通常 Time to First Byte ( TTFB )、別名 First Byte Latency ( FBL )の形式で測定されます。これは、ウェブサイトのコンテンツがユーザーの画面に描画され始めるまでの速さを測定したもので、 First Contentful Paint ( FCP )や Largest Contentful Paint ( LCP )などの Core Web Vitals シグナルに直接影響します。シームレスなユーザー体験を確保するには、 TTFB を 800 ミリ秒以下に維持することが 推奨されています 。このベンチマークは、遅いリクエストを特定するための有用な閾値として機能します。 Amazon CloudFront のようなサービスを活用することで、静的および 動的コンテンツ 両方の TTFB の改善に役立ちます。 クライアントサイドの視点で TTFB を測定する際は、ユーザーのリクエスト開始時点から、サーバーからのレスポンスの最初のバイト受信時点までの時間を対象範囲とします。この計算には、ネットワーク伝送時間やサーバーサイドでのすべての処理時間が含まれており、ウェブサイトのアーキテクチャに応じて、コンテンツ配信ネットワーク( CDN )の処理、オリジンサーバーの処理、データベースのクエリ、およびその他のリクエスト処理タスクなどが含まれます。サーバーサイドの視点で TTFB を測定する場合は、 サーバーがリクエストを受信してから、レスポンスの最初のバイトをネットワーク層に送出する時点までの時間を対象範囲とします。このとき、ネットワーク転送時間は含まれず、 TTFB はレスポンスを開始する前のサーバーの処理時間を本質的に示します。さらに、リクエストフローの途中にサーバーが位置するシナリオでは、サーバーは二重の役割を果たします。一つはダウンストリームからのリクエストを受信するサーバーとして、もう一つはアップストリームの他のサーバーにリクエストを転送するクライアントとして機能するのです。この動作モデルは、 Amazon CloudFront のような CDN 内のサーバーにおいて一般的であり、そのようなサーバーではクライアントサイドとサーバーサイドの両方の TTFB メトリクスが存在することになります。 CloudFront とエッジ関数、 Application Load Balancer 、ウェブサーバー、データベースなどのコンポーネントを含む典型的なウェブサイトアーキテクチャでは、リクエストからレスポンスまでのサイクルは図 1 に示すように進行します。 図 1. 典型的なウェブサイトアーキテクチャにおけるリクエスト-レスポンスサイクルのタイミング 図 1 では、リクエストとレスポンスの開始と終了のそれぞれのタイムスタンプを T を用いて表しています。これらのタイムスタンプを使用して、様々な TTFB を以下のように計算します: ユーザー TTFB は T1 から T18 までの時間間隔です。ユーザーエクスペリエンスをモニタリングし、推奨値を超えた時の問題特定をするために測定すべき指標です。ユーザー TTFB が短いほど、レスポンスが速く、良いユーザーエクスペリエンスであることを示しています。 CloudFront ダウンストリーム TTFB は T2 から T17 までの時間間隔です。キャッシュヒット、つまり、オリジンでの処理を必要とせず CloudFront キャッシュからリクエストが処理される場合には、 TTFB は CloudFront がリクエストを処理してレスポンスを準備するのにかかった時間のみを示します。エッジ関数を使用するのであれば、その実行時間も含まれます。ただし、キャッシュミスの場合には、オリジンがリクエストを処理してレスポンスを準備するまでにかかった時間と、オリジンから CloudFront へレスポンスを転送する時間が追加されます。 CloudFront アップストリーム TTFB は T3 から T14 までの時間間隔です。これは、CloudFront がリクエストをオリジンに送信し、レスポンスを受信するまでのキャッシュミスの場合を表しています。 CloudFront と同様に、オリジン側のシステム内のすべてのサーバーも独自の TTFB を持っています。たとえば、HTML ページを生成するためにデータベースクエリを実行する場合に、T7 から T10 までの時間間隔として、データベース処理時間と伝送時間の両方を測定します。 アップストリームのコンポーネントからダウンストリームへの伝送時間は、ダウンストリーム TTFB からアップストリーム TTFB を引いた値で推定できます。たとえば、CloudFront からユーザーへの最初のバイトの伝送時間は、ユーザー TTFB から CloudFront ダウンストリーム TTFB を引いて計算できます。伝送時間が短いほど、ネットワーク状態が良好で、距離が短いことを示します。 ブラウザ内の JavaScript を使用してユーザー TTFB を測定するには、 Resource Timing API が使用できます。この API では、リクエストの開始時刻、DNS 解決時間、TCP および TLS ハンドシェイク、レスポンスの最初のバイトの受信といったリソースの読み込みに関わるさまざまな段階のタイムスタンプを取得できます。これにより、TTFB の計算やリソースの読み込みに関連するその他の有用なタイミング情報の取得が容易になります。 const timings = {}; new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); entries.forEach(entry => { if (entry.responseStart > 0) { timings.userDNS = (entry.domainLookupEnd - entry.domainLookupStart).toFixed(2); timings.userTCP = (entry.connectEnd - entry.connectStart).toFixed(2); timings.userTLS = (entry.requestStart - entry.secureConnectionStart).toFixed(2); timings.userTTFB = (entry.responseStart - entry.requestStart).toFixed(2); } }); }).observe({ type: 'resource', buffered: true }); このコードスニペットは、ウェブページから読み込まれた各リソースの DNS、TCP、TLS、および TTFB のタイミングを取得しています。同様に、 Navigation Timing API を使用して、ブラウザ内のナビゲーションリクエストに対してこれらのタイミングを取得できます。このデータを使用すると、レイテンシが許容範囲内かどうかを判断できるだけでなく、リクエストの DNS、TCP、TLS 各段階の所要時間を分析することもできます。これらのメトリクスは、パケットが移動することになるユーザーとフロントエンドサーバー間のネットワーク距離や、ネットワークの輻輳状態に影響を受けます。これらの値が大きく、800 ミリ秒のベンチマークに近づいている場合は、よりスムーズなユーザーエクスペリエンスのためにネットワーク状態を改善する必要があることを示しています。 CloudFront は、エッジロケーションでユーザーに近い場所でリクエストを終端することにより、ネットワークパフォーマンスを大幅に向上させることができます。 しかし、サーバーサイドが原因のパフォーマンス問題はこのデータでは可視化できません。そこで Server-Timing が役立ちます。 Server-Timing の実装 あらゆるウェブサーバーでは HTTP レスポンスに Server-Timing ヘッダーを含めることができ、サーバーメトリクスを提供します。このヘッダーはすべてのモダンブラウザでサポートされており、 PerformanceServerTiming インターフェースを使用してメトリクスを簡単に解析および取得できます。 CloudFront はすでに Server-Timing をサポートしており、処理に関連するメトリクスを伝達できます。たとえば、cdn-downstream-fbl メトリクスは前述した CloudFront ダウンストリーム TTFB であり、 cdn-upstream-fbl は CloudFront アップストリーム TTFB です。その他の利用可能なメトリクスとその説明については、 開発者ガイド で確認できます。 CloudFront で Server-Timing を有効化するには、 レスポンスヘッダーポリシー を作成します。 Server-Timing ヘッダーパネルの「有効」オプションを切り替え、サンプリングレートを指定します。他のレスポンスヘッダーの追加または削除も必要に応じて設定します。CloudFront の Server-Timing 機能により、CloudFront がリクエストを十分な速さで処理できているかを評価できます。キャッシュヒットの場合、 cdn-downstream-fbl メトリクスの値は小さくなり、これは CloudFront が迅速にレスポンスを開始したことを示します。逆に、このメトリクスの値が大きい場合は、処理が遅いことを示唆し、CloudFront 側に問題があることを示します。キャッシュミスの場合には、 cdn-upstream-connect と cdn-upstream-dns メトリクスを確認して CloudFront からオリジンへの接続時間の値も評価します。これらのメトリクスの値が小さい場合は、リクエストフローにおける次のサーバー(図 1 に示されている Application Load Balancer など)が正常に稼働しており、接続を素早く確立し、CloudFront のオリジン向けサーバーの近くに配置されていることを示唆しています。たいていの場合、 cdn-upstream-connect や cdn-upstream-dns の値は 0 になります。なぜならば、CloudFront の 持続的接続 機能が以前に確立された接続を再利用しているからです。 cdn-upstream-fbl メトリクスは、オリジンからのレスポンスの最初のバイトが CloudFront に到達する速さを示します。このメトリクスの値が大きく、 cdn-upstream-connect と cdn-upstream-dns の値が小さい場合は、 Application Load Balancer の後段にあるオリジン側のシステムに問題が発生し、速いレスポンスを提供できていないことを示します。理想的には、これらのメトリクスがユーザーの経験するレイテンシ(ユーザー TTFB )に大きく影響を与えてはいけません。 CloudFront の Server-Timing ヘッダーは、 CloudFront のダウンストリーム・アップストリーム両方のパフォーマンスに関するインサイトを提供しますが、リクエスト中にオリジンで何が起こったかを直接教えてはくれません。複数の異なるコンポーネントやテクノロジーで構成される現代のオリジンアーキテクチャの多様性を鑑みれば、包括的な理解のためには、それぞれのパフォーマンスタイミング情報を組み込むことが不可欠です。オリジンサーバーからインサイトを抽出するには、オリジンからの CloudFront へのレスポンスに Server-Timing ヘッダーを独自に実装して含めることができます。 CloudFront がこのヘッダーを置き換えることはありません。代わりに、オリジンから受信した Server-Timing ヘッダーに CloudFront が自身のメトリクスを追加します。独自で実装する Server-Timing ヘッダーに含めるタイミングメトリクスとしては、画像の最適化、 API 呼び出し、データベースのクエリ、エッジコンピューティングなどの重要なバックエンドプロセスの測定値が考えられます。たとえば、 PHP を使用してデータベースクエリを実行している場合、次のようにしてクエリの所要時間を測定できます。 $dbReadStartTime = hrtime(true); // Database query goes here $dbReadEndTime = hrtime(true); $dbReadTotalTime = ($dbReadEndTime - $dbReadStartTime) / 1000000; header('Server-Timing: my-query;dur=' . $dbReadTotalTime); こちらのコードスニペットでは、データベース操作の完了にかかった時間を取得し、 Server-Timing ヘッダー内で my-query メトリクスとして伝達しています。データベースは時に過負荷状態になり、パフォーマンスのボトルネックとなることがあるため、このデータはそのようなシナリオを明らかにするのに役立ちます。 Node.js を使用している場合は、 PerformanceServerTiming インターフェース仕様の 例 を参考にして、 Server-Timing ヘッダーを実装してください。 エッジ関数 によって追加になるレイテンシも Node.js を使用している場合と同様の実装で測定を行います。ネットワーク呼び出しを含んだ複雑な処理を行う Lambda@Edge 関数では特に有益です。以下の例では、オリジンレスポンスイベントにアタッチされた Lambda@Edge 関数で Server-Timing ヘッダーの実装をしています: import json import time # CF headers are available in request object for Lambda@Edge functions attached to origin response event only def lambda_handler(event, context): # Get function's start timestamp handler_start_time = time.time() response = event['Records'][0]['cf']['response'] request = event['Records'][0]['cf']['request'] server_timing_value = [] # List of CloudFront headers to include in server timing for additional inisghts cf_headers = ['cloudfront-viewer-country', 'cloudfront-viewer-city', 'cloudfront-viewer-asn'] # Iterate over each header name and construct the value for the Server-Timing header for header_name in cf_headers: if header_name in request['headers']: header_value = request['headers'][header_name][0]['value'] server_timing_value.append('{}; desc="{}"'.format(header_name, header_value)) # Function's logic goes here # Get function's stop timestamp handler_stop_time = time.time() handler_duration = round((handler_stop_time - handler_start_time) * 1000, 2) server_timing_value.append('{}; dur={}'.format("my-function", handler_duration)) if server_timing_value: # Construct the Server-Timing header server_timing = [{ "key": "Server-Timing", "value": ', '.join(server_timing_value) }] # Add or append the Server-Timing header if 'server-timing' in response['headers']: response['headers']['server-timing'][0]['value'] += ', ' + ', '.join(server_timing_value) else: response['headers']['server-timing'] = server_timing print("Server-Timing:", response['headers']['server-timing']) return response 注目すべき点として、このコードで追加されるメトリクスは、ハンドラーコードの実行時間のみであり、その他の Lambda のタイミングは除外されています。なお、オリジンアーキテクチャの情報が悪意のある攻撃者に悪用されるおそれがあるため、メトリクスには意図的に抽象的な名前を使用しています。 また、ユーザーの地理的位置情報と ASN 番号に関するインサイトを Server-Timing ヘッダーに追加したことにも注目すべき点です。 そして、 クライアントサイドでも Server Timing を取得できるように serverTiming プロパティを使用してコードを拡張します。以下が修正されたコードスニペットとなります。 // Creating a new PerformanceObserver to monitor performance entries new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); for (const entry of entries) { // Object to store timings for various stages const timings = { userDNS: null, // User DNS resolution time userTCP: null, // User TCP handshake time userTLS: null, // User TLS handshake time CFDNS: null, // CDN DNS resolution time CFUpstreamHandshake: null, // CDN upstream TCP handshake time MyQuery: null, // Query time CFUpstreamTTFB: null, // CDN upstream Time To First Byte (TTFB) MyFunction: null, // Function execution time CFDownstreamTTFB: null, // CDN downstream TTFB userTTFB: null, // User Time To First Byte (TTFB) CFRID: null, // CDN Request ID CFCacheStatus: null, // CDN Cache status (Hit or Miss) UserASN: null // User Autonomous System Number (ASN) }; // Iterating through server timing entries for the current performance entry entry.serverTiming.forEach((serverEntry) => { switch (serverEntry.name) { case 'cdn-rid': timings.CFRID = serverEntry.description; break; case 'cdn-cache-miss': timings.CFCacheStatus = "Miss"; break; case 'cdn-cache-hit': timings.CFCacheStatus = "Hit"; break; case 'cdn-upstream-connect': timings.CFUpstreamHandshake = serverEntry.duration; break; case 'cdn-downstream-fbl': timings.CFDownstreamTTFB = serverEntry.duration; break; case 'cdn-upstream-dns': timings.CFDNS = serverEntry.duration; break; case 'cdn-upstream-fbl': timings.CFUpstreamTTFB = serverEntry.duration; break; case 'my-query': timings.MyQuery = serverEntry.duration; break; case 'my-function': timings.MyFunction = serverEntry.duration; break; case 'cloudfront-viewer-asn': timings.UserASN = serverEntry.description; break; } }); // Calculating user-specific timings if the response not served from the local cache if (entry.responseStart > 0) { timings.userDNS = (entry.domainLookupEnd - entry.domainLookupStart).toFixed(2); timings.userTCP = (entry.connectEnd - entry.connectStart).toFixed(2); timings.userTLS = (entry.requestStart - entry.secureConnectionStart).toFixed(2); timings.userTTFB = (entry.responseStart - entry.requestStart).toFixed(2); // Logging metrics for the current entry console.log("Metrics for:", entry.name); console.log("userDNS:", timings.userDNS); console.log("userTCP:", timings.userTCP); console.log("userTLS:", timings.userTLS); console.log("CFDNS:", timings.CFDNS); console.log("CFUpstreamHandshake:", timings.CFUpstreamHandshake); console.log("DBQuery:", timings.MyQuery); console.log("CFUpstreamTTFB:", timings.CFUpstreamTTFB); console.log("lambdaEdge:", timings.MyFunction); console.log("CFDownstreamTTFB:", timings.CFDownstreamTTFB); console.log("userTTFB:", timings.userTTFB); console.log("CFRID:", timings.CFRID); console.log("CFCacheStatus:", timings.CFCacheStatus); console.log("UserASN:", timings.UserASN); console.log("------------------------------------------------------"); } } }).observe({ type: 'resource', // Observing resource-related performance entries buffered: true }); この改良されたコードスニペットでは、Server Timing を取得したのちに、クライアントメトリクスと共に timings オブジェクトに統合しています。これにより、クライアントサイドとサーバーサイドの両方のリクエスト – レスポンスサイクルにおける包括的なパフォーマンスのインサイトを一箇所にまとめることができました。以下は console.log の出力例です。 Metrics for: https://d1234.cloudfront.net/script.php userDNS: 0.00 userTCP: 0.00 userTLS: 5.00 CFDNS: 0 CFUpstreamHandshake: 88 DBQuery: 0.538685 CFUpstreamTTFB: 178 lambdaEdge: 0.09 CFDownstreamTTFB: 229 userTTFB: 233.10 CFRID: mRq-Uvr__3OBDo0IX9ELV5Lrk3lF-bOp4eOIqTEXlFkFn0wIWPKgpA== CFCacheStatus: Miss UserASN: 1257 この例を見てみましょう。 CloudFront の Lambda@Edge 関数の実行と、オリジンサーバーのデータベースクエリの両方を合わせて、最初のバイトをネットワークに送信するまでに 229 ミリ秒かかりました( CFDownstreamTTFB )。最初のバイトは 233 ミリ秒後にクライアントデバイスに到達しているので( userTTFB )、伝送時間は 4 ミリ秒であることを示しています。クライアントデバイスは、以前に確立された TCP および TLS 接続を再利用しており( userTCP 、 userTLS )、 CloudFront の IP アドレスをキャッシュしていました( userDNS )。 CloudFront はオリジンに向けて新しい TCP 接続を確立する際( CFUpstreamHandshake )、 88 ミリ秒かかりました。オリジンはリクエストを 90 ミリ秒以内( CFUpstreamTTFB – CFUpstreamHandshake )で処理しており、素早くレスポンスの最初のバイトを返していることがわかります。結論として、エンドユーザーの全体的なレイテンシは推奨値の 800 ミリ秒を下回っており、満足のいくものであると言えます。 Server-Timing を他のデータで拡充する Server-Timing ヘッダーは、サーバーサイドの処理時間を伝達するために設計されたものですが、その構文は単にその用途に限定されたものではありません。たとえば、CloudFront ではキャッシュのステータスや内部のユニークなリクエスト ID をメトリクスに含んでいます。これらのデータは CloudFront の処理を正確に分析するために不可欠なものです。同様にして、リクエストの経路に関してインサイトを提供するメトリクスを加えて、 Server-Timing ヘッダーを独自に拡充することができます。たとえば、ログを見つけやすくするために、クラスター内のサーバーの内部 ID を追加することもできます。ユーザーの地理的位置情報やデバイスタイプも追加はできますが、 CloudFront のヘッダー の使用で実現できます。先述の Lambda@Edge 関数で使用方法を示した通りです。これらのヘッダーは、オリジンレスポンスイベントに関連付けられた Lambda@Edge 関数、もしくは、ビューワーレスポンスイベントやビューワーリクエストイベントに関連付けられた CloudFront 関数のリクエストオブジェクトで利用できます。オリジンリクエストポリシーでこれらのヘッダーを有効化すると、 オリジンウェブサーバーが CloudFront からのリクエストに含まれるこれらのヘッダーを取り扱えるようになります。こうして、拡充されたメトリクスを Server-Timing ヘッダーに統合するのです。 Amazon CloudWatch で結果を分析する Server-Timing ヘッダーは、パフォーマンス問題を特定し、根本原因を突き止めるのに有用ですが、ウェブサイトパフォーマンスの他の重要な側面に関するインサイトは提供しません。たとえば、JavaScript 実行に関連するエラーや、累積レイアウトシフト( Cumulative Layout Shift )などの特定の Web Vitals メトリクスは、このソリューションでは直接キャプチャされません。もしすでにリアルユーザーモニタリング( RUM ) ベースのウェブサイトモニタリングソリューションを利用しているのであれば、 Server-Timing ヘッダーを統合することで、 既存の手法を置き換えたり、パフォーマンスモニタリングを Server-Timing のみに限定したりするのではなく、既存の手法を補完できます。包括的なウェブサイトモニタリングソリューションの一例として Amazon CloudWatch RUM があります。 CloudWatch RUM のインサイトを前述の手法で拡張するには、 Server-Timing ヘッダーから抽出したメトリクスを取得する カスタムイベント を作成し、 CloudWatch RUM クライアント経由で CloudWatch に送信します。このアプローチにより、すべての CloudWatch RUM のインサイトと Server-Timing を同じサービス内に統合し、両方のデータセットをシームレスに分析できるようになります。 前述のコードスニペットについて、 Server-Timing ヘッダーから抽出したデータとクライアントサイドの測定値を使用して、CloudWatch RUM クライアントを介してカスタムイベントを記録する方法の例を以下に示します: // Sending performance data to a remote server cwr('recordEvent', { type: 'my-server-timing', data: { current_url: entry.name, ...timings // Spread operator to include all timings } }); この例では、 timings オブジェクトのすべてのプロパティと値を、 cwr 関数 に送信される data オブジェクトに含めています。これは、特定のエントリに対してキャプチャされたすべてのタイミングが、 current_url と共に送信されることを意味します。 カスタムイベントは CloudWatch Logs に記録され、CloudWatch Logs Insights を使用してクエリを実行できます。さらに、 メトリクスフィルター を使用して CloudWatch メトリクスを作成して、モニタリング目的のメトリクスアラームを設定することができます。 上記のコードで収集しているタイミングのカスタムメトリクス実装の例を以下に示します: { "event_timestamp": 1710929230000, "event_type": "my-server-timing", "event_id": "9ae82980-4bfb-47f5-8183-b241379e09e1", "event_version": "1.0.0", "log_stream": "2024-03-20T03", "application_id": "c27d1cef-e531-45ad-9bc4-8e03a716c775", "application_version": "1.0.0", "metadata": { "version": "1.0.0", "browserLanguage": "en", "browserName": "Chrome", "browserVersion": "123.0.0.0", "osName": "Mac OS", "osVersion": "10.15.7", "deviceType": "desktop", "platformType": "web", "pageId": "/", "interaction": 0, "title": "TTFB Demo", "domain": "d1234.cloudfront.net", "aws:client": "arw-script", "aws:clientVersion": "1.17.0", "countryCode": "SE", "subdivisionCode": "AB" }, "user_details": { "sessionId": "c9d2514a-8884-4b32-aec0-25203f213f84", "userId": "0f7f2bf3-c9b7-46ab-bc9e-2ff53864ea74" }, "event_details": { "current_url": "https://d1234.cloudfront.net/getmeal.php", "userDNS": "0.00", "userTCP": "0.00", "userTLS": "9.50", "CFDNS": 0, "CFUpstreamHandshake": 90, "MyQuery": 0.517874, "CFUpstreamTTFB": 180, "MyFunction": 0.12, "CFDownstreamTTFB": 233, "userTTFB": "239.30", "CFRID": "ujYncZYVJeIOk6fI7ApFuNt-mJoh8hfL3nZPgAj77z7RdtSzNMTcqQ==", "CFCacheStatus": "Miss", "UserASN": "1257" } } このメトリクスに基づいて、ユーザー TTFB のメトリクス用に以下のフィルターパターンを作成できます: { $.event_details.userTTFB= * && $.event_details.CFCacheStatus= * && $.event_details.UserASN= * && $.metadata.countryCode=*} これにより、国コード、 ASN 番号、 CloudFront キャッシュステータスなどのディメンションを持つユーザー TTFB のメトリクスを作成できます。その後、このメトリクスに対してアラートを作成し、推奨される 800 ミリ秒などの事前定義された静的な閾値を超えた場合に通知を受け取ることができます。また、 CloudWatch 異常検出 を利用することもできます。 最適化とコスト 重要なこととして、Server-Timing ヘッダーがレスポンスサイズを増加させる点を認識してください。これは、 CloudFront のデータ転送アウトに関するコストや、分析システム内でのデータの保存や処理に影響を与える可能性があります。たとえば、前述の Server-Timing ヘッダーの値は約 350 バイトに相当しますが、仮に 100 万リクエストを仮定した場合、追加で 0.325 ギガバイトのデータを転送することになります。ウェブサイトが受信するリクエスト数によっては、これが大きなコストになる場合とならない場合があります。ただし、 Server-Timing に必須情報、特にアクション可能なデータのみを含めることで、このコストを削減できます。たとえば、主にパフォーマンス低下の検出に Server-Timing が必要な場合は、推奨しきい値である 800 ミリ秒を超えるリクエストにのみ追加することを選択できます。さらに、インタラクティブフォームや API 呼び出しなど、ウェブサイト上の重要なリソースの読み込みにのみ適用することで、使用量を最小限に抑えることもできます。これには、クライアントサイドの JavaScript コードで該当するメトリクスに必要なフィルターを実装することで実現できます。 まとめ この投稿では、ウェブサイトパフォーマンスモニタリングにおける TTFB の重要性を探求し、リクエスト-レスポンスサイクルにおいて詳細なインサイトを提供する Server-Timing ヘッダーの活用方法を実証しました。レイテンシの測定とサーバーサイドメトリクス( CloudFront の処理時間、オリジンサーバーのレスポンス時間など)の取得により、ウェブサイトの所有者はパフォーマンス問題の根本原因を特定し、ウェブサイトの最適化に向けた積極的な対策を講じることができます。 本記事は「 How to identify website performance bottlenecks by measuring time to first byte latency and using Server-Timing header 」と題された記事の翻訳となります。 翻訳はプロフェッショナルサービスの 鈴木(隆) が担当しました。
AI コーディングアシスタントに簡単なこと、例えば関数名の変更やファイルの移動を依頼すると、突然復旧作業に追われることがあります。インポートが壊れたり、参照が存在しないファイルを指したりします。5 分前にコンパイルできていたコードベースが、至る所でエラーを投げ始めます。20 秒で終わるはずのリファクタリングが、5 分間のデバッグとクリーンアップセッションに変わってしまうのです。 エージェントにとってリファクタリングが難しい理由 リファクタリングは単なる大規模な検索置換ではありません。コードベースのセマンティック構造全体にわたるグラフトラバーサル問題なのです。関数名を変更すると、変更は連鎖します。ワークスペース全体のすべての呼び出し箇所、それを参照する型定義とインターフェース、import/export 文、テスト、そして(オプションで)ドキュメントとコメント。ファイルの移動はさらに複雑な波及効果を引き起こし、すべての依存ファイルのインポートパス、バレルファイル( index.ts )と再エクスポート、 tsconfig パスやバンドラー設定に組み込まれたモジュール解決の前提、Webpack 設定のような散在する設定ファイルなどに影響します。ここに根本的なミスマッチがあります。LLM はパターンマッチングを通じてもっともらしいコードを生成することに優れていますが、リファクタリングは もっともらしさよりも精度 を要求します。これは創造的なタスクではなく、シンボルの関係、言語固有のセマンティクス、プロジェクトの依存関係グラフの正確な理解を必要とする制約充足問題なのです。「正しく見える」が、深くネストさ れたモジュールの 1 つのインポートを見逃したエージェントは、単に小さなエラーを犯しただけではありません。本番環境まで表面化しないランタイム障害を導入したのです。これが、どれほど洗練されていたとしても、テキスト生成が構造的なコード変換において信頼性に欠けるツールである理由です。 問題:エージェントが効率的ではなく、非効率に動作するとき 多くの AI エージェントがリファクタリングでつまずくのは、 構造的 な編集を テキスト 編集として扱うからです。開発者が直面し続けている失敗モードをいくつか紹介します。 依頼内容: 「このメソッドの名前を変更して」 従来の失敗: エージェントはメソッド定義を更新しましたが、プロジェクト全体の呼び出し箇所を見逃しました。プロンプトで参照を更新するよう明示的に依頼した場合でも、プロセスは遅くエラーが発生しやすいループになりました。古い名前を検索して置換するのです。このプロンプトを考えてみましょう。 expression.js の get_loose_identifier を、それが何をするかをよりよく反映するように名前変更してください。このシンボルの名前変更は 4 つのファイルに伝播し、8 つの参照と 3 つのインポートに影響します。次の図の左側(従来のアプローチ)は、専用のリファクタリングツールなしでこの操作がどのように展開されるかを示しています。最初のファイル( expression.js )でシンボルの名前を変更した後、エージェントはコードベースで get_loose_identifier を検索し、複数の LLM 呼び出しとツール呼び出しを通じて CallExpression.js と AssignmentExpression.js を更新します。努力したにもかかわらず、残りの参照を見逃しています。 Kiro の対処法: 開発者が IDE でこのタスクを手動で実行する方法を考えてみましょう。 get_loose_identifier で F2 を押し、新しい名前を入力して、Enter を押します。IDE は、コードベース全体の 8 つの参照と 3 つのインポートすべてを更新しながら、自動的に名前変更を実行します。これがまさにセマンティックリネームツールが行うことです。次の図の右側(新しいアプローチ)は、Kiro が単一のツール呼び出しで名前変更全体を適切に実行する方法を示しています。 依頼内容: 「このファイルの lint エラーを修正して」 従来の失敗: エージェントは linter の出力をテキスト編集の ToDo リストとして扱いました。1 つのファイルでシグネチャの関数名を camelCase から snake_case に変更しましたが、他のファイルで「参照が見つからない」や「インポートが見つからない」エラーを導入しました。すべての使用箇所への変更の伝播に失敗したのです。 Kiro の対処法: ユーザーが直接名前変更を依頼しなくても、エージェントがセマンティックリネームツールから恩恵を受ける例を示します。ユーザーはエージェントに「 text_helpers.py の lint エラーを修正して」と依頼します。lint エラーは、 utils/text_helpers.py 内の normalizeText と slugifyTitle を snake_case に変更する必要があることを示しています。コードベースの部分的なスナップショットを以下に示します。 これらの修正をテキスト編集として扱うエージェントは、関数定義の名前を変更し、ローカル参照を修正するかもしれませんが、他の場所のインポートや呼び出し箇所を見逃す可能性が高く、実行時に ImportError / NameError を引き起こします。セマンティックリネームツールを使用することで、Kiro は定義だけでなく、 api/routes.py と services/indexer.py のインポートと呼び出しも更新します。以下の画像の通りです。 依頼内容: 「コンポーネントを再編成して – Button.tsx を src/components/ から src/shared/ui/ に移動して」 従来の失敗: エージェントはタスクを単純なファイル操作として扱いました。ファイルの移動は成功しましたが、古い場所を指すすべてのインポート文が壊れています。エージェントはその後、検索置換操作でファイルごとにインポートを修正しようとしましたが、動的インポートを見逃しました。 import('../components/Button') 。 Kiro の対処法: Kiro がインポートパスを自動的に更新する具体的な例を示します。図は、プロジェクト構造の部分的なスナップショットと依存するコードスニペットの一部を示しています。 Button.tsx を src/components/ から src/shared/ui/ に移動した後、Kiro は移動したファイルに関連するすべてのインポート文を自動的に更新します。 主な利点: 組み込みの言語サーバーが編集を処理するため、手動の検索置換は不要です。 言語認識:TypeScript/JavaScript モジュール解決を理解します。 より安全:動作するコードを壊す可能性が低くなります。 エッジケースの処理:パスエイリアス、モノレポなどに対応します。 これは、VSCode のエクスプローラーでファイルをドラッグアンドドロップしたときに起こることとまったく同じです。セマンティックリネームツールはエージェントベースの同等機能です! Kiro エージェントのリファクタリング方法 IDE は、エージェント AI の台頭以前にこの問題をすでに解決していました。VSCode でシンボルの名前を変更するために F2 を押すと、IDE は推測しません。コードの構造を理解する言語サーバーに相談し、ワークスペース全体の編集を計算し、安全に適用します。VSCode のワークスペース編集機能により、単なるテキストパターンではなく、コードの構造を理解するプログラマブルなセマンティック検索置換が可能になります。Kiro エージェントは、LLM 推論だけでリファクタリングをシミュレートしようとはしません。代わりに、エージェントは上記と同じメカニズムを使用して、これらの実証済みの IDE 機能をプログラム的に公開する 2 つの新しいリファクタリングツールを登録します。エージェントがシンボルの名前を変更したりファイルを移動したりする必要がある場合、意図をインテリジェントに認識し、適切なリファクタリングツールを選択して呼び出します。エージェントはリファクタリングワークフローを調整し、IDE の言語サーバーが正確性の検証を支援します。 これらのエージェント登録リファクタリングツールが内部でどのように機能するかを見てみましょう。 セマンティックリネームツール:正しいリネームを実現 このツールは、VSCode のシンボル名前変更 API に直接接続します。F2 を押したときに使用するのと同じものです。 vscode.prepareRename を使用してシンボルが名前変更可能かどうかを検証し(例:キーワードではない)、 vscode.executeDocumentRenameProvider を使用してワークスペース全体で必要なすべての変更を含むワークスペース編集を生成します。TypeScript、JavaScript、TSX、JSX の場合、組み込みの VSCode 名前変更プロバイダーがすべてを処理します。Python、Go、Java などの場合、ツールはインストールされた言語拡張機能とそれらが提供する言語サーバーに依存します。 スマートリロケートツール:すべてを壊さずにファイルを移動 このツールは、VSCode のファイル移動機能を使用して、すべての参照を自動的に更新しながらファイルを再配置します。VSCode のエクスプローラーでドラッグアンドドロップするのと同等のプログラム的な操作ですが、エージェントがあなたのために実行できます。 vscode.WorkspaceEdit.renameFile と vscode.workspace.applyEdit を使用して、ツールは複数のファイルにわたる包括的な変更を生成し、影響を受けるインポートを更新します。 これが重要な理由 創造性よりも精度: リファクタリングは、コードがどのように見えるべきかを LLM に想像させる必要はありません。コードが 実際に何であるか を理解し、外科的に変更できるツールが必要なのです。 実証済みのインフラストラクチャを通じた信頼: これらは実験的な LLM 機能ではなく、開発者が日常的にすでに依存しているリファクタリングインフラストラクチャとの直接統合です。F2 を押したときに機能すれば、エージェントが実行したときにも機能します。 言語に依存しない: 重い作業は言語サーバーによって行われるため、このアプローチは技術スタックと言語全体に一般化されます。 生産性の維持: 20 秒の手動リファクタリングが、5 分間の AI 生成リカバリーミッションになるべきではありません。適切なツールを使用すれば、操作は高速でアトミックなままです。 より大きな視点 構築による正確性という私たちの哲学に基づいて、 IDE 診断統合 を導いたのと同じ原則で、VSCode のリファクタリング機能の全範囲をカバーするようにこのアプローチを拡張しています。エラーが複合する前にキャッチするためにリアルタイム診断を統合したのと同様に、これらの実証済みの決定論的 IDE 機能を、新しい内部スマートリロケートおよびセマンティックリネームツールに拡張しました。 しかし、リファクタリング機能は名前変更と再配置で止まりません。VSCode の言語サーバーは、エージェントが活用すべき豊富な自動コード変換スイートを提供します。コードブロックを再利用可能な関数に抽出するメソッド/関数の抽出、コードを簡素化する変数/関数のインライン化、すべての呼び出し箇所でメソッドパラメータを更新するシグネチャの変更、アロー関数への変換やその他の言語固有の変換は有力な候補です。 このアプローチを取ることで、エージェントが実行される基盤に正確性、セキュリティ、信頼性を組み込むことができます。これらのツールで確立したパターンは、ツールキットへの新しい追加を導きます。LLM に脆弱なテキスト置換スクリプトを生成するよう依頼する代わりに、インテリジェントなコーディングエージェントは、開発者がすでに信頼しているこれらの実証済みの IDE 操作を活用し続けます。IDE が正しく実行する方法を知っているとき、私たちはそれに作業をさせます。エージェントがより有能になるにつれて、これはその出力をより信頼できるものにするための良いテクニックでもあります。 違いを体験する準備はできましたか? Kiro を無料で始めて 、開発ワークフローをどのように変革できるかを確認してください。 Discord の成長するコミュニティに参加して、フィードバックを共有し、質問をし、AI 支援コーディングで構築している他の開発者とつながりましょう。 謝辞 エンジニアリングの洞察と貴重なフィードバックを提供してくれた Al Harris に感謝します。 本記事は 2026 年 2 月 5 日に公開された Pardis Pashakhanloo と Rajdeep Mukherjee による “ Refactoring made right: how program analysis makes AI agents safe and reliable ” を翻訳したものです。翻訳は Solutions Architect の吉村が担当いたしました。























