PostgreSQL چیست؟ آموزش کامل SQL، JSONB و اتصال PostgreSQL به Python و FastAPI

PostgreSQL یک دیتابیس رابطه‌ای قدرتمند برای ساخت Backend و سرویس‌های هوش مصنوعی است. در این آموزش، نصب، SQL، طراحی Table، Constraint، Index، JSONB و ساخت تاریخچه چت با FastAPI و درواره را یاد می‌گیرید.

Share
PostgreSQL چیست؟ آموزش کامل SQL، JSONB و اتصال PostgreSQL به Python و FastAPI

تقریباً هر نرم‌افزار کاربردی به فضایی برای نگهداری اطلاعات نیاز دارد. کاربران، سفارش‌ها، پیام‌ها، تنظیمات، تاریخچه گفت‌وگو، گزارش مصرف API و وضعیت پردازش‌ها باید به شکلی پایدار و قابل جست‌وجو ذخیره شوند.

ذخیره این اطلاعات در فایل JSON یا متغیرهای داخل برنامه ممکن است برای یک نمونه اولیه کافی باشد، اما با افزایش تعداد کاربران و داده‌ها به مشکلاتی مانند جست‌وجوی دشوار، ناسازگاری اطلاعات، هم‌زمانی و از دست رفتن داده می‌رسیم.

PostgreSQL یکی از شناخته‌شده‌ترین سیستم‌های مدیریت پایگاه داده رابطه‌ای و Object-relational است. این Database از SQL، Transaction، Constraint، Index، JSONB، Array، Full-text Search، Extension و قابلیت‌های پیشرفته دیگری پشتیبانی می‌کند.

در این مقاله یاد می‌گیرید:

  • PostgreSQL یا پستگرس چیست
  • چه تفاوتی با MySQL، SQLite و Redis دارد
  • Table، Row، Column و Schema چه هستند
  • چگونه PostgreSQL را با Docker نصب کنیم
  • چگونه Database و Table بسازیم
  • عملیات CRUD را با SQL انجام دهیم
  • Primary Key، Foreign Key و Constraint چه کاربردی دارند
  • Index چگونه Query را سریع‌تر می‌کند
  • Transaction و ACID چه هستند
  • تفاوت JSON و JSONB چیست
  • چگونه PostgreSQL را به Python و FastAPI متصل کنیم
  • چگونه تاریخچه گفت‌وگوی هوش مصنوعی را در PostgreSQL ذخیره کنیم
  • در محیط Production چه نکاتی را باید رعایت کنیم

PostgreSQL چیست؟

PostgreSQL یک سیستم مدیریت پایگاه داده رابطه‌ای متن‌باز است. این نرم‌افزار داده‌ها را معمولاً در Tableهای دارای Column و Row نگهداری می‌کند و امکان تعریف رابطه میان Tableها را فراهم می‌سازد.

برای مثال، یک نرم‌افزار گفت‌وگوی هوش مصنوعی می‌تواند Tableهای زیر را داشته باشد:

users
conversations
messages
api_usage

هر Conversation به یک User تعلق دارد و هر Message به یک Conversation متصل است:

User
  ↓
Conversation
  ↓
Message

PostgreSQL فقط یک محل ذخیره‌سازی ساده نیست. این Database می‌تواند موارد زیر را نیز مدیریت کند:

  • Data Type
  • Constraint
  • Relationship
  • Transaction
  • Index
  • View
  • Materialized View
  • Function
  • Trigger
  • Full-text Search
  • JSONB
  • Array
  • Partitioning
  • Replication
  • Extension

PostgreSQL چگونه تلفظ می‌شود؟

در فارسی از شکل‌های مختلفی استفاده می‌شود:

PostgreSQL
Postgres
پستگرس
پستگرس‌کیوال

خود پروژه استفاده از نام PostgreSQL و شکل کوتاه Postgres را رایج می‌داند. در این مقاله بیشتر از نام اصلی PostgreSQL استفاده می‌کنیم.

پایگاه داده رابطه‌ای چیست؟

در Relational Database داده‌ها در Table ذخیره می‌شوند و رابطه میان آن‌ها با Keyها تعریف می‌شود.

Table کاربران:

idnameemail
1امیرamir@example.com
2ساراsara@example.com

Table گفت‌وگوها:

iduser_idtitle
1011آموزش PostgreSQL
1021طراحی API
1032خلاصه‌سازی متن

فیلد user_id در Table گفت‌وگوها به id در Table کاربران اشاره می‌کند.

این رابطه اجازه می‌دهد تمام گفت‌وگوهای یک کاربر را پیدا کنیم:

SELECT
    conversations.id,
    conversations.title
FROM conversations
WHERE conversations.user_id = 1;

مفاهیم اصلی PostgreSQL

Database

مجموعه‌ای مستقل از اطلاعات و Objectهای پایگاه داده است.

darvareh_app

Schema

Namespaceای داخل Database است که Tableها، Viewها و Objectهای دیگر را سازمان‌دهی می‌کند.

Schema پیش‌فرض معمولاً:

public

نمونه Table در Schema مشخص:

app.users
billing.invoices
analytics.events

Table

ساختاری برای ذخیره داده‌های مشابه است:

CREATE TABLE users (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE
);

Column

هر ویژگی داده را مشخص می‌کند:

id
name
email
created_at

Row

هر رکورد داخل Table یک Row است.

Primary Key

شناسه یکتای هر Row است:

id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY

Foreign Key

رابطه میان دو Table را تعریف می‌کند:

user_id BIGINT REFERENCES users(id)

Constraint

قواعد معتبر بودن داده را در سطح Database اعمال می‌کند.

PostgreSQL چه تفاوتی با SQL دارد؟

SQL یک زبان برای تعریف، خواندن و تغییر داده در Databaseهای رابطه‌ای است.

PostgreSQL نرم‌افزاری است که SQL را اجرا می‌کند و امکانات Database را ارائه می‌دهد.

به بیان ساده:

SQL = زبان
PostgreSQL = سیستم مدیریت پایگاه داده

نرم‌افزارهای دیگری مانند MySQL، MariaDB، SQLite و SQL Server نیز از SQL استفاده می‌کنند، اما Syntax و قابلیت‌های آن‌ها کاملاً یکسان نیست.

PostgreSQL چه تفاوتی با MySQL، SQLite و Redis دارد؟

ویژگیPostgreSQLMySQLSQLiteRedis
نوع اصلیRelationalRelationalEmbedded RelationalIn-memory Data Store
اجرای Server جداگانهبلهبلهخیربله
SQLبلهبلهبلهخیر
Transactionپیشرفتهبلهبلهمدل متفاوت
JSONبله، شامل JSONBبلهمحدودتربا ساختارهای مختلف
مناسب Backend بزرگبلهبلهمحدودتربه‌عنوان مکمل
مناسب Cacheممکن، اما هدف اصلی نیستممکنمعمولاً خیربسیار مناسب
رابطه و Joinبسیار مناسبمناسبمناسب پروژه کوچکهدف اصلی نیست
Extensionگستردهمحدودترمحدودقابلیت‌ها متفاوت
استفاده معمولداده اصلی برنامهداده اصلی برنامهLocal و پروژه کوچکCache، Session و Queue

هیچ‌کدام در تمام سناریوها بهترین نیستند. در یک معماری رایج:

PostgreSQL → منبع اصلی داده
Redis → Cache و داده موقت
Object Storage → فایل‌ها
API درواره → پردازش هوش مصنوعی

مهم‌ترین مزایای PostgreSQL

پشتیبانی قدرتمند از SQL

PostgreSQL از Queryهای پیچیده، Join، Subquery، Window Function، CTE و Aggregate پشتیبانی می‌کند.

یکپارچگی داده

Constraintها اجازه نمی‌دهند هر داده‌ای بدون بررسی وارد Database شود.

Transaction

چند عملیات مرتبط می‌توانند به‌عنوان یک واحد اجرا شوند.

Data Typeهای متنوع

از جمله:

  • Integer
  • Numeric
  • Text
  • Boolean
  • Date
  • Timestamp
  • UUID
  • JSON و JSONB
  • Array
  • Range
  • Network Address
  • Geometric Type

JSONB

امکان نگهداری داده نیمه‌ساختاریافته همراه با Query و Index را فراهم می‌کند.

Extension

قابلیت‌های جدید می‌توانند از طریق Extension اضافه شوند. برای مثال، pgvector برای نگهداری و جست‌وجوی Vectorها در پروژه‌های RAG استفاده می‌شود.

MVCC

PostgreSQL از Multi-Version Concurrency Control استفاده می‌کند تا Reader و Writerها در بسیاری از شرایط با تداخل کمتری هم‌زمان کار کنند.

نصب PostgreSQL با Docker

برای محیط توسعه، Docker یکی از ساده‌ترین روش‌های اجرای PostgreSQL است.

فایل docker-compose.yml:

services:
  postgres:
    image: postgres:18-alpine
    container_name: postgres-local
    restart: unless-stopped
    environment:
      POSTGRES_DB: darvareh_app
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: change-this-local-password
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U app_user -d darvareh_app
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

این تنظیم برای محیط Local است. در Production باید نسخه Image ثابت، Secretها، Backup، شبکه و منابع Server متناسب با زیرساخت تنظیم شوند.

اجرای PostgreSQL:

docker compose up -d

بررسی وضعیت:

docker compose ps

مشاهده Log:

docker compose logs postgres

اتصال با psql:

docker compose exec postgres \
  psql -U app_user -d darvareh_app

اولین Commandهای psql

نمایش Databaseها:

\l

نمایش Tableها:

\dt

نمایش ساختار یک Table:

\d users

خروج:

\q

Commandهای دارای Backslash مربوط به ابزار psql هستند و SQL محسوب نمی‌شوند.

ساخت Table در PostgreSQL

فایل init.sql:

CREATE TABLE users (
    id UUID PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE conversations (
    id UUID PRIMARY KEY,
    user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    title TEXT NOT NULL,
    model_id TEXT NOT NULL,
    metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE messages (
    id UUID PRIMARY KEY,
    conversation_id UUID NOT NULL
        REFERENCES conversations(id)
        ON DELETE CASCADE,
    role TEXT NOT NULL
        CHECK (role IN ('system', 'user', 'assistant')),
    content TEXT NOT NULL
        CHECK (char_length(content) > 0),
    metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_conversations_user_created
    ON conversations(user_id, created_at DESC);

CREATE INDEX idx_messages_conversation_created
    ON messages(conversation_id, created_at ASC);

CREATE INDEX idx_conversations_metadata_gin
    ON conversations USING GIN(metadata);

CREATE INDEX idx_messages_metadata_gin
    ON messages USING GIN(metadata);

بررسی طراحی Tableها

UUID

در این پروژه شناسه‌ها از نوع UUID هستند:

id UUID PRIMARY KEY

UUID در سیستم‌های توزیع‌شده مفید است؛ زیرا برنامه می‌تواند شناسه را بدون مراجعه اولیه به Database تولید کند.

TIMESTAMPTZ

برای زمان از TIMESTAMPTZ استفاده شده است:

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

این نوع، Timestamp را با در نظر گرفتن Time Zone مدیریت می‌کند. معمولاً بهتر است زمان‌ها در Backend بر اساس UTC ذخیره و هنگام نمایش به منطقه زمانی کاربر تبدیل شوند.

CHECK

مقادیر مجاز Role محدود شده‌اند:

CHECK (
    role IN ('system', 'user', 'assistant')
)

ON DELETE CASCADE

اگر Conversation حذف شود، Messageهای آن نیز حذف می‌شوند:

ON DELETE CASCADE

این رفتار باید آگاهانه انتخاب شود. در تمام رابطه‌ها CASCADE بهترین گزینه نیست.

JSONB

Metadata انعطاف‌پذیر در JSONB ذخیره می‌شود:

metadata JSONB NOT NULL DEFAULT '{}'::jsonb

Data Typeهای پرکاربرد PostgreSQL

Data Typeکاربرد
SMALLINTعدد صحیح کوچک
INTEGERعدد صحیح معمول
BIGINTعدد صحیح بزرگ
NUMERICعدد دقیق، مناسب مبالغ
REALعدد اعشاری
DOUBLE PRECISIONعدد اعشاری با دقت بیشتر
TEXTمتن
VARCHAR(n)متن با سقف طول
BOOLEANدرست یا نادرست
DATEتاریخ
TIMEزمان
TIMESTAMPتاریخ و زمان بدون Time Zone
TIMESTAMPTZتاریخ و زمان با مدیریت Time Zone
UUIDشناسه UUID
JSONJSON متنی
JSONBنمایش باینری و قابل Index از JSON
BYTEAداده باینری
ARRAYآرایه
INETآدرس شبکه

برای مبلغ مالی، استفاده از FLOAT می‌تواند خطای اعشاری ایجاد کند. معمولاً NUMERIC یا نگهداری مبلغ در کوچک‌ترین واحد صحیح انتخاب دقیق‌تری است.

Constraint چیست؟

Constraint قواعد داده را در Database اعمال می‌کند.

NOT NULL

فیلد نباید خالی باشد:

name TEXT NOT NULL

UNIQUE

مقدار نباید تکراری باشد:

email TEXT UNIQUE

PRIMARY KEY

شناسه یکتا و غیرخالی:

id UUID PRIMARY KEY

FOREIGN KEY

ارتباط با Table دیگر:

user_id UUID REFERENCES users(id)

CHECK

قانون سفارشی:

price NUMERIC CHECK (price >= 0)

DEFAULT

مقدار پیش‌فرض:

created_at TIMESTAMPTZ DEFAULT NOW()

Constraint فقط برای جلوگیری از خطای برنامه نیست. چند سرویس یا Script ممکن است مستقیماً به Database بنویسند؛ بنابراین قواعد مهم باید در لایه Database نیز اعمال شوند.

عملیات CRUD با SQL

CRUD مخفف Create، Read، Update و Delete است.

INSERT برای ایجاد داده

INSERT INTO users (
    id,
    name,
    email
)
VALUES (
    '5fbc4277-ae63-4b65-a1cf-7b3f2a673da9',
    'امیر',
    'amir@example.com'
);

دریافت Row ایجادشده:

INSERT INTO conversations (
    id,
    user_id,
    title,
    model_id
)
VALUES (
    '8766a142-122b-4f57-b07a-8c8cfeab8241',
    '5fbc4277-ae63-4b65-a1cf-7b3f2a673da9',
    'آموزش PostgreSQL',
    'YOUR_MODEL_ID'
)
RETURNING *;

SELECT برای خواندن داده

SELECT
    id,
    name,
    email,
    created_at
FROM users
ORDER BY created_at DESC;

دریافت یک کاربر:

SELECT *
FROM users
WHERE email = 'amir@example.com';

محدود کردن نتایج:

SELECT *
FROM conversations
ORDER BY created_at DESC
LIMIT 20;

UPDATE برای تغییر داده

UPDATE conversations
SET
    title = 'راهنمای کامل PostgreSQL',
    updated_at = NOW()
WHERE id = '8766a142-122b-4f57-b07a-8c8cfeab8241'
RETURNING *;

همیشه شرط WHERE را بررسی کنید. حذف WHERE می‌تواند تمام Rowهای Table را تغییر دهد.

DELETE برای حذف داده

DELETE FROM conversations
WHERE id = '8766a142-122b-4f57-b07a-8c8cfeab8241';

بدون WHERE تمام Rowها حذف می‌شوند:

DELETE FROM conversations;

در محیط Production پیش از اجرای Queryهای تغییردهنده، Scope آن‌ها را دقیق بررسی کنید.

JOIN چیست؟

JOIN اطلاعات Tableهای مرتبط را ترکیب می‌کند.

نمایش Conversationها همراه نام کاربر:

SELECT
    conversations.id,
    conversations.title,
    conversations.model_id,
    users.name AS user_name
FROM conversations
LEFT JOIN users
    ON users.id = conversations.user_id
ORDER BY conversations.created_at DESC;

INNER JOIN

فقط Rowهایی برگردانده می‌شوند که در هر دو طرف تطبیق دارند.

LEFT JOIN

تمام Rowهای سمت چپ برگردانده می‌شوند؛ حتی اگر در Table سمت راست تطبیقی وجود نداشته باشد.

RIGHT JOIN

تمام Rowهای سمت راست حفظ می‌شوند.

FULL JOIN

Rowهای هر دو طرف حفظ می‌شوند.

Aggregate Functionها

شمارش پیام‌ها:

SELECT COUNT(*)
FROM messages;

تعداد پیام هر Conversation:

SELECT
    conversation_id,
    COUNT(*) AS message_count
FROM messages
GROUP BY conversation_id
ORDER BY message_count DESC;

تعداد Token ثبت‌شده در Metadata:

SELECT
    SUM(
        (metadata->>'total_tokens')::INTEGER
    ) AS total_tokens
FROM messages
WHERE metadata ? 'total_tokens';

CTE چیست؟

Common Table Expression یا CTE با WITH تعریف می‌شود و Query پیچیده را خواناتر می‌کند.

WITH conversation_stats AS (
    SELECT
        conversation_id,
        COUNT(*) AS message_count
    FROM messages
    GROUP BY conversation_id
)
SELECT
    conversations.title,
    conversation_stats.message_count
FROM conversation_stats
JOIN conversations
    ON conversations.id =
       conversation_stats.conversation_id
ORDER BY conversation_stats.message_count DESC;

CTE همیشه به معنی سریع‌تر شدن Query نیست؛ بیشتر یک ابزار ساختاری است و Execution Plan باید بررسی شود.

Transaction چیست؟

Transaction مجموعه‌ای از عملیات مرتبط است که به‌عنوان یک واحد اجرا می‌شوند.

مثال انتقال اعتبار:

BEGIN;

UPDATE wallets
SET balance = balance - 1000
WHERE user_id = 'user_a';

UPDATE wallets
SET balance = balance + 1000
WHERE user_id = 'user_b';

COMMIT;

اگر عملیات دوم شکست بخورد:

ROLLBACK;

در این صورت تغییرات Transaction برگردانده می‌شوند.

ACID چیست؟

Atomicity

تمام عملیات Transaction انجام می‌شوند یا هیچ‌کدام انجام نمی‌شوند.

Consistency

داده از یک وضعیت معتبر به وضعیت معتبر دیگری منتقل می‌شود.

Isolation

Transactionهای هم‌زمان تا سطح تعریف‌شده از یکدیگر جدا هستند.

Durability

پس از Commit موفق، داده باید بر اساس تضمین‌های تنظیم‌شده پایدار بماند.

سطح‌های Isolation

PostgreSQL از سطح‌های مختلف Isolation پشتیبانی می‌کند:

  • Read Committed
  • Repeatable Read
  • Serializable

Read Committed معمولاً سطح پیش‌فرض است.

افزایش Isolation می‌تواند ناسازگاری‌های بیشتری را کنترل کند، اما احتمال Retry شدن Transaction یا کاهش Concurrency را نیز افزایش می‌دهد.

بهترین Isolation Level به نوع عملیات بستگی دارد.

MVCC چیست؟

MVCC مخفف Multi-Version Concurrency Control است.

در این مدل، PostgreSQL نسخه‌هایی از Rowها را مدیریت می‌کند تا بسیاری از Readerها و Writerها بدون قفل‌کردن کامل یکدیگر هم‌زمان کار کنند.

مزایای عملی:

  • SELECT معمولاً Write را مسدود نمی‌کند
  • Transactionها Snapshot متناسب با Isolation Level می‌بینند
  • Concurrency بهتر می‌شود

MVCC باعث ایجاد نسخه‌های قدیمی Row نیز می‌شود. PostgreSQL با Vacuum این نسخه‌های غیرضروری را مدیریت می‌کند.

VACUUM و Autovacuum

هنگام UPDATE یا DELETE، نسخه‌های قدیمی Row ممکن است فوراً از فایل فیزیکی حذف نشوند.

Vacuum برای موارد زیر اهمیت دارد:

  • بازیابی فضای قابل استفاده
  • مدیریت Dead Tupleها
  • حفظ عملکرد Query
  • به‌روزرسانی اطلاعات موردنیاز Planner
  • جلوگیری از مشکلات مربوط به Transaction ID

در بیشتر پروژه‌ها Autovacuum باید فعال باقی بماند. خاموش کردن آن بدون دلیل و مانیتورینگ می‌تواند به افت عملکرد منجر شود.

اجرای دستی تحلیل آماری:

ANALYZE messages;

Vacuum همراه تحلیل:

VACUUM ANALYZE messages;

اجرای VACUUM FULL رفتار و Lock متفاوتی دارد و نباید بدون بررسی روی Table پرترافیک اجرا شود.

Index چیست؟

Index ساختاری است که می‌تواند پیدا کردن Rowها را سریع‌تر کند.

بدون Index، PostgreSQL ممکن است تمام Table را بررسی کند:

Sequential Scan

با Index مناسب، فقط بخش لازم بررسی می‌شود:

Index Scan

نمونه:

CREATE INDEX idx_messages_conversation_created
ON messages(conversation_id, created_at DESC);

Query مرتبط:

SELECT *
FROM messages
WHERE conversation_id = $1
ORDER BY created_at DESC
LIMIT 20;

آیا Index همیشه مفید است؟

خیر. Index هزینه دارد:

  • فضای Disk مصرف می‌کند
  • INSERT را سنگین‌تر می‌کند
  • UPDATE و DELETE باید Index را نیز تغییر دهند
  • Index بدون استفاده نیازمند نگهداری است

برای هر Column به‌صورت خودکار Index نسازید. Index باید بر اساس Queryهای واقعی طراحی شود.

ترتیب Columnها در Index ترکیبی

Index:

CREATE INDEX idx_messages_conversation_created
ON messages(conversation_id, created_at DESC);

برای Queryهایی مناسب است که ابتدا بر اساس conversation_id فیلتر و سپس بر اساس created_at مرتب می‌شوند.

این Index الزاماً برای Query زیر به همان اندازه مفید نیست:

SELECT *
FROM messages
ORDER BY created_at DESC;

ترتیب Columnهای Index به الگوی Query وابسته است.

انواع Index در PostgreSQL

B-tree

رایج‌ترین Index برای:

  • برابری
  • مقایسه
  • Range
  • Sort

GIN

برای مقادیر دارای چند جزء مناسب است:

  • JSONB
  • Array
  • Full-text Search

GiST

برای برخی داده‌های هندسی، Range، Search و Extensionها استفاده می‌شود.

BRIN

برای Tableهای بسیار بزرگ که داده‌ها از نظر فیزیکی با مقدار Column همبستگی دارند، مانند Timestampهای افزایشی، می‌تواند کم‌حجم باشد.

Hash

برای برخی مقایسه‌های برابری کاربرد دارد، اما B-tree در بسیاری از سناریوهای عمومی انتخاب رایج‌تری است.

EXPLAIN و EXPLAIN ANALYZE

برای مشاهده Execution Plan:

EXPLAIN
SELECT *
FROM messages
WHERE conversation_id =
    '8766a142-122b-4f57-b07a-8c8cfeab8241';

برای اجرای واقعی و مشاهده زمان:

EXPLAIN ANALYZE
SELECT *
FROM messages
WHERE conversation_id =
    '8766a142-122b-4f57-b07a-8c8cfeab8241';

EXPLAIN ANALYZE واقعاً Query را اجرا می‌کند. اگر Query شامل UPDATE، DELETE یا INSERT باشد، می‌تواند داده را تغییر دهد. برای بررسی Queryهای تغییردهنده باید از Transaction کنترل‌شده استفاده کنید.

نسخه کامل‌تر برای مشاهده Bufferها:

EXPLAIN (
    ANALYZE,
    BUFFERS,
    FORMAT TEXT
)
SELECT *
FROM messages
WHERE conversation_id = $1;

JSON و JSONB چه تفاوتی دارند؟

PostgreSQL دو Data Type اصلی برای JSON دارد:

JSON
JSONB

JSON

متن JSON را تقریباً با شکل ورودی نگه می‌دارد.

JSONB

JSON را در ساختار باینری قابل پردازش ذخیره می‌کند.

در بیشتر کاربردهایی که نیاز به Query، Operator و Index دارید، JSONB انتخاب رایج‌تری است.

ویژگیJSONJSONB
حفظ دقیق Format ورودیبیشترخیر
پردازش Queryمحدودترمناسب‌تر
Index با GINمحدودبله
سرعت درج اولیهممکن است کمتر پردازش شودنیازمند تبدیل
جست‌وجوی داخلیمحدودترقدرتمندتر
استفاده معمولنگهداری متن اصلی JSONMetadata و داده قابل Query

درج داده JSONB

INSERT INTO conversations (
    id,
    title,
    model_id,
    metadata
)
VALUES (
    '8766a142-122b-4f57-b07a-8c8cfeab8241',
    'گفت‌وگوی آزمایشی',
    'YOUR_MODEL_ID',
    '{
      "language": "fa",
      "source": "web",
      "tags": ["postgresql", "ai"]
    }'::jsonb
);

Query روی JSONB

دریافت مقدار یک Field به‌صورت JSON:

SELECT metadata->'language'
FROM conversations;

دریافت آن به‌صورت Text:

SELECT metadata->>'language'
FROM conversations;

فیلتر بر اساس Field:

SELECT *
FROM conversations
WHERE metadata->>'language' = 'fa';

بررسی وجود Key:

SELECT *
FROM conversations
WHERE metadata ? 'source';

Containment:

SELECT *
FROM conversations
WHERE metadata @> '{"language": "fa"}'::jsonb;

ساخت GIN Index برای JSONB

CREATE INDEX idx_conversations_metadata_gin
ON conversations
USING GIN(metadata);

این Index می‌تواند بعضی Queryهای JSONB، مانند Containment، را سریع‌تر کند.

هر Query دارای metadata->> الزاماً از Index عمومی GIN به بهترین شکل استفاده نمی‌کند. برای یک مسیر پرتکرار می‌توان Expression Index ساخت:

CREATE INDEX idx_conversations_language
ON conversations (
    (metadata->>'language')
);

طراحی Index باید با Query واقعی و Execution Plan هماهنگ باشد.

چه چیزی را در JSONB ذخیره کنیم؟

JSONB برای داده‌های انعطاف‌پذیر مانند این موارد مناسب است:

  • Metadata
  • تنظیمات متغیر
  • پارامترهای مدل
  • اطلاعات Provider
  • Usage جزئی
  • ویژگی‌های اختیاری
  • Snapshot پاسخ بیرونی

داده‌های اصلی و پرتکرار بهتر است Column مشخص داشته باشند:

نامناسب:

{
  "id": "...",
  "user_id": "...",
  "created_at": "...",
  "status": "active"
}

اگر دائماً روی user_id، created_at و status Query می‌زنید، بهتر است Column مستقل باشند.

الگوی مناسب:

Columnهای مشخص → داده اصلی و پرتکرار
JSONB → Metadata انعطاف‌پذیر

Pagination در PostgreSQL

Offset Pagination

SELECT *
FROM messages
ORDER BY created_at DESC
LIMIT 20 OFFSET 40;

مزایا:

  • ساده
  • امکان رفتن مستقیم به صفحه

معایب:

  • Offset بزرگ می‌تواند کند شود
  • با درج داده جدید احتمال جابه‌جایی نتایج وجود دارد

Keyset Pagination

SELECT *
FROM messages
WHERE (
    created_at,
    id
) < (
    $1,
    $2
)
ORDER BY created_at DESC, id DESC
LIMIT 20;

مزایا:

  • مناسب‌تر برای Dataset بزرگ
  • رفتار پایدارتر در Feed

معایب:

  • رفتن مستقیم به صفحه دلخواه دشوارتر است
  • نیازمند Cursor و Sort پایدار است

Connection Pool چیست؟

باز کردن Connection جدید برای هر Request هزینه دارد. Connection Pool مجموعه‌ای از Connectionهای آماده را نگه می‌دارد.

Request
   ↓
دریافت Connection از Pool
   ↓
اجرای Query
   ↓
بازگرداندن Connection به Pool

مزایا:

  • کاهش هزینه ایجاد Connection
  • کنترل تعداد Connectionهای هم‌زمان
  • کاهش فشار روی PostgreSQL
  • مدیریت بهتر منابع

Pool بسیار بزرگ نیز مناسب نیست. اگر هر Instance برنامه ۵۰ Connection و ۲۰ Instance داشته باشد، تعداد نظری Connectionها به ۱۰۰۰ می‌رسد.

اندازه Pool باید بر اساس موارد زیر تعیین شود:

  • تعداد Workerها
  • تعداد Instanceها
  • ظرفیت PostgreSQL
  • مدت Queryها
  • Concurrency
  • وجود Pooler بیرونی
  • Workload واقعی

اتصال Python به PostgreSQL با Psycopg

Psycopg یکی از Adapterهای شناخته‌شده PostgreSQL برای Python است.

نصب:

pip install "psycopg[binary,pool]"

نمونه ساده:

import psycopg

with psycopg.connect(
    "postgresql://app_user:password@localhost:5432/darvareh_app"
) as connection:
    with connection.cursor() as cursor:
        cursor.execute(
            """
            SELECT id, name, email
            FROM users
            WHERE email = %s
            """,
            ("amir@example.com",),
        )

        row = cursor.fetchone()

        print(row)

از Parameter Binding استفاده کنید و مقدار کاربر را با String Formatting وارد SQL نکنید.

نامناسب:

query = (
    "SELECT * FROM users "
    f"WHERE email = '{user_email}'"
)

مناسب:

cursor.execute(
    "SELECT * FROM users WHERE email = %s",
    (user_email,),
)

پروژه عملی: ذخیره تاریخچه چت در PostgreSQL

در این پروژه:

  1. Conversation جدید ساخته می‌شود.
  2. پیام User در PostgreSQL ذخیره می‌شود.
  3. تاریخچه اخیر Conversation خوانده می‌شود.
  4. پیام‌ها به API درواره ارسال می‌شوند.
  5. پاسخ مدل در PostgreSQL ذخیره می‌شود.
  6. پاسخ نهایی به Client برمی‌گردد.

ساختار پروژه

postgres-ai-chat/
├── app.py
├── init.sql
├── requirements.txt
├── docker-compose.yml
└── .env

نصب وابستگی‌ها

فایل requirements.txt:

fastapi
uvicorn[standard]
httpx
psycopg[binary,pool]
python-dotenv

نصب:

pip install -r requirements.txt

تنظیم Environment Variableها

فایل .env:

DATABASE_URL=postgresql://app_user:change-this-local-password@localhost:5432/darvareh_app
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

فایل .gitignore:

.env
.venv/
__pycache__/

کد کامل FastAPI

فایل app.py:

import os
from contextlib import asynccontextmanager
from uuid import UUID, uuid4

import httpx
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb
from psycopg_pool import AsyncConnectionPool
from pydantic import BaseModel, Field

load_dotenv()

DATABASE_URL = os.getenv("DATABASE_URL")
DARVAREH_API_KEY = os.getenv("DARVAREH_API_KEY")
DARVAREH_MODEL_ID = os.getenv("DARVAREH_MODEL_ID")

DARVAREH_URL = (
    "https://api.darvareh.ir/v1/chat/completions"
)

if not DATABASE_URL:
    raise RuntimeError(
        "متغیر DATABASE_URL تنظیم نشده است."
    )

if not DARVAREH_API_KEY:
    raise RuntimeError(
        "متغیر DARVAREH_API_KEY تنظیم نشده است."
    )

if not DARVAREH_MODEL_ID:
    raise RuntimeError(
        "متغیر DARVAREH_MODEL_ID تنظیم نشده است."
    )


class CreateConversationRequest(BaseModel):
    title: str = Field(
        min_length=1,
        max_length=200,
    )


class ConversationResponse(BaseModel):
    id: UUID
    title: str
    model_id: str


class CreateMessageRequest(BaseModel):
    content: str = Field(
        min_length=1,
        max_length=20_000,
    )


class ChatResponse(BaseModel):
    conversation_id: UUID
    user_message_id: UUID
    assistant_message_id: UUID
    content: str
    model: str


@asynccontextmanager
async def lifespan(app: FastAPI):
    pool = AsyncConnectionPool(
        conninfo=DATABASE_URL,
        min_size=1,
        max_size=10,
        open=False,
        kwargs={
            "row_factory": dict_row,
        },
    )

    await pool.open()
    await pool.wait()

    app.state.db_pool = pool

    yield

    await pool.close()


app = FastAPI(
    title="PostgreSQL AI Chat API",
    version="1.0.0",
    lifespan=lifespan,
)


@app.get("/health")
async def health_check():
    try:
        async with app.state.db_pool.connection() as conn:
            cursor = await conn.execute(
                "SELECT 1 AS value"
            )
            row = await cursor.fetchone()

        return {
            "status": "ok",
            "database": (
                "ok" if row["value"] == 1
                else "unavailable"
            ),
        }
    except Exception:
        return {
            "status": "degraded",
            "database": "unavailable",
        }


@app.post(
    "/v1/conversations",
    response_model=ConversationResponse,
    status_code=201,
)
async def create_conversation(
    payload: CreateConversationRequest,
):
    conversation_id = uuid4()

    async with app.state.db_pool.connection() as conn:
        cursor = await conn.execute(
            """
            INSERT INTO conversations (
                id,
                title,
                model_id,
                metadata
            )
            VALUES (
                %s,
                %s,
                %s,
                %s
            )
            RETURNING
                id,
                title,
                model_id
            """,
            (
                conversation_id,
                payload.title,
                DARVAREH_MODEL_ID,
                Jsonb({
                    "language": "fa",
                    "source": "api",
                }),
            ),
        )

        row = await cursor.fetchone()

    return ConversationResponse(**row)


@app.post(
    "/v1/conversations/{conversation_id}/messages",
    response_model=ChatResponse,
)
async def create_message(
    conversation_id: UUID,
    payload: CreateMessageRequest,
):
    user_message_id = uuid4()

    async with app.state.db_pool.connection() as conn:
        cursor = await conn.execute(
            """
            SELECT id
            FROM conversations
            WHERE id = %s
            """,
            (conversation_id,),
        )

        conversation = await cursor.fetchone()

        if not conversation:
            raise HTTPException(
                status_code=404,
                detail={
                    "code": "CONVERSATION_NOT_FOUND",
                    "message": (
                        "گفت‌وگوی موردنظر پیدا نشد."
                    ),
                },
            )

        await conn.execute(
            """
            INSERT INTO messages (
                id,
                conversation_id,
                role,
                content
            )
            VALUES (%s, %s, 'user', %s)
            """,
            (
                user_message_id,
                conversation_id,
                payload.content,
            ),
        )

    async with app.state.db_pool.connection() as conn:
        cursor = await conn.execute(
            """
            SELECT role, content
            FROM (
                SELECT
                    role,
                    content,
                    created_at,
                    id
                FROM messages
                WHERE conversation_id = %s
                ORDER BY created_at DESC, id DESC
                LIMIT 20
            ) AS recent_messages
            ORDER BY created_at ASC, id ASC
            """,
            (conversation_id,),
        )

        history = await cursor.fetchall()

    messages = [
        {
            "role": "system",
            "content": (
                "پاسخ‌ها را دقیق، روشن و "
                "به زبان فارسی ارائه کن."
            ),
        }
    ]

    messages.extend(
        {
            "role": row["role"],
            "content": row["content"],
        }
        for row in history
    )

    request_body = {
        "model": DARVAREH_MODEL_ID,
        "messages": messages,
    }

    headers = {
        "Authorization": (
            f"Bearer {DARVAREH_API_KEY}"
        ),
        "Content-Type": "application/json",
    }

    timeout = httpx.Timeout(
        connect=10.0,
        read=60.0,
        write=20.0,
        pool=10.0,
    )

    try:
        async with httpx.AsyncClient(
            timeout=timeout
        ) as client:
            response = await client.post(
                DARVAREH_URL,
                headers=headers,
                json=request_body,
            )

        if response.status_code == 429:
            raise HTTPException(
                status_code=503,
                detail={
                    "code": "UPSTREAM_RATE_LIMIT",
                    "message": (
                        "سرویس هوش مصنوعی موقتاً "
                        "با محدودیت درخواست مواجه است."
                    ),
                },
            )

        if response.status_code >= 500:
            raise HTTPException(
                status_code=502,
                detail={
                    "code": "UPSTREAM_ERROR",
                    "message": (
                        "سرویس هوش مصنوعی پاسخ "
                        "معتبری برنگرداند."
                    ),
                },
            )

        if response.status_code >= 400:
            raise HTTPException(
                status_code=502,
                detail={
                    "code": "UPSTREAM_REJECTED",
                    "message": (
                        "درخواست توسط سرویس "
                        "بالادستی پذیرفته نشد."
                    ),
                },
            )

        body = response.json()
        assistant_content = (
            body["choices"][0]["message"]["content"]
            .strip()
        )

        usage = body.get("usage", {})

    except httpx.TimeoutException as exc:
        raise HTTPException(
            status_code=504,
            detail={
                "code": "UPSTREAM_TIMEOUT",
                "message": (
                    "سرویس هوش مصنوعی در زمان "
                    "تعیین‌شده پاسخ نداد."
                ),
            },
        ) from exc
    except httpx.RequestError as exc:
        raise HTTPException(
            status_code=502,
            detail={
                "code": "UPSTREAM_CONNECTION_ERROR",
                "message": (
                    "ارتباط با سرویس "
                    "هوش مصنوعی برقرار نشد."
                ),
            },
        ) from exc
    except (
        ValueError,
        KeyError,
        IndexError,
        TypeError,
    ) as exc:
        raise HTTPException(
            status_code=502,
            detail={
                "code": "INVALID_UPSTREAM_RESPONSE",
                "message": (
                    "ساختار پاسخ سرویس "
                    "هوش مصنوعی معتبر نبود."
                ),
            },
        ) from exc

    if not assistant_content:
        raise HTTPException(
            status_code=502,
            detail={
                "code": "EMPTY_UPSTREAM_RESPONSE",
                "message": (
                    "سرویس هوش مصنوعی "
                    "پاسخ خالی برگرداند."
                ),
            },
        )

    assistant_message_id = uuid4()

    async with app.state.db_pool.connection() as conn:
        await conn.execute(
            """
            INSERT INTO messages (
                id,
                conversation_id,
                role,
                content,
                metadata
            )
            VALUES (
                %s,
                %s,
                'assistant',
                %s,
                %s
            )
            """,
            (
                assistant_message_id,
                conversation_id,
                assistant_content,
                Jsonb({
                    "model": DARVAREH_MODEL_ID,
                    "usage": usage,
                }),
            ),
        )

        await conn.execute(
            """
            UPDATE conversations
            SET updated_at = NOW()
            WHERE id = %s
            """,
            (conversation_id,),
        )

    return ChatResponse(
        conversation_id=conversation_id,
        user_message_id=user_message_id,
        assistant_message_id=assistant_message_id,
        content=assistant_content,
        model=DARVAREH_MODEL_ID,
    )


@app.get(
    "/v1/conversations/{conversation_id}/messages"
)
async def list_messages(
    conversation_id: UUID,
    limit: int = 50,
):
    safe_limit = min(max(limit, 1), 100)

    async with app.state.db_pool.connection() as conn:
        cursor = await conn.execute(
            """
            SELECT
                id,
                role,
                content,
                metadata,
                created_at
            FROM messages
            WHERE conversation_id = %s
            ORDER BY created_at ASC, id ASC
            LIMIT %s
            """,
            (
                conversation_id,
                safe_limit,
            ),
        )

        rows = await cursor.fetchall()

    return {
        "data": rows,
        "count": len(rows),
    }

اجرای پروژه

ابتدا PostgreSQL را اجرا کنید:

docker compose up -d

سپس API را اجرا کنید:

uvicorn app:app --reload

آدرس API:

http://127.0.0.1:8000

مستندات FastAPI:

http://127.0.0.1:8000/docs

ساخت Conversation

curl --request POST \
  --url http://127.0.0.1:8000/v1/conversations \
  --header "Content-Type: application/json" \
  --data '{
    "title": "آموزش PostgreSQL"
  }'

نمونه پاسخ:

{
  "id": "8766a142-122b-4f57-b07a-8c8cfeab8241",
  "title": "آموزش PostgreSQL",
  "model_id": "YOUR_MODEL_ID"
}

ارسال پیام

مقدار CONVERSATION_ID را با شناسه واقعی جایگزین کنید:

curl --request POST \
  --url http://127.0.0.1:8000/v1/conversations/CONVERSATION_ID/messages \
  --header "Content-Type: application/json" \
  --data '{
    "content": "تفاوت PostgreSQL و Redis چیست؟"
  }'

Backend پیام User را ذخیره می‌کند، تاریخچه را می‌خواند، درخواست را به درواره می‌فرستد و پاسخ Assistant را نیز در PostgreSQL ثبت می‌کند.

دریافت تاریخچه

curl \
  http://127.0.0.1:8000/v1/conversations/CONVERSATION_ID/messages

نمونه پاسخ:

{
  "data": [
    {
      "id": "1fd16b42-8b69-43f0-8c55-3f3714434e17",
      "role": "user",
      "content": "تفاوت PostgreSQL و Redis چیست؟",
      "metadata": {},
      "created_at": "2026-08-06T12:00:00Z"
    },
    {
      "id": "68e01f32-dca8-41d4-a9af-9381f22a8907",
      "role": "assistant",
      "content": "PostgreSQL معمولاً منبع اصلی داده‌های رابطه‌ای است، در حالی که Redis بیشتر برای Cache، Session و داده‌های موقت استفاده می‌شود.",
      "metadata": {
        "model": "YOUR_MODEL_ID",
        "usage": {}
      },
      "created_at": "2026-08-06T12:00:02Z"
    }
  ],
  "count": 2
}

چرا هنگام فراخوانی API، Transaction باز نگه نداشتیم؟

در کد، Connection پایگاه داده هنگام انتظار برای پاسخ مدل آزاد شده است.

طراحی نامناسب:

شروع Transaction
درج پیام
فراخوانی API با انتظار ۳۰ ثانیه
درج پاسخ
Commit

این کار Connection و Transaction را هنگام عملیات شبکه باز نگه می‌دارد.

طراحی مقاله:

درج و Commit پیام User
آزاد کردن Connection
فراخوانی API
دریافت Connection جدید
ذخیره پاسخ Assistant

در این مدل اگر فراخوانی مدل شکست بخورد، پیام User همچنان ذخیره است. در پروژه کامل می‌توان برای Generation یک Status مانند pending، completed یا failed نگه داشت و Retry کنترل‌شده ایجاد کرد.

PostgreSQL در معماری هوش مصنوعی

PostgreSQL می‌تواند این اطلاعات را نگه دارد:

  • کاربران
  • Conversationها
  • پیام‌ها
  • Prompt Templateها
  • تنظیمات مدل
  • Feedback
  • Evals
  • گزارش Usage
  • وضعیت Job
  • اسناد
  • Metadata
  • نتیجه پردازش
  • حافظه بلندمدت Agent

Redis می‌تواند لایه مکمل باشد:

  • Cache
  • Session
  • Rate Limit
  • Lock
  • Queue
  • حافظه کوتاه‌مدت

Vector Database یا pgvector می‌تواند برای Similarity Search و RAG استفاده شود.

PostgreSQL → داده اصلی و تاریخچه
Redis → Cache و وضعیت موقت
pgvector → جست‌وجوی معنایی
درواره → دسترسی API به مدل هوش مصنوعی

اتصال به API درواره

Base URL درواره:

https://api.darvareh.ir/v1

Endpoint مربوط به Chat Completions:

https://api.darvareh.ir/v1/chat/completions

کلید API را فقط در Backend و Environment Variable نگه دارید:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

برای انتخاب Model ID و مشاهده قیمت به‌روز، صفحه مدل‌های درواره را بررسی کنید.

Migration چیست؟

با تغییر برنامه، Schema پایگاه داده نیز تغییر می‌کند:

  • افزودن Table
  • افزودن Column
  • ساخت Index
  • تغییر Constraint
  • تبدیل Data Type
  • انتقال داده قدیمی

اجرای دستی تغییرات روی Production قابل اتکا نیست. Migrationها تغییرات Schema را نسخه‌بندی می‌کنند.

ابزارهای رایج:

  • Alembic برای SQLAlchemy و Python
  • Django Migrations
  • Prisma Migrate
  • Flyway
  • Liquibase
  • ابزار Migration فریم‌ورک‌ها

هر Migration بهتر است:

  • در Version Control باشد
  • روی نسخه مشابه Production آزمایش شود
  • زمان Lock را بررسی کند
  • مسیر Roll-forward داشته باشد
  • Backup و Recovery Plan را در نظر بگیرد

Backup و Replication یکسان نیستند

Backup

نسخه‌ای برای بازیابی داده در زمان بعدی است.

Replication

داده را روی Server دیگری کپی می‌کند تا Availability یا Read Capacity افزایش یابد.

اگر داده اشتباه حذف شود، ممکن است حذف روی Replica نیز تکرار شود. بنابراین Replica جایگزین Backup نیست.

Point-in-time Recovery

با Base Backup و WAL Archive می‌توان Database را به زمان مشخصی بازگرداند؛ البته باید از قبل تنظیم و آزمایش شده باشد.

Backup بدون Restore Test قابل اعتماد کامل نیست. باید بازیابی آزمایشی انجام شود.

مانیتورینگ PostgreSQL

Metricهای مهم:

Active Connections
Idle Connections
Transaction Rate
Query Latency
Slow Queries
Cache Hit Ratio
Dead Tuples
Autovacuum Activity
Database Size
Table Size
Index Size
Replication Lag
Lock Wait
Disk Usage
WAL Growth
Checkpoint Activity
Connection Pool Usage

Queryهای کند را با داده واقعی، EXPLAIN و EXPLAIN ANALYZE بررسی کنید. افزایش منابع Server همیشه جایگزین اصلاح Query و Index نیست.

اشتباهات رایج در PostgreSQL

ذخیره همه‌چیز در JSONB

JSONB انعطاف‌پذیر است، اما داده اصلی و پرتکرار باید Column مشخص داشته باشد.

ساخت Index برای تمام Columnها

Index اضافی هزینه Write و Disk را افزایش می‌دهد.

نداشتن Foreign Key

اگر رابطه واقعی وجود دارد، حذف Foreign Key می‌تواند داده Orphan ایجاد کند.

استفاده از OFFSET بزرگ

برای Dataset بزرگ، Keyset Pagination را بررسی کنید.

نگه داشتن Transaction هنگام API Call

Transaction طولانی می‌تواند Connection و نسخه‌های Row را برای مدت بیشتری نگه دارد.

نداشتن Connection Pool

ساخت Connection برای هر Request باعث افزایش سربار می‌شود.

Pool بسیار بزرگ

Connection بیش از حد می‌تواند PostgreSQL را تحت فشار قرار دهد.

استفاده از Float برای مبلغ

برای مبلغ دقیق از NUMERIC یا واحد صحیح کوچک‌تر استفاده کنید.

ترکیب زمان Local و UTC

زمان را با قرارداد مشخص ذخیره و نمایش دهید. TIMESTAMPTZ و UTC معمولاً انتخاب مناسبی هستند.

ساخت SQL با String Formatting

پارامترها را با Binding ارسال کنید.

نادیده گرفتن Autovacuum

Autovacuum برای سلامت بلندمدت Database مهم است.

اجرای Migration سنگین بدون بررسی

بعضی تغییرات Schema می‌توانند Lock طولانی ایجاد کنند.

نداشتن Backup آزمایش‌شده

وجود فایل Backup به‌تنهایی کافی نیست؛ Restore باید آزمایش شود.

ذخیره فایل بزرگ در Database بدون ارزیابی

در بسیاری از پروژه‌ها فایل در Object Storage ذخیره و URL و Metadata آن در PostgreSQL نگهداری می‌شود.

ذخیره تاریخچه نامحدود در Context مدل

نگهداری پیام در PostgreSQL به این معنی نیست که باید تمام تاریخچه در هر Request به مدل ارسال شود. تاریخچه باید محدود، خلاصه یا بر اساس نیاز بازیابی شود.

چک‌لیست PostgreSQL برای Production

پیش از انتشار پروژه این موارد را بررسی کنید:

  • Schema بر اساس نیاز واقعی طراحی شده است
  • Primary Key برای Tableها وجود دارد
  • Foreign Keyهای لازم تعریف شده‌اند
  • Constraintهای مهم در Database وجود دارند
  • Data Type مناسب انتخاب شده است
  • زمان‌ها با قرارداد مشخص ذخیره می‌شوند
  • مبلغ با نوع دقیق نگهداری می‌شود
  • Queryهای ورودی Parameterized هستند
  • Indexها بر اساس Query واقعی ساخته شده‌اند
  • Indexهای بدون استفاده بررسی می‌شوند
  • Queryهای مهم با EXPLAIN تحلیل شده‌اند
  • Pagination برای فهرست‌های بزرگ وجود دارد
  • JSONB فقط برای داده مناسب استفاده شده است
  • GIN یا Expression Index در صورت نیاز وجود دارد
  • Connection Pool فعال است
  • اندازه Pool بر اساس کل Instanceها تعیین شده است
  • Transactionهای طولانی کنترل می‌شوند
  • Timeout برای Queryها و Connectionها وجود دارد
  • Migrationها نسخه‌بندی شده‌اند
  • Migration روی داده مشابه Production تست شده است
  • Backup خودکار فعال است
  • Restore آزمایش شده است
  • Replication جایگزین Backup فرض نشده است
  • Disk و WAL مانیتور می‌شوند
  • Autovacuum فعال و مانیتور شده است
  • Slow Queryها ثبت می‌شوند
  • تعداد Connectionها مانیتور می‌شود
  • Secretها داخل Repository نیستند
  • تاریخچه ارسالی به مدل محدود می‌شود
  • خروجی مدل پیش از استفاده ساختاری اعتبارسنجی می‌شود

پرسش‌های متداول

PostgreSQL چیست؟

PostgreSQL یک سیستم مدیریت پایگاه داده رابطه‌ای متن‌باز است که از SQL، Transaction، Constraint، Index، JSONB و Extension پشتیبانی می‌کند.

تفاوت PostgreSQL و Postgres چیست؟

Postgres نام کوتاه و رایج PostgreSQL است. هر دو به یک پروژه اشاره می‌کنند.

آیا PostgreSQL رایگان است؟

PostgreSQL پروژه‌ای متن‌باز است. برای شرایط دقیق استفاده و توزیع باید مجوز رسمی همان نسخه و خدمات زیرساختی انتخاب‌شده بررسی شود.

PostgreSQL بهتر است یا MySQL؟

هر دو Databaseهای معتبر و پرکاربردی هستند. انتخاب به تیم، Hosting، قابلیت‌های موردنیاز، سازگاری نرم‌افزار و تجربه عملی بستگی دارد. PostgreSQL برای SQL پیشرفته، JSONB، Extension و Data Typeهای متنوع شناخته می‌شود.

PostgreSQL بهتر است یا Redis؟

این مقایسه مستقیم نیست. PostgreSQL معمولاً منبع اصلی داده رابطه‌ای است و Redis بیشتر برای Cache، Session، Queue و داده موقت استفاده می‌شود. بسیاری از پروژه‌ها از هر دو استفاده می‌کنند.

تفاوت JSON و JSONB چیست؟

JSON شکل متنی ورودی را بیشتر حفظ می‌کند. JSONB داده را در ساختاری مناسب‌تر برای پردازش، Operator و Index ذخیره می‌کند.

آیا PostgreSQL برای هوش مصنوعی مناسب است؟

بله. می‌توان کاربران، پیام‌ها، Promptها، Usage، Feedback، اسناد و Metadata را در PostgreSQL ذخیره کرد. با Extensionهایی مانند pgvector نیز می‌توان Vectorها را برای بعضی پروژه‌های RAG نگه داشت.

آیا می‌توان فایل را در PostgreSQL ذخیره کرد؟

از نظر فنی امکان نگهداری داده باینری وجود دارد، اما در بسیاری از معماری‌ها فایل در Object Storage و Metadata آن در PostgreSQL ذخیره می‌شود.

چرا به Connection Pool نیاز داریم؟

ایجاد Connection جدید هزینه دارد. Pool تعداد محدودی Connection را نگه می‌دارد و بین Requestها دوباره استفاده می‌کند.

آیا هر Column به Index نیاز دارد؟

خیر. Index باید بر اساس Queryهای واقعی ساخته شود. Index اضافی باعث افزایش مصرف Disk و هزینه Write می‌شود.

آیا PostgreSQL جایگزین Vector Database است؟

با pgvector می‌توان Vector Search را داخل PostgreSQL انجام داد. مناسب بودن آن به حجم داده، Query، Latency و معماری بستگی دارد. در بعضی پروژه‌ها Vector Database تخصصی انتخاب مناسب‌تری است.

آدرس API درواره چیست؟

https://api.darvareh.ir/v1

Endpoint مربوط به Chat Completions:

https://api.darvareh.ir/v1/chat/completions

قیمت مدل‌های درواره را از کجا ببینیم؟

فهرست Model IDها و قیمت به‌روز در صفحه مدل‌های درواره قرار دارد.

جمع‌بندی

PostgreSQL یک Database قدرتمند برای نگهداری پایدار داده‌های Backend، اپلیکیشن و سرویس‌های هوش مصنوعی است. پشتیبانی از SQL، رابطه، Constraint، Transaction، Index و JSONB باعث می‌شود بتوان داده‌های ساختاریافته و بخشی از داده‌های نیمه‌ساختاریافته را در یک سیستم مدیریت کرد.

در پروژه عملی این مقاله، یک API گفت‌وگو با FastAPI ساختیم که Conversation و Messageها را در PostgreSQL ذخیره می‌کند، تاریخچه اخیر را می‌خواند و برای تولید پاسخ به API درواره متصل می‌شود.

برای ساخت یک سیستم قابل اتکا، فقط ایجاد Table کافی نیست. باید Data Type، Constraint، Index، Transaction، Connection Pool، Migration، Backup، Autovacuum و Monitoring نیز متناسب با Workload طراحی شوند.

برای دریافت API Key و افزودن مدل‌های هوش مصنوعی به Backend خود، به وب‌سایت درواره مراجعه کنید. فهرست مدل‌ها، Model ID و قیمت‌های به‌روز نیز در صفحه مدل‌های درواره در دسترس است.

منابع

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.