چند روز پیش از سمت مارکتینگ یه تسک برامون امد که برای انجام دادنش باید دیتای خاصی رو ذخیره میکردیم، از اونجایی که این دیتا شکل نداشت و به جز توی یکی از اندپوینتها، هیچجا روش جستوجو انجام نمیشد تصمیم گرفتیم که از جیسون فیلد استفاده کنیم.
ولی برای کل تیم همون یک نقطه هم جای سوال بود، در نتیجه تصمیم گرفتیم با یه پروژه کوچیک ببینیم جستوجو روی جیسون فیلد پستگرس چقدر زمان بر هست.
کد پروژه توی گیتهاب به آدرس زیر در دسترس هست، میتونید مطالعه و اجرا کنید.
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={"name": i, "values": [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]["time"] _ = Product.objects.get( attributes__values__contains=[rand_num, rand_num + 1], ) list_lookup_time = connection.queries[-1]["time"] return HttpResponse( f"dict lookup time: {dict_lookup_time} " f"list lookup time: {list_lookup_time}" )
دیتای ذخیره شده داخل فیلد attributes به این شکل هستش:
{ "name": 1, "values": [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]["time"]
با اضافه کردن کد زیر به 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