Django3.xxにおけるパフォーマンスの最適化

# 概要

初心者向けの入門記事などは盛んに出ていますがあまりこういった話題が出ていないので、プライベートで[Django project](https://docs.djangoproject.com/ja/3.0/topics/performance/)の翻訳に関わった際の知識と元々翻訳されている部分の知識を組み合わせて書いてみようと思いました。

なお、Qiitaに投稿されている先行例としては

- [ハイパフォーマンスDjango - Model](https://qiita.com/massa142/items/8753ebbe041d42d703df)

がありましたが、2016年の記事となっていたので、Django3.0の公式ドキュメントの範囲に沿って書いていきたいと思います。

※気になる点は実際にドキュメントを確認し、詳細に理解されることをお勧めします。

# 一般的なパフォーマンスチューニング

多くのDjangoユーザーがコードを書くときにロジックが正しく動作するかどうかを意識しています。しかし、それだけではDjangoで効率的にアプリを作れているかというと、そうではありません。

ロジックの出力はそのままに、いかに効率よくパフォーマンスを上げるかというのはアプリを開発する上で非常に重要な点です。

多くの場合、一つの分野での効率化は他の領域でもパフォーマンスの向上につながります。
ただし、いつもそうとは限りません。一つの領域は他の部分を犠牲にしてパフォーマンスを向上させていることもしばしばあります。例えば、プログラムのスピードをあげようとするときにより多くのメモリーを消費することなどが最たる例でしょう。

さらに悪質なケースとしてプログラムのスピードを改善しようとしてメモリを使い切ってしまい、メモリ不足につながってしまうこともあります。このケースは百害あって一利なしと言えるでしょう。
このようなトレードオフの関係があることをパフォーマンスを改善するときには意識しておきましょう。

それゆえ、何を目的としたパフォーマンスの改善なのかを知っておくべき必要があります。また、その目的とした方針に相応の理由があるかを知っておく必要もあります。


# ベンチマーク

コードの中からパフォーマンスが良いか悪いかを想像して探すというのはあまり良い考え方ではないでしょう。
なぜならば、Djangoには多くのツールがあり、中でも[django-debug-toolbar]()は非常に便利なツールです。とりわけ、ページが生成するSQLクエリとそれぞれの実行時間を表示する機能がパフォーマンスチューニングする上で非常に有効です。

それ以外にもサードパーティ製のサイトのパフォーマンスを分析してくれるようなツールもあります。
ただし、これらのサービスはコードのパフォーマンスを診断してくれるようなものではなく、あくまでサイト全体のパフォーマンス診断に限ります。

代表的なサービスとして、

- [Yslow](http://yslow.org/)
- [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/?hl=JA)

などが挙げられるでしょう。

# Djangoにおけるチューニング

## キャッシング

多くの場合、値を計算するのにはコストがかかります（すなわち、リソースを大量に消費し、処理が遅くなります）。そのため、値はすぐにアクセスできるキャッシュに保存され、次に必要になったときにすぐに取り出せるようにしておきます。

これは重要で強力な手法であり、Djangoには包括的なキャッシュフレームワークと、いくつかの小さなキャッシュ機能が含まれています。

### [キャッシングフレームワーク](https://docs.djangoproject.com/ja/3.0/topics/cache/)

Djangoにおけるキャッシングフレームワークは動的なコンテンツを保存し、リクエストごとに再計算しないで済むようにするにでパフォーマンスを大きく向上させます。

より柔軟に取り扱えるように、Djangoは多様なレベルのキャッシュの粒度を提供しており、例えば特定のビューの出力をキャッシュするところからサイト全体をキャッシュするところまで幅広くサポートしています。

キャッシングの実装はあまり適切ではない書き方をされているため、悪いコードの代替手段として認識すべきではありません。ゆえにパフォーマンスチューニングをする上では最後に考えるべき方法の一つです。

### [cached_property](https://docs.djangoproject.com/ja/3.0/ref/utils/#django.utils.functional.cached_property)

クラスのインスタンスのメソッドを複数回呼び出す必要があり、かつその実行に多くのリソースを使用してしまう場合は`cached_property`を使いましょう。

`cached_property`を使うことでプロパティが返す値を保存し、同じインスタンスで再度関数が呼ばれたときにキャッシュに保存してある値を返します。

注意点として、このデコレータが機能するのはメソッドが引数として`self`のみを取り、かつメソッドをプロパティに帰られる場合のみです。

## 遅延

キャッシュが上述のように値を保存しているのに対して、遅延(laziness)は値が実際に必要になるまであえて計算の実行を遅らせるという技術です。

この技術を使うことによって、インスタンス化する前に参照することができる為、多くの用途があります。
例えば、[遅延翻訳](https://docs.djangoproject.com/ja/3.0/topics/i18n/translation/#lazy-translations)は翻訳された文字列がレンダリングされたテンプレートなどで必要になるまで実行されないため、言語が判明する前でも使用できるというメリットがあります。

また、遅延は仕事を避けようとすることで労力を節約する方法であり、実際に値が必要になるまで何もしない、ということは上述した通りです。すなわち、関連する作業にかけるリソースの量が多ければ多いほど遅延によるメリットは増していきます。

Pythonは、[ジェネレーターおよびジェネレーター式の構成要素](https://docs.python.org/ja/3/reference/expressions.html#generator-expressions)を使うことで、遅延評価のための多数のツールが用意されています。コードで遅延パターンを利用したい場合は、Pythonの遅延について読むとより一層幅広い使い方ができます。

### Djangoにおける遅延

Djangoの遅延の良い例は、`QuerySet`の評価にあると言えるでしょう。 `QuerySet`は遅延します。したがって、`QuerySet`を作成し、他の`QuerySet`と組み合わせて渡すことができます。実際にデータベースにアクセスして、説明されているアイテムをフェッチする必要はなく、渡されるのはQuerySetオブジェクトであり、最終的にはデータベースから必要となるアイテムのコレクションではありません。

一方で、特定の操作は`QuerySet`の評価を強制します。 `QuerySet`の評価を意図的に回避することで、データベースへの高価で不必要なトリップを回避できます。

また、Djangoは`keep_lazy（）デコレータ`も提供しています。これにより、遅延引数を指定して呼び出された関数が遅延して動作し、必要な場合にのみ評価されます。したがって、遅延させたい引数厳密に要求されるまで、評価のために呼び出されることはありません。

## データベース

- [データベースアクセスの最適化](https://docs.djangoproject.com/ja/3.0/topics/db/optimization/)
- [データベース](https://docs.djangoproject.com/ja/3.0/ref/databases/#persistent-database-connections)

をご覧いただくことをお勧めします。最適化に関してははクエリとそのコストに関して注意すること、データベースに関しては自身が利用しているDBMSについての記述を読むことが非常に重要です。

## HTTPのパフォーマンス

### [ミドルウェア(Middleware)](https://docs.djangoproject.com/ja/3.0/ref/middleware/)

Djangoでは以下のようなサイトのパフォーマンス改善に関するミドルウェアが存在します。

- [ConditionalGetMiddleware](https://docs.djangoproject.com/ja/3.0/ref/middleware/#django.middleware.http.ConditionalGetMiddleware)

- [GZipMiddleware](https://docs.djangoproject.com/ja/3.0/ref/middleware/#django.middleware.gzip.GZipMiddleware)

[ConditionalGetMiddleware](https://docs.djangoproject.com/ja/3.0/ref/middleware/#django.middleware.http.ConditionalGetMiddleware)はETag及びLast-Modifiedヘッダーに基づいてGETレスポンスに最新のブラウザへのサポートを追加します。

[GZipMiddleware](https://docs.djangoproject.com/ja/3.0/ref/middleware/#django.middleware.gzip.GZipMiddleware)は最新のブラウザ全てのレスポンスを圧縮し、帯域幅と転送時間を節約します。

## セッション

キャッシュされたセッションを使用すればデータベースなど低速のストレージソースからセッションデータを読み込む必要がなくなり、パフォーマンスを向上させることができます。

## 静的ファイル

静的ファイルは最適化を行うべき対象の一つで、以下のような機能がDjangoにあります。

- [ManifestStaticFileStorage](https://docs.djangoproject.com/ja/3.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage)

[ManifestStaticFileStorage](https://docs.djangoproject.com/ja/3.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage)は静的ファイル名にタグを追加することで長期間キャッシュできるようにします。仮にファイルが変更された場合、タグも変更される為、ブラウザは静的ファイルをリロードする為自動的に更新されるような仕組みとなっています。

## Minification

いくつかのサードパーティ製の Django ツールやパッケージは、HTML、CSS、そして JavaScript を最小化する ("minify") 機能を提供します。これらは不要なホワイトスペースや改行、コメントを取り除き、変数名を短縮することで、サイトが公開するドキュメントのサイズを削減してくれます。

## テンプレートのパフォーマンス

### 注意点

- `{% bloak %}`は`{% include %}`よりも高速です。
- 多数の分割されたテンプレートパーツからページを生成するとパフォーマンスに影響することがあります。

### キャッシュテンプレートローダー

[cached template loader](https://docs.djangoproject.com/ja/3.0/ref/templates/api/#django.template.loaders.cached.Loader) を有効にすると、パフォーマンスが劇的に改善する場合が多いです。このローダーを使用することで、テンプレートのレンダリングが必要になった場合に、各テンプレートを毎回コンパイルせずに済むようになるためです。

## 異なるバージョンの利用可能なソフトウェアの使用

使用しているソフトウェアの異なる、よりパフォーマンスの高いバージョンが利用できるかどうかを確認しておくといいでしょう。

このテクニックはすでに十分に最適化されたDjangoサイトのパフォーマンスをさらに向上させたいと考えているユーザー向けとなっており、これらはパフォーマンスの問題に対する魔法の解決策ではなく、基本的なことを正しく行っていないサイトに限界以上の利益をもたらすことはほとんどないということに留意しておいてください。


## Djangoテンプレート言語の代替

ほとんどの場合においてDjangoの組み込みテンプレート言語は完全に適切ですが、Djangoプロジェクトのボトルネックがテンプレートシステムにあるように感じ、これを修正する他の手法が他にないと思われる場合、サードパーティ製のツールで改善できる場合があります。

例えば、[Jinja2](http://jinja.pocoo.org/docs/)を使うと、パフォーマンス、特に速度の向上が得られることがあります。

代替テンプレートシステムは、Djangoのテンプレート言語を共有する範囲が異なります。

なお、注意点として、テンプレートでパフォーマンスの問題が発生した場合、最初にすべきことは、その理由を正確に理解することであり、代替のテンプレートシステムを使用すると、より速く証明される可能性がありますが、同じ問題は、その問題に取り組まなくても利用できる可能性があります。たとえば、テンプレートの高価な処理とロジックをビューでより効率的に実行できます。

## 代替のソフトウェア実装

使用しているPythonソフトウェアが、同じコードをより高速に実行できる別の実装で提供されているかどうかを確認することをお勧めします。

ただし、適切に作成されたDjangoサイトのほとんどのパフォーマンスの問題は、Pythonの実行レベルではなく、非効率的なデータベースのクエリ、キャッシュ、およびテンプレートにあります。不十分なPythonコードの場合、パフォーマンスの問題は、そのコードをより高速に実行することで解決されることはほとんどありません。

代替実装を使用すると、互換性、展開、移植性、またはメンテナンスの問題が発生する可能性があります。言うまでもなく、非標準の実装を採用する前に、アプリケーションが潜在的なリスクを上回るために十分なパフォーマンス向上を提供することを確認する必要があります。

これらの注意事項を念頭に置いて、次の点に注意してください。

- [PyPy](https://pypy.org/)

PyPyはPythonでの実装です（「標準」のPython実装はC言語で行われています）。 PyPyは、通常は大規模のアプリケーションにおいて、パフォーマンスを大幅に向上させることができます。

PyPyプロジェクトの主な目的は、[既存のPython APIおよびライブラリとの互換性](https://www.pypy.org/compat.html)です。 Djangoは互換性がありますが、依存する他のライブラリとの互換性を確認する必要がありますので注意してください。

# まとめ

以上になります。
基本的なことに関してはまとめたので実際に各コンテンツを導入してパフォーマンスを向上させるやり方を書こうかなと思っています。