Menyelesaikan Pertanyaan N+1 dengan DataLoaders
Kelompokkan dan cache carian pangkalan data menggunakan pemuat data untuk menghapuskan ledakan pertanyaan N+1 dalam penyelesai.
Menyelesaikan Pertanyaan N+1 dengan DataLoaders ialah pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI percuma di CoddyKit. Ini ialah pelajaran 2 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Masalah N+1 dalam GraphQL
GraphQL membolehkan klien meminta data bersarang dalam satu permintaan, seperti senarai posts dan author bagi setiap siaran. Bahayanya tersembunyi dalam penyelesai.
Andaikan anda mengambil 100 siaran dengan 1 query, kemudian menyelesaikan pengarang setiap siaran dengan menjalankan satu query bagi setiap siaran. Itu bersamaan 1 + 100 = 101 query — masalah N+1 yang klasik.
- 1 query untuk memuatkan senarai (iaitu 1)
- N query, satu bagi setiap item, untuk memuatkan medan berkaitan (iaitu N)
Pada skala besar, ini memusnahkan kependaman dan membebankan pangkalan data. DataLoader ialah pembaikan standard.
Melihat N+1 dalam Penyelesai Strawberry
Berikut ialah penyelesai Strawberry naif yang mencetuskan N+1. Setiap penyelesai author membuat panggilan pangkalan datanya sendiri.
Jika query memulangkan 50 siaran, penyelesai author ini akan mencetuskan 50 pernyataan SELECT yang berasingan. Query senarai ditambah 50 carian tersebut menghasilkan letupan N+1.
import strawberry
@strawberry.type
class Author:
id: int
name: str
@strawberry.type
class Post:
id: int
title: str
author_id: int
@strawberry.field
async def author(self) -> Author:
# BAD: one DB round-trip per post -> N+1
row = await db.fetch_one(
"SELECT id, name FROM authors WHERE id = :id",
{"id": self.author_id},
)
return Author(id=row["id"], name=row["name"])Idea Teras: Kumpulan dan Cache
DataLoader menyelesaikan N+1 dengan dua teknik:
- Pengumpulan: bukannya menyelesaikan setiap
author_iddengan serta-merta, pemuat mengumpulkan semua kunci yang diminta semasa satu detik gelung peristiwa dan menyelesaikannya bersama-sama dalam satu query berkumpulan (contohnyaWHERE id = ANY(...)). - Pencachean: dalam satu permintaan, kunci yang sama hanya diambil sekali. Meminta pengarang 7 sepuluh kali menghasilkan satu carian.
Hasilnya: 1 query untuk siaran + 1 query berkumpulan untuk semua pengarang = 2 query, bukannya 101.
Cara Pengumpulan Berfungsi pada Gelung Peristiwa
DataLoader Strawberry bergantung pada gelung peristiwa asyncio. Apabila beberapa penyelesai memanggil loader.load(key), pemuat tidak berjalan serta-merta. Ia merekodkan setiap kunci dan memulangkan awaitable yang belum selesai.
Pada detik berikutnya, pemuat mengambil setiap kunci dalam barisan, memanggil fungsi kelompok anda sekali dengan senarai kunci penuh, kemudian menyelesaikan setiap awaitable dengan hasil yang sepadan.
Inilah sebabnya DataLoader hanya berfungsi dalam kod tak segerak: mekanisme penangguhan bergantung pada penjadualan gelung untuk menghantar kelompok selepas kerja segerak semasa selesai.
Menulis Fungsi Pemuatan Kelompok
Teras DataLoader ialah fungsi kelompok. Ia menerima senarai kunci dan mesti memulangkan senarai hasil dalam susunan yang sama tepat seperti kunci.
Dua peraturan yang tidak boleh dirundingkan:
- Panjang senarai yang dipulangkan mesti sama dengan panjang kunci.
- Hasil pada indeks
imesti sepadan dengankeys[i]. Baris yang tiada hendaklah dipetakan kepadaNone(atau Exception), dan tidak boleh dibuang.
Di bawah, kami memetakan baris mengikut id, kemudian mengeluarkannya semula mengikut susunan kunci.
from typing import List, Optional
async def load_authors(keys: List[int]) -> List[Optional[Author]]:
rows = await db.fetch_all(
"SELECT id, name FROM authors WHERE id = ANY(:ids)",
{"ids": keys},
)
by_id = {row["id"]: Author(id=row["id"], name=row["name"]) for row in rows}
# Preserve order; None for missing keys
return [by_id.get(key) for key in keys]Penjajaran Susunan Ditunjukkan
Kontrak pengekalan susunan ialah punca pepijat DataLoader yang paling biasa. Berikut ialah simulasi kendiri: baris tiba dalam susunan sewenang-wenangnya daripada pangkalan data, tetapi kita mesti mengembalikannya sejajar dengan kunci yang diminta.
Jalankan ini untuk melihat cara kamus carian bersama pemahaman yang disusun mengikut kunci menjamin penjajaran yang betul walaupun DB mengembalikan baris dalam susunan yang berbeza atau tidak menyertakan kunci yang tiada.
def batch_load(keys, rows):
by_id = {row["id"]: row["name"] for row in rows}
return [by_id.get(k) for k in keys]
keys = [3, 1, 7, 4]
# DB returns rows shuffled and is missing id=7
rows = [
{"id": 1, "name": "Ada"},
{"id": 4, "name": "Linus"},
{"id": 3, "name": "Grace"},
]
result = batch_load(keys, rows)
print(result) # ['Grace', 'Ada', None, 'Linus']
assert len(result) == len(keys)
for key, name in zip(keys, result):
print(f"key={key} -> {name}")Mencipta DataLoader dalam Strawberry
Strawberry menyediakan kelas DataLoader. Anda membinanya menggunakan fungsi kelompok anda. Panggilan kepada .load(key) mengembalikan sesuatu yang boleh ditunggu dan akan diselesaikan selepas pengelompokan.
Yang penting, satu tika DataLoader menyimpan cache khusus untuk tika tersebut. Anda mesti mencipta pemuat baharu bagi setiap permintaan supaya data lapuk dan kebocoran merentas pengguna tidak pernah berlaku. Kita akan menyambungkannya seterusnya melalui konteks.
from strawberry.dataloader import DataLoader
# batch function from the previous scene
author_loader = DataLoader(load_fn=load_authors)
# Inside a resolver you would now write:
# author = await author_loader.load(self.author_id)
# Many concurrent .load() calls collapse into ONE call to load_authors.Pemuat bagi Setiap Permintaan melalui Konteks GraphQL
Tempat yang kemas untuk menyimpan pemuat berskop permintaan ialah konteks GraphQL. Dengan FastAPI + Strawberry, anda mengatasi get_context untuk membina pemuat baharu pada setiap permintaan.
Ini menjamin tetingkap pengelompokan dan cache diasingkan kepada satu permintaan — tepat sepanjang tempoh yang anda perlukan.
from strawberry.fastapi import GraphQLRouter
from strawberry.dataloader import DataLoader
async def get_context() -> dict:
return {
"author_loader": DataLoader(load_fn=load_authors),
# one loader per relation, all rebuilt per request
}
graphql_app = GraphQLRouter(schema, context_getter=get_context)
# app.include_router(graphql_app, prefix="/graphql")Menggunakan Pemuat dalam Penyelesai
Sekarang penyelesai author membaca pemuat daripada info.context dan memanggil .load(). Strawberry menyuntik info apabila anda mengisytiharkannya sebagai parameter.
Walaupun penyelesai ini dijalankan sekali bagi setiap kiriman, semua panggilan .load() tersebut dikelompokkan menjadi satu SELECT ... WHERE id = ANY(...) — N+1 telah dihapuskan.
import strawberry
from strawberry.types import Info
@strawberry.type
class Post:
id: int
title: str
author_id: int
@strawberry.field
async def author(self, info: Info) -> Author:
loader = info.context["author_loader"]
return await loader.load(self.author_id)Kelebihan Cache dan Hadnya
Dalam satu permintaan, pemuat menyimpan cache berdasarkan kunci, jadi panggilan load(7) yang berulang hanya mengakses DB sekali. Ini amat berguna untuk pertanyaan kembang keluar apabila pengarang yang sama muncul dalam banyak kiriman.
Perhatikan pertukaran yang terlibat:
- Cache adalah bagi setiap permintaan mengikut reka bentuk — jangan sekali-kali berkongsi pemuat antara permintaan, kerana anda akan menghidangkan data lapuk.
- Jika rekod berubah di tengah-tengah permintaan dan anda membacanya semula, anda akan mendapat salinan yang dicache. Panggil
loader.clear(key)selepas mutasi untuk membatalkannya. - Kunci cache ialah nilai kunci mentah, jadi pastikan kunci boleh dicincang dan konsisten (contohnya sentiasa
int, bukan kadangkalastr).
Memuatkan Koleksi dan Kunci Tuple
DataLoaders bukan hanya untuk carian satu-ke-satu. Untuk hubungan satu-ke-banyak (comments bagi satu kiriman), fungsi kelompok mengembalikan satu senarai bagi setiap kunci. Kelompokkan baris mengikut kunci asing, kemudian keluarkan satu senarai bagi setiap kunci yang diminta (senarai kosong jika tiada).
Untuk carian gabungan, gunakan tuple yang boleh dicincang sebagai kunci, contohnya (post_id, locale). Pastikan jenisnya kekal supaya caching terus betul.
from collections import defaultdict
async def load_comments(post_ids):
rows = await db.fetch_all(
"SELECT id, post_id, body FROM comments WHERE post_id = ANY(:ids)",
{"ids": post_ids},
)
grouped = defaultdict(list)
for row in rows:
grouped[row["post_id"]].append(row)
# one list per key, in key order
return [grouped.get(pid, []) for pid in post_ids]Semakan Ringkas: Tempoh Hayat DataLoader
Seorang rakan sepasukan mencipta satu DataLoader pada peringkat modul dan menggunakannya semula untuk seluruh aplikasi bagi "menjimatkan memori." Mengapakah ini pilihan yang salah untuk perkhidmatan FastAPI GraphQL berbilang pengguna?
Imbas Kembali: DataLoaders Mengatasi N+1
Anda telah mempelajari cara menghapuskan ledakan pertanyaan N+1 dalam penyelesai Strawberry + FastAPI:
- N+1 berlaku apabila penyelesai bersarang mengeluarkan satu pertanyaan bagi setiap item induk.
- DataLoader membaikinya dengan mengelompokkan semua kunci daripada satu detik gelung acara menjadi satu pertanyaan dan mencache kunci yang berulang dalam permintaan tersebut.
- Fungsi kelompok mesti mengembalikan hasil yang sejajar dengan kunci input, dengan panjang dan susunan yang sama, serta
Noneatau senarai kosong bagi carian yang gagal. - Bina pemuat bagi setiap permintaan dalam
get_contextdan bacanya daripadainfo.contextdi dalam penyelesai. - Gunakan senarai bagi setiap kunci untuk hubungan satu-ke-banyak dan kunci tuple yang boleh dicincang untuk carian gabungan; panggil
clear()selepas mutasi.
Dengan corak ini, pertanyaan GraphQL yang bersarang secara mendalam kekal pantas dan pangkalan data anda kekal stabil.
Pelajari Kem Intensif Pembangunan Bahagian Belakang FastAPI dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 21
- Pelajaran
- 84
Soalan Lazim
Adakah pelajaran “Menyelesaikan Pertanyaan N+1 dengan DataLoaders” percuma?
Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, termasuk “Menyelesaikan Pertanyaan N+1 dengan DataLoaders”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Menyelesaikan Pertanyaan N+1 dengan DataLoaders”?
Kelompokkan dan cache carian pangkalan data menggunakan pemuat data untuk menghapuskan ledakan pertanyaan N+1 dalam penyelesai. Anda berlatih Kem Intensif Pembangunan Bahagian Belakang FastAPI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Bahagian Belakang FastAPI?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 2 daripada 4.
Berapa lamakah pelajaran “Menyelesaikan Pertanyaan N+1 dengan DataLoaders” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI ini?
Ya. Setiap pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Mentakrifkan Jenis, Pertanyaan dan Mutasi
- Menyelesaikan Pertanyaan N+1 dengan DataLoaders
- Langganan GraphQL Masa Nyata
- Analisis Kos Pertanyaan dan Had Kedalaman