「パフォーマンスは正義」(出展不明)と言われるように、どんなに良いシステムでもパフォーマンスが悪いだけで使われなくなってしまうということは、ままあります。
そんなわけで、11 Best Practices for Low Latency Systemsという記事で低レイテンシなシステムを作るための11のプラクティスが紹介されていたので、ざっくりとご紹介します。
目次
11のベストプラクティス
1. 正しい言語を選ぶ
より低レイテンシなシステムを求めるのであれば、インタプリタで実行されるような言語(Ruby、Pythonなど)よりは、Java、Scala、C++、Goといった言語を使うと良いと言っています。生産性を取るかパフォーマンスを取るか、最後の数ミリ秒を削るような戦いをしているときにインタプリタを選ぶ必要はない、ということですね。
2. メモリに全て置いておく
ディスクI/Oはパフォーマンスを著しく下げるので、データは全てメモリに置くのが定石です。ただ、メモリに置いておくとクラッシュしたときに全てデータが吹き飛んでしまうので、インメモリデータベースであるRedisなどを使って、バックグラウンドでディスク書き込みを行うようにしておくと良いでしょう。
3. データは処理する場所に置いておく
データをネットワーク越しなど離れたところに置いておくと、処理するときに手元までデータを転送するコストがかかってしまいます。できるだけ処理をするプログラムと同じ場所に置くのが良いでしょう。
4. システムは余力を残した状態にする
余力がない状態だとすぐに反応できないので、低レイテンシなシステムを運用するにあたっては一つのサーバをギリギリまで酷使するのは良くないです。
5. コンテキストスイッチは最小に
必要以上に並列処理をしてしまっているとコンテキストスイッチが多発してしまうので、ここもチェック(sarでsystemの値を見る感じかな?)。
6. 読み込みはシーケンシャルに
どんなストレージでも連続したデータを読み込むときに最高のパフォーマンスを発揮すると言っています。また、シーケンシャルに読み込むことによってCPUキャッシュレベルでプリフェッチが行われる可能性があり、これが適切に行われればL1キャッシュにデータがあらかじめ読み込まれるため、パフォーマンス向上につながるとのことです。
7. 書き込みはバッチで
ちょっと良くわからなかったので、原文を貼っておきます。。
This sounds counterintuitive but you can gain significant improvements in performance by batching writes. However, there is a misconception that this means the system should wait an arbitrary amount of time before doing a write. Instead, one thread should spin in a tight loop doing I/O. Each write will batch all the data that arrived since the last write was issued. This makes for a very fast and adaptive system.
8. キャッシュを考慮する
こちらも上手く伝えられそうになかったので、原文を貼っておきます。。
With all of these optimizations in place, memory access quickly becomes a bottleneck. Pinning threads to their own cores helps reduce CPU cache pollution and sequential I/O also helps preload the cache. Beyond that, you should keep memory sizes down using primitive data types so more data fits in cache. Additionally, you can look into cache-oblivious algorithms which work by recursively breaking down the data until it fits in cache and then doing any necessary processing.
9. できるだけノンブロッキングに
ブロッキングしてしまうと、その分処理待ちが発生するため、できるだけノンブロッキングに設計します。そのためにはJVM、C++、Goのメモリモデルをよく知っておく必要があるよと、言っています。
10. できるだけ非同期で
同期処理は処理が終わるまで待たせてしまうので、できるだけ無くすように。
11. できるだけ並列化するように
特にI/Oは並列化するように。