データベース管理は、バックエンド開発の最も重要な側面の 1 つです。データベースを適切に最適化すると、応答時間が短縮され、ユーザー エクスペリエンスが向上します。
この記事では、Django アプリケーションの速度のためにデータベースを最適化する方法について説明します。ただし、各概念を個別に深く掘り下げることはしません。したがって、詳細については、 Django の公式ドキュメントを参照してください。
Django のクエリセットを理解することが最適化の鍵となるため、次の点に注意してください。
len()
、 count()
などの python メソッドによってクエリセットを評価できます。それらを最大限に活用するようにしてください。
データベースのインデックス作成は、データベースからレコードを取得する際のクエリを高速化するための手法です。アプリケーションのサイズが大きくなると、速度が低下する可能性があります。ユーザーは、必要なデータを取得するのにかなり長い時間がかかることに気付くでしょう。したがって、膨大な量のデータを生成する大規模なデータベースを操作する場合、インデックス作成は交渉の余地のない操作です。
索引付けは、さまざまなフィールドに基づいて多数のデータをソートする方法です。データベース内のフィールドにインデックスを作成すると、フィールド値と、それが関連付けられているレコードへのポインターを含む別のデータ構造が作成されます。次に、このインデックス構造がソートされ、バイナリ検索が可能になります。
たとえば、Sale という名前の Django モデルを次に示します。
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, ) charged_amount = models.PositiveIntegerField()
次のように Django モデルを定義する際に、特定のフィールドにデータベース インデックスを追加できます。
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField()
このモデルの移行を実行すると、Django はテーブル Sales にデータベース インデックスを作成し、インデックスが完了するまでロックされます。少量のデータと非常に少数の接続を使用するローカル開発セットアップでは、この移行は瞬時に感じるかもしれませんが、実稼働環境について話すと、ロックを取得して作成するときにダウンタイムを引き起こす可能性のある多数の同時接続を持つ大規模なデータセットがあります。データベースのインデックスには時間がかかる場合があります。
以下に示すように、2 つのフィールドに対して 1 つのインデックスを作成することもできます。
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() class Meta: indexes = [ ["sold_at", "charged_amount"]]
データベース キャッシングは、データベースから迅速な応答を得るための最良の方法の 1 つです。これにより、データベースへの呼び出しが少なくなり、過負荷が防止されます。標準のキャッシュ操作は、次の構造に従います。
Django は、Memcached や Redis などのさまざまなキャッシュ バックエンドを使用できるキャッシュ メカニズムを提供し、同じクエリを複数回実行することを回避できます。
Memcached は、キャッシュされた結果を 1 ミリ秒未満で提供することを保証するオープンソースのインメモリ システムです。セットアップとスケーリングは簡単です。一方、Redis は、Memcached と同様の特性を持つオープンソースのキャッシュ ソリューションです。ほとんどのオフライン アプリは、以前にキャッシュされたデータを使用します。これは、クエリの大部分がデータベースに到達しないことを意味します。
ユーザー セッションは Django アプリケーションのキャッシュに保存する必要があります。Redis はデータをディスク上に保持するため、ログインしているユーザーのすべてのセッションは、データベースではなくキャッシュから発生します。
Django で Memcache を使用するには、以下を定義する必要があります。
ip
ip:port
の値。ip は Memcached デーモンの IP アドレス、 port
は Memcached が実行されているポート、または適切なスキームを使用した Redis インスタンスを指す URL です。
Memcached でデータベース キャッシングを有効にするには、次のコマンドを使用して pip を使用してpymemcache
をインストールします。
pip install pymemcache
次に、次のようにsettings.py
でキャッシュ設定を構成できます。
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } }
上記の例では、Memcached は localhost (127.0.0.1) ポート 11211 で実行され、 pymemcache
バインディングを使用しています。
同様に、Redis を使用してデータベース キャッシングを有効にするには、次のコマンドを使用して pip を使用して Redis をインストールします。
pip install redis
次に、次のコードを追加して、 settings.py
でキャッシュ設定を構成します。
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379', } }
Memcached と Redis を使用して、ユーザー認証トークンを保存することもできます。ログインするすべての人がトークンを提供する必要があるため、これらのすべての手順により、データベースのオーバーヘッドが大幅に増加する可能性があります。キャッシュされたトークンを使用すると、データベースへのアクセスが大幅に高速化されます。
通常、Django のクエリセットは、評価が行われるとその結果をキャッシュし、そのクエリセットをさらに操作する場合は、最初にキャッシュされた結果があるかどうかを確認します。ただし、 iterator()
を使用すると、キャッシュがチェックされず、結果がデータベースから直接読み取られ、結果がクエリセットに保存されません。
さて、これがどのように役立つか疑問に思っているに違いありません。大量のメモリをキャッシュする多数のオブジェクトを返すが、一度しか使用する必要がないクエリセットを考えてみましょう。このような場合は、 iterator()
を使用する必要があります。
たとえば、次のコードでは、すべてのレコードがデータベースからフェッチされ、メモリにロードされ、それぞれを反復処理します。
queryset = Product.objects.all() for each in queryset: do_something(each)
一方、 iterator()
を使用すると、Django は SQL 接続を開いたままにして各レコードを読み取り、次のレコードを読み取る前にdo_something()
を呼び出します。
queryset = Product.objects.all().iterator() for each in queryset: do_something(each)
Django はリクエストごとに新しいデータベース接続を作成し、リクエストが完了すると閉じます。この動作は、デフォルト値が 0 のCONN_MAX_AGE
によって引き起こされます。これは、サイトのトラフィック量によって決まります。音量が大きいほど、接続を維持するのに必要な秒数が長くなります。通常は、60 などの低い数値から始めることをお勧めします。
追加のオプションをOPTIONS
でラップする必要があります。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dashboard', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'CONN_MAX_AGE': '60', } } }
クエリ式は、更新、作成、フィルター、並べ替え、注釈、または集計操作で使用できる値または計算を定義します。
Django でよく使用される組み込みクエリ式は F 式です。それがどのように機能し、役立つか見てみましょう。
注:これらの式はdjango.db.models.expressions
およびdjango.db.models.aggregates
で定義されていますが、便宜上、それらは利用可能であり、通常はdjango.db.models
からインポートされます。
Django クエリセット API では、 F()
式を使用してモデル フィールド値を直接参照します。これにより、モデル フィールド値を参照し、データベースから Python メモリにフェッチすることなく、それらに対してデータベース アクションを実行できます。代わりに、Django はF()
オブジェクトを使用して、必要なデータベース アクティビティを定義する SQL 句を生成します。
たとえば、すべての製品の価格を 20% 引き上げたい場合、コードは次のようになります。
products = Product.objects.all() for product in products: product.price *= 1.2 product.save()
ただし、 F()
を使用すると、次のように単一のクエリでこれを実行できます。
from django.db.models import F Product.objects.update(price=F('price') * 1.2)
select_related()
とprefetch_related()
の使用Django は、データベース要求の数を最小限に抑えてクエリセットを最適化するためのselect_related()
およびprefetch_related()
引数を提供します。
公式のDjangoドキュメントによると:
select_related()
は、外部キー関係を「たどり」、クエリを実行するときに追加の関連オブジェクト データを選択します。
prefetch_related()
は、関係ごとに個別のルックアップを行い、Python で「結合」を行います。
select_related()
選択するアイテムが単一のオブジェクトである場合、 select_related()
を使用します。これは、前方のForeignKey
、 OneToOne
、および後方のOneToOne
フィールドを意味します。
select_related()
を使用して、1 対多および 1 対 1 接続の単一インスタンスのすべての関連オブジェクトを返す単一クエリを作成できます。クエリが実行されると、 select_related()
は外部キー リレーションシップから余分な関連オブジェクト データを取得します。
select_related()
は、SQL 結合を生成することによって機能し、関連するオブジェクトの列をSELECT
式に含めます。その結果、 select_related()
は、同じデータベース クエリで関連アイテムを返します。
select_related()
はより高度なクエリを生成しますが、取得したデータはキャッシュされるため、取得したデータの処理に追加のデータベース リクエストは必要ありません。
構文は単純に次のようになります。
queryset = Tweet.objects.select_related('owner').all()
prefetch_related()
対照的に、 prefetch_related()
は、多対多および多対 1 の接続に使用されます。すべてのモデルを含む単一のクエリが生成され、クエリで指定されたフィルターが生成されます。
構文は単純に次のようになります。
Book.objects.prefetch_related('author').get(id=1).author.first_name
注: SQL を使用して ManyToMany リレーションシップを処理しないでください。大きなテーブルを処理するときに多くのパフォーマンスの問題が発生する可能性があるためです。そのため、prefetch_related メソッドは Python 内でテーブルを結合し、大きな SQL 結合を回避します。
select_related()
とprefetch_related()
の違いについて詳しくは、 こちらをご覧ください。
bulk_create()
とbulk_update()
の使用bulk_create()
は、指定されたオブジェクトのリストを 1 回のクエリでデータベースに作成するメソッドです。同様に、 bulk_update()
は、提供されたモデル インスタンスの特定のフィールドを 1 つのクエリで更新するメソッドです。
たとえば、以下に示すような投稿モデルがあるとします。
class Post(models.Model): title = models.CharField(max_length=300, unique=True) time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title
さて、このモデルに複数のデータ レコードを追加したいとしましょう。次に、次のようにbulk_create()
を使用できます。
#articles articles = [Post(title="Hello python"), Post(title="Hello django"), Post(title="Hello bulk")] #insert data Post.objects.bulk_create(articles)
出力は次のようになります。
>>> Post.objects.all() <QuerySet [<Post: Hello python>, <Post: Hello django>, <Post: Hello bulk>]>
データを更新したい場合は、次のようにbulk_update()
を使用できます。
update_queries = [] a = Post.objects.get(id=14) b = Post.objects.get(id=15) c = Post.objects.get(id=16) #set update value a.title="Hello python updated" b.title="Hello django updated" c.title="Hello bulk updated" #append update_queries.extend((a, b, c)) Post.objects.bulk_update(update_queries, ['title'])
出力は次のようになります。
>>> Post.objects.all() <QuerySet [<Post: Hello python updated>, <Post: Hello django updated>, <Post: Hello bulk updated>]>
この記事では、データベースのパフォーマンスを最適化し、ボトルネックを減らし、Django アプリケーションのリソースを節約するためのヒントについて説明しました。
お役に立てば幸いです。読み続けます!