[Django]DBアクセスを減らす

最近は、外でSurfaceで開発しているので、容量もそれほどなくあまり色々入れたくないというのと、外に持ち運ぶ端末内にお客さんの個人情報を入れたくないということで、開発環境のWEBサーバ―はローカルPC内だが、DBはクラウドサーバー上にあるという変な形でやっている。

この環境だとDBアクセスのオーバーヘッドが顕著にあらわれる。ORMを何も考えないで使っていると、処理によっては、結構、待たされる。ということで、そろそろリリースに向けた見直し中。

1.モデルの使い方

Djangoのモデルを効率よく使う方法は、以下ページに書かれている。

https://docs.djangoproject.com/ja/3.1/topics/db/optimization/

複雑な参照系は、Rawクエリで一発で取ってくるようにしているが、微妙なところはモデルをそのまま使うようにしている。その部分で、余分なクエリが結構発生しているので直す。

1.1 外部キー

1)必要のない参照は消す

プログラム内で外部キーを参照するときに、モデル上のリレーションモデルからキーを参照すると、リレーションモデルの取得が発生するので、書き方を見直す。

https://docs.djangoproject.com/ja/3.1/topics/db/optimization/#use-foreign-key-values-directly

albumモデル上でartistを参照している場合に、albumからartisitのidを取得する場合には以下のようにする。

(正)album.artist_id
(誤)album.artist.id  ※artisitをDBから取得する

この延長で、リレーションのそのまたリレーションからモデルを参照するのは、中間のモデルが必要ない場合は直す。以下では、companyを参照しているが、その前にartistもDBから取得されてしまうので、クエリで取得するように直す。

(例)company = album.artist.company  ※artisitもDBから取得してしまう

2)必要な参照は同時に取る

select_relatedを使って、参照も同時に取る。

https://docs.djangoproject.com/ja/3.1/ref/models/querysets/#django.db.models.query.QuerySet.select_related

3)必要な参照はまとめて取る

prefech_relatedを使って、参照を一気に取る。select_relatedとの違いは、ベースのデータを取得時にselect_relatedは結合で同時に取るというのに対して、prefech_relatedは参照テーブルのデータを事前に一気に取って、メモリ上で結合するという感じ。

https://docs.djangoproject.com/ja/3.1/ref/models/querysets/#django.db.models.query.QuerySet.prefetch_related

1.2 キャッシュの利用

1)メソッドのキャッシュ化

モデルから外部データを取得するメソッドに、cached_propertyを付けることで、モデルインスタンス上でキャッシュされるようになる。

https://docs.djangoproject.com/ja/3.1/ref/utils/#django.utils.functional.cached_property

2)マスタのキャッシュ

DBアクセスを減らすと基本と言えば、マスタ系をキャッシュするということで、Djangoのキャッシュ機能については以下。

https://docs.djangoproject.com/ja/3.1/topics/cache/

ページ単位のキャッシュ等が出てくるが、マスタDB等のキャッシュとしては、以下の「低レベルキャッシュAPI」を使うことが考えられる。

https://docs.djangoproject.com/ja/3.1/topics/cache/#the-low-level-cache-api

バックエンドとして、色々設定できるようだが、簡単なマスタのキャッシュということで、選択肢はローカルメモリしかありえない。

キャッシュ機能を使わなくても、マスタ管理のクラスを作ってグルーバルにインスタンス定義すれば、1)のcashed_propertyで実装できるか。

1.3 一括更新

一括更新(bulk_update)については以下。

https://docs.djangoproject.com/ja/3.1/topics/db/optimization/#update-in-bulk

複合主キーのレガシーDBを使っており、以下パッケージで対応した。

django-compositepk-model

https://github.com/Arisophy/django-compositepk-model/issues/4

1.4 登録

複合主キーの登録については、注意が必要。無指定でModel.saveを使うと、最初にUPDATEをしに行くので、force_insert=Trueを指定するか、QuerySet.createを使う。

https://github.com/Arisophy/django-compositepk-model/blob/main/README.md#5-create-is-better-than-save

2.独自クエリ

一覧画面系は、最初からさっさと独自クエリで実装した。

https://docs.djangoproject.com/ja/3.1/topics/db/sql/#executing-custom-sql-directly

一覧を取得する処理は、カスタムmanager上に実装して、namedtupleで渡すほうが扱いやすい。更新にも使う場合は、modelにつめて返すようにした。