💡

REST API のレスポンス速度を改善するために行なった設計の上で大切なこと

2022/03/30に公開

はじめに

社内のプロジェクトで、大量のデータをjsonで返すAPIを実装した際に、レスポンス速度が遅くなりすぎるという課題が発生したことがありました。
この、課題を解決する過程で、開発者がWebサービスを実装する上で意識した方が良い点をいくつか学んだので記事にまとめたいと思います。

for 分のネストを浅くする

まず、以下のソースコードを見てください。

api.php

$all_datas = $this->model->find($query);

foreach($all_datas as $data){
  foreach($data['data'] as $value_type => $data_by_type){
    foreach($data_by_type as $timestamp => $val){
      $response_array[$timestamp] = $val;
    }
  }
}

このようにfor分のネストが深くなると、OSのメモリ上にデータを格納した状態が長く続き、サーバーに大きな負荷がかかってしまいます。
また、ループが何十にも回るせいで非常に大量の回数が回ることになり、このループを抜けるのに非常に時間がかかってしまいます。

対策

for文のネストは極力浅くする必要がありますし、連想配列を使用してデータを作成する際にも、for文のネストが深くならないようにデータ構造を設計してやる必要があります。
場合によっては、DBの設計についても考慮して、API側でデータを扱う際に使用しやすい形式でDB保存を行う必要もあるかもしれません。

DBに投げるクエリを工夫する

以下のソースコードを見てください。


foreach($user_data as $user_info){
  $user_id = $user_info['id'];

  $data = $this->model->find($user_id);
}

for分の中で何回もDBへの検索を行なっていると、DBへの検索処理を行うたびに時間がロスしてしまいます。
また、DBへの負荷もかかる。

対処法

改善したソースコード

foreach($user_data as $user_info){
  $user_id = $user_info['id'];
  $user_id_list[] = $user_id;
}

$data = $this->model->find($user_id_list);

1回のクエリでデータをまるっと取得できるようにしてやると良いかなと思います。

未使用変数及び無駄な変数は減らす

未使用変数や無駄な変数が残っていると、以下のような問題点があるかなと思います。

  • ソースコードの可読性が落ちる。
  • メモリ上に無駄なメモリ領域が確保されてしまう。

PHP関数内の変数について

PHPでは関数内で定義された変数は、関数の処理終了時にメモリが開放されるようです。
for文のネストが深く、かつループ分の中で変数に値を代入する際には、適宜変数を初期化してやることで意図していない値が代入されることを防いだり、余分なメモリ領域を確保したままループが回り続けるという現象も防げるのかなと思います。

対処法

この辺りはLinterのルールの中に、未使用変数の残存をチェックするルールを追加して、チーム内で共通で使用するようにすれば、解消できるかなと思います。
また、CI上でLinterを実行するようにしておけば、GithubやGitlab上でコードレビューを行う際に事前にチェックができるかなと思います。

jsonのsizeを軽くする

json内部のデータを見直す

jsonのデータの中に不要なデータが多く含まれていると、サーバーからクライアントにjsonを送信する際に無駄な時間がかかってしまいます。
大量のデータを扱っている際には、無駄なデータが含まれているせいで数MB単位でデータが肥大化してしまうこともあるので注意が必要ですね。

nginxでデータを圧縮する

nginxの設定でjsonデータを圧縮することでデータを軽くすることができます。

https://weblabo.oscasierra.net/nginx-gzip/

サーバー側の対策

swapを使用する

swap領域を確保してやることで、物理メモリが圧迫された時にバッファを持たせることができます。
ただし、swap領域を確保する際にサーバーのハードディスクを必要とするため、容量に余裕を持たせておく必要があります。

https://www.softel.co.jp/blogs/tech/archives/3261

バッチ処理を作成する

あらかじめデータを整形したり、計算しておくことで、API内部の処理を減らすことができるようになり、レスポンス速度が改善できる可能性があります。
もし仮に、バッチ処理を挟まずにwebアプリ側のmodel等で処理を実行させると、ネストの深いfor分が複数あった場合には、サーバーに負荷がかかりますし、APIのレスポンス速度も著しく悪化してしまいます。

Redisの使用を検討する

redisを使用してキャッシュを効かせることでDBへのアクセスを減らすことが期待できます。
ただし、redisを使用する際にはキャッシュがOSのメモリ上に保存されるため、サーバーのメモリについても考慮する必要があります。
laravelではデフォルトでSession及びCacheの保存先がstorageディレクトリ内に保存されるため、長期間運用をしているとstorageディレクトリが肥大化し、OS上のストレージを圧迫してしまいます。
サービスのアクセス数が少ないうちは大きな問題にならないかもしれませんが、実運用を考えると早めにRedisを使用する必要があるでしょう。

redisを導入する際にはこちらの記事を参考にしてください。
https://qiita.com/takuyanagai0213/items/2e4fe3fafcb99cb1a10e

Discussion