<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های حسن سعادت</title>
        <link>https://virgool.io/feed/@hassan-saadat</link>
        <description>یه مهندس نرم افزار کنجکاو، عاشق حل مسئله و ساده کردن مفاهیم پیچیده</description>
        <language>fa</language>
        <pubDate>2026-07-06 11:26:31</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/678162/avatar/89OjHq.jpg?height=120&amp;width=120</url>
            <title>حسن سعادت</title>
            <link>https://virgool.io/@hassan-saadat</link>
        </image>

                    <item>
                <title>مسئله اینست؛ pass by value یا pass by reference</title>
                <link>https://virgool.io/@hassan-saadat/pass-by-value-vs-pass-by-reference-kkxh3cyd172y</link>
                <description>باشد که ارجاع شوم، یا تنها مقدار باشم؟این است پرسشی که جان را می‌آزارد؛آیا هنگامی که تابع را فراخوانم،وجودِ «خویش» به خطر می‌افتد یا برجا می‌ماند؟مقدمههمیشه این مفهوم Argument Passing در زبان های مختلف برام گیج کننده بود و جالبه که از زبانی به زبانی دیگه تفاوت داره. اینجا اومدیم برای بیشتر زبان های رایج بررسیش کردیم و در نهایت توی یه جدول خلاصشو نوشتیم. یک بار برای همیشه!از قدیما یادمونه که در زبان هایی مثل C و Java دوتا مفهوم داشتیم وقتی میخواستیم یه چیزی رو به یه تابع پاس بدیم.- Pass by valueوقتی یه آبجکتی رو به یه تابع پاس میدیم یه کپی از اون آبجکت در scope تابع ساخته میشه و تغییراتی که در اون شی داخل تابع اعمال میشه هیچ تاثیری توی آبجکت اصلی نمیذاره.- Pass by referenceوقتی یه آبجکتی رو به یه تابع پاس میدیم پوینتر مربوط به اون آبجکت به تابع داده میشه و هر تغییری که داخل تابع روی آبجکت صورت بگیره مستقیما روی آبجکت اصلی اثر میذاره.توی زبان C چجوریه؟توی C فقط pass by value داریم و برای شبیه سازی pass by reference از مفهوم Pointer استفاده می کنیم.اگه primitive type پاس بدیم که کپی میشه میره.اگه آبجکت(Struct) باشه هم یه کپی ازش میره توی تابع.اگرم بخوایم pass by reference باشه که پوینتر پاس میدیم.// pass by reference using pointer in Cvoid move(Point *p) {p-&gt;x += 1;}Point a = {1, 2};move(&amp;a);printf(&quot;%d&quot;, a.x); // 2توی ++C چجوریه؟این اصطلاح pass by reference اصلا از زبان ++c نشات گرفته! دلیلش این بوده که C فقط pass by value رو ساپورت میکرده برای اینکه کارو راحت کنن و مجبور نشن پوینتر بسازن با گذاشتن یه &amp; کارو در آوردن!بنابراین این زبون جفت pass by value و pass by reference رو بدون نیاز به شبیه سازی داره.void foo(int&amp; x) { // real pass-by-referencex = 10;}int a = 5;foo(a);cout &lt;&lt; a; // 10توی Java چجوریه؟توی جاوا هم pass by value هستش و برای شبیه سازی pass by reference از مفهومpass by value of the referenceاستفاده میشه!اگه primitive type پاس بدی به تابع که کپی اش میره و تغییر توی تابع هیچ اثری توی مقدار اصلی نمیذاره.اگه آبجکت پاس بدی آدرس حافظه اون آبجکت by value کپی میشه! ینی توی تابع یه متغیر دیگه ساخته میشه که اون اشاره میکنه به اون آدرس حافظه کپی شده! بنابراین اگه روی اون متغیر داخل تابع تغییر بدی انگار آبجکت اصلی رو تغییر دادی مثلا بیای مقدار یه فیلدشو عوض کنی. اما اگه آدرسشو عوض کنی مثلا بیای یه آبجکت جدید بسازی و به اون اشاره کنه فقط داخل تابع عوض میشه و تاثیر بیرونی نداره.با یه مثال قضیه رو بفهمیم!public class CustomObject {private int data;public CustomObject(int data) {this.data = data;}public void printInfo() {System.out.println(data);}public void setData(int value) {this.data = value;}}یه کلاس ساده تعریف کردیم تا الان.public class Main {public static void main(String[] args) {CustomObject a = new CustomObject(5);CustomObject b = a;doSomething(a);a.printInfo(); // Prints: 10b.printInfo(); // Prints: 10 (same object)}public static void doSomething(CustomObject temp) {temp.setData(10);}}اینجا چه اتفاقی افتاد؟ متغیر a شد یه آبجکت جدید. b به آدرس حافظه a اشاره میکنه و فانکشن doSomething فراخوانی شد که در نتیجه یه کپی از آدرس حافظه متغیر a به فانکشن پاس داده شد. داخل فانکشن متغیر temp هم به کپی آدرس حافظه a اشاره میکنه. هر تغییری که توی فیلدهای temp بدیم روی آبجکت اصلی اعمال میشه.خب حالا میایم داخل تابع یه آبجکت جدید میسازیم ببینیم اعمال میشه یا نه.public class Main {public static void main(String[] args) {CustomObject a = new CustomObject(5);CustomObject b = a;doSomething(a); // Assigns new object to tempa.printInfo(); // Prints: 5b.printInfo(); // Prints: 5 (original object unchanged)}public static void doSomething(CustomObject temp) {temp = new CustomObject(10); // New reference; original unchanged}}هیچ تغییری توی آبجکت اصلی مشاهده نمیشه! چرا؟‌چون اینجا این شکلی میشه تصویر ما:اینجا متغیر temp به یه آدرس جدید اشاره میکنه واسه همینه که آبجکت اصلی تغییر نمیکنه.حالا یه حالت دیگه هم تست کنیم!public class Main {public static void main(String[] args) {CustomObject a = new CustomObject(5);CustomObject b = a;b = doSomething(a); // b now points to new objecta.printInfo(); // Prints: 5b.printInfo(); // Prints: 10 (different references)}public static CustomObject doSomething(CustomObject temp) {temp = new CustomObject(10);return temp; // Returns new reference}}اینجا تابع رو تغییر دادیم تا آبجکت جدید رو برگردونه.خب الان توی تابع یه آبجکت جدید ساخته شد و temp بهش اشاره میکنه. بعد از اتمام کار تابع یه assignment داریم که b به اون آبجکت جدید اشاره میکنه و a تغییری نمیکنه.نکته:پس اگه توی جاوا خواستیم مثلا یه تابع swap درست کنیم برای int مثلا (primitive type ها در کل) چون pass by reference نداره تغییری اعمال نمیشه ینی این کار نمیکنه:void swap(int a, int b) {int temp = a;a = b;b = temp;}int x = 5, y = 10;swap(x, y);System.out.println(x + &quot; &quot; + y); // 5 10برای اینکار یا باید اون int بره توی یه آبجکت بعد اون آبجکت رو پاس بدیم و تغییرش بدیم یا بریزیمش توی یه آرایه و اون آرایه رو تغییر بدیم. اینجا باز هم pass by value of the reference خواهد بود چون کپی آدرس حافظه اون آبجکت یا آرایه پاس داده میشه به تابع.یکی از این دوتا راه جوابه:Using Wrapper classclass IntWrapper {int value;IntWrapper(int value) { this.value = value; }}void swap(IntWrapper a, IntWrapper b) {int temp = a.value;a.value = b.value;b.value = temp;}IntWrapper x = new IntWrapper(5);IntWrapper y = new IntWrapper(10);swap(x, y);System.out.println(x.value + &quot; &quot; + y.value); // 10 5Using arraypublic class SwapExample {static void swap(int[] arr) {int temp = arr[0];arr[0] = arr[1];arr[1] = temp;}public static void main(String[] args) {int[] nums = {5, 10};swap(nums);System.out.println(nums[0] + &quot; &quot; + nums[1]); // 10 5}}توی Python چطوره؟پایتون نه pass by value هست نه pass by reference بلکه خودش یه چیزی داره با این عنوان:pass by object reference / pass by assignmentاینجا اگر متغیر immutable باشه مقدارش تغییری نمیکنه مثلا اگه int, str باشه اما اگه mutable باشه مثل list, dict, set مقدارش تغییر میکنه!def modify(a, b):a += 1b.append(99)x = 10y = [1, 2]modify(x, y)print(x, y)# Output:10 [1, 2, 99]توی Go چجوریه؟گولنگ مثل C میمونه ینی pass by value هستش و میتونه با پوینتر pass by reference رو هندل کنه به علاوه اینکه یه سری چیزا مثل slice, map, channel هم مثل پوینتر عمل میکنن (reference-like هستن) و تغییر در تابع شی اصلی رو تغییر میده.pass by reference using pointerfunc modify(p *int) {*p = 99}func main() {a := 10modify(&amp;a)fmt.Println(a) // 99}pass by reference using reference-like objects (slice)func modifySlice(s []int) {s[0] = 99}func main() {nums := []int{1,2,3}modifySlice(nums)fmt.Println(nums[0]) // 99}توی javascript چطوریه؟دقیقا مثل Java میمونه ینی برای primitive type ها pass by value هستش ولی برای آبجکت هاpass by value of the referenceعمل میکنه ینی تغییرات روی آبجکت داخل تابع اعمال میشه اما reassign کردنش اعمال نمیشه!البته این مورد هنوز برام کمی ابهام داره اگه کسی از اساتید تعریف درست تری داره خوشحال میشم اصلاحش کنم.جمع بندیمنابعhttps://www.youtube.com/watch?v=-5NC5_sI-vQhttps://www.youtube.com/watch?v=_R63D9Fc48Uhttps://pybit.es/articles/the-mutable-trap-avoiding-unintended-side-effects-in-python</description>
                <category>حسن سعادت</category>
                <author>حسن سعادت</author>
                <pubDate>Fri, 10 Oct 2025 16:34:00 +0330</pubDate>
            </item>
                    <item>
                <title>رپلیکیشن دیتابیس؛ تضمین بقا در دنیای اطلاعات</title>
                <link>https://virgool.io/@hassan-saadat/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%B1%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D8%AF%D8%B1-%D8%AF%DB%8C%D8%AA%D8%A7%D8%A8%DB%8C%D8%B3-ffinhaun8smh</link>
                <description>رپلیکیشن چیه؟رپلیکیشن به کپی کردن پیوسته داده ها از یک دیتابیس (Primary/Master) به یک یا چند دیتابیس دیگه (Secondary/Slave/Replica) میگیم.پیش از ادامه یه نکته ای بگم، رپلیکیشن شاید مبحث ساده و بدیهی به نظر بیاد اما وقتی بخوایم درست تعریفش کنیم و شرح بدیم اصطلاحات پراکنده زیادی هست توش که اکثرا هم معنا هستن و این به شدت گیج کننده است! دلیلش هم اینه که بعد از به کار بردن هر مدل رپلیکیشن، تازه تئوریزه اش کردن و هم اینکه هر اکوسیستم و شرکتی از اصطلاحات خاص خودش استفاده کرده.تعاریفتبدیل replica به primary رو میگیم promote کردندیتابیس replica ایی که نمیشه بهش کانکت شد و کوئری زد (read) تا زمانیکه promote بشه به primary میگن warm standby و به replica ایی که میشه کانکت شد و کوئری های read رو جواب میده میگن hot standby.نکته: رپلیکیشن میتونه بهمون کمک کنه که read و write رو از هم تفکیک کنیم و بتونیم read رو اسکیل کنیم که این توی سیستم های read-heavy ایده خوبیه. ایده تفکیک مسئولیت خوندن و نوشتن ایده اصلی پترن CQRS هستش بنابراین رپلیکیشن ابزاریه که میتونیم برای پیاده سازی CQRS ازش استفاده کنیم.اهداف و کاربردهای رپلیکیشن چیه؟1. Data AvailabilityHigh AvailabilityFault ToleranceDisaster Recoveryاگه دیتابیس primary از کار افتاد replica میتونه به عنوان جایگزین عمل کنه (Failover). اگه اتوماتیکش کنیم میشه Automatic failover.2. Load balancing / Read Scalabilityبار خوندن / read رو بین چنتا سرور تقسیم می کنیم. رپلیکاها خوندن رو هندل می کنن و پرایمری هم میتونه نوشتن و هم خوندن رو هندل کنه. فشار روی پرایمری رو میتونیم کم کنیم اینطوری(فقط خوندن رو بدیم دستش) و latency رو کاهش بدیم.3. Geographical Distributionرپلیکاها نزدیک محل جغرافیایی کاربران هر منطقه مستقر میشن و باعث کاهش latency میشه4. Backup &amp; Recoveryعلاوه بر اینکه خود رپلیکا میتونه به عنوان بکاپ استفاده بشه میتونه کمک کنه از دیتا بکاپ بگیریم بدون اینکه رو primary فشار بیاریم.5. Migrationاگه بخوایم دیتابیس رو به یه دیتابیس دیگه یا ورژن جدیدتر منتقل کنیم میتونیم از رپلیکیشن استفاده کنیم6. Test &amp; Developmentبرای اینکه به سرور primary فشار نیاریم میتونیم رپلیکا بیاریم بالا و بدیم دست تیم توسعهانواع رپلیکیشنانواع رپلیکیشن دیتابیس از دیدگاه های متفاوتStatement based replicationینی عینا statement SQL به رپلیکا منتقل بشه. postgres از این پشتیبانی نمیکنه به صورت native.عیب:‌ روی شبکه میتونه فشار بیاره + دیتابیس رپلیکا هم دقیقا همون محاسبات و بهینه سازی های پرایمری رو مجبوره تکرار کنه خودش!Physical / Binary replicationدیتابیس بایت به بایت رپلیکیت میشه به صورت باینری مثلا فلان بلاک از فلان جای دیسک رفت روی این بلاک.عیب: اگه ورژن دیتابیس رپلیکا جدیدتر باشه نمیتونه بفهمه اون باینری توش چیه +‌ مجبوریم کل دیتابیس رو رپلیکیت کنیم نمیتونیم یه جدول خاص رو فقط رپلیکیت کنیمLogical replicationتغییرات منطقی row by row (رکورد به رکورد) منتقل میشن مثلا این رکورد به جدول فلان اضافه شد.مزیت:‌ امکان رپلیکت به ورژن جدید دیتابیس +‌ امکان رپلیکیت یه جدول خاص به جای کل دیتابیسArchive(file) based / Log shipping replicationفایل های WAL بعد از اینکه پر شدن آرشیو میشن و به سرور رپلیکا منتقل میشن و اونجا replay میشنعیب: امکان ایجاد lag و اختلاف زیاد بین پرایمری و رپلیکاStreaming replicationفایل های WAL به رپلیکا استریم میشن به صورت real-time. (به جای اینکه وایسیم فایل پر بشه بعد کلش منتقل بشه)Synchronous replicationدیتابیس پرایمری منتظر ack تک تک رپلیکاها میمونه و بعد پیام تایید commit رو به کلاینت برمیگردونه.مزیت:‌ اینجوری Strong consistency خواهیم داشت (ر.ک. CAP Theorem) و همه رپلیکاها با پرایمری سینک هستنعیب: اگه شبکه کند باشه یا تعداد رپلیکاها بالا باشه این زمان انتظار latency رو بالا میبرهAsynchronous replicationدیتابیس پرایمری منتظر ack دادن رپلیکاها نمیمونه و سریع پیام تایید commit رو میده به کلاینت. توی background هم یه سری تسک ران میشن که اون دیتا رو به صورت asycn روی رپلیکاها بنویسه.یه حالت دیگه هم داره که میتونیم منتظر یه تعدادی از رپلیکاها باشیم مثلا دوتای اول ack دادن ما commit کنیم. میشه شبه-سنکرون (Semi-Asynchronous)پستگرس به صورت پیش فرض asynchronous replication انجام میده!مزیت: تاخیر/latency کم میشهعیب:‌ Strong consistency رو از دست میدیم و به Eventual Consistency راضی میشیم.One way / Single-master replicationحالت دیفالت رپلیکیشن که در اون دیتا به صورت یک طرفه از پرایمری به رپلیکا منتقل میشه و برعکسش اتفاق نمی افته. توی این حالت پرایمری میتونه هم read و هم write انجام بده ولی رپلیکا فقط read انجام میده و تازه read-only هم میشه (نمیتونی تغییرش بدی چه دیتا چه اسکیما).مزیت: پیاده سازی راحتی داره و باعث میشه بتونیم read رو scale بکنیمعیب: اگه write-heavy باشیم ممکنه یه سرور کفاف ندهTwo way / Bidirectional / Multi-master replicationدر این حالت بیشتر از یه primary/master خواهیم داشت و هر سرور هم primary و هم replica میشه ینی همه سرورا میتونن هم بنویسن و هم بخونن!پستگرس به صورت native ازش پشتیبانی نمی کنه!مزیت: تونستیم read/write رو اسکیل کنیمعیب: پیاده سازیش به شدت پیچیده است چون در صورت نوشتن دیتای متفاوت توی دوتا سرور (ایجاد conflict) باید بیایم و conflict resolution انجام بدیم که کار بسیار سختی هست و به دردسرش نمی ارزه معمولامنابعhttps://youtu.be/aE2UPg3Ckck?si=K-mQtouqveaMILKKhttps://www.postgresql.org/docs/current/high-availability.htmlhttps://www.postgresql.org/docs/current/warm-standby.htmlhttps://www.udemy.com/course/database-engines-crash-course/</description>
                <category>حسن سعادت</category>
                <author>حسن سعادت</author>
                <pubDate>Sun, 28 Sep 2025 13:13:12 +0330</pubDate>
            </item>
                    <item>
                <title>مفهوم Message Broker به زبان آدم!</title>
                <link>https://virgool.io/@hassan-saadat/%D9%85%D9%81%D9%87%D9%88%D9%85-message-broker-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-%D8%A2%D8%AF%D9%85-mrp1bxvxlavb</link>
                <description>قبل از اینکه مفهوم مسیج بروکر رو بخونیم باید روش های ارتباط بین سرویس ها رو بدونیم.1- Syncارتباط مستقیم سرویس ها از طریق پروتکل ‌HTTP یا gRPC.سرویس مبدا request میزنه به سرویس مقصد و منتظر پاسخ میمونه.چالش ها:این زمان انتظار میتونه خیلی بالا باشه و تایم اوت بشه!نرخ ارسال درخواست میتونه از نرخ پردازش اون بیشتر باشه و سرویس مقصد دهنش سرویس بشه!اگه یه سرویس بخواد یه پیام رو به چنتا سرویس برسونه چیکار کنه؟اگه تعداد سرویس ها بالا باشه و بخوایم چنتا زوج از این سرویسا رو به هم وصل کنیم؟ خر تو خر میشهسرویس ها به هم وابسته میشن! coupling داریماگه یه مسیج بخوایم بره اول سرویس ۱ بعد اون پردازش کنه بده سرویس ۲ بعد اون بده سرویس ۳ همچین پایپ لاینی رو چطوری پیاده سازی کنیم؟ حالا پیاده کردیم چجوری دیباگش کنیم!!2- Asyncارتباط غیر مستقیم سرویس هاسرویس ها پیام هاشونو میدن به یه کسی به اسم مسیج بروکر / Message Broker و دیگه فراموشش میکنن حالا هر کی میخواد از پیامه استفاده کنه به خودش مربوطه!تعریف Message Brokerابزاریه که پیام ها رو بین سرویس ها منتقل می کنه.به تولید کننده پیام Producer / Publisher میگیمبه مصرف کننده پیام Consumer / Subscriber میگیمخب این چه کمکی میکنه؟ باعث میشه اینا رو بتونیم داشته باشیم:1- Decouplingوابستگی سرویس ها به هم کم میشه. نیاز نیست چیز زیادی از هم دیگه بدونن2- Scalabilityوقتی لود سیستم بالا میره میتونیم consumer ها به صورت horizontally اسکیل کنیم و تعدادشونو ببریم بالا.اگه سرویس ها مستقیم به هم وصل بودن بالا رفتن لود producer مساوی بود با بالا رفتن لود consumer و از یه جا به بعد حتی با افزایش ریسورس consumer نمیتونست پابه پای producer پیام ها رو پردازش کنه و سیستم رو کند میکرد.اگه consumer کرش میکرد producer پیام ها رو کجا میفرستاد؟ باید وایمیساد تا اون برگرده به سیستمالزاما همیشه لود سیستم بالا نیست که اسکیل کنیم ممکنه تایم های خاصی از روز لود بره بالا اونوقت نمیتونستیم ریسورس ها رو بهینه تخصیص بدیم به consumer (یا همیشه ریسورس کم داشت یا زیاد)3- Resilience / Reliabilityمسیج بروکر یه سری مکانیزم / قابلیت فراهم می کنه که پیام ها حفظ بشن و به دست consumer برسن مثل:persistenceretry &amp; acknowledgementfault tolerance / replicationload handling / backpressureدر این صورت حتی اگه مثلا consumer کرش کنه مسیج ها حفظ میشن و سیستم پایدار میمونه و بقیه چیزا.3- Monitoringمسیج بروکر میتونه ابزارها و متریک های در اختیارمون قرار بده که باهاش سیستم رو مانیتور کنیم و بگردیم دنبال باگ ها و مشکلات پرفورمنسیش.انواع انتقال پیام Asyncسه مدل انتقال پیام آسنکرون داریم. این دسته بندی بر اساس Distribution Pattern یا الگوی توزیع پیام ها هستش.1- Point to Point (one-to-one)توی این مدل هر پیام دقیقا فقط به یک consumer میرسه و یکبار پردازش میشه به خاطر همین میگن یک به یک.یک به یک بودن به این معنا نیست که دقیقا یه producer و یه consumer وجود داره! میتونیم چنتا producer داشته باشیم و یه consumer و یا بر عکس و یا چنتا producer و چنتا consumer! صرفا پیام ها به یک consumer تحویل داده و پردازش میشه.این مدل با ساختار داده ی Queue/صف پیاده سازی میشه (پیش فرض FIFO) اما فقط یه صف معمولی نیست! بلکه مسیج بروکر امکانات دیگه ای مثل load balancing - persistence - retry - acknowledgement هم در اختیارمون قرار میده.این صف میتونه custom بشه که Priority Queue یا صف با اولویت هم ساپورت کنه.مثال ملموسش میشه Django + Celery + Redis/RabbitMQ که تیکه هایی از کد بک اند میشن producer ها و ورکرهای Celery میشن consumer های ما.ردیس این مدل رو با Redis List و کامندهای RPUSH/LPUSH و BRPOP/BLPOP هندل میکنهربیت خودش اصلا Queue-oriented عهکافکا این رو با مفهوم consumer group شبیه سازی می کنه وقتی که فقط یه کانسیومر توی گروپ باشه (چرا میگیم شبیه سازی چون در صف واقعی پیام ها پس از مصرف حذف میشن ولی توی کافکا حذف نمیشه صرفا offset کانسیومر به جلو میره)2- Pub/Sub (one-to-many)پابلیشر پیام ها رو به یک Topic(در کافکا) / Exchange(در ربیت) میفرسته و همه کانسیومرهایی که اون تاپیک رو Subscribe کردن میتونن پیام ها رو بخونن. publisher و subscriber کاملا مستقل از هم هستند و loosely coupling داریم.چرا یک به چند شد؟ یک پیام به چند مصرف کننده میرسه و میتونه توسط همشون پردازش بشه.کاربرد: push notificationردیس این مدل رو با استفاده از Redis channel پیاده می کنهربیت با استفاده از نوع خاصی از Exchange به اسم fanoutکافکا میتونه با تعریف چنتا consumer group که هر کدوم یه کانسیومر داشته باشن این رو شبیه سازی کنه (بازم شبیه سازی چون پیام ها واقعا حذف نمیشن)3- Streaming (one-to-many-with-replay)اینم مثل قبلی یه producer توی یه تاپیک مینویسه و کانسیومرهایی که میخوان میان متصل میشن و میخونن ازش اما تفاوتش اینه که پیام ها توی یه ساختار log/stream ذخیره میشن و بعدا قابلیت replay دارن. توی مدل pub/sub اگه یه کانسیومر بعدا وصل بشه به پیامای قبلی دسترسی نداره صرفا پیام ها رو از موقعی که اومده میبینه ولی توی استریم میتونه پیامای قبلی رو هم بخونه. همچنین توی ساختار pub/sub معمولا پیام ها volatile هستن ینی پس از مصرف کامل حذف میشن(تا زمان کوتاهی فقط نگهداری میشن) اما در استریم روی دیسک persist میشن (همیشه یا به اندازه retention time تعریف شده). به علاوه ترتیب پیام ها در استریم حفظ میشه اما الزاما در pub/sub حفظ نمیشه.کاربرد: event sourcing - log processingردیس‌ با Redis stream این مدل رو پیاده سازی میکنهربیت با صف هایی که قابلیت persist + ack دارن میتونه زورشو بزنه ولی بازم replay نداره و نمیتونهکافکا اصلا کارش همیشهمسئله Delivery Semantics / Guarantees یا ضمانت تحویلوقتی یه پیام بین producer و consumer جابجا میشه سه مدل مختلف ضمانت تحویل رسیدن پیام داریم:1- At most onceیک پیام حداکثر یکبار به مصرف کننده تحویل داده میشه به این معنی که ممکنه اصلا به مقصد نرسه(گم بشه)!مزیت: latency پایین و throughput بالاعیب:‌ امکان گم شدن پیامهاکاربرد: ارسال metric های مانیتورینگ که حجم کمی data loss قابل پذیرشه مثلا ایونت های telemetry2- At least onceیک پیام حداقل یکبار به مقصد میرسه به این معنی که ممکنه چندبار تکراری هم برسه! ولی هیچ پیامی گم نباید بشه.مزیت: هیچ پیامی گم نمیشهعیب: امکان وجود پیامهای تکراریکاربرد: سیستم های پرداخت (به شرطی که عملیات پرداخت idempotent باشه و تکراری ها رو ignore کنه)3. Exactly onceیک پیام دقیقا یکبار پردازش بشه نه گم بشه و نه تکراری داشته باشیم. سخت ترین مدل از نظر پیاده سازیمزیت: مطمئن ترین مدل تحویلعیب: پیاده سازی پیچیده - پرفورمنس پایینکاربرد:‌ سیستم های مالی حساس بانکی یا tradingتکنیک های Reliabilityمسیج بروکرها برای اینکه بهشون اطمینان کنیم که پیامهای سرویس ها رو بسپریم دستشون یه سری تکنیک پیاده سازی کردن که از دیتاها محافظت کنن و انتقالشون رو تضمین کنن، یه سری تکنیک هم در سطح اپلیکیشن هستش که باید خودمون پیاده سازی کنیم.به اولی میگیم Broker level reliability techniques ینی تکنیک های سطح بروکر و به دومی میگیم application level reliability techniques.Broker Level Reliability Techniques1. Persistence / Durabilityاولین چیزی که از مسیج بروکرا میخوایم اینه که وقتی کرش میکنن یا ریستارت میشن پیام های ما نابود نشه!(البته این بسته به میزان حیاتی بودن دیتا داره اگه خیلی حیاتی نباشه که بذار نابود شه)به این منظور بروکرا میان دیتا رو روی دیسک ذخیره میکنن.ردیس به صورت دیفالت persist نمیکنه و اختیاریه. میتونه با استفاده از مکانیزم های RDB snapshotting یا AOF (append only file) دیتا رو روی دیسک persist کنه.ربیت هم با یه سری فلگ میتونی تنظیمش کنی که persist کنه. باید message و queue و exchange رو به عنوان durable تعریف کنی.کافکا اصلا خوراکش همینه به صورت پیش فرض برات persist میکنه. البته بهتره retention policy ست کنی تا دیسک با دیتای قدیمی / بدردنخور هدر نره یا پر نشه.2- Acknowledgement (Ack / Nack)کانسیومر اعلام میکنه که تونست پیام رو پردازش کنه (ack) یا نتونست (nack در ربیت).ردیس در حالت pub/sub روی مود fire &amp; forget هست و ack نداره. در حالت stream دارهربیت دوتا مود auto ack و manual ack داره که اولی به محض رسیدن پیام حذفش میکنه(این خطریه) و مود دوم اگه کانسیومر نتونست پردازش کنه(مثلا کرش کنه) پیامه برمیگرده توی صف. یه nack هم داره.کافکا دو مرحله ack داره از producer به broker و از consumer به broker. از پرودیوسر به بروکر سه تا مود داره یه مود acks=0 که میشه همون fire &amp; forget. یه مود acks=1 که رپلیکای لیدر باید تایید بده و یه مود acks=all که همه رپلیکاها باید تایید بدن(که پیام دریافت و ذخیره شده). از کانسیومر به بروکر هم کانسیومر بعد از پردازش پیام میتونه commit کنه اینجوری offset اش میره جلو ولی اگه کرش کنه و نتونه commit کنه دور بعدی که میاد بالا پیامه رو میبینه دوباره3- Retry &amp; Dead Letter Queue (DLQ)یه پیام بعد از چندبار ریترای و پردازش ناموفق میره به یه صفی به اسم DLQ که بعدا بررسی بشه که مشکل چی بوده.ردیس pub/sub نداردش. Redis Stream هم نداره ولی میشه دستی پیادش کرد.ربیت به صورت native داردش و میتونی یه DLX (Dead letters Exchange) تعریف کنی پیاما بره اون توکافکا هم به صورت native نداره ولی میتونی یه تاپیک DLQ تعریف کنی پیاما رو بریزی اون تو4- Replicationمیتونیم دیتای بروکر رو رپلیکیت کنیم که در صورت کرش کردن یه نود بقیه نودها جاشو بگیرن.ردیس stand-alone نداره همچین ویژگی ای باید از Redis Sentinel یا Redis cluster استفاده بشه.ربیت با Mirrored Queues اینکارو میکنه.کافکا به صورت کلاستری پیاده میشه. پارتیشن ها روی بروکرهای مختلف رپلیکیت میشن.5- Transactions / Exactly once Semantics (EOS)یه پیام دقیقا یکبار پردازش بشه نه گم بشه نه تکراری داشته باشیم.ردیس به صورت native نداره باید در سطح اپلیکیشن idempotency رو پیاده کنیم.ربیت هم مثل ردیسه دقیقا.کافکا به صورت native داره اما پیچیده است.6- Backpressure &amp; Flow Controlوقتی consumer نمیرسه پیاما رو پردازش کنه بروکر میاد سرعت producer رو کم میکنه.ردیس pub/sub نداره ولی Redis Stream به صورت محدود داره.ربیت داره.کافکا داره خود کانسیومر سرعتش رو تنظیم میکنه.Application Level Reliability Techniques1- Idempotencyتعریف عملیات idempotent:‌ عملیاتی که با چندبار انجام دادنش نتیجه نهایی یکسان باشه.مثال:-- idempotentset user_balance = 100;-- non-idempotentset user_balance += 100;idempotency تضمین میکنه که اگه یه پیام چندبار پردازش بشه مشکلی پیش نیاد. مثلا دوبار از حساب کاربر پول کسر نشه یا دوبار نتونه خرید انجام بده و ... .وقتی به صورت at least once داریم از بروکر استفاده می کنیم پای این وسط میاد و باید حواسمون بهش باشه و پیام های تکراری رو در سطح اپلیکیشن هندل کنیم.2- Deduplication Logicحذف پیامای تکراری مثلا با استفاده از unique id یا شناسه تراکنش.3- Consistency Checkچک کردن این که وضعیت سیستم پس از پردازش پیام درست باشه مثلا موجودی کاربر منفی نباشه.</description>
                <category>حسن سعادت</category>
                <author>حسن سعادت</author>
                <pubDate>Sat, 13 Sep 2025 21:05:46 +0330</pubDate>
            </item>
                    <item>
                <title>ایندکس، یکبار برای همیشه!</title>
                <link>https://virgool.io/@hassan-saadat/database-index-once-and-for-all-lw1hp3qlqhgp</link>
                <description>تعریف ایندکسAn index is an on-disk structure associated with a table or view that speeds retrieval of rowsfrom the table or view.ایندکس یه ساختار داده(data structure) کمکی برای افزایش سرعت سرچ توی دیتابیسه. بدون ایندکس مجبوریم کل جدول رو اسکن کنیم (Full Table Scan) که برای جدولای بزرگ مثلا چند میلیون ردیف خیلی کند انجام میشه. در واقع مثل فهرست کتاب میمونه که میتونی بفهمی یه مطلب خاص توی کدوم صفحات هستش.ایندکس باعث میشه سرعت خوندن از جدول بره بالا ولی overhead اش اینه که باعث کند شدن Insert/Update/Delete میشه چون باید ایندکس آپدیت بشه. اگه از ایندکس درست استفاده کنیم سرعت Where - Join - Order By - Group By افزایش پیدا می کنه.استراتژی ایندکس برای افزایش سرعت یه چیزه! کوچیک کردن فضای جستجو یا search space. سریع ترین راه برای جستجو روی چند میلیون ردیف اینه که همه اون چند میلیون ردیف رو بررسی نکنیم صرفا فضای جستجو رو تا حد امکان کاهش بدیم.مفهوم Heap و نحوه ذخیره جدول و ایندکس روی دیسکیه ساختار داده است که دیتابیس با اون قالب روی دیسک مینویسه و نوشتنش هیچ ترتیب خاصی نداره. مشابه مفهوم heap - stack توی Memory که توی برنامه نویسی خصوصا C/C++ داریم، منتها اینجا ذخیره روی دیسکه و همچنین هیچ ربطی به heap توی ساختار داده و الگوریتم نداره.شکل زیر یه شمای کلی از نحوه ذخیره جدولا روی دیسک و همچنین ایندکس ها میده:نحوه ذخیره سازی جداول و ایندکس روی دیسکتوضیح تصویر: اینجا مثلا هزارتا ردیف/row داریم و فرضا توی هر page (که میدونیم توی پستگرس یه فایل با سایز ثابت 8KB عه) 3 تا ردیف جا میشه (به صورت باینری نوشته میشه البته). دیتابیس به هر ردیف یه آیدی خودش اختصاص میده به اسم row_id/tuple_id. توی قسمت ایندکس میایم مقدار ستون به همراه row_id و page_number رو ذخیره میکنیم. خود ایندکس هم به شکل پیج پیج ذخیره میشه. اینجا خطی نشون دادیم ولی بسته به نوع ایندکس میتونه درخت یا چیزای دیگه باشه.10 (1, 0) =&gt; value=10 is present on row_id=1 and page number=040 (4, 1) =&gt; value=40 is present on row_id=4 and page number=1در ادامه انواع ایندکس رو بررسی می کنیم بر اساس دسته بندی های مختلف.1- انواع ایندکس (از لحاظ ذخیره سازی فیزیکی)داده های یه جدول به صورت پیش فرض بدون هیچ ترتیب خاصی در Heap روی دیسک ذخیره میشن.1. Clustered Indexاگر از ایندکس کلاستر شده استفاده کنیم میتونیم دیتابیس رو مجبور کنیم که دیتاها رو بر اساس یه ستون خاص (معمولا primary key) به صورت مرتب شده ذخیره کنه انگار که داخل جدول خودش یه جور ایندکس عه.این مدل ایندکس توی SQL Server و MySQL(InnoDB) وجود داره و primary key میشه clustered index و جدول مرتب شده نگهداری میشه. Postgres این مدل ایندکس رو نداره و همه ایندکس ها secondary هستن ینی داده های جدول بدون هیچ ترتیب خاصی روی Heap/Disk نوشته میشن و یه ساختار داده خارج جدول میاد کمکش. البته postgres دستور cluster داره که میاد جدول رو بر اساس یه ایندکس مرتب میکنه ولی دائمی نیست و با اضافه/کم شدن ردیفای جدید ترتیب خراب میشه.2. Non-Clustered Indexایندکس کلاستر نشده یه داده ساختاریه که جدای از جدول ساخته میشه و کاری به ترتیب ذخیره شدن دیتاها توی جدول نداره مثل BTree و Gin و ... .2- انواع ایندکس (از نظر ساختار داده)ایندکس توی دیتابیس Postgres چندین نوع داره:1. B+Treeاین مدل ایندکس برای عملگرهای مقایسه / comparison (&lt; &gt; = &lt;= &gt;=) و Range query و نگهداری مقادیر unique و Order by عالی عمل میکنه (مقادیر رو مرتب شده ذخیره میکنه). پستگرس وقتی بهش میگی ایندکس بساز به طور دیفالت این مدل ایندکس رو میسازه. البته خود پستگرس به این میگه BTree ولی توی پیاده سازی از B+Tree در واقع استفاده می کنه طبق سورس کد.این مدل ایندکس به درد Cardinality پایین مثل enum و true/false نمیخوره.2. Ginمخفف Generalized Inverted Index یا ایندکس معکوس عمومیه. برای full text search و داده های Array , JSONB مناسبه. حالا چرا بهش میگن معکوس؟ در واقع معکوس نیستش مثل ایندکس عادی میاد مقادیر رو به ردیف ها مپ می کنه اینجوری:a -&gt; [row1, row2, ...]b -&gt; [row4, row6, ...]فقط فرقش اینه که میاد و مقدار اون ستون رو میشکنه و توکنایز میکنه مثلا برای Full text search میاد کلمات جمله رو توکنایز میکنه و هر کلمه رو به ردیف ها مپ می کنه:regular index:&quot;how are you?&quot; =&gt; [row1, row2, ...]inverted index:&quot;how&quot; =&gt; [row1, row10]&quot;are&quot; =&gt; [row2, row 20]&quot;you&quot; =&gt; [row3, row4, row12]نحوه ذخیره جیسون در ایندکسحالا جنرالایز یا عمومی برای چیه؟ ینی فقط برای full text search نیست برای جیسون و آرایه و هر تایپی که براش مشخص کنی کار میکنه فقط باید بهش operator class / opclass لازم رو بدی تا بتونه خودش مقادیر ستون لازم رو توکنایز کنه.این مدل ایندکس بدرد دیتاست های کوچولو و جدولایی که زیاد آپدیت میشن نمیخوره.3. Hashاین یه هش مپ ساده است که میاد مقادیر رو میبره توی یه هش فانکشن و مقدار هش به ردیف مربوطش مپ میشههش ایندکسبرای مقایسه برابری / equality بدرد میخوره ولی چون به صورت دیفالت همون ایندکس BTree کار اینم انجام میده تازه مرتب شده هم ذخیره میکنه خیلی از این استفاده نمیشه. بیشتر اوقات BTree بهترم عمل می کنه. هش ایندکس فقط برخی موارد که داده های اون ستون یکنواخت پراکنده شده باشن مثل uuid بهتر عمل می کنه.4. Gistمخفف Generalized Search Tree عه و برای داده هایی که ساختار خطی ندارن بدرد می خورده مثل داده های جغرافیایی. مثلا میخوایم روی محدوده یک کیلومتری یک نقطه جغرافیایی فیلتر کنیم. اینجا این از BTree بهتر عمل می کنه و بهینه تره. توی equality خوب عمل نمیکنه به اندازه BTree ولی برای محدوده عالیه.5. SP-Gistمخفف Space-Partitioned Gist عه و ورژن بهینه شده قبلیه منتها برای داده های sparse و پراکنده یا غیر یکنواخت.6. BRIN (Block Range Index)بدرد جداول خیلی بزرگ با داده هایی میخوره که به طور طبیعی مرتب شده ان و داده ها با مکان فیزیکیشون correlation دارن مثل جدول لاگ ایونت ها. این ایندکس خیلی کم حجمه و حتی درخت هم نیست و فقط میاد محدوده بلاک ها رو مپ میکنه به بازه های مشخص مثلا بازه زمانی مشخص مثل تصویر:ایندکس BRINبرای مقایسه محدوده بدرد میخوره برای عملگر برابری/equality فایده نداره.اگه دیتا رو بزنی حذف کنی بعد Vacuum بزنی اونوقت correlation طبیعی بین داده ها و مکان فیزیکی اونها روی دیسک به هم میریزه و کمتر بهینه میشه چون دیتابیس مجبوره بیشتر بگرده.3- انواع ایندکس (از لحاظ ویژگی کاربردی)1. Single Column Indexفقط روی یه ستون اعمال میشهCREATE INDEX idx_name ON grades(name);2. Multi-Column/Composite Indexترکیب چنتا ستون رو ایندکس می کنیم. مثلا:CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);قانون Leftmost Prefix Ruleترتیب تعریف ستون ها مهمه! ایندکس(منظورمون ایندکس BTree هست اینجا) اول بر اساس کلید اول مرتب میکنه بعد برای مقادیری که کلید اول یکسان دارن بر اساس کلید دوم مرتب میکنه بعد مقادیری که کلید اول و دوم یکسان دارن بر اساس کلید سوم مرتب میکنه.ینی ایندکس مرکب فقط روی ((ستون اول)) یا ترکیب ((ستون اول + ستون‌های بعدی به ترتیب)) می‌تونه مستقیم کمک کنه در بقیه موارد چیزی رو گردن نمیگیره!حالت های مختلف عملکرد ایندکسبر این اساس بهتره کلیدها رو جوری بچینیم که ستونهای با cardinality بالاتر اول قرار بگیرند و بعد ستون های با cardinality کمتر.تفاوت Composite Index و Single Column Indexتفاوت ایندکس تک ستونی و ایندکس ترکیبی3. Partial Indexفضای ایندکس رو محدود میکنیم با شرط گذاشتن. به جای کل جدول فقط بخشی از جدول ایندکس میشه. مثلا:CREATE INDEX idx_users_active_emailON users(email)WHERE is_active = true;SELECT * FROM users WHERE is_active = true AND email = &#039;test@example.com&#039;;تو این مثال ما همیشه داریم روی یوزرای فعال کوئری میگیرم پس بهتره روی ایندکس شرط بذاریم که با کل جدول ایندکس نسازیم صرفا جاهایی که شرط صدق میکنه. اینجوری فضای جستجو باز هم کوچیکتر میشه و ایندکس بهینه تر.4. Covering Index / Include Indexیه سری ستون دیگه که جزو کلید ایندکس نیستن رو توی ایندکس لحاظ می کنیم تا دیگه دیتابیس نره توی heap دنبالشون بگرده مستقیم از خود ایندکس برشون داره بیاره. مثلا:SELECT age, emailFROM usersWHERE name = &#039;Ali&#039;;CREATE INDEX idx_user_name ON users(name) INCLUDE (age, email);توی مثال بالا کوئری مثلا ما همیشه روی اسم کوئری میگیریم ولی همیشه هم فیلد ایمیل و سن رو میخوایم، به جای اینکه بعد از گرفتن مقادیر name با Index scan / Bitmap Index scan بریم توی Heap دنبالشون بگردیم میتونیم با Index Only Scan (که سریعتره) بدون اینکه به Heap بریم سریع دیتا رو fetch کنیم!به ستون هایی که جزو کلید ایندکس هستن ینی روی اونها کوئری گرفته میشه میگیم key column و به اونهایی که جزو کلید ایندکس نیستن میگیم non-key column.5. Unique Indexتضمین میکنه مقدار تکراری توی ایندکس نداشته باشیم / همه مقادیر یونیک باشن. مثلا:CREATE UNIQUE INDEX idx_email ON users(email);6. Function/Expression Indexایندکسی که روی نتیجه یه عبارت ساخته میشه نه ستون خام. مثلا اینجا که بیشتر سرچ ها روی اسم با حروف کوچیک ملت انجام میشه این ایندکس رو ساختیم.CREATE INDEX idx_lower_name ON customers(LOWER(first_name));بلوم فیلتر / Bloom Filterبلوم فیلتر یه data structure عه که میتونه به این سوال پاسخ بده:&quot;آیا این مقدار عضو این مجموعه هست یا خیر؟&quot;بلوم فیلتر به این سوال میتونه یه &quot;نه&quot; قاطع و یا یه &quot;بله&quot; احتمالی بده. ینی اگه بگه نه واقعا اون مقدار عضو اون مجموعه نیست ولی اگه بگه بله احتمالا هست ولی ممکنه هم نباشه ینی false positive داره (false negative نداره اصلا).ترید آف ما اینجاست که حافظه بیشتری save می کنیم (کمتر مصرف می کنیم) و در عوض دقت پایینتری میگیریم.- خب این به چه درد میخوره؟برای بهینه سازی کوئری ها میتونه استفاده بشه و تعداد مراجعه به دیتابیس/دیسک/مرجع اصلی رو کاهش بده از طرفی حجم خیلی کمی حافظه مصرف می کنه. توی شبکه توی دیتابیس Cassandra (برای کاهش IO) توی ردیس و کلی جای دیگه کاربرد داره و جلوی کوئری های اضافی رو میگیره.پستگرس یه نوع ایندکس فرعی هم به اسم Bloom داره که برای بررسی عضویت توی جدولایی که بزرگ هستن و میخوایم روی چنتا فیلد با هم فیلتر AND بزنیم کاربرد داره.CREATE EXTENSION IF NOT EXISTS bloom;CREATE INDEX idx_users_bloom ON users USING bloom (gender, status, age);- چجوری کار میکنه؟یه آرایه بیتی درست میکنیم و با مقدار اولیه صفر همه مقادیر رو پر می کنیم، بعد یه Hash Function تعریف می کنیم و هر مقداری که میخوایم به دیتابیسمون اضافه کنیم یه بار هم میبریم توی هش فانکشن. هش فانکشن یه عدد میده توی رنج اون آرایه، اون اندیس رو با مقدار 1 مقداردهی می کنیم. هر دفعه سوال شد آیا این مقدار توی این مجموعه هست اونو میبریم توی هش فانکشن اگه بیت مربوطه 0 بود ینی قطعا این مقدار توی مجموعه وجود نداره و اگه 1 بود ینی احتمالا وجود داره و باید بریم بگردیم پیداش کنیم. همین!نحوه کار بلوم فیلترتوی مثال شکل مشخصه قشنگ. البته این به واقعیت نزدیکتره چون بلوم فیلتر برای کاهش false positive میاد از سه تا hash function استفاده می کنه و مقدارا رو با هم and می کنه.- اگه همه بیت ها پر بشن چی میشه؟در این صورت دقتش خیلی میاد پایین(همه رو میگه yes و باید بری دیتابیس رو بگردی!) و باید تعداد بیت ها رو افزایش داد و دوباره یه جدیدشو ساخت مثلا.- اگه یه داده حذف بشه چه کنیم؟نمیتونیم بیت ها رو دست بزنیم چون نمیدونیم فقط مال اون دیتای حذف شده است یا مشترکه با بقیه دیتاهای موجود. توی Bloom filter سنتی این امکان وجود نداره ولی توی بلوم فیلترهای جدیدتر مثل Counting Bloom filter یا Cuckoo filter این امکان هستش. اونجا به جای استفاده از یه بیت میایم از یه integer استفاده می کنیم و هر بار یه دونه count+1 می کنیم. اینجوری میشه دیتا حذف هم کرد ولی هزینه ای دادیم این بوده که حافظه بیشتری مصرف می کنیم.خب تا اینجا با انواع ایندکس آشنا شدیم حالا دست به آچار بشیم!آنالیز کوئری هابرای آنالیز کوئری های Postgres و اینکه بفهمیم چه مراحلی رو طی می کنه تا دیتا رو بگیره آیا از ایندکس داره استفاده می کنه یا نه از explain و explain analyze استفاده می کنیم. explain فقط برنامه ریزی دیتابیس رو نشون میده ولی explain analyze هم برنامه ریزی و تخمین رو نشون میده و هم کوئری رو ران می کنه و مقادیر آماری واقعی رو نشون میده.نمونه کوئری explainاین میگه که عملیات Seq Scan انجام شده زمان تخمینی fetch کردن اولین tuple مساوی 0 عه و زمان تخمینی fetch کردن آخرین tuple مساوی 289 و خورده ای ثانیه است. تعداد تقریبی ردیف ها 12 میلیون و خورده ایه و طول هر tuple مساوی 31 بایته که میشه مجموع سایز field های اون جدول مثلا 4 بایت integer به اضافه 8 بایت double و ... .نمونه کوئری explain analyzeاین هم میگه چقدر زمان برد برنامه ریزی کنم چقدر زمان اجرا طول کشید. البته خیلی از این میتونه بیشتر پیچیده بشه و باید حتما خودت با این ابزار کار کنی تا دستت بیاد!انواع اسکنجدول در دیتابیس postgresانواع اسکن جدول در دیتابیس postgresحالا بریم سراغ یه سری سوالات که ممکنه براتون پیش بیاد!پرسش و پاسخ1. کی ایندکس به درد نمیخوره؟وقتی که جدول کوچیکه یا Cardinality ستون کمه(Gender, status, true/false)2. تفاوت covering index و Multi-column index/composite index چیه؟توی ایندکس مرکب/چندستونی همه ستون های ایندکس key column هستن ینی ایندکس بر اساس اونها ساخته و مرتب میشه ولی توی ایندکس پوششی ایندکس فقط روی key column ها ساخته و مرتب میشه ولی مقدار non-key column ها رو هم توی ایندکس ذخیره می کنه که دیگه به Heap مراجعه نکنه و با Index only scan کارو در بیاره و پرفورمنس رو ببره بالا. ایندکس مرکب برای زمانیه که روی همه ستونهای ایندکس داره کوئری گرفته میشه ولی کاورینگ ایندکس برای زمانیه که فقط میخوایم روی key column کوئری بگیریم ولی یه سری فیلد اضافه هم همیشه همراهش لازم داریم.البته جایی که از covering index ساپورت نمیشه (مثلا جنگو به صورت پیش فرض ساپورت نمیکنه مگه که لایبرری اضافه کنی یا دستی مایگریشن بنویسی) میشه از composite index استفاده کرد به جاش ایندکس سنگین تری ساخته میشه به نسبت و خب قاعدتا پروفورمنس پایین تری داریم (چون ایندکس به ترتیب روی همه کلیدها مرتب میشه) و حجم بیشتری اشغال می کنه.منابع:https://www.youtube.com/watch?v=_HG2eB27j00&amp;t=2s&amp;ab_channel=JaneExplainsIThttps://www.youtube.com/watch?v=2l-nCkPQVuQ&amp;ab_channel=PostgresOpenhttps://youtu.be/BHCSL_ZifI0?si=giZTFfQQL4Qu0d3Fhttps://learn.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described?view=sql-server-ver17https://www.udemy.com/course/database-engines-crash-course/https://medium.com/threadsafe/unleashing-the-power-of-composite-indexes-in-postgresql-909ac95fc476https://www.youtube.com/watch?v=V3pzxngeLqw&amp;ab_channel=ByteByteGohttps://vrgl.ir/cHttn</description>
                <category>حسن سعادت</category>
                <author>حسن سعادت</author>
                <pubDate>Wed, 27 Aug 2025 18:28:07 +0330</pubDate>
            </item>
                    <item>
                <title>مفهومMVCC؛ قلب تپنده دیتابیس</title>
                <link>https://virgool.io/@hassan-saadat/%D8%B1%D9%88%D8%B4-mvcc-%D8%AF%D8%B1-postgres-dkda77datx38</link>
                <description>MVCC یا Multi Version Concurrency Control یا کنترل همروندی چند نسخه ای یه روش برای افزایش همزمانی و کارایی در دیتابیس هایی مثل Postgres و MySQL و SQL Server استفاده میشه.داستان از اونجایی شروع میشه که دو نفر میخواستن همزمان روی یه دیتابیس عملیات انجام بدن مثلا یکی میخواست بخونه یکی هم بنویسه. اون فردی که میخواست بنویسه بایستی دیتابیس رو قفل میکرد به یکی از سه روش Table Lock - Page Lock - Row Lock تا کارشو انجام بده و بعدش بقیه بتونن کارشونو انجام بدن.ینی تا زمانی که نفر دوم کارش تموم نشده بود تو نمیتونستی دیتابیس یا بخشی ازش رو بخونی و این خودش باعث کاهش شدید همزمانی و کارایی میشد و باعث deadlock هم میشد.روش MVCC اومد گفت از هر ردیف جدول چنتا نسخه نگه میداریم و هر Transaction یه snapshot از دیتابیس رو می بینه و باهاش کاراشو میکنه. ینی وقتی یه نفر داره مینویسه با اون اسنپ شاتی که اول تراکنش گرفته کاراشو میکنه و تا زمانی که commit نکرده بقیه اون تغییرات جدید رو نمیبینن. و اینطوری کسی بلاک نمیشه.نکته جالب اینجاست که وقتی یه ردیف رو آپدیت می کنیم روی اون ردیف اعمال نمیشه و به جاش یه ردیف جدید درست میشه با شناسه اون تراکنش و ردیف قدیمی علامت میخوره تا بعدا توسط VACCUM دیلیت بشه (البته دیلیت نمیشن صرفا فضاشون برای نوشتن ردیف های جدید قابل استفاده میشه).مثال:توی این مثال الکس میخواد ۱۰۰ دلار بده به کریس، Postgres میاد برای هر کدوم یه row جدید درست می کنه.عدد xmin نشون دهنده شناسه تراکنشیه که اون ردیف باهاش درست شده و xmax نشون دهنده شناسه تراکنشیه که اون ردیف رو دیلیت کرده.نکته: اگه یه تراکنش دومی بخواد روی این دوتا ردیف کار کنه چه اتفاقی میفته؟ دیگه جا برای xmax نداریم که!پس اون تراکنش بلاک میشه تا تکلیف تراکنش اول مشخص بشه!نکته جالب:دلیل کندی Count(*) هم توی Postgres همین MVCC هستش چون شمردن تعداد ردیف ها کار ساده ایه ولی وقتی MVCC استفاده میکنی بیشتر از تعدادی که باید ردیف وجود داره و باید یه for بندازی ببینی کدوماش قابل دیدنه و کدوماش قدیمیه که باید دیلیت بشه بعدا. Postgres میاد یه همچین if ای به ازای هر ردیف میزنه و counter رو یه دونه میبره بالا اگه ویزیبل بود!این دوستمون توی ویدیوی یوتیوبش خیلی خوب این قضایا رو توضیح داده:https://youtu.be/GtQueJe6xRQ?si=o4xQrHcQXYmsZS1qقضیه VACCUM:وکیوم میاد dead tuple ها رو علامت میزنه به عنوان اینکه میشه روی اون ردیف های مرده دیتای جدید رایت کرد. وکیوم فضای مصرف شده ردیف های مرده رو به دیسک برنمیگردونه! تنها دستور VACCUM FULL میتونه اینکارو کنه ولی این دستور بلاکینگ هستش و کل جدول رو لاک می کنه (EXCLUSIVE LOCK) و نمیذاره کسی روی جدول کاری بکنه ولی وکیوم خالی بلاکینگ نیست.وکیوم به صورت دوره ای اجرا نمیشه بلکه به صورت event-based بر اساس یه سری threshold و با فرمول زیر فعال میشه که بهش میگیم auto vaccum:num_dead_tuples &gt; autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * total_rowsمقدار autovaccum_vaccum_threshold میگه حداقل چقدر row داشته باشیم باید اتو وکیوم صورت بگیره. مثلا برای یه دونه ردیف لازم نیست انجام بشه و بیخوده.وکیوم و وکیوم فول رو میشه به صورت دستی هم اجرا کرد.قضیه VACCUM Freeze:اتو وکیوم علاوه بر اینکه میاد dead tuple ها رو قابل استفاده مجدد می کنه یه کار دیگه هم میکنه و اون فریز کردن xid های خیلی قدیمیه (برای ردیف های زنده فقط) چون اون ردیف ها دیگه xmin نیاز ندارن داشته باشن صرفا یه چیزی مثل FrozenTransactionId =2 ینی xmin=2 (این رکورد برای همه تراکنش ها ویزیبله) براشون کفایت میکنه.قضیه XID Wraparound:عدد xid یا همون Transaction تایپش unit32 عه ینی یه عدد بدون علامت ۳۲ بیتیه که بازه اش از صفره تا دو به توان ۳۲ که میشه ۴ میلیارد و خورده ای. دیتابیس برای اینکه بدونه توی هر تراکنش کدوم row ها برای اون تراکنش قابل دیدن باشن و کدوما نباشن از یه مقایسه استفاده می کنه. اون میاد xid تراکنش فعلی رو با xmin اون row ها مقایسه می کنه اینجوری:int32(xid1 - xid2) &lt; 0 =&gt; xid1 is older than xid2حالا نکته این چیه؟ حاصل مقایسه دوتا unit32 میتونه از دو به توان ۳۱ بیشتر باشه که دیگه توی int32 نمیگنجه چون بازه int32 = (-2^31, 2^31-1) هستش و از اونجا به بعد مقایسه ها اشتباه انجام میشه و دیتاهای جدید ممکنه از دست برن و دیتاهای قدیمی زنده بشن!اینجاست که دیتابیس میاد و همیشه اختلاف xid فعلی و آخرین xid فریز نشده رو محاسبه می کنه که نزدیک به 2^31 نباشه و اگه نزدیک بود چندین بار وارنینگ میده میگه VACCUM کن! اگه گوش نکنی وقتی به عدد بحرانی(دو میلیارد و خورده ای) 2^31 - 1000000 برسه خودش دیگه xid جنریت نمیکنه و وقتی xid جدید نباشه هیچ عملیات INSERT/UPDATE/DELETE نمیتونه انجام بشه و فقط میتونی SELECT کنی و در واقع دیتابیس read-only میشه!با وکیوم و وکیوم فریزهای منظم این اختلاف همیشه کم میمونه و خطری وجود نداره.نکته اینجاست که در حالت نرمال(اجرای منظم وکیوم و فریز) وقتی xid به 2^32 ینی ۴ میلیارد و خورده ای میرسه دوباره صفر میشه و ادامه میده (۳ میشه در واقع چون ۰ و ۱ و ۲ رزرو شده ان!) و مشکلی هم پیش نمیاد و اتفاقا چیز ضروری هستش و هر یکبار صفر شدن این xid رو بهش میگن یه epoch که تعداد epoch هم توی یه متغیری ذخیره میشه.نکته دیگه:توی ورژن های قدیمی postgres مثل قبل از ورژن ۸.۱ اتو-وکیوم وجود نداشت و وکیوم و وکیوم فریز به صورت دستی باید انجام میشد و قضیه xid wraparound خیلی جدی بود و امکان data loss و shut down شدن دیتابیس وجود داشت. توی ورژن های جدید نهایتا read-only میشه.</description>
                <category>حسن سعادت</category>
                <author>حسن سعادت</author>
                <pubDate>Sun, 27 Jul 2025 17:39:11 +0330</pubDate>
            </item>
            </channel>
</rss>