Webアプリ性能をnscd使って改善した話

# はじめに

Webサイトにおけるパフォーマンス(サイト表示速度)を改善するポイントはいくつもあります。
以下一例です。

* フロントエンドの改善
    * リクエスト回数を減らす
    * 通信量を減らす
    * レンダリング速度の最適化
    * CDN活用
* サーバサイド
    * (Memcached/Redis等)キャッシュ活用
    * アプリロジック改善
        * n+1問題回避やらいろいろ
    * APサーバと静的コンテンツ配信サーバ分離
    * 開発言語バージョンアップ
    * DBチューニング
        * インデックス見直し
        * SQLチューニング

[弊社サービス](https://8122.jp/)では、これまでも上記に取り組み、少しずつ改善はしていました。
それもそろそろ今のアーキテクチャでは頭打ちかな？という状況でしたが、最近実施して意外にも
大きく改善したことがあったのでご紹介します。

## 環境構成

* インフラは全てAWS
* APサーバには、apache2.4 + php7.2 + opcache
* Cacheサーバには、ElastiCache(セッション用、キャッシュ用の２つ）
* DBサーバには、RDS for PostgreSQL

よくある構成ですね。^^;

## 主題：nscdでDNSキャッシュ

弊社の環境では、Route53のPrivateDNSでRDS／ElastiCacheエンドポイントに別名のホスト名を
割り当ててそれをAPサーバから使うことで、リソース切り替えを容易にしています。（疎結合！）
~~この方式の弊害としては、PrivateDNSだとALIASレコードが使えないため、CNAMEレコードを設定する必要が
あり、これにより名前解決に余計に１回クエリが飛んでしまうというもの。~~
PrivateDNSでもALIASレコードは使えるようです（コメント欄参照）

また、phpは(apache)プロセス内でDNSクエリ結果をキャッシュしないようで、毎回DNSクエリを投げます。
このRDS／ElastiCacheの名前解決処理により、遅延が発生している可能性を疑いました。

そこで、nscdを用いてDNSクエリ結果をキャッシュを試してみました。
nscd(name service cache daemon)とは、その名の通り、DNSやNISなどの一般的なネームサービスの
内部DBを保持し、passwd、group、hostsファイルなどの情報をキャッシュするサービスです。

### nscd導入手順

```terminal
$ sudo yum install nscd
$ sudo vi /etc/nscd.conf  # 後述のnscd.confの内容にする
$ sudo systemctl start nscd
$ sudo systemctl enable nscd
$ sudo apachectl graceful # Apache利用の場合
```

```config:/etc/nscd.conf
	logfile				/var/log/nscd.log
#	threads				4
#	max-threads			32
	server-user			nscd
#	stat-user			somebody
	debug-level			0
#	reload-count		5
	paranoia			no
#	restart-interval	3600

	enable-cache			hosts		yes
	positive-time-to-live	hosts		60
	negative-time-to-live	hosts		5
	check-files				hosts		yes
	shared					hosts		yes

	enable-cache		passwd		no
	enable-cache		group		no
	enable-cache		services	no
	enable-cache		netgroup	no
```

### 効果

結果として、 <font color="OrangeRed">**約30% サイトの平均レスポンスタイムが改善しました。**</font>
（当社比。もちろん、SDKや各種エンドポイントの利用頻度によって効果は違います）
DNSクエリをtcpdumpで確認してみると、RDS/ElastiCacheだけではなくS3やCloudFrontなど他の
AWSリソースにもリクエストを投げており、それらSDKを利用したアクセスにも効果がありました。

### 注意点

DNSキャッシュしている期間は、フェイルオーバー遅延などが起こるので、許容できる範囲でttlを設定する必要があります。

## おまけの改善：リリース回数が増えると性能劣化する問題

次に、nscdでレスポンスが速くなったことで **リリース後にレスポンスが遅くなる** という問題が顕在化しました。
（nscdの改善に比べればおまけみたいなものですが）

### 原因

OPcacheのCache Fullが発生し、キャッシュにのりきらなくなったことが原因でした。
※OPcacheについては、[php.net](http://php.net/manual/ja/book.opcache.php)や[Qiita記事](OPcache導入してみた！（速さ検証もあるよ！）)を参照ください。
[Opcache Control Panel](https://gist.github.com/ck-on/4959032)を使って性能劣化時のOPcacheキャッシュ利用状況を取得してみました。
取得した結果(抜粋)が以下です。
<img width="629" alt="ocp.png" src="https://qiita-image-store.s3.amazonaws.com/0/51108/9d158e57-518b-c5aa-f469-0eb7cb1a5dc1.png">
Cache Fullが1になってますね。^^;

弊社では、php実装したWebアプリは、
**デプロイサーバでコード取得→composer install→APサーバ配布→シンボリックリンク切り替え**
という流れでアトミックにデプロイしています。よくある方法ですね。
最近リリース頻度が増えてきており、１日に数回リリースすることがあります。（多分２桁はいかないけど）

OPcacheは、一度キャッシュしたデータは放置すると消えません。
キャッシュをクリアするには再起動するかopcache_reset()を実行する必要があります。
なので、リリースを繰り返した結果キャッシュサイズ上限を超えると、毎回コンパイルが走り、性能が劣化することになります。

### 対策

* Apache再起動してキャッシュクリア
* 上限超えないように共有メモリサイズ（opcache.memory_consumption）を増やす
* 変更なしファイルは同じタイムスタンプでデプロイする（不要なキャッシュを回避）

などなど、いろいろやり方はあると思いますが、
今回は簡単にできるhttpd再起動(apachectl graceful)の定期実行でキャッシュクリアするようにしました。

### 効果

一部サーバのレスポンスタイムの推移です。後半は性能劣化が起きなくなっています＼(^o^)／
<img width="728" alt="restime_avg.png" src="https://qiita-image-store.s3.amazonaws.com/0/51108/e6c4f7e5-3f05-a884-e3da-259ac82104ea.png">

## まとめ

頭打ちと思っていても、まだまだ改善できる余地はあるのだなぁ（スキル不足w）と実感。
ちなみに今回の改善をする前に、php5.6 -> php7にあげたのですが、その時に全然改善しなかったのは
他のボトルネックが大きすぎて、見えづらかったのかも。と今になって思いました。

最後に、今回あげた改善例も種類は違えどキャッシュの改善。
やっぱりキャッシュ戦略大事ですね！！