Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django ORM パフォーマンスTips

takeaship
November 10, 2022

Django ORM パフォーマンスTips

みんなのPython勉強会 #87 での発表資料です。
https://startpython.connpass.com/event/263565/

実務でDjangoアプリケーション開発をするなかで、パフォーマンス上の問題に対処する機会がよくあったので、その知見を共有しました。
N + 1問題を中心に、効率的なクエリを発行できるORMのコードを書くためのポイントをまとめています。

takeaship

November 10, 2022
Tweet

More Decks by takeaship

Other Decks in Programming

Transcript

  1. ユーザ Djangoアプリ DB クエリ実⾏ Request Request クエリ発⾏ クエリ発⾏ 結果返却 結果返却

    モデルに変換 モデルに変換 Response Response ユーザ Djangoアプリ DB
  2. 回避策① N:1や1:1の関係の場合: select_related()を使う 発行されるSQL orders = Order.objects .filter(created_at__gte='2022-11-01') .select_related('customer').all() for

    order in orders: print(order.customer.name) SELECT * FROM ORDER INNER JOIN CUSTOMER ON ORDER.customer_id = CUSTOMER.id WHERE ORDER.created_at >= '2022-11-01';
  3. 回避策② 1:NやN:Nの関係の場合、prefetch_related()を使う 発行されるSQL customers = Customer.objects .filter(address='Tokyo') .prefetch_related('order_set').all() for customer

    in customers: for order in customer.order_set.all(): print(order.orderNumber) SELECT * FROM CUSTOMER WHERE CUSTOMER.address = 'Tokyo'; SELECT * FROM ORDER WHERE ORDER.customer_id IN (1, 2, 3, 4, 5);
  4. 回避策③ 子レコードのcountだけ欲しい、 などならannotateを使う 発行されるSQL customers = Customer.objects .filter(address='Tokyo') .annotate(order_count=models.Count('order')).all() for

    customer in customers: print(customer.order_count) SELECT CUSTOMER.*, COUNT(ORDER.id) AS order_count FROM CUSTOMER LEFT OUTER JOIN ORDER ON CUSTOMER.id = ORDER.customer_id WHERE CUSTOMER.address = 'Tokyo' GROUP BY CUSTOMER;
  5. create()するたびにINSERT文が飛ぶ 回避策 names = ['hoge','fuga','piyo'] for name in names: Customer.objects.create(name=name)

    customer_objecs = [Customer(name=name) for name in names] Customer.objects.bulk_create(customer_objecs)
  6. save()するたびにUPDATE文が飛ぶ 回避策 customers = Customer.objects.all() for customer in customers: customer.name

    = 'new name' customer.save() customers = Customer.objects.all() for customer in customers: customer.name = 'new name' Customer.objects.bulk_update(customers, fields=['name'])
  7. テーブル構造 ROOT string label string root_type CATEGORY string label string

    parent_id Entity string label string parent_id has has
  8. 愚直に書くと… for root_label, categories in roots: root = Root.objects.create(label=root_label) for

    category_label, entities in categories: category = Category.objects.create(label=category_label, for entity_label in entities: Entity.objects.create(label=entity_label, category=ca
  9. entityだけまとめて bulk_create することに落ち着いた entity_obj_list = [] for root_label, categories in

    roots: root = Root.objects.create(label=root_label) for category_label, entities in categories: category = Category.objects.create(label=category_label, for entity_label in entities: entity_obj_list.append(Entity(label=entity_label, cat Entity.objects.bulk_create(entity_obj_list)