Webアプリを高速化するサーバーサイドのベストプラクティス10選

Webアプリケーションの速度は、ユーザー体験やSEOに大きく影響します。クライアントサイドの最適化だけでなく、サーバーサイドの工夫も欠かせません。この記事では、開発・運用にすぐ活かせるサーバーサイドの高速化ベストプラクティスを10個紹介します。

目次

1. レスポンスのキャッシュを活用する

頻繁に変わらないレスポンスは、サーバーやCDNでキャッシュすることで処理を省略できます。特に以下のようなサーバーサイドのキャッシュ手法を活用することで、レスポンス速度を大きく改善できます。

サーバー側のキャッシュ方法

例1:Apacheでのmod_cacheによるキャッシュ設定(httpd.conf)

<IfModule mod_cache.c>
  <IfModule mod_cache_disk.c>
    CacheQuickHandler On
    CacheEnable disk "/"
    CacheRoot "/var/cache/apache2/mod_cache_disk"
    CacheDefaultExpire 3600
    CacheIgnoreNoLastMod On
  </IfModule>
</IfModule>

例2:NGINXでのキャッシュ設定

location /api/ {
    proxy_pass http://backend;
    proxy_cache my_cache;
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
    add_header X-Cache-Status $upstream_cache_status;
}

ポイント

  • HTMLは短め(例:数分〜10分)、画像やJS/CSSは長め(例:1時間〜1日)に設定
  • APIレスポンスは変更頻度に応じてキャッシュ時間を調整
  • クエリ付きURLや動的URLにも適用するには正規化と一体で考える
  • キャッシュヒット率やキャッシュステータスをログで確認・調整する

2. データベースクエリを最適化する

N+1問題とは?

N+1問題とは、データベースから関連データを取得する際に、1つのメインクエリ(1)に加えて、ループ内で個別にN回の追加クエリが発行される問題です。これにより、無駄なクエリが大量に発行され、パフォーマンスが大きく低下します。

例:10件の投稿とそれぞれの投稿者情報を取得する場合(悪い実装)

-- 投稿一覧を取得
SELECT * FROM posts;

-- それぞれの投稿に対して投稿者を取得(10回繰り返し)
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
... (合計10回)

改善案:JOINを使って1回のクエリでまとめて取得

SELECT posts.*, users.name FROM posts
JOIN users ON posts.user_id = users.id;

あるいは、ORMを使って事前読み込み(Eager Loading)を活用しましょう。

N+1問題や不要なJOIN、多すぎるクエリ発行はパフォーマンスの低下要因です。

例:Eloquent(Laravel)での遅延読み込みの回避

// 悪い例(N+1)
$posts = Post::all();
foreach ($posts as $post) {
  echo $post->user->name;
}

// 良い例(事前読み込み)
$posts = Post::with('user')->get();

3. 静的ファイルは別サーバーまたはCDNへ

画像・CSS・JSなどの静的アセットは、Webサーバーとは分離して、高速なCDN(コンテンツ配信ネットワーク)で配信することで、レスポンス時間を大幅に短縮できます。

例:Cloudflare CDNを利用する構成

  • Webサーバー:example.com
  • CDNアセット:cdn.example.com/assets/...

主なCDNサービスの紹介

サービス名特徴
Cloudflare無料プランあり、DDoS対策・セキュリティ機能も充実
Amazon CloudFrontAWS連携に強く、リージョンを柔軟に選択可能
Google Cloud CDNGCP連携に最適、HTTP/2やQUICに対応
Fastly超低レイテンシ、高速キャッシュ更新が可能
Akamai世界最大規模のCDN網、大企業やメディアサイト向けに最適
さくらのクラウド CDN国内データセンター主体で低遅延、日本企業向けに導入しやすい

CDNはユーザーの地理的な近くから静的ファイルを配信することで、待ち時間を短縮し、Webサーバーの負荷も軽減します。


4. レスポンスの圧縮(gzip, brotli)

サーバー側でHTMLやCSS、JavaScript、JSONなどのテキストベースのレスポンスを圧縮することで、通信量を減らし、読み込み時間を短縮できます。とくに、モバイル回線など帯域が限られている環境では効果が大きくなります。

具体的に有効なケース

  • HTMLテンプレートを毎回動的に生成するアプリケーション
  • JSON APIを多く返すRESTful Webサービス
  • JavaScriptやCSSが複数あるシングルページアプリケーション

主な圧縮方式

  • gzip:最も広く使われている圧縮方式。多くのブラウザが対応。
  • brotli:gzipよりも高い圧縮率。HTTP/2以降を使う環境では推奨される。

Apacheでのgzip設定例(mod_deflate)

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json
</IfModule>

NGINXでのBrotli設定例

brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript text/xml;

ブラウザは圧縮されたレスポンスをどう処理する?

圧縮されたレスポンスは、ブラウザが自動的に展開(解凍)してから解析・描画します。

  1. リクエスト時にブラウザが対応形式を通知Accept-Encoding: gzip, deflate, br
  2. サーバーが対応形式で圧縮してレスポンスContent-Encoding: gzip
  3. ブラウザが自動で解凍し、HTMLやCSSなどを処理

ユーザーが特別な設定をする必要はなく、HTTPの標準仕様に基づいて動作します。

※画像や動画など既に圧縮されているバイナリ形式のファイルには基本的に再圧縮は不要です。


5. コネクションの再利用(Keep-Alive)

HTTP通信では、リクエストごとにTCP接続を確立する必要があります。しかし、毎回新しく接続を張るとオーバーヘッド(接続確立時間やリソース消費)が大きくなり、パフォーマンスが低下します。

これを防ぐために、Keep-Alive(持続的接続)を利用して、複数のHTTPリクエスト/レスポンスを同じコネクションで処理することで、処理の高速化とサーバー負荷の軽減が実現できます。

主な用途・効果

  • Webページ読み込み時にCSS/JS/画像など多数のファイルを取得する際に効果を発揮
  • APIの連続リクエスト処理でも接続時間の短縮が期待できる
  • HTTP/2ではより高効率に同時処理(多重化)が可能になる

ApacheでのKeep-Alive設定例(.htaccess)

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

NGINXでのKeep-Alive設定例

keepalive_timeout 65;
keepalive_requests 100;

ブラウザ側がKeep-Alive対応である限り、Webサーバーが設定していれば自動的に活用されます。

例:Apache設定(.htaccess)

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

6. 適切なスレッド/プロセス管理

Webアプリケーションは、同時に多数のリクエストを受ける可能性があります。これに対して適切なスレッド数やプロセス数を確保しておかないと、処理待ちが発生してレスポンスが遅延し、最悪の場合はサーバーが落ちてしまうこともあります。

主な構成の種類

  • プリフォーク型(Pre-Fork):プロセスごとにリクエストを処理(例:Apache + PHP)
  • ワーカースレッド型:軽量なスレッドで複数リクエストを同時処理(例:NGINX, Apache Worker)
  • イベント駆動型:非同期で大量の同時接続に強い(例:Node.js, NGINX)

チューニングの具体例(Apache MPM)

<IfModule mpm_prefork_module>
  StartServers           5
  MinSpareServers        5
  MaxSpareServers       10
  MaxRequestWorkers     150
  MaxConnectionsPerChild 3000
</IfModule>

運用上のポイント

  • サーバーのCPUコア数やメモリ量に応じてプロセス数を調整する
  • アクセスログやモニタリングツール(top, htop, ps, systemctlなど)で負荷を常時観察
  • 子プロセスの使い捨て(例:MaxConnectionsPerChild)でメモリリーク対策
  • コンテナ環境(Dockerなど)では制限値に注意(CPUシェアやメモリ制限)

適切なスレッドやプロセスの管理は、リソースの有効活用とサービスの安定稼働の要です。


7. クエリパラメータの正規化とキャッシュ対策

同じリソースに異なるクエリパラメータを付けてアクセスすると、サーバーやCDN側は別のリソースとして扱ってしまい、キャッシュが効かないことがあります。これを防ぐには、**URLの正規化(不要なクエリの除去)**を行い、キャッシュのヒット率を上げることが重要です。

よくあるケース

  • example.com/page?id=123
  • example.com/page?id=123&utm_source=google

上記は同じページのつもりでも、CDNやVarnishは別のURLと見なしてしまい、キャッシュが無効化されます。

対策方法

  1. UTMパラメータなどを除外する
  2. 順序や重複を正規化する
  3. 必要ない場合はクエリを全削除する

例:Varnishでクエリパラメータを削除する設定

sub vcl_recv {
  set req.url = regsub(req.url, "\?.*$", "");
}

NGINXでキャッシュキーからクエリを除外する例

location / {
    proxy_pass http://backend;
    proxy_cache_key "$scheme://$host$request_uri";
    if ($request_uri ~* "\?.*") {
        set $request_uri $uri;
    }
}

注意点

  • 本当に必要なクエリ(例:検索結果や絞り込み条件)は除外しないこと
  • Google Analytics等のトラッキングクエリはキャッシュに含めないのが基本

キャッシュとクエリの扱いは一体で考え、意図しないキャッシュミスを防ぎましょう。


8. セッションストアの見直し

セッションをファイルベースで保存していると、同時アクセスが増えた場合にI/O負荷やスケーラビリティの制限が発生しやすくなります。特にロードバランサーを利用した複数台構成では、セッション情報の共有が困難になります。

解決策:メモリストアの利用

  • RedisMemcached などのインメモリデータベースを利用すると、読み書きが高速で分散環境にも対応しやすくなります。

PHPでRedisセッションを使う設定(php.ini)

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"

LaravelでRedisを使う(.env)

SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1

効果

  • セッションデータのアクセスが高速化される
  • ロードバランシングされた環境でもセッションが一元管理される
  • ファイルロックやファイル破損のリスクが減少

セッションストレージは安定稼働のカギとなる重要な要素です。


9. ログレベルと出力量の最適化

開発中は細かいログ(DEBUGなど)を出力することが有益ですが、本番環境では過剰なログ出力がディスク容量を圧迫し、CPU負荷やI/O待ちを引き起こす原因になります。

運用上の対策

  • ログレベルを INFOERROR に制限
  • ログファイルの**ローテーション(自動切り替え)**を設定する
  • エラーログとアクセスログを分離して保存する

例:rsyslogのログローテーション設定(/etc/logrotate.d/myapp)

/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
}

例:PHPでログレベルを制限する(php.ini)

error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED
log_errors = On
error_log = /var/log/php-error.log

ログ出力はトラブル対応に不可欠な一方、無制限な出力はシステムリソースを圧迫するため、出力量と保存期間のバランスを見直しましょう。


10. タイムアウト・リトライ設定の見直し

バックエンドAPIや外部サービスとの通信が遅延・失敗した場合に備えて、適切なタイムアウトとリトライ制御を設けることは、Webアプリ全体の応答性を保つために重要です。

なぜ必要か?

  • 外部APIが無応答の場合に待ち続けると、全体の処理が止まりレスポンスが遅延
  • タイムアウトやリトライが未設定だと、障害の原因が分かりにくくなる

設定例(PHP cURL)

$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // 接続タイムアウト
curl_setopt($ch, CURLOPT_TIMEOUT, 5);        // レスポンスタイムアウト
$response = curl_exec($ch);
curl_close($ch);

設定例(NGINXでバックエンドとの通信にタイムアウト)

location /api/ {
    proxy_pass http://backend;
    proxy_connect_timeout 5s;
    proxy_read_timeout 10s;
    proxy_send_timeout 10s;
}

補足

  • リトライ回数や間隔を設定する際は、指数的バックオフを取り入れると効果的
  • タイムアウトとリトライはセットで管理することが大切です

安定したシステム運用のためには、障害発生時の挙動を事前に設計し、復旧までの影響を最小限に抑える工夫が必要です。

まとめ:パフォーマンスは小さな工夫の積み重ね

サーバーサイドの最適化は、一つの設定変更やコード改善で大きく速度に影響を与えることがあります。上記の10個のベストプラクティスは、どれも実運用で役立つものばかりです。まずは一つずつ取り入れて、安定した高速なWebアプリを目指しましょう。

Contact

ウェブサイトの制作や運用に関わる
お悩みやご相談
お気軽にお問い合わせ下さい

ウェブサイトと一口に言っても、企業サイトやECサイト、ブログ、SNSなど、その“カタチ”は目的に応じてさまざまであり、構築方法や使用する技術も大きく異なります。株式会社コナックスでは、お客様のご要望やブランドの個性を丁寧に汲み取り、最適なウェブサイトの“カタチ”をご提案いたします。

デザイン、ユーザビリティ、SEO対策はもちろん、コンテンツ制作やマーケティング戦略に至るまで、あらゆるフェーズでお客様のビジネスに寄り添い、成果につながるウェブサイトづくりをサポートいたします。私たちは、ウェブサイトの公開をゴールではなくスタートと捉え、お客様のビジネスの成功に向けて共に伴走してまいります。