abtinmo
abtinmo
خواندن ۴ دقیقه·۴ سال پیش

ایندکس کردن 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
djangopostgresqlindexجنگوپایتون
شاید از این پست‌ها خوشتان بیاید