بخش اول: ورود به جهان قدرتمند PostgreSQL
بخش دوم: جادوی جستجو و کوئری‌نویسی
بخش سوم: بهینه‌سازی و ساختارهای پیشرفته
بخش چهارم: امنیت، پشتیبانی، عملکرد و نگه‌داری
کارگاه‌ها و مثال‌های کاربردی

ساختار Page و مفهوم CTID


وقتی در PostgreSQL یک جدول می‌سازید و داخلش داده می‌ریزید، شاید فکر کنید داده‌ها «در جدول» ذخیره می‌شوند.
اما واقعیت این است که پشت هر جدول، یک فایل واقعی روی دیسک وجود دارد — و PostgreSQL داده‌ها را در قالب صفحات (Pages) و تاپل‌ها (Tuples) داخل این فایل‌ها نگهداری می‌کند.


🔹 هر جدول = یک فایل روی دیسک

هر جدول در PostgreSQL در فایل مخصوص خودش ذخیره می‌شود.
اگر به مسیر داده‌های PostgreSQL (مثل /var/lib/postgresql/18/main/base/) سر بزنید، می‌توانید فایل‌های جدول‌ها را ببینید.

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

SELECT relfilenode FROM pg_class WHERE relname = 'demo_users';

🧱 Page چیست؟

PostgreSQL داده‌ها را در واحدهایی به نام Page ذخیره می‌کند.
هر Page معمولاً ۸ کیلوبایت (۸KB) حجم دارد.

می‌توانید تصور کنید هر جدول از تعداد زیادی بلوک ۸KB تشکیل شده که هر کدام چندین ردیف (tuple) را در خود جای می‌دهند:

┌──────── page 0 ────────┐
│ row1 | row2 | row3 ... │
└────────────────────────┘
┌──────── page 1 ────────┐
│ row4 | row5 | row6 ... │
└────────────────────────┘

وقتی داده‌های جدید اضافه می‌کنید، PostgreSQL در صورت نیاز صفحه‌های جدیدی به فایل اضافه می‌کند.


🧪 بیایید این را در عمل ببینیم

ساخت جدول تستی
CREATE TABLE demo_users (
  id SERIAL PRIMARY KEY,
  username TEXT,
  email TEXT,
  bio TEXT
);

الان جدول خالی است و اندازه‌اش تقریباً صفر است:

SELECT pg_size_pretty(pg_relation_size('demo_users')) AS heap_size,
       pg_size_pretty(pg_total_relation_size('demo_users')) AS total_size;

📦 خروجی:

 heap_size | total_size 
------------+------------
 ۰ bytes    | 24 kB

این ۲۴KB مربوط به فایل‌های کنترلی مثل Free Space Map (FSM) است، نه داده واقعی.


درج ۵۰ ردیف داده
INSERT INTO demo_users (username, email, bio)
SELECT 'user_' || g,
       'user' || g || '@example.com',
       'This is bio for user ' || g
FROM generate_series(1, 50) g;

حالا جدول واقعاً داده دارد. دوباره اندازه را بررسی کنیم:

SELECT pg_size_pretty(pg_relation_size('demo_users')) AS heap_only,
       pg_size_pretty(pg_total_relation_size('demo_users')) AS total;

خروجی ممکن است چیزی شبیه این باشد:

 heap_only | total
------------+---------
 ۸ kB       | 32 kB

🎯 یعنی جدول حالا حداقل یک Page (۸KB) برای داده‌ها دارد.


🔍 بررسی CTID، XMIN و XMAX

بیایید نگاهی به ساختار درونی هر ردیف بیندازیم:

SELECT ctid, xmin, xmax, username
FROM demo_users
LIMIT 5;

نتیجه نمونه:

ctidxminxmaxusername
(۰,۱)۱۲۳۴۵۰user_1
(۰,۲)۱۲۳۴۵۰user_2
توضیح هر ستون:
ستونتوضیح
ctidموقعیت فیزیکی رکورد روی دیسک (Page و Slot). مثلاً (۰,۱) یعنی در صفحه ۰، جایگاه ۱.
xminشناسه تراکنشی که رکورد را ایجاد کرده.
xmaxشناسه تراکنشی که رکورد را حذف یا به‌روزرسانی کرده (اگر صفر باشد یعنی هنوز زنده است).

⚙️ رفتار CTID هنگام UPDATE

بیایید یک ردیف را تغییر دهیم:

UPDATE demo_users
SET bio = bio || ' - updated version'
WHERE username = 'user_1';

سپس دوباره بررسی کنیم:

SELECT ctid, xmin, xmax, username
FROM demo_users
WHERE username = 'user_1';

خروجی جدید:

ctidxminxmaxusername
(۲,۴)۴۵۶۷۸۰user_1

🔁 می‌بینید که CTID عوض شد!
چون PostgreSQL هنگام UPDATE در واقع رکورد جدیدی می‌نویسد و رکورد قبلی را منقضی می‌کند — این همان رفتار MVCC است.


📊 بررسی رشد فایل با INSERT بیشتر

INSERT INTO demo_users (username, email, bio)
SELECT 'user_' || g,
       'user' || g || '@example.com',
       'Another bio ' || g
FROM generate_series(51, 1000) g;

سپس:

SELECT pg_size_pretty(pg_relation_size('demo_users')) AS heap_only;

خروجی مثلاً:

 heap_only 
------------
 ۳۲۰ kB

📈 هر بار که داده اضافه می‌شود، فایل جدول با گام‌های ۸KB بزرگ‌تر می‌شود.


🧠 جمع‌بندی: مفاهیم کلیدی

مفهومتوضیح
Pageبلوک ۸KB که داده‌ها در آن ذخیره می‌شوند.
CTIDموقعیت فیزیکی رکورد در قالب (page, slot)
XMIN / XMAXشناسه تراکنش‌های ایجاد / حذف رکورد
FSM (Free Space Map)نقشه فضای خالی برای درج داده جدید
Page Growthهر بار ۸KB داده جدید → افزایش یک Page

CTID ابزاری عالی برای فهمیدن نحوه‌ی ذخیره فیزیکی داده‌ها در PostgreSQL است.
در واقع هر بار که داده‌ای را تغییر می‌دهید، یک نسخه‌ی جدید از آن در جای دیگری از فایل نوشته می‌شود.

اگر بخواهید دقیق‌تر ببینید داده‌ها کجا روی دیسک ذخیره شده‌اند، می‌توانید مسیر فایل را از PostgreSQL بگیرید:

SELECT pg_relation_filepath('demo_users');

و سپس فایل را در سیستم‌عامل بررسی کنید.

🧩 فایل‌های جانبی جدول‌ها در PostgreSQL

نگاهی عمیق‌تر به درون فایل‌های واقعی PostgreSQL

وقتی در پوشه‌ی داده‌های PostgreSQL (مثلاً /var/lib/postgresql/18/main/base/...) به دنبال جدول‌ها می‌گردید، احتمالاً چیزی شبیه این می‌بینید 👇

۱۶۴۷۷
16477_fsm
16477_vm
16477_toast
16477_toast_fsm

شاید تعجب کنید این فایل‌های اضافی چیستند.
در واقع PostgreSQL برای هر جدول اصلی (heap) چند فایل جانبی هم می‌سازد تا بتواند فضای دیسک را بهتر مدیریت کند، سرعت جست‌وجو را بالا ببرد و داده‌های حجیم را جداگانه نگه دارد.


⚙️ ساختار کلی فایل‌های جدول

فایلوظیفهتوضیح کوتاه
۱۶۴۷۷Heap fileداده‌های واقعی جدول
16477_fsmFree Space Mapنگهداری فضای خالی صفحات
16477_vmVisibility Mapوضعیت قابل‌مشاهده بودن صفحات
16477_toastTOAST tableذخیره‌ی داده‌های بسیار بزرگ
16477_toast_fsmFSM برای TOASTفضای خالی جدول TOAST

۱. Heap file – قلب جدول

فایل اصلی جدول بدون هیچ پسوندی است، مثلاً ۱۶۴۷۷.
در این فایل، داده‌های واقعی (هر ردیف یا tuple) در قالب page‌های ۸KB ذخیره می‌شوند.

📦 ویژگی‌ها:

  • شامل داده‌ی واقعی ستون‌هاست
  • هر صفحه شامل چند tuple است
  • وقتی داده زیاد می‌شود، PostgreSQL به طور خودکار صفحات جدید اضافه می‌کند
ls -lh /var/lib/postgresql/18/main/base/16384/16477
# => 320K (بسته به تعداد رکوردها)

۲. Free Space Map – دنبال جای خالی!

فایل 16477_fsm که پسوند _fsm دارد، برای مدیریت فضای آزاد داخل جدول استفاده می‌شود.

🔹 PostgreSQL باید بداند هنگام INSERT، کجا فضا دارد تا داده‌ی جدید بنویسد.
به جای اینکه کل جدول را بگردد، از FSM (Free Space Map) کمک می‌گیرد.

می‌توان گفت FSM شبیه دفترچه‌ای است که PostgreSQL در آن یادداشت می‌کند کدام صفحه هنوز جا دارد.

📘 هر صفحه‌ی FSM اطلاعات مربوط به هزاران صفحه‌ی heap را نگه می‌دارد.
به همین دلیل ممکن است جدول شما فقط ۸KB داده داشته باشد ولی FSM آن ۲۴KB باشد — طبیعی است.

📊 برای مشاهده اندازه:

SELECT
  pg_size_pretty(pg_relation_size('demo_users')),
  pg_size_pretty(pg_relation_size('demo_users_fsm'));

۳. Visibility Map – کمک‌یار VACUUM و Index-Only Scan

فایل 16477_vm که پسوند _vm دارد، نقشه‌ی دیدپذیری داده‌هاست.

PostgreSQL در آن علامت می‌زند که هر صفحه:

  • آیا تمام ردیف‌هایش برای همه تراکنش‌ها قابل مشاهده هستند؟
  • یا هنوز داده‌های موقتی و منقضی‌شده دارد؟

💡 چرا مهم است؟

  • اگر یک صفحه کاملاً قابل مشاهده باشد، VACUUM لازم نیست آن را دوباره بررسی کند (در زمان صرفه‌جویی می‌شود).
  • اگر در یک کوئری از Index-Only Scan استفاده شود، PostgreSQL می‌تواند بدون مراجعه به جدول اصلی، بداند داده معتبر است یا نه — فقط با کمک Visibility Map.

بنابراین _vm یک فایل کوچک ولی حیاتی برای بهبود کارایی است.


۴. TOAST – جایی برای داده‌های خیلی بزرگ

PostgreSQL محدودیت اندازه‌ی هر page را ۸KB تعیین کرده.
اما اگر ستونی از نوع TEXT یا BYTEA یا JSONB داشته باشید که خیلی بزرگ باشد (مثلاً 1MB یا بیشتر)، دیگر در page جا نمی‌شود.

اینجاست که PostgreSQL از TOAST استفاده می‌کند.

TOAST = The Oversized-Attribute Storage Technique 🍞

📦 وقتی داده‌ای بزرگ‌تر از حد مجاز باشد:

  • PostgreSQL آن را به چند تکه تقسیم می‌کند.
  • تکه‌ها را در یک جدول جدا (_toast) ذخیره می‌کند.
  • در جدول اصلی فقط اشاره‌گر (Pointer) به آن نگه می‌دارد.

می‌توانید ببینید چه جدول TOASTی برای هر جدول ساخته شده:

SELECT reltoastrelid::regclass AS toast_table
FROM pg_class
WHERE relname = 'demo_users';

اگر جدول شما داده‌های حجیم نداشته باشد، ممکن است هنوز TOAST ایجاد نشده باشد.


۵️⃣ فایل‌های TOAST جانبی

برای هر جدول TOAST، PostgreSQL خودش نیز فایل‌های _fsm و _vm دارد.
چون آن هم یک جدول واقعی است.

بنابراین ممکن است در فایل‌سیستم ببینید:

16477_toast
16477_toast_fsm
16477_toast_vm

📘 این یعنی حتی داده‌های فشرده و بزرگ هم مثل هر جدول دیگری مدیریت و بهینه‌سازی می‌شوند.


🧩 تصویر ذهنی ساده

┌────────────────────────────┐
│ demo_users (16477)         │  ← داده‌های واقعی جدول
│ ├── 16477_fsm              │  ← فضای خالی در صفحات
│ ├── 16477_vm               │  ← وضعیت دیدپذیری صفحات
│ └── 16477_toast (درصورت نیاز)  ← داده‌های بزرگ
│     ├── 16477_toast_fsm    │
│     └── 16477_toast_vm     │
└────────────────────────────┘

🔍 تفاوت pg_relation_size و pg_total_relation_size
SELECT
  pg_size_pretty(pg_relation_size('demo_users')) AS heap_only,
  pg_size_pretty(pg_total_relation_size('demo_users')) AS total_including_fsm_vm;

📊 تفاوت مهم:

  • pg_relation_size() فقط heap اصلی را محاسبه می‌کند.
  • pg_total_relation_size() شامل heap + fsm + vm + toast است.

✅ جمع‌بندی

فایلتوضیحکاربرد
relfilenodeداده‌ی واقعی جدولذخیره ردیف‌ها (heap)
relfilenode_fsmFree Space Mapثبت فضای خالی برای درج بعدی
relfilenode_vmVisibility Mapبهینه‌سازی VACUUM و Index Scan
relfilenode_toastTOAST Tableنگهداری داده‌های بزرگ‌تر از ۸KB
relfilenode_toast_fsmFSM برای TOASTفضای آزاد در داده‌های TOAST

🧠 خلاصه ذهنی:
هر جدول PostgreSQL در واقع یک اکوسیستم کوچک است — با فایل‌هایی که هرکدام نقش خود را دارند:

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

لطفا کلمات کلیدی را وارد کنید