ایندکس کردن JsonField در posgresql و جنگو

چند روز پیش از سمت مارکتینگ یه تسک برامون امد که برای انجام دادنش باید دیتای خاصی رو ذخیره می‌کردیم،‌ از اونجایی که این دیتا شکل نداشت و به جز توی یکی از اندپوینت‌ها، هیچ‌جا روش جست‌وجو انجام نمی‌شد تصمیم گرفتیم که از جیسون فیلد استفاده کنیم.ولی برای کل تیم همون یک نقطه هم جای سوال بود، در نتیجه تصمیم گرفتیم با یه پروژه کوچیک ببینیم جست‌وجو روی جیسون فیلد پستگرس چقدر زمان بر هست.کد پروژه توی گیت‌هاب به آدرس زیر در دسترس هست، می‌تونید مطالعه و اجرا کنید.https://github.com/abtinmo/django_jsonfield_indexفرض بر این هست که دنبال کننده این مطلب آشنایی اولیه با جنگو و کامند‌لاین را دارد.اولین قدم نصب کردن جنگو و کتابخونه رابط پستگرس هست.pip install django psycopg2-binaryساخت پروژه جنگو به اسم jstest (میدونم اسم هوشمندانه انتخاب شده‌ :)‌)django-admin startproject jstest cd jstest python3 manage.py migrateخوب برای نوشتن اندپوینت یک اپ تست اضافه می‌کنیمpython3 manage.py startapp testappحالا بریم برای تغییر دادن کد.در فایل jstest/settings.py اپ جدیدمون رو به لیست اپ‌ها اضافه می‌کنیم و دیتابیس sqlite رو با پستگرس ‌جایگزین می‌کنیمINSTALLED_APPS = [ ... 'testapp', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'YOUR_DB_NAME', 'USER': 'YOUR_DB_USER', 'PASSWORD': 'YOUR_DB_PASSWORD', 'HOST': 'localhost', 'PORT': '', } }داخل فایل testapp/models.py مقادیر زیر را وارد می‌کنیم:from django.db import models from django.utils import timezone class Product(models.Model): name = models.CharField(max_length=20) attributes = models.JSONField() create_date = models.DateTimeField(default=timezone.now)و سپس تغییرات را با دستور زیر روی دیتابیس وارد می‌کنیم:python3 manage.py makemigrations python3 manage.py migrateالان برای تست کردن سرعت جست‌وجو باید مقداری داده داخل جدول product وارد کنیم، برای تست ما مقدار ۲ میلیون رکورد یه رقم منطقی حساب می‌شد.اگه از کد من استفاده می‌کنید، می‌تونید با دستور زیر دیتای مورد نظر را وارد کنیدpython3 manage.py create_sample_dataوگرنه این فانکشن براتون دیتا رو ایجاد می‌کنه، هر جوری که براتون راحت تر هست، کد رو اجرا کنید و داده‌ها را به جدول اضافه کنید.from itertools import islice from testapp.models import Product batch_size = 5000 objs = ( Product( name='product No.%s' % i, attributes={&quotname&quot: i, &quotvalues&quot: [i, i + 1]}, ) for i in range(2_000_000) ) while True: batch = list(islice(objs, batch_size)) if not batch: break Product.objects.bulk_create(batch, batch_size)حالا بریم سراغ نوشتن اندپوینت برای بررسی سرعت جست‌وجو داده توی jsonfıeldبرای این کار مقادیر زیر را به فایل testapp/views.py اضافه کنید، پایین تر، کد رو توضیح میدمfrom .models import Product from django.db import connection from django.http import HttpResponse from random import randrange def product_view(request): rand_num = randrange(0, 2_000_000) _ = Product.objects.get(attributes__name=rand_num) dict_lookup_time = connection.queries[-1][&quottime&quot] _ = Product.objects.get( attributes__values__contains=[rand_num, rand_num + 1], ) list_lookup_time = connection.queries[-1][&quottime&quot] return HttpResponse( f&quotdict lookup time: {dict_lookup_time} &quot f&quotlist lookup time: {list_lookup_time}&quot )دیتای ذخیره شده داخل فیلد attributes به این شکل هستش:{ &quotname&quot: 1, &quotvalues&quot: [1,2] }ما باید بتونیم روی فیلد‌های name و value جست‌وجو انجام بدیم و برای این کار به شکل رندم عدد تولید می‌کنیم و با دستورات زیر جست‌وجو انجام میدیمProduct.objects.get(attributes__name=rand_num) #جست و جو روی مقدار فیلد name Product.objects.get(attributes__values__contains=[rand_num, rand_num + 1]) #جست و جو روی مقدار فیلد valuesو با این دستور هم زمان طول کشیدن اخرین query روی دیتابیس پیدا می‌شود.connection.queries[-1][&quottime&quot]با اضافه کردن کد زیر به jstest/urls.py کار ما با جنگو تمام میشود.from django.contrib import admin from django.urls import path from testapp.views import product_view urlpatterns = [ path('admin/', admin.site.urls), path('', product_view), ]با دستور زیر پروژه رو اجرا کنید و سپس آدرس localhost:8000 رو روی مرورگر باز کنیدpython3 manage.py runserverالان احتمالا با مقادیری مثل چیزی که توی عکس‌های زیر می‌بینید برخورد کنید.مقدار یک ثانیه برای پروژه‌ای که هیچ لودی روش نیست، اصلا قابل قبول نیست، پس حالا کنسول پستگرس رو باز می‌کنیم و شروع می‌کنیم به ایندکس کردن داده‌هابا دستور زیر کل ولیو‌های فیلد attributes را ایندکس می‌کنیم، انتظار داریم که سرعت کار بیشتر بشهCREATE INDEX JsonFieldIndex ON testapp_product USING GIN(attributes jsonb_path_ops);سرعت جست‌ و جو حدودا نصف شد ولی بازم برای یه پروژه که زیر بار نیست خیلی زیاد هستش.پس بریم برای هر کدوم از فیلد‌های name و values به شکل جدا ایندکس تعریف کنیماول ایندکس قبلی را حذف می‌کنیمdrop index JsonFieldIndex; و حالا برای هر کدوم از فیلد‌ها یه ایندکس جدا تعریف می‌کنیمCREATE INDEX JsonFieldIndexList ON testapp_product USING GIN((attributes->'values') jsonb_path_ops); CREATE INDEX JsonFieldIndex ON testapp_product USING HASH((attributes -> 'name'));حالا نتیجه به این شکل تغییر می‌کنهتفاوت خیلی زیاد شد نه؟‌:)اگر علاقه دارید که در مورد تفاوت ایندکس کردن‌های جیسون فیلد در پستگرس بیشتر بدونید، می‌تونید مطلب زیر را مطالعه کنیدhttps://dev.to/scalegrid/using-jsonb-in-postgresql-how-to-effectively-store-index-json-data-in-postgresql-5d7eاگر علاقه دارید در مورد انواع ایندکس در پستگرس بدونید، لینک زیر به دردتون می‌خورهhttps://www.postgresql.org/docs/12/indexes-types.html