Database Engineering

پیاده‌سازی Event Sourcing با PostgreSQL برای قابلیت اطمینان ردپای حسابرسی

در منظره مهندسی پایگاه داده مدرن، تقاضا برای ردپاهای داده‌ای غیرقابل تغییر هرگز به این اندازه بالا نبوده است. چه برای انطباق مالی، اشکال‌زدایی گذارهای پیچیده وضعیت، و چه بازسازی رفتار سیستم پس از یک قطعی، اسنپ‌شات‌های سنتی «وضعیت فعلی» اغلب ناکافی هستند. اینجاست که Event Sourcing درخشش خود را نشان می‌دهد. با ثبت هر تغییر وضعیت به عنوان دنباله‌ای از رویدادها، ما پایگاه داده را از یک مکانیزم ذخیره‌سازی ساده به یک دفتر کل جامع و خودمستند از تاریخچه سیستم تبدیل می‌کنیم.

اگرچه Event Sourcing اغلب با معماری‌های رویدادمحور که از پیام‌رسان‌هایی مانند Kafka استفاده می‌کنند مرتبط است، اما زیربنای این الگو پایگاه داده است. PostgreSQL در اینجا با تضمین‌های ACID قوی، پشتیبانی قدرتمند از JSONB و توانایی ذاتی برای پردازش کارهای append-only به‌صورت کارآمد، مزیت منحصر‌به‌فردی ارائه می‌دهد. در این پست، بررسی می‌کنیم که چگونه می‌توان لایه Event Sourcing را با استفاده از PostgreSQL معماری کرد تا قابلیت اطمینان مطلق ردپای حسابرسی را تضمین نماید.

فلسفه اصلی: Append-Only الزامی است

قانون کاتولیک (بنیادین) Event Sourcing این است که رویدادها باید افزوده شوند، نه اینکه اصلاح یا حذف گردند. اگر شما یک رویداد را به‌روزرسانی کنید، زنجیره علیت را می‌شکنید. در PostgreSQL، این معمولاً با ساختاردهی طرح (Schema) برای تکیه بر یک ستون هویت (معمولاً serial یا bigserial به عنوان کلید اصلی) که به‌صورت یکنوا افزایش می‌یابد، محقق می‌شود. ترتیب رویدادها در وضعیت برنامه شما کاملاً به ترتیبی بستگی دارد که آن‌ها در پایگاه داده نوشته شده‌اند، که توسط لاگ‌های تراکنش پایگاه داده (WAL) تضمین می‌شود.

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

طراحی طرح برای جریان‌های رویداد

طراحی طرح نیازمند تعادل بین عملکرد کوئری و ماهیت نوشتن-محور Event Sourcing است. یک رویکرد استاندارد شامل یک جدول است که به عنوان ذخیره‌ساز رویداد عمل می‌کند. ستون‌های کلیدی شامل شناسه یکتای رویداد، شناسه جریان (برای گروه‌بندی رویدادهای مرتبط)، نوع رویداد، باری (Payload) (اغلب JSONB برای انعطاف‌پذیری) و یک شماره نسخه برای قفل‌گذاری بهینه‌گرایانه (Optimistic Locking) هستند.


CREATE TABLE events (
    id BIGSERIAL PRIMARY KEY,
    stream_id UUID NOT NULL,
    event_type VARCHAR(255) NOT NULL,
    version INTEGER NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT unique_event_version 
        UNIQUE (stream_id, version)
);

-- ایجاد ایندکس روی stream_id و version برای خواندن کارآمد جریان‌ها
CREATE INDEX idx_events_stream_version 
ON events (stream_id, version);

-- ایجاد ایندکس روی event_type برای جستجوی سراسری در تمام جریان‌ها
CREATE INDEX idx_events_type 
ON events (event_type);

ستون payload با استفاده از JSONB حیاتی است. این به ما اجازه می‌دهد داده‌های رویداد ناهمگن (مانند OrderCreated در مقابل PaymentFailed) را بدون نیاز به ستون‌های SQL سخت‌گیرانه برای هر فیلد ذخیره کنیم. نوع JSONB PostgreSQL همچنین به ما اجازه می‌دهد تا ویژگی‌های خاص را درون باری رویداد به‌صورت کارآمد با استفاده از ایندکس‌های GIN در صورت نیاز کوئری کنیم.

نوشتن رویدادها با قفل‌گذاری بهینه‌گرایانه

قابلیت اطمینان در Event Sourcing فقط درباره افزودن داده نیست؛ بلکه درباره اطمینان از سازگاری زمانی است که فرآیندهای متعدد تلاش می‌کنند همان جریان را به‌روزرسانی کنند. اینجاست که کنترل همزمانی بهینه‌گرایانه (OCC) حیاتی می‌شود. ما از ستون version برای جلوگیری از به‌روزرسانی‌های از دست رفته استفاده می‌کنیم.

هنگام ذخیره یک رویداد جدید، برنامه نسخه مورد انتظار را محاسبه می‌کند (معمولاً last_known_version + 1). تراکنش پایگاه داده تنها در صورتی commit می‌شود که ردیف فعلی با آن stream_id و version همچنان مطابقت داشته باشد. اگر فرآیند دیگری مداخله کرده باشد، بررسی نسخه شکست می‌خورد و تراکنش Rollback می‌شود.


-- SQL شبه برای ذخیره یک رویداد با بررسی نسخه
INSERT INTO events (stream_id, event_type, version, payload, created_at)
VALUES (
    'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
    'OrderCreated',
    5, 
    '{"orderId": 123, "total": 99.99}',
    NOW()
)
ON CONFLICT (stream_id, version) 
DO NOTHING;

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

این مکانیزم تضمین می‌کند که هیچ دو عملیات همزمانی نمی‌تواند توالی رویدادها را فاسد کند. اگر تعارضی رخ دهد، برنامه می‌تواند وضعیت فعلی جریان را بازیابی کرده و عملیات را تکرار کند، که به‌صورت مؤثر شرایط رقابتی (Race Conditions) را به‌صورت شایسته مدیریت می‌کند.

کوئری تاریخچه و بازسازی وضعیت

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


-- دریافت تمام رویدادها برای یک جریان خاص به ترتیب زمانی
SELECT payload 
FROM events 
WHERE stream_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
ORDER BY version ASC;

برای سیستم‌های با حجم بالا، خواندن هر رویداد از ابتدای زمان کارآمد نیست. یک الگوی رایج نگهداری یک «اسنپ‌شات» از وضعیت فعلی است که در یک جدول جداگانه ذخیره می‌شود. هنگام تکرار، سیستم آخرین اسنپ‌شات را بارگذاری کرده و سپس فقط رویدادهایی را که پس از آن نقطه رخ داده‌اند اعمال می‌کند. این رویکرد ترکیبی بهترین ویژگی‌های هر دو جهان را ارائه می‌دهد: قابلیت اطمینان Event Sourcing برای ردپاهای حسابرسی و عملکرد ذخیره‌سازی وضعیت سنتی.

تضمین قابلیت اطمینان ردپای حسابرسی

برای بهره‌برداری واقعی از PostgreSQL برای ردپاهای حسابرسی، باید پایگاه داده را به‌درستی پیکربندی کنید. اطمینان حاصل کنید که Replication Slots برای جلوگیری از مشکلات آرشیو لاگ در صورت استفاده از بازیابی نقطه-در-زمان (Point-in-Time Recovery) پیکربندی شده‌اند. علاوه بر این، Write-Ahead Log (WAL) PostgreSQL مکانیزمی را برای بازیابی پایگاه داده به هر نقطه‌ای در زمان فراهم می‌کند که به‌عنوان پشتیبان نهایی برای جریان رویداد شما عمل می‌کند.

برای انطباق، در نظر بگیرید که رمزنگاری سطح جدول را فعال کنید یا از pgcrypto برای رمزنگاری فیلدهای حساس درون ستون payload استفاده کنید، تا اطمینان حاصل شود که خود ردپای حسابرسی امن است.

نتیجه‌گیری

پیاده‌سازی Event Sourcing با PostgreSQL، پایگاه داده شما را به یک ردپای حسابرسی قابل اطمینان و غیرقابل تغییر تبدیل می‌کند. با پایبندی سخت‌گیرانه به الگوهای append-only، بهره‌برداری از JSONB PostgreSQL برای باری‌های انعطاف‌پذیر و اعمال قفل‌گذاری بهینه‌گرایانه روی نسخه‌ها، سیستمی ایجاد می‌کنید که هم مستحکم و هم شفاف است. اگرچه این الگو پیچیدگی را در خواندن و بازسازی وضعیت معرفی می‌کند، اما مزایا—قابل ردیابی بودن، آسانی اشکال‌زدایی و انطباق—برای برنامه‌های مدرن و حیاتی بی‌نظیر هستند.

هنگامی که به جلو حرکت می‌کنید، به خاطر داشته باشید که Event Sourcing ابزاری برای مدیریت پیچیدگی است، نه یک تیر جادویی. آن را جایی استفاده کنید که تاریخچه تغییرات به اندازه وضعیت فعلی اهمیت دارد و به PostgreSQL اجازه دهید کار سنگین اطمینان از اینکه این تاریخچه هرگز از بین نمی‌رود را انجام دهد.

Share: