
サーバーサイド
イベント

マガジン
技術ブログ
ウェブサイトのパフォーマンス問題はよくあることですが、根本原因の特定は困難な作業となります。この投稿では、 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 」と題された記事の翻訳となります。 翻訳はプロフェッショナルサービスの 鈴木(隆) が担当しました。
はじめに こんにちは!セーフィー株式会社でサーバーサイドエンジニアをしている坂上(@Bobtaroh)です。 セーフィー開発本部では2025年9月に「生成AI利用推進プロジェクト」を立ち上げ、20名以上のメンバーが自発的に参加し、以下の5つの軸で活動しています。 情報収集活性化 技術検証・導入 ナレッジDB管理 生成AI利用活性化 ←今回はここ! 社外発信 プロジェクトの詳細はこちらの記事をご覧ください。 今回は「生成AI利用活性化」の一環として実施したClaude Codeライブコーディングについてご紹介します。 ライブコーディングイベントについて フロントエンドエンジニア
急成長を遂げるメガベンチャー企業の現場では、マイクロサービス化やインフラの複雑化に伴い、「サーバーは正常に稼働しているはずなのに、なぜかユーザーから『使えない』という報告が届く」といった課題に直面することが増えています。 従来の内部リソース(CPUやメモリなど)を対象とした監視だけでは、ネットワーク経路の不備やフロントエンドの描画トラブルといった「ユーザー側の体感品質」までを把握することは困難です。 そこで注目されているのが「シンセティックモニタリング(外形監視)」です。 そこで今回は疑似ユーザーを用いて外部から能動的にサービスを監視するこの手法について、その仕組みから具体的な活用シーン、さらには実運用で陥りやすい罠を防ぐための設計ポイントまでを詳しく解説します。 属人化や場当たり的な改善から脱却し、プロダクト全体の信頼性を底上げするためのガイドとして活用してください。 import haihaiInquiryFormClient from "https://form-gw.hm-f.jp/js/haihai.inquiry_form.client.js";haihaiInquiryFormClient.create({baseURL: "https://form-gw.hm-f.jp",formUUID: "927d2c4e-f06c-45b1-bd36-0240e55ccf72",}) ▼システム開発の流れに関する記事はこちら▼ システム開発の全体像をわかりやすく解説!工程と役割を入門ガイド シンセティックモニタリング(外形監視)とは シンセティックモニタリングとは、システムのネットワーク外部から、ユーザーの挙動を模したスクリプトやシミュレーションを用いて稼働状況を機械的に監視する手法を指します。 一般的には「外形監視」と呼ばれることが多いですが、英語のSynthetic Monitoringを直訳した「シンセティック監視」や「合成監視」と表現されることもあります。 これらはすべて、実際のユーザーがアクセスしてくるのと同様の経路で、外部のサーバーや拠点から定期的にリクエストを送り、アプリケーションが正しく動作しているかを確認する仕組みを指しています。 従来のシステム監視は、サーバーのCPU利用率やメモリ消費量といった内部のリソース状況を把握することに主眼を置いてきました。 しかし、マイクロサービス化が進みインフラが複雑化した現代のプロダクトにおいては、内部が正常であってもネットワーク経路や外部APIの不調により、ユーザーがサービスを利用できないケースが増えています。 シンセティックモニタリングは、まさにユーザーと同じ視点からプロダクトを眺めることで、システム内部の数値だけでは見えてこない「外から見た健康状態」を可視化します。 何を見ているのか この監視手法で重点的に計測されるのは、可用性や応答時間、表示速度、エラーの有無といった、ユーザーが直接的に感じる体感品質です。 例えば特定の重要な導線においてページが完全に表示されるまでに何秒かかっているか、ボタンをクリックした際に期待通りのレスポンスが返ってくるかといった項目を数値化します。 これにより、インフラ層の監視だけでは見逃してしまいがちな「サーバーは動いているが、ユーザー体験は著しく損なわれている」という状況をいち早くキャッチすることが可能になります。 特に複雑なフロントエンドの処理やサードパーティ製のスクリプトを多用しているプロダクトでは、バックエンドのログにエラーが出ていなくても、ブラウザ側で描画が止まっているといった事態が起こり得ます。 シンセティックモニタリングは、定期的に特定のシナリオを実行し続けることで、こうしたサイレントな劣化を許容範囲外の数値として検知します。 品質の全体最適を目指すQAマネージャーにとっては、開発チームが気づかないようなエンドユーザー視点の不備を定量的に証明するための、客観的なデータソースとなります。 どんなときに必要か? シンセティックモニタリングが威力を発揮するのは、単なる致命的な障害の検知だけではありません。 軽微なパフォーマンス低下や、特定の機能が期待通りに動作しないといったUX劣化を、実際のユーザーから報告を受ける前に早期発見したい場合に極めて有効です。 例えばリリース直後の新機能が本番環境で意図したパフォーマンスを出せているか、あるいはグローバル展開しているサービスにおいて特定の地域からのみ接続が遅くなっていないかといった確認に、定時実行されるシミュレーションが役立ちます。 また、24時間365日一定の負荷をかけ続ける特性を活かし、リリース作業やインフラ構成の変更に伴う影響をリアルタイムで追跡する際にも必要とされます。 実ユーザーのアクセスが少ない深夜帯であっても、合成されたリクエストが継続的に流れるため、メンテナンス後にサービスが正常に復旧したかを即座に判断できます。 場当たり的な対応から脱却し、安定した品質を維持し続けるためのガードレールとして、また経営層や他部門に対してサービスレベルを客観的に示す指標として、欠かせない役割を担います。 シンセティックモニタリングの仕組み シンセティックモニタリングの根幹は、プログラムされたボットが特定のスクリプトに従い、あたかも本物の人間が操作しているかのような「疑似ユーザー」として対象のサービスにアクセスすることにあります。 このボットは、世界各地に点在する監視拠点やクラウド上の実行環境から放たれ、あらかじめ定義された動作を忠実に行うことで、システムの反応やパフォーマンスをデータ化します。 実ユーザーのアクセスを待つ受動的な監視とは異なり、能動的にリクエストを生成し続けるため、アクセスが少ない時間帯でも安定して品質を測定できるのが大きな特徴です。 基本の流れ 監視プロセスの基本は、外部環境から対象サイトへ定期的にアクセスし、その結果を記録して異常があればアラートを飛ばすというサイクルで成り立っています。 具体的には設定された数分おきなどのインターバルでボットがリクエストを送信し、レスポンスコードの妥当性や応答速度を計測します。 ここで重要なのは、監視サーバーを対象のアプリケーションが稼働しているのと同じインフラ内ではなく、必ず「サイトとは別のネットワーク(外側)」に配置することです。 これにより、内部ネットワーク内では気づけないプロバイダー間の経路障害や、CDNの配信トラブルといった外生的な要因によるサービス停止も確実に捉えられるようになります。 本物のブラウザ挙動に近づけるポイント 単一のHTMLファイルが正常に取得できるかを確認するだけでは、現代の動的なウェブアプリケーションの品質を担保するには不十分です。 実効性のある監視を行うためには、ページを表示する際に必要となるJavaScript、画像、CSS、フォントといった全てのリソースを読み込み、ブラウザ上でレンダリングされるまでの時間を計測する考え方が求められます。 最近の監視ツールはヘッドレスブラウザを利用してこれらを実行するため、スクリプトの実行エラーでコンテンツが表示されないといった、サーバーサイドのログだけでは判別できないフロントエンド側の不備も、実際のユーザー体験に近い形で数値化することが可能です。 シナリオ監視の考え方 より高度な品質管理を実現するのが、単一URLのチェックに留まらない「シナリオ監視」という手法です。 これは、ユーザーがサイトを訪れてから離脱するまでの主要なフロー、例えば「トップページからログインし、商品を検索してカートに入れ、決済を完了させる」といった一連の動作をステップごとにシミュレーションします。 文字入力やボタンクリックなどの具体的なアクションを再現することで、特定の画面遷移で発生する遅延や機能不全をピンポイントで検知できます。 ビジネス上最も重要なコンバージョン導線を常時監視下に置くことは、QAマネージャーが全体最適な品質保証体制を築く上で、経営へのインパクトを直接的に守る強力な武器となります。 シンセティックモニタリングの種類 シンセティックモニタリングは、単純な死活監視から複雑なユーザー行動のシミュレーションまで、その目的と深さに応じていくつかの種類に分けられます。 何をどこまで監視するかという範囲は、単一のURLの稼働確認から、複数のページを跨ぐカスタマージャーニー全体の正常性確認まで多岐にわたります。 メガベンチャーのようにサービスが大規模化し、マイクロサービスが複雑に絡み合う環境では、全ての監視を一律にするのではなく、各コンポーネントの重要度や特性に合わせてこれらの手法を適切に組み合わせることが、全体最適な品質設計の第一歩となります。 URL監視 URL監視は、最も基本的かつ即効性のある監視手法です。 特定のURLに対してHTTPリクエストを送り、サーバーから返ってくるレスポンスコードが正常(200 OKなど)であるか、また期待する文字列がレスポンスボディに含まれているかを直接的に確認します。 これに加え、リクエストを投げてから最初の1バイトが返ってくるまでの時間(TTFB)などを計測することで、サービスが「最低限提供可能な状態にあるか」を評価します。 APIエンドポイントや静的なランディングページの死活確認に適しており、構成がシンプルであるため、大量のマイクロサービス群を横断的に俯瞰する際のベースラインとして非常に効率的です。 Web監視 Web監視は、単なるサーバーの応答確認に留まらず、HTTP接続を通じて実際のページ表示に近い形で応答を取得し、ネットワーク区間を含めたパフォーマンスを観測する手法です。 DNSの解決時間、TCPコネクションの確立時間、SSLハンドシェイクの時間といった詳細なメトリクスを分解して取得できるため、ボトルネックがアプリケーション層にあるのか、あるいはネットワークインフラやCDNの経路にあるのかを切り分けるのに役立ちます。 ユーザーがページを開く際に通過するインターネット上の各区間を可視化することで、システム内部の監視だけでは説明しきれない「体感の重さ」の原因を論理的に特定できるようになります。 Webシナリオ監視 Webシナリオ監視は、実際のブラウザ上で動作するスクリプトを用い、ユーザーの操作を忠実に再現して、画面遷移やUI操作への反応を追う高度な手法です。 ログイン、検索、カート投入、決済といった一連の流れにおいて、ボタンのクリックやフォームへの入力が期待通りに処理され、視覚的に正しい結果が表示されるかを監視します。 単一の通信だけでは捉えきれない、非同期処理による描画遅延やスクリプトエラーによる進行不可といった、より深いレイヤーのユーザー体験を保護できます。 QAリードとして、事業に直結する重要な導線の品質を、リリース後も担保し続けるための最も確実な手段といえます。 実施形態 シンセティックモニタリングの実施形態には、大きく分けてSaaS型とオンプレ型(自社設置型)の2つの選択肢があります。 SaaS型は、サービス提供側が世界各地に保有する監視拠点からリクエストを飛ばすため、導入が極めて迅速であり、異なるプロバイダーや地理的に分散した拠点からの接続性を容易に検証できるのが大きなメリットです。 一方で、自社の社内ネットワーク内からのみアクセス可能なシステムや、特定のセキュリティ要件が厳しい環境下では、自社内に監視エージェントを設置するオンプレ型が選ばれることもあります。 メガベンチャーにおける全体最適を考えるならば、外部からの視点が必要なエンドユーザー向け機能にはSaaS型を、内部基盤の安定には自社設置型を使い分けるハイブリッドな構成が有効です。 何がうれしい?メリットと限界 シンセティックモニタリングを導入する最大の意義は、品質保証の範囲をリリース前のテストフェーズから、本番環境の継続的な品質監視へと拡張できる点にあります。 メガベンチャーのような変化の激しい環境において、システムが「動いているはず」という推測ではなく、「ユーザーから見て正しく動いている」という事実を常時把握することは、全体最適を担うリーダーにとって強力な拠り所となります。 これにより、各チームでバラバラになりがちな品質基準を、ユーザー体験という共通の軸で統合し、組織全体の信頼性を底上げすることが可能になります。 主なメリット 具体的なメリットとしてまず挙げられるのが、24時間365日の定期実行による障害や遅延の早期検知です。 実ユーザーのアクセスが途絶える深夜や早朝であっても、ボットが一定間隔でリクエストを送り続けるため、アクセスの集中する日中が始まる前に異常を察知し、迅速にアラートを飛ばすことができます。 また、数値化された応答時間を通じてユーザー視点でのUX劣化を客観的に把握できるため、ページ読み込みの遅延による離脱や売上低下といったビジネスリスクに対し、影響が深刻化する前に先回りして手を打てるようになります。 さらに、近年重要視されているオブザーバビリティの向上や、SRE文脈におけるSLO(サービスレベル目標)およびSLI(サービスレベル指標)の運用においても、外形からの計測値は非常に信頼性の高いデータソースとして機能し、経営層やPdMと同じ言葉で品質を語るための基盤となります。 デメリット/注意点 一方で、この手法には限界や注意点も存在します。 ボットによるシミュレーションである以上、デバイス、ブラウザ、ネットワーク環境が多岐にわたる実ユーザーの体験を完全に再現することはできません。 想定外の操作や、特定のユーザー属性でのみ発生するレアケースといった事象の検知には不向きです。 またプロダクトのUI変更に合わせてテストシナリオを更新し続ける保守コストが発生し、監視対象やシナリオが複雑化するほど、ツールのライセンス費用や管理工数といった運用コストも増大します。 加えて、外形監視は「何が起きているか」を検知するのには優れていますが、バックエンドのどのマイクロサービスが原因で遅延しているかといった詳細な原因特定には至りません。 そのため、根本解決には内部監視やログ分析との併用が不可欠となります。 内部監視との違い 内部監視と外形監視は、どちらかが優れているというものではなく、互いの死角を補い合う関係にあります。 内部監視がサーバーのCPU、メモリ、ディスクI/O、DBのクエリ実行速度といった「システムの内側」の健康状態を見るものであるのに対し、シンセティックモニタリングはあくまでネットワークの境界を越えた「ユーザー側の視点」に特化しています。 内部のメトリクスが正常であっても、ネットワーク経路や公開設定の不備でサービスが利用できないことは珍しくありません。 逆に外形監視で異常を検知した際、その原因を特定するためには内部監視のデータが欠かせません。 品質推進をリードする立場としては、これら両面からシステムを捉えることで、属人化を排した持続可能な監視体制を構築できます。 RUMとの違い 実ユーザーの行動を直接計測するRUM(Real User Monitoring)との使い分けも重要です。 RUMが「実際にアクセスしたユーザーのデバイス上で何が起きたか」という多様な実態を把握するのに対し、シンセティックモニタリングは「設計された条件下の疑似ユーザーによる定点観測」です。 使い分けの目安としては、環境を固定して「いつでも同じ条件で再現できる異常検知」やパフォーマンスのトレンド把握を行いたい場合は外形監視が適しています。 一方で、実際にユーザーがどのような通信環境でストレスを感じているかといった「市場での実態把握」を行いたい場合はRUMが適しています。 リリース直後の動作確認や安定的な死活監視には外形監視を活用し、中長期的なUX改善の意思決定にはRUMのデータを参照するというように、目的ごとに使い分けることがQAの価値創出につながります。 導入・運用の勘所:失敗しない設計チェックリスト シンセティックモニタリングを形骸化させず、組織の品質基盤として機能させるためには、ツールの導入そのものよりも「何を、どう、どこまで監視するか」という設計の解像度が成否を分けます。 特にメガベンチャーのようにサービスが多岐にわたる場合、全ての機能を網羅しようとすれば運用コストが肥大化し、逆に絞り込みすぎれば重要な障害を見逃すことになります。 QAマネージャーとしては、開発効率を落とさず、かつ経営的なリスクを最小化するポイントを見極めた、持続可能な設計が求められます。 監視対象の優先順位 監視対象を定める際は、システム的な網羅性よりも「ユーザー体験の断絶がビジネスに与えるインパクト」を優先すべきです。 まずはサービスが価値を生むための根幹となる重要フロー、すなわちログイン、商品検索、カート投入、そして決済といった主要なユーザージャーニーから着手するのが定石です。 これらのフローは複数のマイクロサービスを跨いで動作することが多く、どこか一箇所で不具合が生じてもサービス全体が停止したのと同等の損失を生みます。 周辺機能の細かな挙動に目を向ける前に、これらクリティカルな動線が常時正常であることを担保することで、品質管理の全体最適を最短距離で実現できます。 頻度設計:短くすれば良いわけではない 監視の実行頻度は、短ければ短いほど異常検知は早まりますが、一概に短縮すれば良いわけではありません。 頻度を上げればそれだけインフラや外部APIへの負荷が増し、監視ツールのコストも増大します。 最適な設計は、対象の重要度、システム負荷、そしてアラートを受けて動く運用体制のバランスの上に成り立ちます。 目安としては、エンドユーザーが直接触れるウェブサイトの主要導線であれば1分から5分間隔、社内向けの業務システムや更新頻度の低い管理画面であれば5分から15分間隔といったように、重み付けを行うのが現実的です。 無用な負荷を避けつつ、実効性のある「検知の鮮度」を保つ調整が不可欠です。 ロケーション設計:どの地域からの体感を担保するか ロケーション設計では、どの地点からアクセスした際の品質を担保すべきかを定義します。 一般ユーザー向けサービスであれば、国内外の主要都市に配置された監視拠点を利用し、ネットワーク経路による遅延や地域限定の障害を可視化します。 一方で、社内限定のシステムや開発環境を監視したい場合には、社内ネットワーク内にエージェントを設置する「プライベートロケーション」の考え方が必要になります。 自社のユーザーボリュームが多い地域を優先しつつ、特殊なアクセス経路を持つターゲット層が存在する場合は、それらを含めた複数地点からの比較を行うことで、より精度の高い体感品質の観測が可能になります。 アラート設計:誤検知を減らし、復旧を早める アラート設計のゴールは、単に「通知を飛ばすこと」ではなく「復旧を早めること」にあります。 一過性のネットワークのゆらぎによる誤検知を防ぐため、連続して失敗した場合のみ通知する、あるいは応答時間のしきい値を段階的に設けるといった工夫が必要です。 また外形監視は「外から見て壊れている」ことは分かっても「なぜ壊れたか」の特定が弱いという特性があります。 そのため、アラート通知には該当するAPM(アプリケーションパフォーマンス管理)のダッシュボードやログへの直接リンクを含め、原因究明の初動をスムーズにする導線まで設計しておくことが、現場の板挟みを防ぐ鍵となります。 ツール選定の比較軸 ツールを選定する際は、単なる多機能さではなく、組織の運用フローに適合するかを基準に判断します。 まずエンジニア以外でもメンテナンスが可能な「シナリオ作成の容易さ」や、クリックや入力といった複雑な操作をどこまで正確に再現できるかが重要です。 次に、ブラウザ実行の忠実度を確認し、ページを構成するサードパーティ製スクリプトまで含めて計測可能かを見極めます。 さらに、国内外の監視地点の選択肢が自社のターゲット層と合致しているか、そして既存のAPMやログ基盤と統合し、異常検知から原因特定までをシームレスにつなげられるかという点が、将来的に破綻しないQA体制を築くための重要な比較軸となります。 まとめ シンセティックモニタリングは、単なる障害検知のツールではなく、ユーザー体験(UX)を数値化し、ビジネスの継続性を守るための「QA戦略の柱」となる手法です。 本記事で解説した内容のポイントを振り返ります。 ユーザー視点の可視化: 外部ネットワークから疑似ユーザーとしてアクセスすることで、内部監視では見落とす「サイレントな劣化」を検知できる。 目的に応じた手法の選択: URL監視、Web監視、シナリオ監視を組み合わせ、重要導線の品質を24時間365日担保する。 補完関係の理解: 内部監視やRUMと優劣を競うのではなく、それぞれの強みを活かして併用することが、原因特定と実態把握の最短ルートとなる。 戦略的な設計: 優先順位、頻度、ロケーション、アラートの4点を最適化することで、運用コストを抑えつつ高い信頼性を維持できる。 QAマネージャーとして品質の全体最適を推進する上で、シンセティックモニタリングから得られる客観的なデータは、開発・PdM・経営層と共通の言語で語るための強力な武器になります。 まずは事業の核となる重要フローの監視から着手し、リリース速度と品質が両立する持続可能な体制を築いていきましょう! QA業務効率化ならPractiTest テスト管理の効率化 についてお悩みではありませんか?そんなときはテスト資産の一元管理をすることで 工数を20%削減できる 総合テスト管理ツール「 PractiTest 」がおすすめです! PractiTest (プラクティテスト) に関する お問い合わせ トライアルアカウントお申し込みや、製品デモの依頼、 機能についての問い合わせなどお気軽にお問い合わせください。 お問い合わせ この記事の監修 Dr.T。テストエンジニア。 PractiTestエバンジェリスト。 大学卒業後、外車純正Navi開発のテストエンジニアとしてキャリアをスタート。DTVチューナ開発会社、第三者検証会社等、数々のプロダクトの検証業務に従事。 2017年株式会社モンテカンポへ入社し、マネージメント業務の傍ら、自らもテストエンジニアとしテストコンサルやPractiTestの導入サポートなどを担当している。 記事制作: 川上サトシ (マーケター、合同会社ぎあはーと代表)





















