Siz mağazaya on məhsul almağa gedirsiniz. On dəfə gedib hər dəfə bir məhsul ala bilərsiniz, ya da siyahı tutub hamısını bir dəfəyə ala bilərsiniz. Keçən dərsdəki resolver-lər bazaya «bir məhsul-bir gediş» qaydası ilə gedir: hər post üçün şərhlər barədə ayrıca sorğu. Bu, məşhur N+1 problemidir. Bu dərsdə onu Prisma jurnalında görəcək və DataLoader ilə həll edəcəksiniz — bir «takt»ın sorğularını birinə yığan kiçik kitabxana.
N+1 necə yaranır
{ posts { title comments { text } } } sorğusuna baxaq. Query.posts bir SQL sorğusu icra edib N post qaytarır. Sonra hər post üçün Post.comments çağırılır və hər çağırış öz WHERE "postId" = … sorğusunu göndərir. Cəmi 1 + N sorğu — adı da buradandır. 10 post olanda 11 sorğu, 100 post olanda 101 sorğu olur.
Bu iki səbəbdən pisdir. Hər sorğu şəbəkə üzərindən bazaya gedişdir, bağlantı hovuzu isə məhduddur. Üstəlik səviyyələr bir-birini vurur: hər şərhin müəllifi də varsa, sorğu sayı 1 + N + N·M olacaq. Müştəri isə cəmi bir sorğu göndərib.
N+1-i Prisma jurnalında görmək
Klient log: ['query'] ilə yaradılıb (keçən dərs). Sorğunu Sandbox-da icra edin və serverin konsoluna baxın (çıxış qısaldılıb):
prisma:query SELECT ... FROM "public"."Post" ... ORDER BY ... DESC
prisma:query SELECT ... FROM "public"."Comment" WHERE ... "postId" = $1 ...
prisma:query SELECT ... FROM "public"."Comment" WHERE ... "postId" = $1 ...
prisma:query SELECT ... FROM "public"."Comment" WHERE ... "postId" = $1 ...
... 7 more queries, one for each remaining postPost-a sorğu birdir, Comment-ə isə hər post üçün bir sorğu var. Jurnalda sorğuları saymağa öyrəşin: N+1-i tapmağın ən asan yolu budur.
💡 Prisma bir taktda edilən
findUniqueçağırışlarını özüINilə bir sorğuya birləşdirə bilir. Ona görəfindUniqueOrThrowilə yazılmışPost.authorjurnaldaPost.comments-dən yaxşı görünə bilər: oradafindManyvar, onu Prisma birləşdirmir. Buna güvənmək olmaz — bu üsul yalnızfindUniqueüçün işləyir. DataLoader isə istənilən mənbə (başqa REST API, Redis, servis) üçün yararlıdır və eyni zamanda nəticələri sorğu müddətində keşləyir.
İdeya: açarları yığıb bir dəfə soruşmaq
Hamısını bir gedişdə almaq üçün siyahı lazımdır. GraphQL Post.comments-in 10 resolver-ini cavab gözləmədən ardıcıl çağırır. Belə sinxron kodun hamısı Node.js hadisə dövrünün (event loop) bir taktına (tick) — gözləmə fasilələri arasındakı bir iş hissəsinə — sığır. Deməli, resolver bazaya dərhal getməyə bilər, «mənə 3-cü postun şərhləri lazımdır» deyə növbəyə yazar. Taktın sonunda növbədə bütün 10 açar olacaq — və bir WHERE "postId" IN (…) sorğusu göndərib, hər resolver-ə öz hissəsini paylamaq olar. Bu ideyanın oyuncaq versiyası: