Midnight Commit
105 subscribers
3 photos
1 file
41 links
Beyond Syntax.
The fundamentals behind great software.
Download Telegram
Channel created
شرایط خوب نیست، هرروز اونور باهامون بازی میکنه و اینور هم یه داغ تازه روی دلمون میزاره. همینجا توی شروع بگم که امیدوارم به زودی زود ایرانمون از این عذاب 47 ساله آزاد بشه.
چطور کلاس‌هامون مثل آبجکت‌های داخلی پایتون رفتار کنن؟ 🤔
وقتی اولین کلاس‌هامون رو توی پایتون می‌نویسیم، معمولاً تمام تمرکزمون روی __init__ و نگهداری داده‌هاست.
ولی پشت صحنه، یه مفهوم خیلی مهم وجود داره که باعث میشه آبجکت‌های ما مثل آبجکت‌های داخلی خود پایتون رفتار کنن:
Python Data Model همون چیزیه که باعث میشه بتونیم از len()، عملگرها، حلقه‌ها، sorted() و کلی قابلیت دیگه روی کلاس‌هامون استفاده کنیم.
یه مثال ساده 👇
فرض کنید یه کلاس برای یک دسته کارت (Deck) نوشتیم:
from collections import namedtuple

Card = namedtuple("Card", ["rank", "suit"])


class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "spades diamonds clubs hearts".split()

def __init__(self):
self._cards = [
Card(rank, suit)
for suit in self.suits
for rank in self.ranks
]

def __len__(self):
return len(self._cards)

def __getitem__(self, position):
return self._cards[position]

فقط دو تا Special Method پیادlenکردیم:
__len__
__getitem__
اما همین دو تا کلی قابلیت به کلاس ما اضافه می‌کنن. 😄

چه قابلیت‌هایی رایگان گرفتیم؟ 🎁
len(deck) ← به لطف __len__
دسترسی با اندیس (deck[0]) ← به لطف __getitem__
برش (Slicing) ← به لطف __getitem__
پیمایش با for ← به لطف __getitem__
استفاده از in ← به لطف __getitem__
random.choice() ← به لطف __getitem__
بدون اینکه هیچ‌کدوم از این قابلیت‌ها رو مستقیماً پیاده‌سازی کرده باشیم!

اصلاًleneciiterdهاgetitemتدهایی مثل:
__len__()
__iter__()
__getitem__()
__contains__()
__repr__()
__str__()
__eq__()
__hash__()

قرار نیست مستقیماً توسط ما صدا زده بشن.
مثلاً وlenنویسیم:
len(obj)

پایتون پشت صحنه این رو اجرا می‌کنه:
obj.__len__()

همین داستان برای عملگرها هم صدق می‌کنه.
a + b

در واقع میشه:
a.__add__(b)

یعنی پایتون با استفاده از Data Model می‌فهمه هر آبجکت باید چطور رفتار کنه.
نکته‌ی جالب اینجاست که تقریباً هر قابلیتی که از آبجکت‌های داخلی پایتون انتظار داریم، از طریق همین Special Methodها تعریف شده. یعنی وقتی این قراردادها رو رعایت کنیم، کلاس‌هامون هم به شکل طبیعی با کل اکوسیستم پایتون هماهنگ می‌شن. 🚀

حرف اصلی Python Data Model 💡
هدف این نیست که برای هر قابلیت، متدهای جدیدی طراحی کنیم.
هدف اینه که آبجکت‌هامون از قراردادهای پایتون پیروی کنن تا به‌صورت طبیعی با کل اکوسیستم پایتون هماهنگ باشن.
به همین دلیله که خیلی از قابلیت‌های پایتون، به جای اینکه بر اساس نوع (Type) یک شیء کار کنن، بر اساس رفتار (Behavior) اون تصمیم می‌گیرن.

💡در نهایت هدف Python Data Model فقط اضافه کردن چند قابلیت به کلاس‌ها نیست؛ هدف اینه که کلاس‌هامون شهروندهای درجه‌یک اکوسیستم پایتون باشن. این کار هم تجربه‌ی توسعه برای خودمون رو بهتر می‌کنه، هم استفاده و توسعه‌ی کد رو برای بقیه ساده‌تر و طبیعی‌تر؛ مخصوصاً وقتی در حال ساخت یک کتابخانه یا Framework باشیم. 🚀
#️⃣ #Python #FluentPython


🔈 MidnightCommit 🌙
البته که ‏Special Methodها فقط به __len__ محدود نمی‌شن 👀
‏Data Model پر از Special Methodهای مختلفه که هر کدوم مسئول بخشی از رفتار آبجکت‌ها هستن.
🚨 Let's Encrypt و تحریم کاربران ایرانی
یکی از مهم‌ترین سرویس‌های صدور رایگان گواهی SSL/TLS، یعنی Let's Encrypt، تو نسخه جدید توافق‌نامه کاربران (Subscriber Agreement v1.7)، بندی اضافه کرده که دریافت‌کنندگان گواهی باید تأیید کنن مشمول تحریم‌های جامع آمریکا یا محدودیت‌های صادراتی نیستن.

این یعنی چی؟

اگر این سیاست به‌صورت عملی برای کاربران داخل ایران اجرا بشه، دریافت یا تمدید گواهی‌های Let's Encrypt برای وب‌سایت‌ها و سرویس‌های ایرانی میتونه با مشکل مواجه بشه.
از اونجایی که بخش بزرگی از وب امروز برای HTTPS به Let's Encrypt وابسته است، این تغییر میتونه روی توسعه‌دهندگان، مدیران سرور و کسب‌وکارهای زیادی تأثیر بذاره.

اگر از Let's Encrypt استفاده می‌کنید:
1. وضعیت و اطلاعیه‌های رسمی Let's Encrypt رو دنبال کنید.
2. برای شرایط اضطراری، داشتن یک CA جایگزین یا برنامه مهاجرت میتونه تصمیم هوشمندانه‌ ای باشه.
3. اگر زیرساخت شما تمدید خودکار گواهی‌ها رو انجام میده، حتما اون را زیر نظر داشته باشید.

فعلاً باید منتظر موند و دید این بند جدید در عمل چطوری اجرا میشه و آیا برای کاربران ایرانی محدودیت عملی ایجاد میکنه یا نه.
#️⃣ #DevOps #Web #News


🌙 CHANNEL | GROUP
Midnight Commit
در عمل چطوری اجرا میشه
خب البته مثل اینکه صرفا یک شفاف سازی حقوقیه و تغییری در عملکرد ایجاد نمیکنه
چرا همه‌ی Sequenceهای پایتون شبیه هم نیستن؟ 🤔
وقتی از Sequenceها صحبت می‌کنیم، معمولاً ذهنمون میره سمت list، tuple، str یا bytes.
اما پشت صحنه، پایتون اون‌ها رو به دو دسته‌ی اصلی تقسیم می‌کنه.
-📦 ‏Container
-‏📄 Flat Sequences
تفاوت این دو، فقط در API یا Immutable بودنشون نیست؛ بلکه به نحوه‌ی ذخیره شدن داده‌ها در حافظه برمی‌گرده.

📦 Container Sequences
‏Container Sequenceها داده‌ها رو مستقیماً نگهداری نمی‌کنن؛ بلکه Reference به آبجکت‌های دیگه رو ذخیره می‌کنن.
مثل:
- ‏list
- ‏tuple
- ‏collections.deque
هر عنصر داخل این Sequenceها یک آبجکت مستقل در حافظه است که Header مخصوص خودش (شامل نوع، Reference Count و سایر متادیتاهای داخلی) و همچنین فضای مربوط به داده‌ی خودش رو داره.
در واقع، خود list فقط آرایه‌ای از Pointerها رو نگهداری می‌کنه و هر Pointer به یک آبجکت پایتونی اشاره می‌کنه.
به همین دلیل می‌تونیم داخل یک list همزمان int، str، dict یا حتی یک list دیگه داشته باشیم.

📄 Flat Sequences
‏Flat Sequenceها رویکرد متفاوتی دارن.
در این نوع Sequenceها، داده‌ها به‌صورت پشت سر هم (Contiguous) و بدون نگهداری Reference برای هر عنصر ذخیره میشن.
مثل:
- ‏str
- ‏bytes
- ‏bytearray
- ‏array.array
اینجا خبری از Header جداگانه برای هر عنصر نیست؛ چون عناصر آبجکت‌های مستقل پایتونی محسوب نمیشن و فقط مقدار خام (Raw Value) اون‌ها در حافظه ذخیره میشه.
همین موضوع باعث میشه Flat Sequenceها هم حافظه‌ی کمتری مصرف کنن و هم هنگام پیمایش یا پردازش، به دلیل Locality بهتر حافظه، عملکرد بالاتری داشته باشن.

تفاوت اصلی 💡
📦 ‏Container Sequence
عتاصر به صورت Reference ذخیره میشن.
هر عنصر یک آبجکت مستقل با header مخصوص خودشه.
امکان نگهداری انواع مختلف داده وجود داره.
انعطاف پذیری بیشتری دارن.

📄 ‏Flat Sequence
داده ها مستقیما در حافظه ذخیره میشن.
عناصر header جداگانه ندارن.
معمولا همه ی عناصر از یک نوع هستن.
مصرف حافظه کمتر و performance بهتر.

چرا این موضوع مهمه؟
در پروژه‌های معمولی شاید تفاوت محسوسی احساس نکنیم.
اما وقتی با میلیون‌ها داده، پردازش‌های سنگین یا توسعه‌ی کتابخانه‌هایی که Performance در اون‌ها اهمیت داره سروکار داریم، انتخاب بین یک Container Sequence و یک Flat Sequence می‌تونه روی مصرف حافظه، سرعت اجرای برنامه و حتی طراحی API تأثیر قابل‌توجهی بذاره.
گاهی انتخاب درست یک Structure، از بهینه‌سازی‌های پیچیده‌ی بعدی ارزشمندتره.

📚 منبع این مطلب فصل Overview of Built-in Sequences از کتاب Fluent Python هست.

#️⃣ #Python #FluentPython


🌙 CHANNEL | GROUP
🧐فکر میکردم System Design فقط کشیدن چند تا باکسه...
تا قبل از اینکه خودم بخوام یک System Design انجام بدم، از دور همه‌چیز خیلی ساده به نظر میرسید.
چند تا مستطیل، چند تا فلش بینشون و تمام!
اما وقتی چند وقت پیش اولین System Design جدی خودم رو شروع کردم، فهمیدم اصلاً نمیدونم باید از کجا شروع کنم.

سؤال‌هایی مثل این مدام توی ذهنم میچرخید:
کش رو کجا باید قرار بدم؟
اصلاً به Queue نیاز دارم؟
‏CDN لازمه؟
‏Database چی انتخاب کنم؟
‏Load Balancer از همون اول لازمه؟
این سیستم اصلاً قراره Scale بشه یا نه؟
کم‌کم فهمیدم اشتباه من این بود که از آخر مسیر شروع کرده بودم.

⚖️قبل از اینکه اصلاً به System Design فکر کنیم، باید بدونیم هر کدوم از این ابزارها و مفاهیم دقیقاً چه مشکلی رو حل می‌کنن.
مثلاً:
- ‏Cache
- ‏CDN
- ‏Load Balancer
- ‏Message Queue
- ‏Reverse Proxy
- ‏Object Storage
- ‏Database Replication
- ‏Database Sharding
- ‏Consistency
- ‏CAP Theorem

تا وقتی ندونیم هر کدوم چه کاربردی دارن، کنار هم قرار دادنشون فقط تبدیل میشه به کشیدن چند تا باکس روی یک صفحه.

📌بعد از کمی مطالعه، فهمیدم روند طراحی سیستم معمولاً خیلی منطقی‌تر از چیزیه که اول به نظر میاد.
تقریباً میشه اینطوری بهش نگاه کرد:
1️⃣ نیازمندی‌های سیستم رو مشخص کن
سیستم قراره چه کاری انجام بده؟ چه قابلیت‌هایی باید داشته باشه؟

2️⃣ محدودیت‌ها و انتظارات رو مشخص کن
چند کاربر همزمان خواهیم داشت؟ میزان ترافیک چقدره؟ اولویت با Performance هست یا Cost؟ Availability مهم‌تره یا Consistency؟

3️⃣ یک طراحی ساده ارائه بده
بدون Cache، Queue یا هر چیز اضافه‌ای. فقط اجزای اصلی سیستم.

4️⃣ گلوگاه‌ها رو پیدا کن
حالا کم‌کم سؤال‌ها خودشون به وجود میان:
اگر ترافیک زیاد شد چی؟
اگر دیتابیس پاسخگو نبود چی؟
اگر فایل‌ها زیاد شدن چی؟
و دقیقاً توی همین نقطه هست که ابزارهایی مثل Cache، Queue، CDN و... وارد طراحی میشن؛ نه از همون اول.

🧠برای من، بزرگ‌ترین درس این بود که System Design حفظ کردن ابزارها نیست؛ یاد گرفتن نحوه‌ی فکر کردنه.
شاید یکی از دلایلی که این روزها بیشتر از قبل به یادگیری System Design علاقمند شدم، پیشرفت سریع ابزارهای هوش مصنوعیه. هرچی نوشتن کد ساده تر و در دسترس تر میشه، ارزش مهارت‌ هایی مثل تحلیل نیازمندی‌ها، شناخت Trade-offها و طراحی معماری مناسب بیشتر خودش رو نشون میده.
به نظرم توی سال‌های آینده، این مهارت‌ها بیش از هر زمان دیگری افراد رو از هم متمایز خواهند کرد.
من تازه اول این مسیرم، اما فکر میکنم همین تغییر طرز فکر، مهم‌ترین چیزی بود که تا الان یاد گرفتم. 🚀

#️⃣ #SystemDesign #Backend


🌙 CHANNEL | GROUP
چرا Read و Write در PostgreSQL همدیگه رو متوقف نمی‌کنن؟🐘
وقتی برای اولین بار درباره‌ی PostgreSQL مطالعه می‌کردم، یکی از مفاهیمی که زیاد باهاش برخورد می‌کردم MVCC (Multi-Version Concurrency Control) بود.
اوایل فکر می‌کردم فقط اسم یک قابلیت دیگه از PostgreSQL باشه، اما هرچی بیشتر درباره‌ش خوندم، بیشتر متوجه شدم که بخش بزرگی از رفتار این دیتابیس بر پایه‌ی همین مفهوم طراحی شده.

بیاید اول یک سناریوی ساده رو تصور کنیم.
فرض کنید یک API در حال خواندن اطلاعات یک کاربره.
همون لحظه، درخواست دیگه‌ای میاد و اطلاعات همون کاربر رو تغییر میده.

حالا چند سؤال پیش میاد:
🤔 آیا عملیات خواندن باید متوقف بشه تا عملیات نوشتن تموم بشه؟
🤔 یا عملیات نوشتن باید منتظر بمونه تا عملیات خواندن به پایان برسه؟
اگر جوابمون «بله» باشه، با افزایش تعداد کاربران، دیتابیس به مرور زمان پر از Lock میشه و تعداد زیادی از Queryها فقط منتظر آزاد شدن منابع باقی میمونن.

نتیجه؟
کاهش Concurrency
افزایش زمان پاسخگویی
افت Performance

اینجاست که PostgreSQL از MVCC استفاده می‌کنه.💡
ایده‌ی اصلی MVCC اینه که به جای بازنویسی مستقیم یک رکورد، یک نسخه‌ی جدید از اون رکورد ایجاد بشه.
در نتیجه، هر Transaction تصویری از دیتابیس رو می‌بینه که در لحظه‌ی شروع خودش معتبر بوده. یعنی اگر یک Query قبل از تغییر یک رکورد شروع شده باشه، همچنان همون نسخه‌ی قدیمی رو مشاهده می‌کنه؛ حتی اگر در همین فاصله Transaction دیگه‌ای اطلاعات رو تغییر داده باشه. از طرف دیگه، Transactionهای جدید نسخه‌ی جدید رکورد رو خواهند دید.
به همین دلیل، در بسیاری از سناریوها عملیات Read و Write بدون اینکه همدیگه رو Block کنن، به صورت همزمان انجام میشن.

این روش چه مزیتی داره؟ 🚀
به جای اینکه دیتابیس دائماً درگیر Lock کردن رکوردها باشه، می‌تونه تعداد بسیار بیشتری Transaction همزمان رو مدیریت کنه.
نتیجه‌ی این تصمیم:
Concurrency بالاتر
Lockهای کمتر
Response Time بهتر
مقیاس‌پذیری بیشتر در سیستم‌های پرترافیک
به همین دلیله که PostgreSQL حتی تحت بارهای سنگین هم عملکرد بسیار خوبی از خودش نشون میده.

اما این روش رایگان نیست... ⚠️
وقتی برای هر تغییر، نسخه‌ی جدیدی از رکورد ایجاد میشه، نسخه‌های قدیمی همچنان داخل دیتابیس باقی میمونن.
البته این نسخه‌ها برای همیشه نگهداری نمیشن. ‏PostgreSQL سرویسی به نام VACUUM داره که وظیفه‌ی شناسایی و پاکسازی نسخه‌های قدیمی و بلااستفاده رو بر عهده داره. اگر این فرآیند به درستی انجام نشه، حجم دیتابیس به مرور افزایش پیدا می‌کنه و حتی ممکنه روی Performance هم تأثیر منفی بذاره.
به همین دلیل، MVCC و VACUUM دو مفهوم کاملاً وابسته به هم هستن.

آیا MVCC یعنی هیچ Lockای وجود نداره؟
نه.
یکی از برداشت‌های اشتباه اینه که MVCC باعث حذف کامل Lockها میشه. در واقع PostgreSQL همچنان در بعضی عملیات، مثل تغییر همزمان یک رکورد توسط چند Transaction یا بعضی تغییرات ساختاری (DDL)، از Lock استفاده می‌کنه.
اما تفاوت اصلی اینجاست که عملیات خواندن معمولی، در اکثر مواقع نیازی به منتظر موندن برای عملیات نوشتن نداره.
همین تفاوت، یکی از دلایل اصلی عملکرد خوب PostgreSQL در سیستم‌های پرترافیکه.

‏MVCC فقط یک قابلیت برای افزایش سرعت نیست.💡
در واقع، یک تصمیم معماریه که روی نحوه‌ی اجرای Transactionها، مدیریت همزمانی و حتی طراحی بخش‌های مختلف PostgreSQL تأثیر گذاشته.

شناخت مفاهیمی مثل MVCC، VACUUM، WAL و Query Planner کمک می‌کنه دلیل خیلی از رفتارهای دیتابیس رو بهتر درک کنیم و تصمیم‌های بهتری در طراحی سیستم بگیریم.

#️⃣ #Database #Backend


🌙 CHANNEL | GROUP
تا وقتی پروژه‌های کوچیک می‌نویسیم، معمولاً فقط چند متد معروف مثل append() یا pop() رو استفاده می‌کنیم.
اما list و tuple متدها و قابلیت‌های بیشتری دارن که بعضی از اون‌ها کمتر شناخته شدن، در حالی که می‌تونن کد رو خواناتر و حتی ساده‌تر کنن.
یه Cheat Sheet از متدهای مهم list و tuple آماده کردم که امیدوارم به دردتون بخوره. 🚀

#️⃣ #Python


🌙 CHANNEL | GROUP
ListTuples Cheatsheet.pdf
2.5 MB
این هم فایل PDF چیت شیت
اگر درست وسط یک Transaction برق سرور قطع بشه، PostgreSQL چیکار می‌کنه؟🐘
فرض کنید یک Transaction در حال انتقال پول بین دو حسابه.
ابتدا موجودی حساب اول کم میشه و بعد موجودی حساب دوم افزایش پیدا می‌کنه.
حالا تصور کنید درست بعد از انجام مرحله‌ی اول، برق سرور قطع بشه یا سیستم Crash کنه.
سؤال اینجاست:
🤔 از کجا معلوم دیتابیس بعد از روشن شدن دوباره، وضعیت درستی داشته باشه؟
اگر اطلاعات مستقیماً روی دیسک نوشته بشن، ممکنه فقط بخشی از تغییرات ذخیره شده باشه و دیتابیس در یک وضعیت ناسازگار (Inconsistent) قرار بگیره.

اینجاست که WAL (Write-Ahead Logging) وارد ماجرا میشه.💡
ایده‌ی اصلی WAL خیلی ساده‌ست:
قبل از اینکه هر تغییری روی فایل‌های اصلی دیتابیس نوشته بشه، اطلاعات مربوط به اون تغییر ابتدا داخل یک Log ثبت میشه.
یعنی PostgreSQL اول تغییرات رو در فایل‌های WAL ذخیره می‌کنه و فقط بعد از اون سراغ اعمال تغییر روی فایل‌های اصلی دیتابیس میره.
به همین دلیل بهش میگن Write-Ahead Logging؛ یعنی «اول Log رو بنویس، بعد داده‌ی اصلی رو تغییر بده.»

چرا این کار مهمه؟ 📝
فرض کنید بعد از ثبت شدن Log، اما قبل از به‌روزرسانی فایل‌های اصلی، سرور Crash کنه.
در نگاه اول شاید به نظر برسه اطلاعات از بین رفتن.
اما اینطور نیست.
وقتی PostgreSQL دوباره اجرا میشه، فایل‌های WAL رو بررسی می‌کنه و تمام تغییراتی که ثبت شده اما هنوز روی دیتابیس اعمال نشدن رو دوباره اجرا (Replay) می‌کنه.
در نتیجه، دیتابیس می‌تونه خودش رو به آخرین وضعیت صحیح برگردونه.

‏WAL فقط برای Crash Recovery نیست 🚀

خیلی‌ها WAL رو فقط ابزاری برای بازیابی بعد از Crash می‌دونن.
در حالی که کاربردهای مهم دیگه‌ای هم داره.
از جمله:
Crash Recovery
Streaming Replication
Point-in-Time Recovery (PITR)
تقریباً تمام قابلیت‌های مربوط به بازیابی اطلاعات و ساخت Replica در PostgreSQL، به نوعی به WAL وابسته هستن.

آیا این یعنی هر تغییر دوبار نوشته میشه؟
🤔
تقریباً بله.
ابتدا تغییر داخل فایل WAL ثبت میشه.
بعد همون تغییر روی فایل‌های اصلی دیتابیس اعمال میشه.
شاید در نگاه اول این کار هزینه‌ی اضافه به نظر برسه، اما مزیتی که در حفظ یکپارچگی داده‌ها و امکان بازیابی سیستم ایجاد می‌کنه، ارزش این هزینه رو داره.
به همین دلیل WAL یکی از مهم‌ترین دلایل قابل اعتماد بودن PostgreSQL در سیستم‌های Production محسوب میشه.

ارتباط WAL با Transactionها
🖇
وقتی یک Transaction رو Commit می‌کنیم، PostgreSQL فقط زمانی موفق بودن اون رو اعلام می‌کنه که رکوردهای مربوط به اون Transaction با موفقیت داخل WAL ثبت شده باشن.
این یعنی حتی اگر چند میلی‌ثانیه بعد سرور خاموش بشه، PostgreSQL هنوز اطلاعات لازم برای بازیابی اون Transaction رو در اختیار داره.

به همین خاطر WAL یکی از ستون‌های اصلی تضمین Durability در ویژگی‌های ACID به حساب میاد.
‏WAL فقط یک فایل Log نیست.
در واقع، یکی از مهم‌ترین تصمیم‌های معماری PostgreSQLه که قابلیت‌هایی مثل Crash Recovery، Replication و Backupهای پیشرفته بر پایه‌ی اون ساخته شدن.

#️⃣ #Database #Backend


🌙 CHANNEL | GROUP
چرا بعضی object ها در Python hashable هستن و بعضی‌ها نه؟ 🤔
اگر تا حالا با خطایی شبیه این مواجه شده باشین:
TypeError: unhashable type: 'list'

احتمالاً براتون سؤال پیش اومده که اصلاً hashable بودن یعنی چی؟
و مهم‌تر از اون، چرا پایتون برای بعضی object ها این محدودیت رو گذاشته؟

‏Hash چیه؟ 🧠

‏Hash یک عدد صحیحه که از روی وضعیت یک object تولید میشه.
توی پایتون می‌تونیم با تابع hash() ببینیمش:
hash("hello")
hash((1, 2, 3))

نکته مهم اینجاست که Hash قرار نیست محتوای object رو ذخیره کنه یا بتونیم از روش داده اصلی رو بازسازی کنیم.
هدف Hash اینه که یک شناسه سریع و قابل اعتماد از وضعیت فعلی یک object تولید کنه.

‏Hashable بودن یعنی چی؟ 📦

یک object زمانی Hashable محسوب می‌شه که:
1. دارای مقدار Hash باشه.
2. مقدار Hash اون در طول عمر object تغییر نکنه.
3. رفتارش با متد __eq__ سازگار باشه.

در واقع اگر دو object برابر باشن:
a == b

باید Hash یکسانی هم داشته باشن:
hash(a) == hash(b)


چرا همه object ها Hashable نیستند؟
اینجا مفهوم Immutable بودن اهمیت پیدا می‌کنه.
فرض کنید یک List قابل Hash شدن بود:
items = [1, 2, 3]

اگر Hash این object محاسبه بشه و بعد محتویات List تغییر کنه:
items.append(4)

دیگر Hash قبلی نماینده وضعیت فعلی object نخواهد بود.
به همین دلیل اکثر object های Mutable مثل:
- ‏list
- ‏dict
- ‏set
به صورت پیش‌فرض Hashable نیستن.
در مقابل Objectهای Immutable مثل:
- ‏int
- ‏str
- ‏bytes
- ‏tuple
معمولاً Hashable هستن.

چطور یک آبجکت Hashable بسازیم؟ 🛠

برای ساخت یک آبجکت Hashable باید دو متد رو تعریف کنیم:
__eq__
__hash__
مثلاً:
class User:
def __init__(self, username):
self.username = username

def __eq__(self, other):
return self.username == other.username

def __hash__(self):
return hash(self.username)

توی این مثال Hash بر اساس username تولید می‌شه.
>>> denver = User("denver")
>>> hash(denver)
6843638685520863834
>>>


نکته مهم اینه که فیلدی که برای Hash استفاده می‌کنیم نباید بعداً تغییر کنه؛ در غیر این صورت رفتار object غیرقابل پیش‌بینی می‌شه.

چرا Hashable بودن مهمه ؟ 💡

بخش بزرگی از ساختارهای داده و مکانیزم‌های پرکاربرد پایتون بر پایه Hash ساخته شدن.
از جمله:
- ‏dict
- ‏set
- ‏frozenset
- بسیاری از سیستم‌های Cache و Memoization

وقتی یه hashable, object باشه، پایتون می‌تونه اون رو به سرعت پیدا، مقایسه و ذخیره کنه.
به همین دلیل Hashable بودن فقط یک ویژگی کوچیک توی Python Data Model نیست؛ بلکه یکی از مفاهیم بنیادی‌ای هست که بخش بزرگی از Performance ساختارهای داده پایتون بر پایه اون شکل گرفته.

#️⃣ #Python #Programming


🌙 CHANNEL | GROUP
چرا در Python می‌تونیم Functionها رو مثل داده جابه‌جا کنیم؟ 🤔
بعضی از قابلیت‌های پایتون اونقدر طبیعی به نظر می‌رسن که اصلاً بهشون فکر نمی‌کنیم.
مثلاً اینکه یک Function رو به Function دیگه پاس بدیم، داخل یک متغیر ذخیره کنیم یا حتی از یک Function برگردونیم.
اما پشت این سادگی، یکی از مهم‌ترین ویژگی‌های طراحی Python قرار گرفته.

‏Higher-Order Function یعنی چی؟ 🧠
یک Function زمانی Higher-Order محسوب میشه که حداقل یکی از این دو ویژگی رو داشته باشه:
- یک Function دیگه رو به عنوان آرگومان دریافت کنه.
- یک Function دیگه رو به عنوان خروجی برگردونه.
برای مثال:
sorted(users, key=len)

در اینجا len خودش یک فانکشن هست که به عنوان آرگومان به sorted پاس داده شده.
بنابراین sorted یک Higher-Order Function محسوب میشه.

چرا این امکان توی Python وجود داره؟ ⚙️
دلیلش اینه که توی پایتون، فانکشن ها First-Class Object هستن.
یعنی Functionها تقریباً مثل هر Object دیگه‌ای رفتار می‌کنن:
می‌تونن داخل متغیر ذخیره بشن.
می‌تونن داخل Collectionها قرار بگیرن.
می‌تونن به Functionهای دیگه پاس داده بشن.
می‌تونن از Functionها برگردونده بشن.
مثلاً:
def greet():
print("Hello")

handler = greet

اینجا handler و greet به یک Function اشاره می‌کنن.

این موضوع چه فایده‌ای داره؟ 💡
در نگاه اول ممکنه صرفاً یک قابلیت جالب به نظر برسه.
اما بخش بزرگی از قابلیت‌های مدرن Python بر پایه‌ی همین ایده ساخته شدن:
- ‏Decoratorها
- ‏Callbackها
- ‏Strategy Pattern
- ‏Event Handling
- ‏Functional Programming Tools
در واقع وقتی Functionها هم مثل داده قابل جابه‌جایی باشن، می‌تونیم رفتار برنامه رو به شکل بسیار انعطاف‌پذیرتری طراحی کنیم.

‏Decoratorها از کجا میان؟ 🎭
یکی از معروف‌ترین کاربردهای Higher-Order Functionها، Decoratorها هستن.
‏Decoratorها در اصل Functionهایی هستن که یک Function دیگه رو دریافت می‌کنن و نسخه‌ی جدیدی از اون رو برمی‌گردونن.
به همین دلیل فهم Decoratorها بدون درک Higher-Order Functionها معمولاً باعث میشه فقط نحوه‌ی استفاده از اون‌ها رو یاد بگیریم، نه دلیل کار کردنشون رو.

جمع‌بندی ✍️
‏Higher-Order Function ها نتیجه مسقتیم این هستن که فانشکن ها توی پایتون، First-Class Object محسوب میشن.
و همین ویژگی پایه‌ی بسیاری از مفاهیم مهم زبان، از جمله Decoratorها، محسوب میشه.
گاهی پشت یک قابلیت ظاهراً ساده، یکی از بنیادی‌ترین ایده‌های طراحی زبان قرار گرفته.

📚 منبع این مطلب فصل Higher-Order Functions از کتاب Fluent Python هست.

#️⃣ #Python #Programming #FluentPython


🌙 CHANNEL | GROUP
اگر جدول PostgreSQL حجیم بشه و عملکردش افت کنه، PostgreSQL چیکار می‌کنه؟🐘
فرض کنید داریم روی یک جدول حساب کاربری کار می‌کنیم. هر روز کلی UPDATE و DELETE انجام می‌شه. موجودی تغییر می‌کنه، سفارش لغو می‌شه، رکورد پاک می‌شه...
تو PostgreSQL به خاطر MVCC، وقتی یه رکورد آپدیت یا حذف میشه، نسخه‌ی قدیمی‌ش فوراً حذف نمی‌شه. یه نسخه‌ی جدید ساخته می‌شه و نسخه‌ی قدیمی به عنوان تاپل مرده (Dead Tuple) تو جدول می‌مونه. با گذشت زمان این تاپل‌های مرده جمع می‌شن و جدول و ایندکس‌ها حجیم ‌میشن، حجمشون زیاد می‌شه ولی داده‌ی واقعی زیاد نشده.
حالا تصور کنیم بعد از چند ماه جدولمون خیلی بزرگ‌تر از چیزی که باید باشه شده، کوئری‌ها کند شدن، حافظه بیشتر مصرف می‌شه و سیستم سنگین شده.
سؤال اینجاست:
🤔 PostgreSQL چطور این بهم‌ریختگی رو تمیز می‌کنه و فضا رو پس می‌گیره؟

اینجاست که VACUUM وارد ماجرا می‌شه. 💡
ایده‌ی VACUUM خیلی ساده‌ست:
قبل از اینکه جدول کاملاً حجم بلا استفاده و فضای مفید رو از دست بدیم، PostgreSQL با VACUUM میاد تاپل‌های مرده رو پیدا می‌کنه، علامت می‌زنه که فضاشون آزاد شده و قابل استفاده مجدد هست. یعنی فضایی که قبلاً توسط رکوردهای قدیمی اشغال شده بود رو دوباره در اختیار دیتابیس قرار می‌ده.
به همین دلیل بهش می‌گن Vacuum؛ یعنی «جاروبرقی کشیدن» دیتابیس!

چرا این کار مهمه؟ 📝
بدون VACUUM، جدول و ایندکس‌ها به مرور متورم می‌شن (Table Bloat). این تورم باعث می‌شه اسکن جدول کندتر بشه، حافظه بیشتر مصرف بشه و در نهایت عملکرد کلی سیستم افت کنه. VACUUM با تمیز کردن این فضاهای مرده، جدول رو سبک و سریع نگه می‌داره.
هرچند وظیفه‌ی اصلیش بازیابی فضاست، اما مزایای مهم دیگه‌ای هم داره:
آزاد کردن فضای اشغال‌شده توسط نسخه‌های قدیمی رکوردها
کمک به به‌روزرسانی آمار جدول برای اینکه پلنر کوئری بهتر تصمیم بگیره
حفظ سلامت دیتابیس در بلندمدت

VACUUM فقط برای تمیزکاری نیست 🚀
تقریباً همه سیستم‌های بزرگ PostgreSQL به VACUUM وابسته هستن تا بتونن سال‌ها بدون افت عملکرد کار کنن. PostgreSQL حتی یه مکانیزم هوشمند به اسم Autovacuum داره که خودش به صورت خودکار تشخیص می‌ده کی باید بیاد تمیزکاری کنه.

ارتباط VACUUM با Transactionها 🖇
‏VACUUM خیلی خوب با مکانیزم تراکنش و MVCC کار می‌کنه. وقتی تراکنش‌ها تموم بشن و دیگه به نسخه‌های قدیمی نیاز نباشه، VACUUM میاد و اون فضاها رو جمع می‌کنه. این یعنی حتی تو محیط‌های پرتراکنش هم دیتابیس می‌تونه خودش رو تمیز نگه داره.
#️⃣ #Database #Backend


🌙 CHANNEL | GROUP
‏Caching Patternها دقیقاً چی هستن؟ 🧠
وقتی صحبت از Cache میشه، معمولاً ذهنمون میره سمت ابزارهایی مثل Redis یا Memcached.
اما انتخاب تکنولوژی فقط بخشی از ماجراست.
سؤال مهم‌تر اینه که: Application قراره چطور با Cache و Database تعامل داشته باشه؟
پاسخ این سؤال رو Caching Patternها مشخص می‌کنن.

در واقع، Caching Patternها استراتژی‌هایی هستن که نحوه‌ی خواندن و نوشتن داده بین Application، Cache و Database رو تعیین می‌کنن.
انتخاب Pattern مناسب می‌تونه روی Performance، Data Consistency، Latency و حتی پیچیدگی سیستم تأثیر زیادی داشته باشه.

1.‏Cache-Aside (Lazy Loading) 📦

رایج‌ترین الگوی کشینگ.
توی این الگو Application خودش مسئول مدیریت ارتباط بین Cache و Database هست.

- ‏Read Flow
اول Cache بررسی میشه.
اگه داده وجود داشته باشه (Cache Hit)، همون برگردونده میشه.
اگه وجود نداشته باشه (Cache Miss)، داده از Database خونده میشه، داخل Cache قرار می‌گیره و سپس به کاربر برگردونده میشه.

- ‏Write Flow
داده مستقیماً در Database به‌روزرسانی میشه.
سپس مقدار مربوطه توی Cache حذف یا Invalid میشه.
اولین درخواست بعدی دوباره داده رو از Database می‌خونه و Cache رو به‌روز می‌کنه.

- مناسب برای
سیستم‌هایی که تعداد عملیات Read بسیار بیشتر از Write هست.

2.‏Read-Through 📖

توی این الگو Application مستقیماً با Database ارتباطی نداره.
اگر داده داخل Cache نباشه، خود Cache مسئول دریافت اون از Database، ذخیره کردن و برگردوندن داده به Application هست.
در نتیجه منطق بارگذاری داده از دوش Application برداشته میشه.

- مناسب برای
سیستم‌هایی که می‌خوان مدیریت Cache تا حد ممکن از منطق برنامه جدا باشه.

3.‏Write-Through ✍️

توی این الگو، هر بار که داده‌ای نوشته میشه، هم Cache و هم Database به‌صورت همزمان به‌روزرسانی میشن.
به همین دلیل، داده‌ی موجود در Cache همیشه با Database همگام باقی می‌مونه.

- مناسب برای
سیستم‌هایی که تحمل خواندن داده‌ی قدیمی (Stale Data) رو ندارن.

4.‏Write-Behind (Write-Back) 🚀

توی این Pattern، اول داده داخل Cache نوشته میشه و پاسخ بلافاصله به Application برگردونده میشه.
توی Cache توی پس‌زمینه (Asynchronously) تغییرات رو در Database ذخیره می‌کنه.
این کار باعث میشه عملیات Write با سرعت بسیار بیشتری انجام بشه.

- مناسب برای
سیستم‌های Write-Heavy که Performance عملیات نوشتن در اون‌ها اهمیت زیادی داره.

5.‏Write-Around 🔄

توی این روش، عملیات Write کاملاً Cache رو دور می‌زنه.
داده مستقیماً داخل Database ذخیره میشه و فقط زمانی وارد Cache میشه که بعداً یک عملیات Read روی اون انجام بشه.
این کار از پر شدن Cache با داده‌هایی که احتمالاً فقط یک‌بار نوشته میشن جلوگیری می‌کنه.

- مناسب برای
داده‌هایی که به‌ندرت بعد از نوشتن دوباره خوانده میشن.

‏Cache Eviction Policy 🗑
هر Cache ظرفیت محدودی داره.
وقتی این ظرفیت پر بشه، باید تصمیم بگیره کدوم داده حذف بشه تا برای داده‌های جدید جا باز بشه.
رایج‌ترین Eviction Policyها:
1. ‏TTL (Time-To-Live): هر داده مدت زمان مشخصی اعتبار داره و بعد از اون منقضی میشه.
2. ‏LRU (Least Recently Used): داده‌ای حذف میشه که مدت بیشتری استفاده نشده.
3. ‏LFU (Least Frequently Used): داده‌ای حذف میشه که کمترین تعداد دفعات استفاده رو داشته.
4. ‏FIFO (First In, First Out): قدیمی‌ترین داده، بدون توجه به میزان استفاده، حذف میشه.

جمع‌بندی 💡
هیچ Caching Patternی بهترین انتخاب برای همه‌ی سیستم‌ها نیست.
هر کدوم برای حل یک مسئله طراحی شدن و انتخاب بین اون‌ها به نیازهای سیستم، میزان Read و Write، حساسیت به Consistency و محدودیت‌های Performance بستگی داره.
درک این Patternها کمک می‌کنه قبل از انتخاب ابزارهایی مثل Redis، بدونیم دقیقاً چه رفتاری از سیستم انتظار داریم.

📚 این مطلب مروری بر رایج‌ترین Caching Patternها و Cache Eviction Policyها در طراحی سیستم‌های Backend بود.

#️⃣ #SystemDesign #Backend


🌙 CHANNEL | GROUP
1
چرا نباید از Mutableها به عنوان Default در Python استفاده کنیم؟ 🤔
توی Python، مقدارهای default آرگومان‌های تابع فقط یک‌بار و در زمان تعریف تابع ساخته میشن، نه هر بار که تابع اجرا بشه. این یعنی اگر از یک object mutable مثل list یا dict به عنوان مقدار پیش‌فرض استفاده کنیم، اون object بین تمام callهای تابع shared باقی می‌مونه.

این موضوع در ظاهر شاید مشکلی ایجاد نکنه، اما وقتی تابع چند بار صدا زده میشه، همون state قبلی حفظ میشه و روی نتیجه‌ی callهای بعدی تأثیر می‌ذاره. در واقع تابع به جای اینکه مستقل و بدون وابستگی به گذشته باشه، وارد یک وضعیت stateful ناخواسته میشه.

ریشه‌ی این رفتار به این برمی‌گرده که پایتون، default argumentها رو در زمان تعریف تابع evaluate می‌کنه، نه در زمان اجرا. برای همین object مربوط به default، ثابت می‌مونه و فقط mutate میشه، نه اینکه دوباره ساخته بشه.

به همین دلیل استفاده از mutableها به عنوان default می‌تونه باعث bugهای خیلی subtle و سخت‌دیباگ بشه. راه استاندارد اینه که به جای اون‌ها از None استفاده کنیم و داخل تابع object جدید بسازیم تا هر بار یک state کاملاً مستقل داشته باشیم.

#️⃣ #Python #Programming


🌙 CHANNEL | GROUP
ایندکس در PostgreSQL دقیقاً یعنی چی؟ 🤔
وقتی درباره‌ی performance دیتابیس صحبت می‌کنیم، یکی از اولین مفاهیمی که وسط میاد Index هست.
ایندکس رو میشه شبیه یه ساختار کمکی در نظر گرفت که به دیتابیس کمک می‌کنه به جای اینکه برای پیدا کردن یک رکورد کل جدول رو scan کنه، سریع‌تر به داده‌ی مورد نظر برسه.
اما نکته مهم اینه که Index فقط “سرعت بیشتر” نیست؛ در واقع یک trade-off بین سرعت خواندن و هزینه نوشتن + نگهداری ساختار داده هست.
هر ایندکس باعث میشه عملیات SELECT سریع‌تر بشه، اما در عوض INSERT و UPDATE و DELETE سنگین‌تر بشن، چون دیتابیس باید اون ساختار کمکی رو هم به‌روز نگه داره.

‏B-Tree Index 📚

رایج‌ترین و پیش‌فرض‌ترین نوع ایندکس در PostgreSQL، B-Tree هست.
این نوع ایندکس برای داده‌هایی طراحی شده که قابل مرتب‌سازی هستن (مثل عدد، تاریخ یا حتی string). ساختارش به شکلیه که جستجو، range query و sorting رو خیلی سریع می‌کنه.
به همین دلیل، وقتی روی یک ستون ساده مثل id یا created_at کوئری می‌زنیم، معمولاً B-Tree اولین انتخاب دیتابیس هست. چون هم برای equality search خوبه، هم برای rangeها.
اما هزینه‌اش اینه که هر تغییر روی داده باید در ساختار درختی ایندکس هم اعمال بشه، که توی سیستم های write-heavy می‌تونه فشار ایجاد کنه.

‏Hash Index 🔑

‏Hash Index بیشتر برای equality check طراحی شده؛ یعنی وقتی دقیقاً دنبال یک مقدار مشخص هستیم.
در این حالت، به جای ساختار درختی، از یک hash function استفاده میشه تا مستقیم به موقعیت داده برسیم.
اما محدودیت مهمش اینه که برای range query یا مرتب‌سازی کاربرد نداره، و به همین دلیل در عمل کمتر از B-Tree استفاده میشه. حتی در PostgreSQL هم تا مدت‌ها محدودیت‌های خاص خودش رو داشت و هنوز هم use-caseهای محدودی داره.

‏GIN Index (Generalized Inverted Index) 🔍
‏GIN یکی از جذاب‌ترین ایندکس‌های PostgreSQL هست، مخصوصاً برای داده‌های غیرساخت‌یافته.
این نوع ایندکس بیشتر برای حالت‌هایی استفاده میشه که یک رکورد شامل چندین value هست؛ مثل JSONB، arrayها یا full-text search.
به جای اینکه کل row رو index کنه، GIN بر اساس “اجزای داخلی داده” عمل می‌کنه. یعنی هر token یا key رو جداگانه index می‌کنه و همین باعث میشه برای search روی داده‌های پیچیده خیلی سریع باشه.
اما هزینه‌اش اینه که ساخت و update کردنش نسبت به B-Tree سنگین‌تره، چون ساختار داخلی پیچیده‌تری داره.

‏GiST Index 🌳
‏GiST یک framework برای ساخت ایندکس‌های سفارشی‌تره، نه یک ساختار ثابت.
‏PostgreSQL ازش برای داده‌هایی استفاده می‌کنه که ساختار غیرمعمول دارن؛ مثل geometry data، range types یا حتی full-text search در برخی سناریوها.
ویژگی مهم GiST اینه که انعطاف پذیر هست، یعنی می‌تونه برای انواع مختلف داده گسترش داده بشه، اما در عوض دقت و performance اون کاملاً به implementation و نوع داده بستگی داره.

‏BRIN Index 📦
‏BRIN برای جدول‌های خیلی بزرگ طراحی شده؛ جایی که داده‌ها به صورت طبیعی روی disk مرتب هستن (مثلاً بر اساس زمان).
به جای اینکه تک‌تک rowها رو index کنه، BRIN فقط summary از blockهای داده نگه می‌داره. همین باعث میشه بسیار سبک و کم‌هزینه باشه.
اما trade-off اینه که دقتش کمتره و بیشتر برای queryهای range روی داده‌های بزرگ و append-only مناسب هست، نه جستجوی دقیق.

جمع‌بندی 🧩
‏Indexها در PostgreSQL فقط ابزار سرعت نیستن؛ در واقع یک انتخاب طراحی هستن.
هر نوع ایندکس یک trade-off مشخص داره:
‏B-Tree برای general use
‏Hash برای equality ساده
‏GIN برای داده‌های پیچیده و full-text
‏GiST برای سناریوهای قابل توسعه و خاص
‏BRIN برای دیتاست‌های خیلی بزرگ و ordered
درک درست این تفاوت‌ها باعث میشه قبل از اینکه “ایندکس بذاریم”، دقیق بدونیم داریم چه هزینه‌ای رو از سیستم می‌گیریم و چه چیزی به دست میاریم.

#️⃣ #Backend #Database


🌙 CHANNEL | GROUP
🔥1
‏Idempotency در API یعنی چی؟ 🤔
اگر بخوایم خیلی ساده شروع کنیم، Idempotency یعنی:
اگر یک درخواست رو چند بار پشت سر هم اجرا کنیم، نتیجه نهایی سیستم تغییر نکنه.

یعنی مهم نیست این عملیات یک بار اجرا بشه یا ده بار؛ در نهایت باید سیستم به یک state یکسان برسه.
این مفهوم بیشتر از اینکه “ویژگی خوشگل API” باشه، یک ضرورت در سیستم‌های واقعیه؛ مخصوصاً جایی که شبکه unreliable هست و retry اتفاق می‌افته.

مشکل از کجا شروع میشه؟ 🧠
در دنیای واقعی، درخواست‌ها همیشه “یک بار و دقیق” اجرا نمی‌شن.
ممکنه:
درخواست به سرور برسه ولی response برنگرده
کلاینت retry کنه بدون اینکه بدونه عملیات قبلی موفق بوده یا نه
یا حتی شبکه وسط کار قطع بشه
حالا اگر API ما idempotent نباشه، هر retry می‌تونه یک side effect جدید بسازه. مثلاً یک پرداخت دوبار انجام بشه، یا یک رکورد چند بار ایجاد بشه.

‏Idempotency چطور این مشکل رو حل می‌کنه؟ ⚙️
ایده‌ی اصلی اینه که سرور باید بتونه تشخیص بده این درخواست قبلاً انجام شده یا نه.
اگر قبلاً انجام شده باشه، به جای اجرای دوباره‌ی logic، همون نتیجه قبلی رو برمی‌گردونه.
برای همین خیلی از سیستم‌ها از چیزی مثل idempotency key استفاده می‌کنن؛ یک شناسه که کلاینت همراه request می‌فرسته تا سرور بفهمه این عملیات تکراریه یا جدید.

همه‌ی عملیات‌ها idempotent نیستن 💡
بعضی عملیات‌ها ذاتاً idempotent هستن، بعضی‌ها نه.
مثلاً عملیات‌هایی مثل GET معمولاً idempotent هستن چون فقط داده رو می‌خونن.
اما عملیات‌هایی مثل POST به صورت پیش‌فرض idempotent نیستن، چون هر بار می‌تونن یک resource جدید بسازن.
برای همین در API design باید حواسمون باشه کجا داریم state رو تغییر می‌دیم و چطور از duplicate execution جلوگیری کنیم.

چرا این موضوع مهمه؟ 🚧
‏Idempotency یکی از اون مفاهیمیه که شاید اولش خیلی ساده به نظر بیاد، اما توی سیستم‌های واقعی حیاتی میشه.
چون بدونش:
‏retry logic خطرناک میشه
پرداخت‌ها ممکنه دوبار انجام بشن
داده‌ها duplicate میشن
و debugging سیستم خیلی سخت‌تر میشه
در واقع هر جا پای شبکه وسیستم های توزیع شده وسط باشه، idempotency تبدیل به یک پیش نیاز جدی میشه، نه یک انتخاب.

جمع بندی ✍️
‏Idempotency یعنی اینکه تکرار یک operation، نتیجه‌ی نهایی سیستم رو تغییر نده.
این مفهوم کمک می‌کنه سیستم‌ها در برابر retry، خطاهای شبکه و عدم قطعیت در اجرای requestها مقاوم‌تر بشن.


#️⃣ #Backend #SystemDesign


🌙 CHANNEL | GROUP
🔥1
چرا در سیستم‌های توزیع‌شده نمی‌تونیم همه چیز رو همزمان داشته باشیم؟ 🤔
وقتی یک سیستم کوچیک داریم، معمولاً همه‌چیز ساده به نظر می‌رسه. یک Application داریم، یک Database و همه‌ی درخواست‌ها هم به همان نقطه ارسال میشن. اما وقتی سیستم بزرگ‌تر میشه و برای مقیاس‌پذیری از چندین سرور، چندین دیتابیس یا Regionهای مختلف استفاده می‌کنیم، یه مشکل اساسی به وجود میاد:
چطور مطمئن بشیم همه‌ی بخش‌های سیستم همیشه با هم هماهنگ هستن؟
اینجاست که مفهومی به اسم CAP Theorem وارد میشه.

‏CAP Theorem دقیقاً چی میگه؟ 🧠
‏CAP Theorem که توسط Eric Brewer مطرح شد، بیان می‌کنه که یک سیستم توزیع‌شده نمی‌تونه به‌صورت همزمان هر سه ویژگی زیر رو به طور کامل تضمین کنه:
1. ‏Consistency
2. ‏Availability
3. ‏Partition Tolerance
اما نکته مهم اینه که این سه مفهوم دقیقاً چه معنی‌ای دارن؟

‏Consistency یعنی چی؟ 📚
‏Consistency یعنی هر درخواست Read، جدیدترین مقدار نوشته‌شده را دریافت کنه.
فرض کنید یه کاربر اطلاعات حسابش رو توی یه Node تغییر می‌ده.
اگر سیستم Consistent باشه، وقتی همون لحظه از Node دیگری اطلاعات رو بخوانیم، باید همون مقدار جدید رو ببینیم.
در واقع، تمام Nodeها باید یک view یکسان از داده داشته باشند.

‏Availability یعنی چی؟ ⚡️
‏Availability یعنی سیستم حتی توی شرایط مشکل هم بتونه پاسخ درخواست‌ها رو بده. یعنی اگر یک درخواست به سیستم ارسال شد، نباید صرفاً به خاطر مشکل داخلی یا از دسترس خارج شدن بخشی از سیستم، هیچ پاسخی دریافت نکنه.
توی سیستم‌هایی که Availability اولویت بالایی داره، معمولاً ترجیح داده می‌شه سیستم پاسخ بده، حتی اگر داده‌ی برگشتی آخرین نسخه نباشه.

‏Partition Tolerance یعنی چی؟ 🌐
‏Partition یعنی ارتباط بین بخش‌های مختلف یک سیستم توزیع‌شده قطع بشه.
مثلاً فرض کنید یک دیتابیس توی دو ریجن مختلف دارید:
‏Region A
‏Region B
اگه ارتباط شبکه بین این دو قطع بشه، هر کدوم از اونها دیگر نمی‌تونن تغییرات دیگری رو دریافت کنن. Partition Tolerance یعنی سیستم بتونه با وجود این مشکل شبکه همچنان کار کنه.

چرا نمی‌توانیم هر سه تا رو داشته باشیم؟ 🤔
مشکل اصلی زمانی اتفاق میوفته که Partition وارد بحث میشه.
توی یه سیستم توزیع‌شده، قطع شدن شبکه یک اتفاق غیرممکن نیست؛ بلکه چیزیه که باید براش طراحی کنیم.
حالا فرض کن ارتباط بین دو Node قطع شده:
یه کاربر داده‌ای رو در Node اول تغییر می‌ده.
‏Node دوم هنوز از این تغییر خبر نداره.
حالا یه درخواست Read به Node دوم می‌رسه.
اینجا سیستم باید یک انتخاب انجام بده:
اگر مقدار جدید را برنگردونه، Consistency رو از دست داده.
گر درخواست رو رد کنه تا مطمئن شود داده درسته، Availability رو از دست داده.
و این همون trade-off اصلی CAP است.

سیستم‌های CP و AP 🧩
به همین دلیل، سیستم‌های توزیع‌شده معمولاً یکی از این دو مسیر را انتخاب می‌کنند.
1- ‏CP (Consistency + Partition Tolerance)
توی این مدل، سیستم ترجیح می‌دهه داده همیشه درست باشه، حتی اگه مجبور بشه بعضی درخواست‌ها رو رد کنه یا منتظر بمونه. این مدل برای سیستم‌هایی مناسبه که صحیح و معتبر بودن داده اهمیت بالایی داره.
مثلاً سیستم‌های مالی که نمی‌تونن اطلاعات اشتباه برگردونن.

2- ‏AP (Availability + Partition Tolerance)
توی این مدل، سیستم ترجیح می‌دهه همیشه پاسخ بده، حتی اگر بعضی Nodeها موقتاً داده‌ی قدیمی داشته باشن. بعداً وقتی ارتباط برقرار شد، داده‌ها با هم هماهنگ می‌شن.
این مدل توی سیستم‌هایی که Availability اهمیت زیادی داره، مثل بعضی شبکه‌های اجتماعی یا سیستم‌های توزیع‌شده بزرگ، استفاده می‌شه.

پس CA چی میشه؟ 🤔
از نظر تئوری، یه سیستم می‌تونه Consistency و Availability رو همزمان داشته باشه. اما فقط وقتی که Partition اتفاق نیوفته. توی دنیای واقعی، مخصوصاً در سیستم‌های توزیع‌شده بزرگ، Partition اجتناب‌ناپذیره.
برای همینه که در عمل بیشتر درباره‌ی انتخاب بین CP و AP صحبت می‌کنیم.

یه سوءتفاهم رایج درباره CAP ⚠️
این برداشت که "یه سیستم فقط میتونه دو مورد از این سه مورد رو داشته باشه"، یه برداشت ساده شده هست و خیلی دقیق مفهوم رو بیان نمیکنه. درواقع ایده اصلی درمورد تضمین کردن صحبت میکنه: "وقتی Partition داشته باشیم، دیگه نمیتونیم همزمان Consistency و Availability رو هم کامل تضمین کنیم."

جمع‌بندی 💡
‏CAP Theorem یکی از پایه‌ای‌ترین مفاهیم توی طراحی سیستم‌های توزیع‌شده هست.
به ما یاد می‌ده که توی مقیاس بالا، بعضی تصمیم‌ها دیگه فقط تصمیم فنی نیستند؛ بلکه انتخاب بین trade-offهای مختلف هستند.
هیچ سیستم توزیع‌شده‌ای “بهترین حالت مطلق” نداره.
طراحی خوب یعنی بدونیم کجا Consistency مهم‌تره، کجا Availability، و بر اساس نیاز واقعی سیستم تصمیم بگیریم.

#️⃣ #backend #system_design


🌙 CHANNEL | GROUP
جنایات جنگی توی برنامه نویسی، اشتباه ترین کارای ممکن به فان ترین شکل ممکن.

یه جاهایی انقدر ترسناک میشه که آدم بجای خندیدن فقط خیره میشه به صفحه


https://youtube.com/playlist?list=PLr1FHxz1zgsFdBXnE-uQ9_29GVNtPdqj5&si=JqgG_sGEIqQKKYRM

#️⃣ #reference


🌙 CHANNEL | GROUP