<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های حمزه قائم پناه</title><link>https://virgool.ir/feed/@hamze</link><description>مهندس نرم‌افزار و عاشق توسعه فردی - تکنیکال لید - اکس هم بنیان‌گذار و مدیرفنی و پرداکت استارتاپ کشمون</description><language>fa</language><pubDate>2026-11-10 23:02:57</pubDate><image><url>https://files.virgool.ir/upload/users/223711/avatar/CciHnz.jpg</url><title>حمزه قائم پناه</title><link>https://virgool.ir/@hamze</link></image><item><title>Designing Data-Intensive Application Book</title><link>https://virgool.ir/@hamze/designing-data-intensive-application-xkgutv24rmyw</link><description>سلام :) این خلاصه کتاب Designing Data-Intensive Application هست و خب چون خیلی شلوغم نرسیدم دوباره بخونمش و ویرایش کنم و چون سال‌ها درفت مونده بود، گفتم پابلیش کنمش با این حال هم شاید به کسی کمک کرد که کتاب رو بتونه مرور کنه و خلاصه‌ای فارسی از کتاب موجود باشه. اگر پیشنهاد ویرایش هم داشتین قطعا استقبال می‌کنم.Reliabilityدر نرم‌افزار معمولا انتظارات ما از یک سیستم reliable این‌هاست:برنامه، عملکردی رو داشته باشه که کاربر انتظار داره.اگر کاربر اشتباهی انجام بده و یا نرم‌افزار در مسیر غیر منتظره‌ای عمل کنه، سیستم می‌تونه تحملش کنه.به اندازه کافی برای یوزکیس‌های ضروری زیر بار و حجم مورد انتظار، خوب عمل می‌کنه.سیستم از هرگونه دسترسی و سواستفاده غیر مجاز، جلوگیری می‌کنه.به طور خلاصه یعنی: ادامه به کار کردن به طور درست، حتی وقتی چیزها خوب پیش نمی‌رن.چیزایی که درست پیش نمی‌رن بهشون می‌گیم faults و به سیستمی که بتونه این خطاها رو پیش‌بینی کنه و باهاشون کنار بیاد رو می‌گیم fault-tolerant یا resilient. گاهی وقتا ما به صورت عمدی در سیستم خطا ایجاد می‌کنیم که مطمئن بشیم در این زمان سیستم به درستی از پسش برمیاد، Netflix Chaos Monkey یک نمونه از این روش هست.خطاهای سخت‌افزاریخطاهایی مثل کرش کردن هارد دیسک، مشکل خوردن رم، قطع شدن برق سرور یا قطعی شبکه و... مثلا یک هارد دیسک عمری بین ۱۰ تا ۵۰ سال داره، پس یک دیتاسنتر با ۱۰ هزار دیسک، معمولا روزی یکی از دیسک‌هاش می‌میره. برای این مشکلات راهکار وجود داره، میشه دیسک‌ اضافه گذاشت و RAID کرد، برای دیتاسنتر ژنراتور برق گذاشت و... معمولا در بسیاری از موارد افزونگی (redundancy) قطعات سخت‌افزار مشکل رو حل می‌کنه و تا حد قابل‌قبول سیستم رو از این جنبه قابل اتکا می‌کنه.خطاهای نرم‌افزاریدر خطاهای سخت‌افزاری معمولا همبستگی (correlation) وجود نداره، مثلا وقتی هارد یک سرور مشکل می‌خوره، برای هارد بقیه سرورها مشکلی پیش نمیاد. اما در خطاهای نرم‌افزاری این همبستگی وجود داره. راهکار سریعی برای مشکلات نرم‌افزاری نیست اما چیزهای کوچیکی هست که کمک می‌کنه: تست کردن، ایزوله‌سازی پروسس‌ها، اجازه کرش کردن پروسس‌ها و رستارت شدن، مانیتورینگ، تحلیل عملکرد سیستم در پرداکشن. مثلا اگر سیستم صف پیام‌ (message queue) باید پیام‌های ورودی و خروجیش برابر باشه، می‌تونه به طور مرتب این رو چک کنه و اگر اختلافی وجود داشت، فورا هشدار بده.خطاهای انسانیانسان‌ها سیستم‌های نرم‌افزاری رو می‌سازن و کسی که سیستم‌ها رو درحال اجرا نگه میداره هم انسانه و حتی اگر قصدشون هم خوب باشه، آدمیزاد به خطاپذیر بودن شناخته میشه. یک بررسی نشون داده که ارورهای کانفیگوریشن که توسط اپراتور انجام میشه، دلیل اصلی از دسترس خارج شدن سرویس‌هاست. این رویکردها برای جلوگیری از خطاهای انسانی پیشنهاد میشه:سیستم طوری طراحی بشه که فرصت بروز خطا حداقل بشه. به عنوان مثال، طراحی خوب abstraction, API ها و رابط مدیریتی.محیطی که افراد بیشترین اشتباهات می‌کنن رو از محیطی که می‌تونه باعث شکست بشه جدا کنین. به طور خاص یک محیط غیر از پرداکشن داشته باشین که بشه توش به طور امن همه تست‌ها رو با دیتای واقعی انجام داد.در تمام سطح‌ها تست انجام بدید، از unit test تا تست integration کل سیستم و تست‌های دستی.امکان بازیابی سریع ارورهای انسانی رو ایجاد کنین. به عنوان مثال سریع بشه تغییرات تنظیمات رو roll back کرد. کد جدید رو به تدریج دپلوی کنین و ابزارهایی تهیه کنین که داده‌ها رو دوباره حساب کنن.مانیتورینگ با جزئیات و واضح راه بندازین، مثل متریک‌های عملکردی و نرخ خطاها. بهش telemetry می‌گیم. مانیتوریگ می‌تونه در فهم سریع مشکل کمک کنه.آموزش‌ها و تمرین‌های مدیریتی خوب پیاده سازی کنین - جنبه‌های پیچیده و مهم و فراتر از حوزه این کتاب.Scalabilityوقتی یک سیستم امروز reliable هست، معنیش این نیست که فردا هم وقتی کاربرهای همزمانش از ۱۰ هزار به ۱۰۰ هزار رسید، هنوز reliable باشه. scalabilty یعنی توانایی سازگاری با افزایش لود. معنیش اینه که بتونی به همچین سوالاتی پاسخ بدی: اگر سیستم در مسیر مشخصی رشد کرد، آپشن ما برای سازگاری با این رشد چیه؟ چطور می‌تونیم منابع رو برای مدیریت بار اضافی، اضافه کنیم؟تعریف Load: ما به یک تعریف مختصر برای لود در سیستم‌مون نیاز داریم. لود می‌تونه با چندتا عدد بیان بشه که بهش load parameters می‌گیم. می‌تونه تعداد کاربر همزمان باشه، نسبت نوشتن به خوندن دیتابیس باشه، تعداد درخواست در ثانیه به سرور باش، hit rate کش باشه و ...تعریف Performance: برای فهمیدن تاثیر لود بر عملکرد ما نیاز به اعداد مرتبط با عملکرد داریم. این می‌تونه تعداد رکوردی که در ثانیه می‌تونن پردازش بشن در سیستمی مثل Hadoop باشه. می‌تونیم برای این منظور میانه (median - 50th percentile) رو در نظر بگیریم و یا ۹۵٪ یا ۹۹٪ و یا ۹۹.۹٪ رو در نظر بگیریم.راهکارهای وفق پیدا کردن با لود: معمولا دو راهکار داریم. scaling up یا همون رشد عمودی (vertical scaling) یعنی افزایش قدرت ماشین و scaling out یا رشد افقی (horizontal scaling) که یعنی پخش کردن لود میان چند ماشین کوچک‌تر.Maintainabilityاین رو می‌دونیم که بیشتر هزینه نرم‌افزار صرف توسعه‌اش نمیشه، بلکه صرف نگهداری اون - حل باگ‌ها، عملیاتی نگه‌داشتن سیستم، بررسی شکست‌ها، سازگاری با پلتفرم‌های جدید، تغییرات برای یوزکیس‌های جدید، بازپرداخت بدهی‌های فنی و افزودن فیچرهای جدید میشه. در نتیجه باید سیستم طوری طراحی بشه که قابل نگهداری باشه وگرنه تبدیل به یک legacy system میشه که کار باهاش دردناک خواهد بود. به این سه اصل در طراحی سیستم نرم‌افزاری باید توجه داشت:اصل Operability: کار رو برای تیم‌های عملیاتی آسون کنین تا با سیستم روون کار کنن. داشتن مانیتورینگ خوب، پشتیبانی خوب از اتومیشن و اتصال به ابزارهای استاندارد، عدم وابستگی به یک ماشین خاص، داشتن داک خوب، دسترسی‌ها مدیریتی برای وضعیت سیستم و بازنویسی رفتار پیش‌فرض.اصل Simplicity: فهم سیستم رو با حذف هر پیچیدگی ممکن از سیستم برای مهندسان جدید آسون کنین. به کار نبردن کاپلینگ ماژول‌ها، وابستگی‌های درهم تنیده، نام‌ها و اصطلاحات غیر استاندارد، کارهای ابداعی برای افزایش عملکرد و... یکی از بهترین راه‌های خذف پیچیدگی، استفاده از abstraction هست.اصل Evolvability: کار ایجاد تغییرات آینده روی سیستم رو برای مهندسان آسون کنین، با یوزکیس‌های پیش‌بینی نشده که به دلیل تغییر نیازمندی‌ها به وجود میان، سازگارش کنین که این مورد با این اسامی هم می‌گیم: extensibility, modifiability, or plasticityData Models and Query LanguagesRelational Model Versus Document Modelبحث اصلی درباره دیتا مدل‌های بر مبنای داکیومنت، انعطاف‌پذیری روی الگو (schema)، عملکرد بهتر به دلیل محلی بودن (locality of data) و در بعضی موارد نزدیک‌تر بودن ساختار با اون چیزی که برنامه استفاده می‌کنه، هست. نمیشه به طور کلی گفت که کد مدل داده منجربه کد نویسی ساده‌تر میشه. بستگی به نوع رابطه‌هایی داره که بین موارد وجود داره. برای داده‌های خیلی به‌هم پیوسته، مدل داکیومنتی نامناسبه و مدل رابطه‌ای قابل قبوله و مدل‌های نموداری (graph) طبیعی‌ترین هستن.نوع schema-on-read: ساختار دیتا ضمنی تعریف شده و تنها زمانی تفسیر میشه که دیتا خونده میشه.نوع schema-on-write: روش سنتی دیتابیس‌های رابطه‌ای، الگو صریح تعریف شده و دیتابیس مطمئن میشه همه دیتاهایی که می‌خوان نوشته بشن در اون قالب هستن.ساختار با schema-on-read برای زمانی مفیده که اشیای با ساختارهای متفاوتی داریم که نمی‌خوایم برای هرکدوم یک جدول جداگانه داشته باشیم و یا زمانی که ساختار دیتا مرتبط با یک منبع بیرونیه و ممکنه تغییر کنه.لوکالیتی به این صورته که لازم نیست به چندین جدول با چندین جستجو ایندکس دسترسی پیدا کنی که نیاز به جستجو دیسک و زمان بیشتر داشته باشه. اما از طرفی داکیومنت هم موقع لود همه‌اش لود میشه و اگر بخشی ازش رو نیاز داشته باشی و یا بخشی رو بخوای آپدیت کنی، نمیشه. برای همین توصیه میشه داکیومنت‌ها رو تا حد امکان کوچیک نگهداریم.Query Languages for Dataنوع imperative language: در روش‌های قدیمی باید مشابه حلقه تمام عناصر رو چک می‌کردیم و اونی که می‌خواستیم رو پیدا می‌کردیم. یعنی ما روش انجام دادن رو باید مشخص می‌کردیم.نوع declarative query language: این روش مشابه SQL فقط الگو رو مشخص می‌کنه و با اینکه اون پشت چطور انجام میشه کاری نداره.نوع MapReduce programming model: این روش نه declarative و نه کامل imperative هست، بلکه یک چیزی بین این دو هست. منطقش با یک تیکه کد بیان میشه و به طور مکرر به‌وسیله چارچوب پردازش، فراخونی میشه. این کار بر اساس نقشه (map - یکسری تغییرات یا دیتای جدید ایجاد کنی) و کاهش (reduce - فیلتر و عملیات روی خروجیت انجام بدی) که در خیلی از زبان‌های برنامه‌نویسی وجود داره، انجام میشه. مپ‌ردیوس فقط روی دیتایی که بهش به عنوان ورودی پاس داده شده عمل می‌کنه و نمی‌تونه عملیات دیتابیس بیشتری انجام بده. اما اینکه بخوای دو تابع جاوا اسکریپتی توی کدت بنویسی کار رو از اینکه بخوای یک کوئری بنویسی سخت تر می‌کنه، این بود که مونگو aggregation pipeline رو اضافه کرد.Graph-Like Data Modelsاگر روابط چند-به-چند از روابط اصلی برنامه‌تون هست، گفتیم که مدل داکیومنتی مناسبتون احتمالا نیست. اگر روابط بین دیتاها پیچیده‌تر بشن، طبیعی‌تره که مدل‌سازی داده‌ها رو گراف کنیم. گراف دو بخش داره، نودها (vertices - nodes - entities) و لبه‌ها (edges - relationships - arcs) انواع مختلفی از دیتاها می‌تونن به صورت گراف مدل‌سازی بشن. سوشال‌ها: نودها افراد و یال‌ها نمایانگر افرادی که هم رو می‌شناسن. گراف وب: نودها صفحات وب و یال‌ها لینک‌های به صفحات دیگر. می‌تونه این گراف‌ها اینقدر همگن نباشه و اشیای متفاوتی رو نگه داره. مثلا فیسبوک یک گراف بزرگ داره با انواع مختلف نودها مثل افراد، لوکیشن‌ها، ایونت‌ها، کامنت‌ها و... هر لبه یا یال شامل شناسه نودی که ازش شروع شده و شناسه نودی که بهش ختم شده، یک لیبل که نوع رابطه بین دو نود رو نشون میده و یک کالکشن از جزئیات مرتبطش هست. گراف‌ها برای تکامل‌پذیری عالی هستن، وقتی یک امکان جدید به برنامه اضافه می‌کنی، یک گراف می‌تونه به راحتی گسترش پیدا کنه تا تغییرات رو پوشش بده.The Cypher Query Languageسایفر یک declarative query language است که برای Neo4j تولید شده. نمونه کد ایجاد نود و یال:CREATE&#xA;    (NAmerica:Location {name:&#39;North America&#39;, type:&#39;continent&#39;}),&#xA;    (USA:Location           {name:&#39;United States&#39;,   type:&#39;country&#39;   }),&#xA;    (Idaho:Location         {name:&#39;Idaho&#39;,                 type:&#39;state&#39;       }),&#xA;    (Lucy:Person              {name:&#39;Lucy&#39; }),&#xA;    (Idaho)  -[:WITHIN]-&gt;     (USA)    -[:WITHIN]-&gt;  (NAmerica),&#xA;    (Lucy)    -[:BORN_IN]-&gt;  (Idaho)حالا فرض کن دنبال کسایی بخوایم بگردیم که از آمریکا به اروپا مهاجرت کردن:MATCH&#xA;    (person) -[:BORN_IN]-&gt; () -[:WITHIN*0..]-&gt; (us:Location {name:&#39;United States&#39;}),&#xA;    (person) -[:LIVES_IN]-&gt; () -[:WITHIN*0..]-&gt; (eu:Location {name:&#39;Europe&#39;})&#xA;RETURN person.nameGraph Queries in SQLما می‌تونیم دیتای گراف رو در دیتابیس‌ها رابطه‌ای هم قرار بدیم، اما سوال اینجاست که با SQL می‌تونیم روش کوئری بزنیم؟ جواب بله هست اما کار سختیه، باید با جوین پیش‌بریم و تعداد جوین‌ها ممکنه ثابت نباشه. برای همین بود که چیزی به نام recursive common table expressions ارائه شد. همین کد ۴ خط بالا در ۲۹ خط با این روش پیاده‌سازی میشه.Triple-Stores and SPARQLاین مدل معادل property graph model هست، در این مدل همه اطلاعات در سه بخش بیان میشن: فاعل، بیان‌کننده و مفعول (subject, predicate, object) به عنوان مثال: حمزه، دوست دارد، سیب. به روش Turtle format میشه چندین مورد رو در یک خط بیان کرد:@prefix : &lt;urn:example:&gt;.&#xA;_:lucy           a :Person;     :name &#34;Lucy&#34;                   :bornIn _:idaho.&#xA;_:idaho        a :Location;  :name &#34;Idaho&#34;                  :type &#34;state&#34;     :within _:usa.&#xA;_:usa            a :Location;  :name &#34;United States&#34;   :type &#34;country&#34; :within _:namerica.&#xA;_:namerica  a :Location;  :name &#34;North America&#34; :type &#34;continent&#34;.The RDF data modelزبان Turtle که مثالش رو آوردیم، فرمت قابل خوندن انسان از چارچوب شرح منابع (Resource Description Framework) هست. گاهی هم RDF در فرمت XML نوشته میشه. این چارچوب برای تبادل داده در سراسر اینترنت طراحی شده.The SPARQL query languageزبان SPARQL برای استفاده از دیتا مدل RDF در triple-stores هاست. نمونه کوئری مشابه:PREFIX : &lt;urn:example:&gt;&#xA;SELECT ?personName WHERE {&#xA;    ?person :name ?personName.&#xA;    ?person :bornIn / :within* / :name &#34;United States&#34;.&#xA;    ?person :livesIn / :within* / :name &#34;Europe&#34;.&#xA;}The Foundation: Datalogزبان Datalog از SPARQL و یا Cypher قدیمی‌تره و خیلی از این نوع زبان‌ها بر پایه اون نوشته شدن. مشابه triple هست با این تفاوت: predicate(subject, object). این روش نیاز به تفکر متفاوتی نسبت به سایر زبان کوئری‌هایی که گفتیم داره اما روش بسیار قدرتمندی هست چون می‌تونه کوئری‌های متفاوت رو باهم ترکیب و دوباره استفاده کنه. این باعث میشه که با دیتاهای پیچیده همخوانی بیشتری داشته باشه.Storage and Retrievalدر این بخش از نگاه دیتابیس‌ها می‌خوایم ببینیم چطور دیتایی که بهشون می‌دیم رو ذخیره می‌کنن و چطور دوباره وقتی می‌خوایم‌شون، پیدا می‌کنن‌شون.و Index: نگهداری یک‌سری متادیتای اضافی که به عنوان نشانگر عمل می‌کنه و کمک می‌کنه تا داده‌های مورد نظر رو پیدا کنیم. اگر بخوایم یک داده رو به روش‌های مختلف پیدا کنیم، به چندین index متفاوت روی بخش‌های مختلف داده نیاز داریم. اضافه کردن index فرآیند نوشتن رو کند‌تر می‌کنه چون هربار که دیتای جدیدی می‌خواد نوشته بشه، باید تمام دیتاهایی که نوشته شدن، ایندکس‌شون به‌روز بشه.Hash Indexبا ایندکس‌های دیتا key-value شروع کنیم. این نوع دیتا شبیه دیکشنری هست که به صورت hash map (hash table) پیاده‌سازی شده. ساده‌ترین استراتژی ایندکس کردن اینه که توی مموری هش‌مپ رو نگه داریم که هر کلید مپ شده به محل قرار گیری دیتا روی هارد دیسک. برای جلوگیری از اینکه لاگ فایل روی دیسک خیلی بزرگ بشه و کمبود فضا ایجاد نشه، میتونیم فرآیند فشرده سازی (compaction) انجام بدیم، چون ما هیچوقت فایل لاگ رو آپدیت نمی‌کنیم. در یک فایل جدید، کلیدهای تکراری رو حذف می‌کنیم و فقط آخرین به‌روزرسانی از هر کلید رو ذخیره می‌کنیم. وقتی این فرآیند تموم شد از سگمنت جدید برای خوندن استفاده می‌کنیم و سگمنت قدیمی رو با خیال راحت پاک می‌کنیم. خوندن به این صورت خواهد بود که اول میریم سراغ جدیدترین نسخه hash map و اگر اونجا کلیدمون نبود به ترتیب می‌ریم سراغ نسخه‌های قدیمی‌تر.SSTables and LSM-Treesما می‌تونیم بیایم و اجباری کنیم که ترتیب key-value ها براساس key مرتب بشه. به این فرمت Sorted String Table می‌گیم. همینطور اجبار می‌کنیم که هر کلید فقط یکبار در سگمنت مرج‌شده وجود داشته باشه.الگوریتم Log-Structured Merge-Tree (LSM-tree) الگوریتمی هست که در levelDB و RocksDB استفاده میشه. storage engine مشابه‌ای در Cassandra و HBase استفاده میشه که هردو الهام گرفته از Google Bigtable paper هستن. به استوریج انجین‌هایی که بر اساس اصل ادغام و فشرده‌سازی فایل‌های مرتب شده کار می‌کنن، اغلب LSM storage engine می‌گن. Lucene که یک ایندکسینگ انجین برای full-text search هست که توسط Elasticsearch و Solar استفاده میشه، از روش مشابه‌ای برای ذخیره دیکشنری خود استفاده می‌کنن که البته خیلی روش پیچیده‌تری از key-value هست. دو نوع compaction متداول داریم:فشرده‌سازی leveled: داده در سطوح مختلفی نگهداری میشه که هر سطح شامل SSTable هاست. معمولا طوری سازماندهی میشن که لایه پایین‌تر شامل دیتاهای کوچک‌تر و جدیدتر باشه و لایه‌های بالاتر شامل دیتاهای بزرگتر و قدیمی‌تر باشه.فشرده‌سازی size-tiered: در این روش انتخاب برای فشرده‌سازی SSTable براساس سایزشونه. کوچکترین انتخاب میشه و با بزرگ‌ترین مرج میشه.B-Treeایندکس متداول اکثر دیتابیساس، مشابه SSTables جفت key-value مرتب شده بر اساس ایندکس رو نگه می‌داره. اما شیوه کارش اینطوره که دیتابیس رو به بلاک‌ها / پیج‌هایی با سایز مشخص می‌شکنه، (4KB یا بیشتر) و در هر زمان در یک پیج می‌نویسه یا می‌خونه. هر پیج می‌تونه به یک پیج دیگه رفرنس بده، البته داخل دیسک بجای مموری. وقتی در یک پیج جا برای اضافه کردن نباشه، اون پیج به دو پیج نیمه پر تقسیم میشه. B-Tree با n کلید، همیشه عمق O(log n) داره.فرض کنین وسط دو نیم کردن پیج‌ها دیتابیس کرش کنه و رفرنس والدشون آپدیت نشده باشه، اونوقته که دو تا پیج یتیم خواهیم داشت که رفرنسی بهشون نداریم، برای این مشکل از دیتا استراکچر دیکه‌ای روی دیسک استفاده می‌کنیم به نام write-ahead log یا redo log که به صورت append-only file هست که هر تغییرات B-Tree قبل از انجام درون این فایل نوشته میشه تا در صورت کرش براون اساس بازسازی بشه. همینطور برای حل مشکل همزمانی از latches یا همون lightweight locs استفاده میشه.OLAP (Online Analytical Processing): Data WarehouseOLTP (Online Transaction Processing): It supports day-to-day business operations.وقتی واقعا دیتا زیاد باشه، انجام کارهای تحلیلی می‌تونه سیستم اصلی که داره به مشتری سرویس میده رو با اختلال روبرو بکنه. برا همین یک نسخه readonly از اطلاعات رو در data warehouse نگهداری می‌کنیم که قابلیت کوئری‌های پیچیده رو داره. به طور مثال Microsoft SQL Server and SAP HANADistributed Dataدلایلی که از دیتابیس‌های غیر مترکز استفاده کنیم ایناس:پایداری بیشتر به دلیل اینکه ترافیک رو بین اون‌ها می‌تونیم بالانس کنیم.در دسترس بودن و خطاناپذیری بیشتر به دلیل اینکه حتی اگر کل دیتاسنتر هم بیاد پایین، یکی دیگه هست که جایگزینش بشه.تاخیر کمتر به این دلیل که در سراسر دنیا که کاربر داریم، می‌تونه از نزدیک‌ترین دیتاسنتر جغرافیایی، دیتا رو بگیره و تاخیر شبکه براش کاهش پیدا کنه.ما می‌تونیم به صورت عمودی رشد بدیم، یعنی CPU و RAM رو قدرتش رو دو برابر کنیم که این هزینه‌اش بیشتر از استفاده از دو سیستم مجزاست و هم از طرفی لزوما خروجی دو برابر نمیشه. اگرم بیایم از دو سیستم مجزا و یک دیسک اشتراکی استفاده کنیم دردسر محدودیت‌های قفل دسترسی هر سیستم برای scale کردن ایجاد میشه. راهکار دیگه معماری shared-nothing هست که به صورت افقی رشد می‌دیم. در ادامه درباره این روش صحبت می‌کنیم.Replication Vs. Partitioningرپلیکا کردن: نگهداری کپی از یک دیتا روی چندین نود مختلف، احتمالا در چند محل مختلف. رپلیکا کردن افزونگی ایجاد می‌کنه. اگر یک نود دردسترس نباشه، داده از طریق سایر نود‌ها ارائه میشه. و می‌تونه باعث افزایش عملکرد بشه.پارتیشن‌بندی: تقسیم کردن یک دیتابیس بزرگ به بخش‌های کوچک‌تری به نام پارتیشن به طوری که پارتیشن‌های مختلف به نودهای مختلف می‌تونن اختصاص داده بشن (همینطور به نام sharding شناخته میشه)این دو مکانیزم‌های مختلف هستن اما می‌تونن به صورت همزمان باهم استفاده بشن.Replicationبه هر نودی که یک کپی از دیتابیس رو نگه‌ می‌داره، می‌گیم رپلیکا. هر دیتایی که نیازه در دیتابیس نوشته بشه باید توسط همه رپلیکاها پردازش بشه. راه‌حل متداول به نام leader-based replication شناخته میشه (یا atcive/passive و یا master/slave). که به این صورت کار می‌کنه:یکی از رپلیکاها به عنوان لیدر شناخته میشه (master or primary) زمانی که کاربر می‌خواد توی دیتابیس بنویسه، باید درخواستش رو به لیدر بفرسته که دیتای جدید رو در استوریج خودش بنویسه.سایر رپلیکاها که به عنوان دنبال‌کننده شناخته میشن (read replica, slave, secondries or hot standbys) زمانی که لیدر دیتای جدید رو در استوریج خودش نوشت، همنیطور تغییرات دیتا رو برای همه دنبال‌کننده‌هاش به عنوان بخشی از replication log یا change stream می‌فرسته. هر دنبال‌کننده، لاگ رو از لیدر می‌گیره و کپی خودش رو بر اون اساس آپدیت می‌کنه.زمانی که کاربر می‌خواد دیتا رو از دیتابیس بخونه، می‌تونه به لیدر یا هرکدوم از دنبال‌کننده‌ها کوئری بزنه.این روش پیش‌فرض بسیاری از دیتابیس‌ها و حتی message broker هاست.حالا این به روز رسانی دنبال‌کننده‌ها می‌تونه به دو صورت باشه:به صورت synchronous: لیدر صبر می‌کنه که دنبال‌کننده تایید کنه که تغییرات رو دریافت کرده و بعد به کاربر تایید رو بر می‌گردونه.به صورت asynchronous: لیدر بدون صبر کردن برای گرفتن تایید از دنبال‌کننده، به کاربر تایید رو میده.مشکل هم‌زمان بودن اینه که در صورتی که یکی از دنبال‌کننده‌ها به مشکل بخوره و تایید رو نده، کل سیستم متوقف میشه و دیگه امکان نوشتن دیتای جدید وجود نخواهد داشت. راهکار مناسب اینه که یکی از دنبال‌کننده‌ها همزمان باشه و بقیه به صورت غیرهمزمان باشن، در صورتی که دنبال‌کننده همزمان کند یا به مشکل خورد، اون به غیرهمزمان و یک دنبال‌کننده دیکه به همزمان تغییر پیدا می‌کنه، به این روش semi-synchronous می‌گیم.اتصال یک دنبال‌کنند جدید:زمانی که یک نود به مشکل می‌خوره یا میخوایم تعداد رپلیکاها رو افزایش بدیم، نیاز داریم که مطمئن بشیم در حین انتقال دیتا از لیدر به دنبال‌کننده، دیتای جدید نوشته نشده که در دنبال‌کننده از دستش بدیم. این مراحل رو طی می‌کنیم که البته در بعضی از سیستم‌ها اتومات انجام میشه:یک snapshot از لیدر می‌گیریم.در نود دنبال‌کننده جدید کپی می‌کنیم.دنبال‌کننده به لیدر متصل میشه و تمامی دیتاهای تغییرات از زمانی که snapshot گرفته شده به بعد رو دریافت می‌کنه که نیازمند که جایگاه replication log اون snapshot ای که گرفتیم مشخص باشه که در PostgreSQL بهش log sequence number و در MySQL بهش binlog cordinates می‌گیم.وقتی همه تغییرات گرفته و اعمال شد می‌گیم که caught up کرده و از اینجا به بعد تغییرات لیدر رو لحظه‌ای دریافت می‌کنه.زمانی که یک دنبال‌کننده به هر دلیلی مشکل می‌خوره به کمک لاگی که داره می‌دونه که آخرین تغییری که اعمال کرده چی بوده و همه تغییرات بعد اون رو از لیدر دریافت می‌کنه و خودش رو به روز می‌کنه.اگر لیدر به مشکل بخوره:اگر به هر دلیلی لیدر به مشکل بخوره، فرآیند failover به صورت دستی یا اتومات اجرا میشه. یک فرآیند اتومات شامل گام‌های زیر میشه:تعیین به مشکل خوردن لیدر: معمولا در صورتی که از یک تایمی بیشتر پاسخ‌دهی طول بکشه (مثلا ۳۰ ثانیه) بجز مواردی که برای maintenance خودمون آوردمیش پایین، لیدر رو مرده در نظر می‌گیریم.انتخاب لیدر جدید: این فرآیند می‌تونه از طریق روند انتخابات که توسط اکثریت دنبال‌کننده‌ها انتخاب بشه و یا انتخاب از قبل توسط controller node انجام بشه. معمولا مناسب‌ترین نود برای لیدر شدن، نودی هست که به‌روز ترین تغییرات رو داره تا دیتای کمتری از دست بره.پیکربندی مجدد سیستم که همه از لیدر جدید استفاده کنند. درخواست‌های نوشتن کاربران به لیدر جدید باید بره. همینطور باید مطمئن بشیم که اگر لیدر قبلی برگشت، متوجه بشه که تبدیل به دنبال‌کننده شده و لیدر جدید رو بشناسه.یکسری مشکلات درطی فرآیند failover ممکنه رخ بده:اگر رپلیکاها به صورت غیرهمزمان باشن، ممکنه یکسری دیتاها روی لیدر باشه که رپلیکاها هنوز دریافت نکرده باشن که راهکار متداول اینه که بعد برگشت لیدر قدیم، دیتاهایی که replicate نشدن، پاک بشن.این پاک کردن دیتاهای رپلیکیت نشده زمانی که یک سیستم ذخیره‌سازی بیرون از دیتابیس متصل به دیتابیسه، می‌تونه خطرناک باشه، مثلا ردیسی که داره از id که به صورت auto increment دیتابیس استفاده می‌کنه و بعد از تغییر لیدر، یکسری از این id ها توسط لیدر قبلی ارسال شده و بعد چون رپلیکیت نشده بوده دوباره این id ها استفاده میشن.یا سناریویی که دو نود فکر کنن لیدر هستن.و یا تعیین زمان مناسب برای timeout تغییر لیدر، ممکنه سیستم زیر بار بوده و زمان پاسخ‌گویی افزایش پیدا کرده.همه این موارد باعث میشه که گاهی در سیستم‌هایی که امکان اتومات کردن فرآیند failover وجود داره، باز هم به صورت دستی انجام بشه.انواع پیاده‌سازی Replication Logs:روش Statement-based replication: ساده‌ترین روش، لیدر هر درخواست نوشتن رو لاگ می‌گیره و به دنبال‌کننده‌ها می‌فرسته. در ورژن‌های قبل ۵.۱ MySQL استفاده میشده.روش Write-ahead log shipping: به روش log-structured storage engine (SSTables and LSM-Trees) و یا به روش B-Trees که قبلا در موردشون صحبت کردیم. در هر دو روش لاگ به صورت append-only sequence ای از بایت‌ها که شامل همه نوشتن‌ها در دیتابیس میشه هست. در PosgreSQL و Oracle استفاده میشه. استفاده از روش WAL این بدی رو داره که لاگ‌ها براساس ساختار دیتابیس هستن و در نتیجه این امکان رو نداریم که فرمت استوریج متفاوتی در لیدر و دنبال‌کننده‌ها داشته باشیم. این یعنی مثلا برای upgrade ورژن دیتابیس‌ها نیاز به downtime داری و نمیشه اول دنبال‌کننده‌ها و بعد لیدر رو به‌روز کرد.روش Logical (row-based) log replication: روش جایگزینی که بهمون این امکان رو میده که replication log از storage engine به صورت decouple باشه. یک logical log یک مجموعه از رکورد‌هاست که نوشتن در دیتابیس رو به این صورت شرح میدن: برای ردیف insert شده، لاگ شامل مقادیر جدید همه ستون‌هاست. برای ردیف پاک شده، لاگ شامل اطلاعات کافی برای شناسایی ردیف پاک شده‌است. برای ردیف آپدیت شده، لاگ شامل اطلاعات شناسایی ردیف یکتا آپدیت شده‌ به همراه مقادیر همه ستون‌هاست (حداقل مقادیر جدید همه ستون‌هایی که تغییر کردن). MySQL binlog از این روش استفاده می‌کنه. این روش برای ارسال دیتاها به data warehouse یا ساخت کاستوم ایندکس‌ها یا کش‌ها مناسبه.روش Trigger-based replication: تمامی روش‌هایی که صحبت شد بدون درگیر شدن با کد اپ بودن، اما گاهی ما می‌خوایم یکسری دیتای خاص رو فقط رپلیکیت کنیم یا از یک نوع دیتابیس به یک نوع دیگه رپلیکیت کنیم یا لازم داریم که یک منطق حل تعارض رو پیاده کنیم، نیاز خواهیم داشت که رپلیکیشن رو در لایه اپلیکیشن انجام بدیم. در بسیاری از دیتابیس‌های رابطه‌ای این فیچر برای این کار وجود داره: triggers and stored procedures - یک تریگر این امکان رو میده که کدی رو رجیستر کنیم که زمانی که دیتا تغییر کرد اتومات اجرا بشه. این روش سربار بیشتری داره و احتمال ایجاد باگ هم افزایش پیدا می‌کنه ولی آزادی عمل بیشتری میده.Problems with Replication Lagread-your-writes consistency (read-after-write consistency):فک کن کاربر یک دیتایی رو توی دیتابیس نوشته، اون دیتا میره سمت دیتابیس لیدر که نوشته بشه و بعد به دیتابیس‌های فالور ارسال بشه، توی این زمان ممکنه کاربر بره و اون دیتا رو بخونه اما هنوز رپلیکای فالور شاید اون دیتا رو نداشته باشه و کاربر فک کنه تغییراتش پریده. این روش‌ها پیشنهاد میشه:دیتایی که کاربر ممکنه ویرایش کرده باشه از لیدر خونده بشه و سایر دیتاها از فالورها خونده بشه. مثلا همیشه اطلاعات پروفایل خود کاربر از لیدر خونده میشه.اگر بیشتر بخش‌های برنامه توسط کاربر قابل ویرایش باشه. نیاز به تصمیم‌گیری هست، مثلا اگر دیتایی قبل از یک دقیقه از آخرین ویرایش خونده می‌خواست بشه از لیدر خونده بشه. و همینطور فالورها مانیتور بشه که به فالوری که بیش از یک دقیقه از لیدر عقبه کوئری‌ای زده نشه.کاربر تایم‌استمپ آخرین نوشتنش رو نگه داره و موقع درخواست خوندن از رپلیکایی بخونه که حداقل تا اون زمان آخرین نوشتن آپدیت شده باشه.اگر رپلیکاها در چندین دیتاسنتر مختلف در مناطق جغرافیایی مختلف باشن، پیچیدگی بیشتری بوجود میاد چون درخواست‌هایی که نیازه به لیدر بره باید به دیتاسنتری که لیدر توی اونه مسیریابی بشه.اگر کاربر به طور همزمان با چند دیوایس مثل گوشی و سیستمش با برنامه کار کنه، پیچیدگی بیشتری می‌تونه اضافه بشه که بتونه در لحظه در دیوایس دیگرش تغییرات رو ببینه:cross-device read-after-write consistency:روشی که نیاز به ذخیره آخرین زمان ایجاد تغییر کاربر داشت، نیاز داره که centralized بشه که در هر دو دیوایس بتونه زمان رو داشته باشه.اگر رپلیکا ها در دیتاسنترهای مختلفی باشن، و دیوایس‌ها از اینترنت‌های مختلفی استفاده کنن، ممکنه به رپلیکاهای متفاوتی مسیریابی بشن. اگر روش نیازمند اتصال به لیدر باشه، ممکنه لازم باشه که اول همه درخواست‌های کاربر رو به یک دیتاسنتر هدایت کنید.Monotonic Readsیک سناریو مشکل دیگه که می‌تونه ایجاد بشه اینه که کاربر توی زمان به سمت عقب بره، فرض کن دیتا رو از یک رپلیکا می‌گیره و قلی یک کامنت گذاشته، دوباره صفحه رو رفرش می‌کنه و اینبار رندوم به یک رپلیکای دیگه وصل میشه که هنوز کامنت قلی رو دریافت نکرده. monotonic read گارانتی می‌کنه که این نوع مشکلات بوجود نمیاد.یک راه‌حلش اینه که هر کاربر همیشه درخواست خوندن‌هاش به یک رپلیکا بره، اگر چه اگر رپلیکایی مشکل خورد به یک رپلیکای دیگه دوباره مسیریابی میشه.Consistent Prefix Readsسناریو مشکل بعدی اینطوره که فرض کن قلی یک کامنت میذاره و صغری اون رو جواب میده، حالا یک نفر سوم که داره این کامنت‌ها رو می‌خونه با یک لگ پیام صغری رو می‌گیره و پیام قلی رو با لگ بیشتری دریافت می‌کنه و این باعث میشه طوری به نظر برسه که صغری حتی قبل از سوال پرسیدن قلی جواب داده. این یک مشکل متداول در دیتابیس‌های پارتیشن بندی شده (sharded) هست و برای حلش نیاز به گارانتی consistent prefix reads داریم.یک راهکار اینه که نوشتن‌هایی که باهم مرتبط هستن در یک پارتیشن یکسان نوشته بشن. اما اگر این امکان نباشه الگوریتمی برای ردیابی نوشتن‌های مرتبط وجود داره که بعدا درباره‌اش صحبت می‌کنیم.Multi-Leader Replicationاین مدل به بیش از یک نود امکان نوشتن رو میده، ولی همچنان رپلیکیشن وجود داره و هر نودی که عملیات نوشتن رو انجام میده باید تغییرات رو به همه نودها ارسال کنه. در این روش هر لیدر نسبت به بقیه لیدرها نقش فالور رو هم داره. به این مدل master-master و یا active/active replication هم می‌گن.معمولا به ندرت منطقی میاد که در یک دیتاسنتر از این مدل استفاده بشه.موارد استفاده:Multi-datacenter operationفرض کنین دیتابیس با چندین رپلیکا در چندین دیتاسنتر دارین، با این مدل باید در هر دیتاسنتر یک لیدر داشته باشین، درون هر دیتاسنتر ساختار لیدر و فالور معمول رو داریم و بین هر دیتاسنتر، لیدر دیتاش رو رپلیکا می‌کنه به لیدری که در دیتاسنتر دیگه وجود داره.ابزارهایی که برای پیاده سازی این روش می‌شه استفاده کرد: Tungsten Replicator for MySQL, BDR for PostgreSQL, and GoldenGate for Oracleاین روش نقاط منفی‌ای هم داره: اگر دیتای یکسانی به طور همزان در دو دیتاسنتر تغییر کنه، این کانفلیکت نوشتن باید مدیریت بشه. و یا مواردی مثل auto-incrementing keys, triggers, and integrity constraints می‌تونن در این روش، ایجاد مشکل بکنن.Clients with offline operationسناریو دیگه‌ای که چند لیدری استفاده میشه، زمانیه که نیاز داریم که اپ‌مون بدون اینترنت کار کنه. در نتیجه روی هر دیوایس‌مون اون اپ یک دیتابیس داره که مشابه لیدر عمل می‌کنه و موقعی که به اینترنت متصل شد، با سایر لیدرها دیتاش رو سینک می‌کنه. برای مثال CouchDB برای این سبک عملکردها طراحی شده.Handling Write Conflictsبزرگترین مشکل رپلیکیشن مولتی لیدر اینه که کانفلیکت نوشتن ممکنه رخ بده و این یعنی وجود conflict resolution ضروریه. مثالش اینه که دو کاربر به طور همزمان عنوان یک مقاله رو تغییر بدن.اگر بیایم و به صورت synchronous مغایرت‌ها رو حل کنیم، یعنی اینکه صبر کنیم تا تغییرات روی همه رپلیکا‌ها نوشته بشه و بعد به کاربر بگیم موفق بوده، دیگه از منافع مولتی‌لیدر استفاده نمی‌کنیم و بهتره بریم سراغ تک‌لیدری.ساده‌ترین استراتژی رویارویی با مغایرت‌ها، اجتناب ازشونه، اگر بتونیم مطمئن بشیم که همه نوشتن‌های مرتبط با یک رکورد در یک لیدر یکسان انجام میشه، اونوقت مغایرت پیش نخواهد اومد. مثلا به ازای هر کاربر یک دیتاسنتر home وجود داشته باشه که همه نوشتن‌های مرتبط با پروفایلش به اونجا میره و از نگاه اون تک لیدری به نظر میرسه.اگر در مولتی‌لیدری، بر اساس زمان رسیدن تغییرات بخوان دیتای نهایی رو مشخص کنن، لگ باعث خواهد شد که در لیدرهای متفاوت، دیتاهای متفاوتی به عنوان وضعیت نهایی ذخیره بشه. راهکارهای حل این مورد:به هر نوشتن یک شناسه یکتا یا timestamp بدیم که بر اون اساس بالاترینش به عنوان وضعیت نهایی شناخته بشه. (به این روش Last Write Wins می‌گیم) این روش می‌تونه منجر به از دست رفتن دیتا بشه.به هر رپلیکا یک شناسه یکتا بدیم و نوشتن‌هایی که از رپلیکای با شناسه بالاتره در اولویت باشه، این روش هم منجر به از دست دادن دیتا میشه.به روشی مقادیر رو باهم ترکیب کنیم، مثلا به ترتیب الفبا مرتب کنیم و باهم ترکیب کنیم، مثلا میشه (عنوان اول/عنوان دوم)مغایرت رو در یک ساختار داده که همه اطلاعات رو نگه‌داره ذخیره کنیم و کدی بنویسیم که بعدا به یک روشی در آینده مغایرت رو حل کنه. (شاید با پرسیدن از کاربر)Custom conflict resolution logicمنطق حل مغایرت میشه در زمان نوشتن یا خواندن فراخوانی بشه، توجه داشته باشین که این حل مغایرت در سطح یک رکورد یا داکیومنت انجام میشه و نه در سطح atomic transaction.Multi-Leader Replication Topologiesتوپولوژی دایره‌ای (circular): به تریتب به صورت دایره‌ای لیدرها بهم تغییرات رو اطلاع میدن.توپولوژی ستاره‌ای (star): یک لیدر در مرکز قرار می‌گیره و سایر لیدرها با اون تغییرات‌شون به اشتراک میذارن.توپولوژی همه به همه (all-to-all): تمام لیدرها با هم تغییرات‌شون رو به اشتراک میذارن.در توپولوژی دایره‌ای و ستاره‌ای، ممکنه نوشتن‌ها نیاز باشه که در چند نود منتقل بشن تا به همه رپلیکاها برسن. و برای اینکه این دست‌به‌دست کردن نوشتن‌ها در حلقه بینهایت نیوفته، هر نود یک شناسه یکتا داره که نوشتن‌ها با اون شناسه تگ زده میشن و زمانی که دیتایی به نودی میرسه که با شناسه خودشه، اون دیتا رو نادیده می‌گیره و دست‌به‌دست نمی‌کنه. مشکل دیگه توپولوژی ستاره و دایره‌ای اینه که اگر یک نود مشکل بخوره، چرخه نوشتن متوقف میشه تا اون نود برگرده، یا اینکه باید reconfiguration انجام بشه که معمولا به صورت دستی انجام میشه. در توپولوژی همه‌به‌همه هم سناریو مشکل اینطوره که کاربر اول دیتاش روی لیدر اول نوشته میشه، کاربر دوم همون دیتارو روی لیدر سوم آپدیت می‌کنه ولی لیدر دوم این نوشتن‌ها رو در ترتیب نادرست دریافت می‌کنه (بخاطر latency شبکه) یعنی اول آپدیت رو می‌گیره و بعد نوشتن رو. برای حلش باید مطمئن بشیم که همه نودها اول نوشتن رو پردازش کنن و بعد آپدیت رو و فقط فرستادن timestamp با هر نوشتن، کافی نیست (چون نمیشه به ساعت‌ها برای مدیریت کارآمد ترتیب نوشتن‌ها اعتماد کرد). راهکار مناسب می‌تونه version vectors باشه که در ادامه درباره‌اش صحبت خواهیم کرد.Leaderless Replicationدر روش‌های قبلی، لیدر تصمیم می‌گیره که به چه ترتیبی نوشتن انجام بشه و دنبال‌کننده‌ها به همون ترتیب، نوشتن رو انجام میدن. اما یک راهکار متفاوت اینه که همه رپلیکاها مستقیما نوشتن رو از کاربر قبول کنن، این روش که به Dynamo-style شناخته میشه در Dynamo, Riak, Cassandra, Voldmort استفاده میشه. در بعضی از پیاده‌سازی ها خود کاربر اطلاعات رو به رپلیکا‌ها میفرسته و در بعضی‌ها یک نود هماهنگ‌کننده، از طرف کاربر این کار رو انجام میده.اگر در روش لیدر دار، یک رپلیکا از دسترس خارج میشد، بعد برگشت failover اجرا میشد. اما در روش بدون لیدر، سناریو failover وجود نداره. دیتا به ۳ رپلیکا میره و دوتاشون ok میدن و سومی هیچ پاسخی نمیده. کاربر اوکی در نظر میگیره و ادامه میده. بعدا موقع دریافت اطلاعات، اطلاعات رو از هر سه سرور می‌گیره، به همراه یک Version number که برای مشخص کردن این که کدوم اطلاعات جدیدتره استفاده میشه.Read repair and anti-entropyالگو رپلیکایی باید بتونه مطمئن بشه که در نهایت همه دیتاها در همه رپلیکاها کپی میشه. بعد بازیابی یک نود، دو راهکار وجود داره:راهکار Read Repair: وقتی کاربر از چندین نود به طور موازی خوند، می‌تونه دیتای غیربه‌روز رو شناسایی کنه و دیتای جدید‌تر رو دوباره به رپلیکایی که دیتاش به‌روز نیست بنویسه. این راهکار برای مقادیری که به طور مکرر خونده میشن مناسبه.راهکار Anti-entropy process: بعضی از datastore ها یک پردازش بک‌گراند دارن که که به طور مرتب به دنبال تفاوت دیتا بین رپلیکاها می‌گردن و هر دیتای از دست رفته رو از یک رپلیکا در رپلیکای دیگه کپی می‌کنن. در این روش، هیچ ترتیب خاصی برای کپی نوشتن‌ها وجود نداره و ممکنه تاخیر زیادی قبل کپی کردن داده‌ها وجود داشته باشه.Quorums for reading and writingبرای اینکه بدونیم حداقل تعداد رپلیکایی که باید تایید نوشتن رو بدن و در خوندن باید ازشون دیتا رو گرفت رو بدونیم، بیان تعداد کل رپلیکاها رو n و هر نوشتن باید توسط تعداد w نود تایید بشه تا نوشتن موفق در نظر گرفته بشه و هر کوئری باید حداقل از r نود خونده بشه. تا زمانی که w + r &gt; n باشه، ما انتظار داریم که دیتای به روز در خوندنمون دریافت کنیم چون حداقل یکی از r نودمون، دیتای به روز رو خواهد داشت. این مقادیر n, w, r کانفیگور میشه. به طور معمول n یک عدد فرد و w = r = (n + 1) / 2 در نظر گرفته میشه.حتی اگر برنامه‌مون طوریه که اطلاعات غیربه‌روز بهش برسه مشکل خاصی ایجاد نمی‌کنه، بازهم مهمه که سلامت رپلیکیشن‌مون رو مانیتور کنیم که اگر خیلی عقب‌موندن، آگاه بشیم (مثلا مشکلی در شبکه یا یک نود زیرفشار). در رپلیکیشن براساس لیدر، به دلیل اینکه نوشتن‌ها در لیدر و دنبال‌کننده‌ها به یک ترتیب انجام می‌شه، و هر نود یک جایگاه در رپلیکیشن لاگ داره، با تفریق جایگاه الان دنبال‌کننده از جایگاه الان لیدر، میشه مقدار رپلیکیشن لگ رو اندازه‌گیری کرد. در سیستم‌های بدون رهبر، ترتیب مشخصی برای نوشتن‌ها وجود نداره که مانیتور کردن رو سخت‌تر می‌کنه.وقتی کاربر مشکل شبکه بخوره، دسترسیش به تعدادی از نودها قطع میشه با اینکه کاربرهای دیگه به اون نودها دسترسی دارن. اما اگر نودهایی که دسترسی داره کمتر از حدنصاب باشن، دو راهکار داریم:ارور برگردونیم.نوشتن رو قبول کنیم و روی یک نودهایی بنویسیم که در دسترس هستن اما جزو اون n نود نیستن که مقادیر معمولا روشون قرار می‌گیرن.به روش دوم sloppy quorum می‌گیم. نوشتن و خوندن همچنان به w و r پاسخ موفق نیاز دارن، اما ممکن شامل نودهایی باشن که به عنوان نود home اون مقادیر تعریف نشدن. وقتی مشکل شبکه برطرف شد، هر نوشتنی که یک نود از طرف نود دیگه به طور موقت پذیرفته بوده، به نود home مربوطه‌اش ارسال میشه که به این کار hinted handoff می‌گیم. توی این روش نمی‌تونیم مطمئن باشیم که داده کهنه خونده نمیشه، چون آخرین مقدار ممکنه در یک نود خارج از n نود نوشته شده باشه. این روش در همه پیاده‌سازی‌های Dynamo اختیاری هست و در Riak پیش‌فرض فعاله، در Cassandra و Voldemort پیشفرض غیرفعاله.در معماری بدون لیدر پیاده سازی چند دیتاسنتری هم امکان‌پذیره. Cassandra و Voldemort اینطوره که n رو همه نودها در همه دیتاسنترها در نظر می‌گیرن و کاربر معمولا به حدنصابی از نودها در همون دیتاسنتر برای نوشتن نیاز داره. و بعد به صورت async به بقیه دیتاسنترها منقل میشه. در Riak عدد n تعداد رپلیکاها در یک دیتاسنتر رو نشون میده.به طور کلی در قبال دو عملیات A و B، سه حالت وجود داره، عملیات A قبل از B رخ داده باشه، عملیات B قبل از A رخ داده باشه و یا A و B به طور همزمان (concurrent) رخ داده باشن. اگر یک عملیات قبل از دیگری رخ داده باشه، ما عملیات قبلی رو overwrite می‌کنیم، اما اگر به صورت همزمان رخ داده باشن، ما یک کانفلیکت داریم که باید حل بشه. در واقع معنی concurrent این نیست که در یک زمان انتفاق افتاده باشن، چون ممکنه به‌دلیل کندی شبکه، به طور همزمان به ما نرسن، اما همچنان concurrent هستن، در واقع concurrent یعنی دو عملیات که از یکدیگر باخبر نباشن.Partitioningهر پارتیشن یک دیتابیس کوچیکه. دلیل اصلی پارتیشن‌بندی داده، scalability هست. پارتیشن‌های متفاوت می‌تونن در نودهای مختلف قرار بگیرن. پارتیشن‌بندی معمولا همراه با رپلیکیشن انجام میشه که کپی هر پارتیشن در چندین نود نگهداری بشه. روی یک نود می‌تونه چندین پارتیشن باشه، که یکی‌شون لیدر هست و بقیه فالور سایر پارتیشن‌ها. اگر این پارتیشن‌بندی طوری انجام بشه که فشار روی یک نود باقی بمونه و باقی پارتیشن‌ها بیکار باشن به این پارتیشن با لود بالای نامتناسب می‌گیم hot spot. برای حلش می‌تونیم رکورد‌ها رو به نود ها به طور رندوم اساین کنیم اما به این روش نمی‌دونیم چی کجاس و به طور همزمان باید به همه نود‌ها کوئری بزنیم.پارتیشن‌بندی با رنج کلید: اگر دیتا مدل‌مون به صورت key-value باشه میشه مشابه دیکشنری براساس کلید به صورت پیداش کرد. یک راه پارتیشن‌بندی، اساین کردن یک رنج از کلیدها به هر پارتیشنه. این رنج می‌تونه لزوما مساوی نباشه. برای پخش‌کردن دیتا به صورت مساوی، حدود پارتیشن‌ها لازمه که براساس دیتاها مشخص بشه. در هر پارتیشن می‌تونیم کلیدها رو به صورت سورت شده نگه‌داریم. بدی این روش اینه که می‌تونه موجب hot spot بشه، مثلا دیتابیسی که براساس تاریخ پارتیشن بندی شده و دیتای سنسورها درونش ذخیره میشه، همه نوشتن‌ها به یک دیتابیس واحد میرن. راهکارش اینه که از کلید دیگه‌ای بجای تاریخ استفاده کنیم.پارتیشن‌بندی با کلیدهای هش: به دلیل ریسک hot spot بسیاری از distributed datastore‌ها از این روش استفاده می‌کنن. فرض کن هش ۳۲ بیتی داری، هرچقدر رشته ورودیت مشابه هم باشه، هش‌اش به طور مساوی در محدوده اعداد توزیع شده است. حالا فرض کن در یک شبکه اجتماعی همه پست‌های یک کاربر در یک بازه زمانی رو می‌خوایم، با این روش، هرکدومشون توی یک شارد هست و باید به همه ریکوئست بزنیم. راهکاری که کاساندرا داره اینه که compound primary key داره که شامل چند ستونه، فقط بخش اول کلید هش هست که پارتیشن رو مشخص کنه اما بقیه ستون‌ها به عنوان index پیوسته برای سورت دیتا استفاده میشه.اما اگر بیشتر خوندن و نوشتن‌ها برای یک کلید یکسان باشه، بازم ما hot spot رو داریم. فرض کن یک سلبریتی در شبکه اجتماعی با میلیون‌ها فالور می‌تونه طوفان راه بندازه و کلی نوشتن بیاره سمت یک نود، یک راهکار اینه که به اول یا آخر کلید یک عدد رندوم دو رقمی اضافه کنیم، با اینکار به ۱۰۰ کلید به طور مساوی تقسیمش کردیم، اما باید حواسمون به تریدآف‌اش هم باشه که موقع خوندن هم باید به ۱۰۰ کلید متفاوت رجوع کنیم و این ۱۰۰ کلید رو باهم ترکیب کنیم. بهتره که این راهکار رو فقط برای hot key های خاصی انجام بدیم و یکجا ذخیره کنیم که کدوم کلیدها split شدن.پارتیشن‌بندی secondary index با Document: وقتی ایندکس ثانویه داریم، در این روش هرپارتیشن کاملا مجزا است و هرکدوم جداگانه ایندکس ثانویه‌اش رو نگه می‌داره که فقط داکیومنت‌های توی همون پارتیشن رو پوشش میده. به همین دلیل document-partitioned index به local index هم شناخته میشه. به این روش که به همه پارتیشن‌ها درخواست میزنیم و دیتا رو دریافت و ترکیب می‌کنیم و برمی‌گردونیم، می‌گیم scatter/gather.پارتیشن‌بندی secondary index با Term: یک گلوبال ایندکس می‌سازیم که همه دیتاها در همه پارتیشن‌ها رو پوشش میده. اگرچه همه اون ایندکس رو روی یک پارتیشن نمی‌تونیم ذخیره کنیم. گلوبال ایندکس هم باید پارتیشن‌بندی بشه ولی می‌تونه به صورت متفاوتی از key index پارتیشن‌بندی بشه به این روش term-partitioned می‌گیم. این‌کار خوندن رو سریع‌تر می‌کنه اما باعث کندتر شدن نوشتن میشه.Rebalancing Partitionsبه فرایند انتقال داده از یک نود به نود دیگه در یک کلاستر، میگیم rebalancing. حداقل نیازمندی‌های این کار ایناس:بعد ریبالانسینگ، لود (نگهداری داده‌ها، خوندن و نوشتن‌ها) به صورت منصفانه‌ای بین نودهای کلاستر تقسیم شده باشه.در حین ریبالانسیگ، دیتابیس به پذیرفتن خوندن و نوشتن‌ها ادامه بده.بیشتر از دیتاهای ضروری، نباید بین نودها جابه‌جا بشه که مطمئن باشیم ریبالانسینگ، سریع و با مینیموم لود شبکه و دیسک انجام میشه.استراتژی‌های ریبالانسینگ:کاری که نباید بکنی: hash mod N: توی این روش می‌گیم که خب مثلا ۱۰ تا پارتیشن داریم که از ۰ تا ۹ هستن، پس بیایم hash % 10 بگیریم و خروجیش یک عدد بین ۰ تا ۹ هست و بریم تو اون پاریتیشن ذخیره کنیم، مشکل این روش اینه که اگر تعداد پارتیشن‌ها بشه ۱۱ تا، نتیجه hash % 11 برای همه ایندکس‌ها متفاوت میشه و همه رو باید جابه‌جا کنیم.و fixed number of partitions: فرض کن یک دیتاست یک کلاستر با ۱۰ نود داره که به ۱۰۰۰ پارتیشن از بیرون تقسیم شده، پس حدودا ۱۰۰ پارتیشن به هر نود اساین شده. الان اگر یک نود به کلاستر اضافه بشه، این نود از هر نود دیگه می‌تونه چند پارتیشن قاپ بره تا دوباره به طور مساوی تقسیم بشن. اگر هم نودی از کلاستر حذف بشه، معکوس همین حالت اتفاق میوفته. فقط همه پارتیشن بین نودها جابه‌جا میشه. تعداد پارتیشن‌ها تغییری نمی‌کنه و فقط assignment پارتیشن به نودها تغییر می‌کنه.و Dynamic partitioning: برای دیتابیس‌هایی که key range partitioning هستن، اینکه یک تعداد ثابت پارتیشن با حدود ثابت داشتن می‌تونه راضی کننده نباشه. در این روش یک thershold تعریف میشه و اگر یک پارتیشن به سقف محدوده رسید، به دو پارتیشن مساوی می‌شکنه و اگر به کف محدوده رسید، با پارتیشن مجاورش ترکیب میشه. خوبی این روش اینه که بنا به حجم داده، تعداد پارتیشن‌ها کم و زیاد میشه.و Partitioning proportionally to nodes: در دو مورد قبلی، تعداد پارتیشن‌ها ثابت بود، اما در این مورد، پارتیشن‌ها هم متناسب با تعداد نودهاست. در این مورد، سایز پارتیشن متناسب با دیتاست تغییر می‌کنه، بدون اینکه تعداد نودها تغییر کنه. وقتی یک نود اضافه میشه، بخشی از داده‌های هر پارتیشن به صورتی که درنهایت ترافیک عادلانه‌ای روی همه نودها باشه، برداشته میشه و به نود جدید منتقل میشه.ریبالانسینگ اتومات یا دستی: این پروسه می‌تونه اتومات، توسط یک آدم و یا اتومات پیشنهاد و با تایید یک نفر اجرا بشه. فرض کنین که یک نود زیر فشاره و کند شده و نود دیگه متوجه میشه و اتومات میاد ریبالانسینگ کلاستر انجام بشه، حالا فشار روی نود کند شده بیشتر میشه و علاوه بر اون روی نتورک و نودهای دیگه هم فشار میاد و می‌تونه باعث cascading failure بشه. به این دلیل بهتره که همیشه یک آدمیزاد توی این پروسه دخیل باشه.درباره Request Routing:سه راهکار وجود داره:اوکی باشه که کاربر به هر نودی خواست درخواست بده، اگر نود بتونه اون درخواست رو پاسخ میده و گرنه به نودی که مرتبطه می‌فرسته و جواب رو می‌گیره و به کاربر میده.همه درخواست‌ها به یک بخش مسیریابی بره و این بخش هیچ عملیاتی درباره درخواست‌ها انجام نمیده و فقط مسیریابی رو می‌کنه.لازمه که کلاینت از پارتیشن‌بندی و وظایف‌شون باخبر باشه و با نود مربوطه مستقیم ارتباط برقرار کنه.در همه این حالت‌ها لازمه که از تغییرات باخبر باشن که بتونن درست عمل کنن. معمولا از سرویس‌های coordination مثل Zoo-Keeper استفاده میشه که از تغییرات routing باخبر میشن.Transactionsدر دیتاسیستم‌ها کلی فاجعه می‌تونه رخ بده، سخت‌افزار یا نرم‌افزار به مشکل بخوره، اپ کرش کنه، مشکل شبکه پیش بیاد، race condition بوجود بیاد و...معنی transaction یک روش برای برنامه‌هاست که چندین خوندن و نوشتن رو باهم گروه کنن و تبدیل به یک واحد منطقی بکنن. درواقع یعنی همه خوندن و نوشتن‌ها در یک عملیات انجام بشن. حالا می‌خواد همه‌اش انجام بشه (commit) یا خطا بخوره (abort, rollback) و اینطوری می‌تونیم مطمئن باشیم که عملیاتی نصفه انجام نشده.The Meaning of ACIDبه گارانتی ایمنی که transaction میده غالبا ACID می‌گن که مخفف Atomicity, Consistency, Isolation, Durability هست.معنی Atomicity: معنی لغتش یعنی چیزی که نمیشه به بخش‌های کوچک‌تر شکوندش، در اینجا یعنی اینکه اگر مثلا چندین نوشتن در دیتابیس داریم، باهم یکی میشن و به هر دلیلی اگر بخشی از نوشتن‌ها انجام نشه، همه تغییرات به حالت قبل برمی‌گرده.معنی Consistency: خیلی معنی گسترده‌ای داره، ولی در ACID یعنی اینکه وقتی توی داده‌هات منطقی داری که باید همیشه درست باشه، مثلا توی سیستم حسابداریت اعتبار و بدهی‌ها باید همیشه بالانس باشه. اگر قبل ترنزکشن بالانس بوده و عملیات ترنزکشن هم بالانس باقی میذارش، می‌تونی مطمئن باشی که بعد انجام ترنزکشن هم بالانس خواهد بود.معنی Isolation: اگر به طور همزمان چندین کاربر به یک دیتا بخوان دسترسی پیدا کنن، ممکنه مشکل concurrency (race condition) بوجود بیاد. Isolation در اینجا یعنی اینکه ترنزکشن‌های همزمان از یکدیگه ایزوله هستن و نمی‌تونن روی هم تاثیر بذارن.معنی Durability: این تعهد، تضمین میده که هر داده‌ای که با موفقیت ذخیره میشه، هر اتفاقی که بیوفته، از دست نمیره، حتی اگر سخت‌افزار مشکل بخوره یا دیتابیس کرش کنه. در دیتابیس‌های رپلیکایی، یعنی این داده حداقل در چند نود نوشته شده.Weak Isolation Levelsزمانی concurrency رخ میده که یک ترنزکشن دیتایی بخونه که همون موقع داره توسط ترنزکشن دیگه‌ای تغییر داده میشه و یا زمانی که دو ترنزکشن هم‌زمان دارن یک دیتا رو تغییر میدن. پیدا کردن مشکل کانکارنسی، واقعا سخته و با تست‌کردن واقعا سخته بخوای پیداش کنی. برای همین دیتابیس سعی می‌کنه این مشکل به سمت لایه اپ نیاد، به طوری که انگار اصلا مشکل کانکارنسی وجود نداره. اما مثلا serializable isolation که یعنی در هر زمان فقط یک درخواست اجرا بشه، هزینه زیادی داره و در بعضی از اپ‌ها ممکنه نخوایم این هزینه رو بدیم.بررسی چند Isolation levelسطح Read Committed:اولیه‌ترین سطح ایزوله کردنه. دو تا گارانتی میده:وقتی داری از دیتابیس می‌خونی، فقط چیزی رو می‌بینی که کامیت شده (no dirty reads). اگر یک ترنزکشن درحال نوشتن یکسری داده در دیتابیس باشه و هنوز کامیت یا abort نکرده باشه‌اش، آیا ترنزکشن دیگه می تونه ببینه‌اش؟ اگر آره، بهش می‌گیم dirty read. دو تا دلیل داره که ازش استفاده کنیم، اول اینکه تغییرات کامل نشده هنوز: مثلا می‌بینه ایمیل جدید اومده اما هنوز کانتر ایمیل جدید اضافه نشده. دوم اینکه ممکنه بعد اینکه دیتارو گرفتیم، ترنزکشن ناموفق بشه و roll back بشه ولی ما دیتاش رو گرفتیم.وقتی داری در دیتابیس می‌نویسی، فقط چیزی رو تغییر میدی که کامیت شده (no dirty writes). یعنی نوشتن، تا زمانی که ترنزکشن قبلی کامیت یا abort بشه، به تعویق میوفته. این راهکار مساله race condition اضافه کردن دو پردازش به مقدار یک counter همزمان رو حل نمی‌کنه. اما این مشکل که فرض کن دو نفر می‌خوان یک ماشین رو بخرن و برای یکی نمایش پیغام خرید بده و برای اون یکی ایمیل خرید بره رو حل می‌کنه.سطح Snapshot isolation:هر ترنزکشن، از consistent snapshot دیتابیس داده‌ها رو می‌خونه. یعنی فقط دیتاهایی رو می‌خونه که تا قبل از شروع ترنزکشن در دیتابیس کامیت شدن. اینطوری وقتی یک بک‌آپ طولانی می‌گیریم. یکسری دیتاها در طول پروسه بک‌آپ آپدیت نمیشن. برای این کار، دیتابیس چندین نسخه از کامیت‌های مختلف نگه میداره (Multi-Version Concurrency Control) برای اینکار، هر کامیت یک ID می‌گیره و بر اون اساس مشخص میشه که کدوم نسخه کامیت به ازای هر ترنزکشن استفاده بشه. در این روش، زمانی که garbage collector میاد و نسخه‌ای که هیچ ترنزکشنی ازش استفاده نمی‌کنه رو پاک می‌کنه، index های مرتبط با اون نسخه رو هم پاک می‌کنه. اسم دیگه ای که بهش می‌گن repeatable read هست.Preventing Lost Updatesدو سطح ایزولیشن که تا الان صحبت کردیم، درباره ترنزکشن‌هایی بود که در حال خوندن نوشتن‌های همزمان بودن که کاری برای مشکل lost update (همزمان اضافه کردن یک کانتر) نمی‌کنن که این مشکل در چرخه read-modify-write پیش میاد.Atomic write operationsخیلی از دیتابیس‌ها امکان نوشتن اتومیک رو میدن که با این روش نیازی به چرخه read-modify-write نیست:UPDATE counters SET value = value + 1 WHERE key = &#39;foo&#39;;تا زمانی که عملیات اتومیک تموم نشده، ترنزکشن‌های دیگه نمی‌تونن مقدارش رو بخونن.Explicit lockingبا استفاده از FOR UPDATE دیتابیس همه row ها روهایی که کوئری SELECT برمی‌گردونه رو lock می‌کنه:BEGIN TRANSACTION;&#xA;&#xA;SELECT * FROM figures&#xA;    WHERE name = &#39;robot&#39; AND game_id = 222&#xA;      FOR UPDATE;&#xA;&#xA;-- Check whether move is valid, then update the position&#xA;-- of the piece that was returned by the previous SELECT.&#xA;UPDATE figures SET position = &#39;c4&#39; WHERE id = 1234;&#xA;&#xA;COMMIT;Automatically detecting lost updatesدیتابیس‌ها می‌تونن به صورت اتومات lost update ها رو شناسایی کنن و ترنزکشن رو متوقف کنن و مجبور به تلاش مجدد بکننش. در ایزولیشن repeatable PostgreSQL, serialization Oracle, snapshot isolation SQL Server به طور اتومات جلوی این اتفات گرفته میشه.Compare-and-setدیتابیس‌هایی که ترنزکشن ندارن، گاهی عملیات aotmic compare and set دارن. در این روش اگر مقدار از آخرین باری که خونده تغییر نکرده باشه، آپدیت اجرا نمیشه و باید بعدش چک کنیم که در صورتی که انجام نشده، تکرار بشه.-- This may or may not be safe, depending on the database implementation&#xA;UPDATE wiki_pages SET content = &#39;new content&#39;&#xA;    WHERE id = 1234 AND content = &#39;old content&#39;;Write Skew and Phantomsنوع دیگه‌ای از race condition بجز dirty writes و lost updates داریم، که بهش می‌گیم write skew، فرض کن شیفت آن‌کال بیمارستان اینطوره که باید حداقل یک دکتر همیشه باشه و امروز هلن و جک آن‌کال هستن، هر دوشون احساس مریضی می‌کنن و تقریبا به طور همزمان درخواست مرخصی می‌دن، هر دو ترنزکشن هم‌زمان، تعداد دکتر آن‌کال رو ۲ دریافت می‌کنن و هردو مرخصی می‌گیرن، حالا هیچ دکتری آن‌کال نیست که براساس قوانین درست نیست. write skew زمانی اتفاق میوفته که دوتا ترنزکشن به طور همزمان یک object رو می‌خونن و بخشی از اون رو آپدیت می‌کنن (ممکنه object های مختلفی رو آپدیت کنن). در این مورد، atomic single-object operation کمکی نمی‌کنه چون چند object درگیرن. همینطور automatic detection هم نمی‌تونه این مورد رو شناسایی کنه. بهترین راهکار اگر نخوایم از serializable isolation استفاده کنیم، استفاده از explicity lock هست.در همه موارد write skew این سه گام وجود داره:یک کوئری SELECT که با جستجو کردن یکسری شرایط، چک می‌کنه آیا شرایط خاصی وجود داره؟بنا به نتیجه کوئری اول، کد برنامه تصمیم می‌گیره که چطور ادامه بده.اگر برنامه تصمیم می‌گیره که ادامه بده، یک نوشتنی (INSERT, UPDATE, DELETE) در دیتابیس انجام میده. براساس این تغییر ایجاد شده، اگر نتیجه کوئری اول رو مجدد بگیری، نتیجه متفاوتی دریافت می‌کنی.معنی phantom به این افکت می‌گن که نوشتن در یک ترنزکشن، روی کوئری سرچ در ترنزکشن دیگه تغییر ایجاد کنه.Materializing conflictsفرض کن سیستم رزرو اتاق جلسه می‌خوایم بنویسیم که افراد یک اتاق رو برای یک بازه زمانی خاص رزرو می‌کنن. اینجا اگر از SELECT FOR UPDATE استفاده کنیم، چیزی SELECT نمیشه، پس چیزی هم lock نمیشه. راهکار می‌تونه این باشه که یک جدول تعریف کنیم و برای بازه‌های زمانی مثلا ۱۵ دقیقه‌ای به ازای هر اتاق و بازه زمانی یک ردیف تعریف کنیم، مثلا برای بازه زمانی ۶ ماه آینده. اینطور، حالا می‌تونیم از SELECT FOR UPDATE استفاده کنیم. به این روش Materializing conflicts می‌گیم. اما این جذاب نیست که پای کنترل concurrency به کدمون برسه و بیاد داخل data model مون، برای همین این راهکار آخرین گزینه‌مون هست و مثلا serializable isolation خیلی گزینه بهتری هست.Serializabilityقوی‌ترین سطح ایزوله‌سازی، گارانتی می‌کنه که اگرچه ممکنه ترنزکشن‌ها موازی اجرا بشن، اما نتیجه نهایی با اینکه هرکدوم به‌ترتیب و سریالی اجرا بشن، یکی خواهد بود.در اجرای ترنزکشن‌ها یک بخش خوبی از زمان صرف ارتباطات شبکه بین برنامه و دیتابیس میشه و اگر بخوایم فقط یک ترنزکشن در زمان پردازش کنیم، توان عملیاتی وحشتناک میشه، چون دیتابیس بیشتر زمانش رو باید منتظر بمونه. راهکار می‌تونه این باشه که از multi-statement transactions ها استفاده نکنیم و بجاش همه کد ترنزکشن رو به صورت stored procedure به دیتابیس بفرستیم. به این صورت ترنزکشن خیلی سریع‌تر بدون انتظار disk I/O یا شبکه، انجام میشه. اما معایبی هم داره که باعث شده محبوبیتش از دست بره، هر دیتابیسی زبون خودش رو داره و به روز نشدن و زشت به نظر میرسن. کدهای تو دیتابیس، مدیریت کردنشون سخته، سخته دیباگ بشن و مدیریت ورژن‌، مانیتورینگ، دپلوی‌شون سخته. دیتابیس خیلی پرفورمنسش مهمه و اگر کدش بد زده بشه، خیلی بدتر از کد بد سمت اپ هست. با این حال بعضی از دیتابیس‌ها از زبان‌های برنامه‌نویسی پشتیبانی می‌کنن. سریع‌ترن. اگر برنامه‌ای با نوشتن خیلی زیاد داشته باشیم، اینکه پردازش single-threaded transaction داشته باشیم، می‌تونه bottleneck جدی ایجاد کنه که یک راهکار می‌تونه پارتیشن‌بندی باشه که هر کدوم به طور جداگانه نوشتن رو انجام بدن.Two-Phase Locking (2PL)برای حدود ۳۰ سال این الگوریتم به طور گسترده برای serializability دیتابیس استفاده میشد. به این صورت عمل می‌کنه که چندین ترنزکشن این اجازه رو دارن که به طورهمزمان یک شی رو بخونن، با این شرط که هیچ‌کس در حال نوشتن اون نباشه. اما به محض اینکه یکی بخواد بنویسه، دسترسی اختصاصی، اجباری میشه. در این روش نه فقط نوشتن سایر نوشتن‌ها رو بلاک می‌کنه، بلکه خوندن‌ها رو هم بلاک می‌کنه و برعکس. این روش همه نوع race condition ها رو جلوگیری می‌کنه. برخلاف snapshot isolation که هیچ‌وقت reader ها writer ها رو بلاک نمی‌کردن و نه writerها readerها رو بلاک می‌کردن.اگر ترنزکشن A یک شی رو خونده و ترنزکشن B می‌خواد روی شی بنویسه، B باید صبرکنه تا A کارش رو commit یا abort کنه.اگر ترنزکشن A درحال نوشتن روی شی باشه و B بخواد مقدارش رو بخونه، B باید صبر کنه تا A کارش رو commit یا abort کنه.برای پیاده‌سازی این روش دو نوع قفل داریم، shared mode و exclusive mode. هر ترنزکشنی که می‌خواد بخونه میاد و lock in shared mode می‌کنه، چندین ترنزکشن می‌تونن به طور همزمان این نوع قفل رو بکنن. اما اگر ترنزکشنی exclusive lock روی شی‌ای زده باشه، باید صبر کنن. اگر ترنزکشنی بخواد بنویسه، در صورتی می‌تونه که هیچ‌کدوم از دو نوع قفل روش نباشه و اون موقع exclusive lock میزنه. وقتی ترنزکشنی قفل زد، باید اون lock رو تا پایان ترنزکشن نگه‌داره. بزرگترین مشکل این روش پرفرمنسشه که بخاطر کار با lock ها و از سمت دیگه وقوع deadlockهاست.حالت deadlock حالتیه که ترنزکشن A همینطور منتظر آزاد کردن lock توسط B گیر می‌کنه. در این موارد دیتابیس اتومات deadlock ها رو شناسایی و abort می‌کنه‌شون و لازمه که این ترنزکشن‌ها از سمت اپ دوباره انجام بشن.Predicate locksدر روش predicate lock به این روش عمل می‌کنیم:اگر ترنزکشن A می‌خواد شی‌ای رو بخونه که یک condition ای داره (SELECT query) باید shared mode prediacate lock روی اون شرط کوئری بذاره. اگر ترنزکشن همزمان B روی اون شرایط exclusive lock گذاشته باشه، A باید صبر کنه.اگر ترنزکشن A بخواد insert, update or delete انجام بده، باید چک کنه که مقادیر جدید و قدیم با هیچ predictate lock ای شامل میشن، اگر شامل بودن باید صبر کنه تا ترنزکشنی که قفل رو زده، تموم بشه.این روش، قفل روی حتی objectهایی که هنوز وجود ندارن و ممکنه در آینده بوجود بیان هم میزنه (phantoms). اگر two-phase locking شامل predictate lock بشه، دیتابیس جلوی همه write skew و انواع race condition‌ها میشه و ایزولیشنش serializable خواهد بود.Index-range locksمتاسفانه predicate lockها به خوبی عمل نمی‌کنن، اگر کلی قفل توسط ترنزکشن‌های فعال وجود داشته باشه، چک کردن برای قفل‌های مرتبط، کار زمان‌بری میشه. برای همین معمولا بجاش از index-range locking استفاده میشه (بهش next-key locking هم می‌گن). روش کارش روی مثال سیستم رزرو اتاق به این صورته:میگه که index روی room_id هست، و دیتابیس میاد از این ایندکس برای پیدا کردن رزروهای اتاق ۱۲۳ استفاده می‌کنه و به سادگی روی نتایجش قفل میزنه.یا روش جایگزین، اگر time-based index استفاده شده باشه برای پیدا کردن رزروهای درحال انجام، قفل روی این رنج زمانی همه اتاق‌ها گذاشته میشه.درواقع قفل روی فیلدی گذاشته میشه که براش index تعریف شده و رنج بیشتری رو نسبت به روش قبلی لاک می‌کنه.Serializable Snapshot Isolation (SSI)الگوریتم SSI بهمون full serializability رو میده اما مشکل پرفرمنس کمی نسبت به snapshot isolation داره. به روش Two-phase locking بهش مکانیزم pessimistic concurrenct control میگیم: بر این اساسه که هرچیزی ممکنه بد پیش بره، پس بهتره صبر کنن تا موقعیت امن باشه تا هر اقدامی انجام بشه. شبیه mutual exclusion برای محافظت از ساختار داده ها در برنامه‌نویسی‌های multi-thread هست. serial execusion نهایت این pessemistic هست که کل دیتابیس یا پارتیشن رو قفل می‌کنیم تا ترنزکشن انجام بشه. در مقابل serializable snapshot isolation یک تکنتیک کنترل همزمانی optimistic هست. که به این صورته که بجای متوقف کردن هرچیزی که ممکنه خطرناک باشه، ترنزکشن‌ها کارشون ادامه میدن، به این امید که همه چی خوب پیش بره، موقع کامیت کردن، دیتابیس چک می‌کنه که آیا اتفاق بدی ممکنه بیوفته؟ اگر بله، ترنزکشن abort میشه و باید retry کنه. در محیط‌هایی که ترنزکشن‌های زیادی اتفاق میوفته، روش خوبی نیست چون کلی ترنزکشن abort میشن.Detecting stale MVCC readsشناسایی خوندن تاریخ‌گذشته در MVCC: ایزولیشن snapshot معمولا با multy-version concurrency control پیاده‌سازی میشه. اینطور که ترنزکشن بعد شروع، تمام خوندن‌هاش رو از snapshot ای که موقع شروع ترنزکشن گرفته شده می‌خونه و بعد موقع کامیت، دیتابیس چک می‌کنه که آیا هیچ‌کدوم از نوشتن‌هایی که بعد از گرفتن snapshot نادیده گرفته شدن، کامیت شدن؟ اگر بله، ترنزکشن باید abort بشه. چرا صبر می‌کنیم تا کامیت که abort کنیم؟ چون که شاید ترنزکشن اصلا نوشتن نداشته باشه.روش Serializable Snapshot Isolation نسبت به two-phase locking سریع‌تره، چون نیازی به صبر کردن پشت lock برای ترنزکشن‌ها نیست و مشابه snapshot isolation نوشتن‌ها، خوندن‌ها رو بلاک نمی‌کنن و برعکس. و در مقایسه با serial execution، این روش محدود به توان عملیاتی یک هسته CPU نیست و می‌تونه conflict های کل ماشین و حتی پارتیشن‌های مختلف در چند ماشین رو شناسایی کنه. چیزی که پرفرمنس کلی SSI رو مشخص می‌کنه نرخ abort هاست که برای کاهشش لازمه که read-write transaction ها تا حد خوبی کوتاه باشن (ترنزکشن‌هایی که فقط می‌خونن و طولانی هستن، اوکی می‌تونن باشن).Network Faults in Practiceبنا به دلایل مختلف ممکنه بخشی از سیستم به مشکل بخوره، خیلی از سیستم‌ها لازمه که به طور اتومات بخش مشکل‌خورده رو شناسایی کنن:لودبالانسر لازمه که ارسال درخواست‌ها رو به نودی که مرده متوقف کنه.در دیتابیس‌های distributed با single-leader replication اگر لیدر مشکل خورد، یکی از دنبال‌کننده‌ها لازمه که تبدیل به لیدر جدید بشه.خبر گرفتن از نودها می‌تونه راهکار خوبی باشه اما نمیشه روش حساب کرد، اگر TCP acknowledge بگه که packet رسیده، بازم ممکنه اپ قبل هندل کردنش کرش کرده باشه، برای اینکه مطمئن شیم موفق بوده، لازمه که پاسخ مثبت از برنامه رو بگیریم. اگر timeout تنها راه شناسایی مشکل باشه، چقد باید واستیم تا بگیم مشکل خورده؟ زیاد واستیم، کاربر باید منتظر بمونه، اگر کم واستیم، ممکنه اشتباهی بخاطر تاخیر در پاسخ، فکر کنیم که نود مرده با اینکه یک تاخیر موقت بوده. وقتی یک نود مرده در نظر گرفته میشه، مسئولیتش باید به یک نود دیگه منتقل بشه، که این‌کار فشار بیشتری به نودهای دیگه و شبکه وارد می‌کنه و تهش می‌تونه باعث cascading failure بشه. متاسفانه زمان مشخصی وجود نداره برای زمان جابجایی پکت (d) و زمان پردازشش (r) که بگیم اگر 2d+r طول کشید، یعنی از دست رفته. برای همین اینکه یک نود رو به طور دائم مرده در نظر بگیریم، مشکل سازه. مشابه موقع رانندگی که زمان طی مسیر وابسته به ترافیکه، شبکه هم همینطوره. انواع تاخیر در packet ها ممکنه اتفاق بیوفته:اگر چندین نود به طور همزمان تلاش کنن که پکت به یک مقصد یکسان ارسال کنن، network switch باید به صف کنه‌شون و دونه دونه به لینک شبکه مقصد ارسال کنه. در لینک شبکه‌های شلوغ، ممکنه لازم شه که یک پکت صبر کنه تا یک slot بتونه بگیره (بهش network congestion می‌گیم) و اگر خیلی زیاد باشن که صف پر بشه، پکت drop میشه و این یعنی اینکه با اینکه نتورک اوکیه، اما باید دوباره ارسال بشه.اگر پکت به ماشین مقصد برسه و اگر همه هسته‌های CPU مشغول باشن، درخواستی که اومده باید در صف واسته.در محیط‌های virtualized، سیستم‌عامل گاهی برای ده‌ها میلی‌ثانیه متوثق میشه تا سیستم‌عامل دیگه‌ای از هسته‌های CPU استفاده کنه. در این زمان VM نمی‌تونه دیتایی که از شبکه اومده رو مصرف کنه و اون دیتا buffer میشه و این تاخیر شبکه رو بیشتر می‌کنه.در flow control که TCP داره، که هر نود، نرخ ارسالش رو محدود می‌کنه تا از overload جلوگیری کنه. که این یعنی یک صف بیشتر در ارسال‌کننده حتی قبل از اینکه دیتا وارد شبکه بشه.از این گذشته، TCP اگر acknowledge در یک timeout به دستش نرسه، پکت رو از دست‌رفته در نظر می‌گیره و مجدد ارسال می‌کنه. اگرچه اپ این از دست‌رفتن پکت و ارسال دوباره رو نمی‌بینه و چیزی که میبینه یک تاخیر در شبکه‌اس.تفاوت UDP با TCP: بعضی از برنامه‌هایی که نسبت به تاخیر حساس هستن، مثل ویدئوکنفرانس یا voice over IP از UDP بجای TCP استفاده می‌کنن. یک trade-off بین reliability و variability of delays وجود داره: در UDP دیگه flow control اجرا نمیشه و دوباره پکت‌های ازدست‌رفته رو ارسال نمی‌کنه. مناسب زمانیه که دیتا اگر تاخیر بخوره، بی‌ارزش میشه.در public cloud و دیتاسنترهای اشتراکی، کلی از منابع بین کلی از مشتری‌ها مشترک هستن و میزان تاخیر متغیره، بهتره که بجای قرار دادن یک timeout ثابت، سیستم‌ها به طور مستمر زمان پاسخ رو اندازه بگیرن و یک مقدار متغیری (jitter) بهش اضافه کنن.Synchronous Versus Asynchronous Networksبیان خط تلفن‌های سیمی رو تصور کنیم، یک تماس تلفنی، نیاز به یک ارتباط نقطه به نقطه با یک پهنای باند ثابت داشت که دیتای صدا رو منتقل کنه، این روش هم قابل اتکا بود و هم زمان طی مسیر شبکه، ثابت و مشخص بود. به این صورت که مقدار بیت لازم از شبکه در موقع تماس رزرو شده بود و گارانتی شده بود که دقیقا ۱۶ بیت از صدا هر ۲۵۰ میکروثانیه منتقل میشه. به این نوع شبکه‌ها Synchronous می‌گیم. اما در شبکه‌های الان دیتاهای متفاوتی با سایزهای متفاوت و نامشخصی منتقل میشن و شبکه رزرو یک ارتباط نمیشه و طوری طراحی شدن که در سریع‌ترین حالت، دیتا در شبکه منتقل بشه.Unreliable Clocksدر اپ‌ها زمان و Clock مهمه و از جنبه‌های متفاوتی بهش وابسته هستن:آیا ریکوئست timeout شده؟زمان پاسخ‌دهی صدک ۹۹ این سرویس چقدره؟چه مقدار کوئری در ثانیه به طور میانگین در ۵ دقیقه اخیر توسط سرویس هندل شده؟چقدر زمان هرکاربر در سایت گذرونده؟چه زمانی مقاله منتشر شده؟در چه تاریخ و زمانی، ایمیل یادآوری ارسال شده؟چه زمانی کش منقضی میشه؟زمان timestamp روی پیام ارور برای چه زمانی هست؟کامپیوترهای مدرن حداقل دو نوع مختلف clock دارن: time-of-day clock و monotonic clock.و time-of-day clock: بر اساس تقویم میاد و تاریخ و زمان رو برمی‌گردونه. که براساس تعداد ثانیه‌های گذشته از نیمه شب 1 jan 1970 هست. معمولا time-of-day با Network Time Protocol سینک میشه که این یعنی timestamp یک ماشین با بقیه ماشین‌ها یکیه. اگر ساعت محلی خیلی جلوتر از سرور NTP باشه، به اجبار تنظیم مجدد میشه و این یعنی در زمان به عقب برمی‌گرده.و Monotonic clocks: مناسب اندازه گیری مدت زمان هستن (فاصله زمانی بین دو اتفاق) مثل timeout یا زمان پاسخگویی سرویس. مقدار خود clock بی‌معنی می‌تونه باشه، مثلا تعداد نانوثانیه‌هایی که از روشن کردن سیستم می‌گذره و بین دو تا سیستم، عدد متفاوتی بهت میده. در سرورهای با چند CPU می‌تونه تایمر جداگانه‌ای به ازای هر CPU باشه. اگر فرکانس ساعت بیشتر از 0.05% تندتر یا کندتر باشه، NTP درستش می‌کنه. اما این باعث جلو یا عقب پریدن ساعت نمیشه.ساعت monotonic نیازی به synchronization نداره اما ساعت time-of-day لازمه که براساس NTP سرور یا یک منبع زمان خارجی ست بشه. روش‌های ما برای دریافت ساعت، به روش‌های متفاوتی می‌تونه صحیح نباشه:ساعت کوارتز داخل کامیپوتر خیلی دقیق نیست و بنا به گرمای سیستم دقتش رو از دست بده.اگر ساعت کامپیوتر خیلی با NTP سرور متفاوت باشه، به اجبار سینک میشه و برنامه‌ها شاهد به عقب برگشتن زمان ممکنه باشن.اگر یک نود به طور اتفاقی پشت فایروال برای NTP سرور قرار بگیره، ممکنه زمان اشتباهش تا مدتی قابل شناسایی نباشه.همگام‌سازی NTP بنا به تاخیر شبکه می‌تونه تحت تاثیر قرار بگیره.ممکنه بعضی از NTP سرورها به درستی پیکربندی نشده باشن، با اینکه کلاینت‌های NTP از چندین سرور مقادیر رو دریافت می‌کنن، اما بازم این ریسک وجود داره.در ماشین‌های مجازی، ساعت سخت‌افزاری شبیه‌سازی شده که می‌تونه چالش‌های بیشتری بوجود بیاره. وقتی یک هسته CPU بین چندین VM مشترکه، هر VN برای ده‌ها میلی‌ثانیه متوقف میشن تا VM دیگه عملیاتش رو انجام بده و این از دید برنامه‌ها اینطوره که ساعت جلو پریده.اگر برنامه‌رو روی وسیله‌ای اجرا می‌کنی که کنترل کامل نداری، به ساعت سخت‌افزاری احتمالا نمیشه اعتماد کرد، بعضی از کاربرها، ساعت‌شون رو روی زمان نادرستی ست می‌کنن.در نتیجه‌ی Leap seconds ما دقایقی خواهیم داشت که ۵۹ یا ۶۱ ثانیه هستن، که این فرضیات زمانی سیستم رو بهم می‌ریزه.این امکان‌پذیره که به دقت خیلی بالا به کمک دریافت‌کننده‌های GPS و Percision Time Protocol دست پیدا کنیم. اما این نیاز به تلاش و تخصص زیادی داره و کلی مسیر برای اشتباه شدن ساعت وجود داره.مشکلات مرتبط با ساعت، فهمیدنش راحت نیست و بدون اینکه متوجه بشیم، ممکنه لحظه به لحظه جلوتر یا عقب‌تر بیوفته، برای همین مانیتورینگ پیوسته ساعت، مهمه تا آسیب زیادی وارد نکنه.یک سناریو رو تصور کنیم که در multi-leader replication کلاینت اول روی نود ۱ یک کانتر رو مقدار میده و این مقدار replicate میشه به نود ۳ و اونجا، کانتر یکی بهش اضافه میشه ولی توی نود ۳ کلاک ۴ میلی‌ثانیه عقبه و وقتی رپلیکیت میشه به نود ۲، timestamp‌ اش عقب‌تر از مقدار دهی اولیه نود ۱ هست، در نتیجه به روش Last Write Wins مقدار نود ۱ به عنوان مقدار نهایی در نظر گرفته میشه و تغییر نود ۲ میپره. راهکارش اینه که بجای استفاده از monotonic clock که بهش phisical clocks هم می‌گیم، از logical clocks استفاده کنیم که به این صورته که بجای اندازه‌گیری زمان روز، ترتیب relative رویدادها رو نشون میده. در روش snapshot isolation معمولا از شمارشگر شناسه ترنزکشن استفاده میشه و اینطور میفهمه که ترتیب انجام ترنزکشن‌ها چیه، اما اگر دیتابیس‌ها distributed باشن، این کار شدنی نیست و باید از synchronized time-of-day clock استفاده کرد و برای رفع چالش معمولا از TrueTime API استفاده می‌کنن که یک بازه زمان رو میده که شامل حداقل و حداکثر (earliest, lastes) زمانه و با مقایسه این بازه با بازه ترنزکشن دیگه، بررسی می‌کنن که کدوم زودتر اتفاق افتاده، ولی برای مثال گوگل از GPS receiver و یا atomic clock ها در دیتاسنتر‌هاش استفاده می‌کنه.سناریو دیگه‌ای که ساعت در سیستم‌های distributed می‌تونه خطرناک باشه، زمانیه که دیتابیس single leader per partioion داریم، لیدر فقط زمانی می‌تونه نوشتن‌ها رو قبول کنه که هنوز لیدر باشه و یک راهش اینه که lease رو از نودهای دیگه بگیره، lease یک چیزی شبیه lock هست که مشخص می‌کنه کدوم نود تا یک بازه زمانی مشخص leader هست و قبل منقضی شدن باید renew بشه وگرنه یک نود دیگه می‌گیردش. حالا فرض کنیم که مثلا ۱۰ ثانیه قبل منقضی شدن، نود لیدر تمدیدش می‌کنه ولی آخرین بار که چک کرده، ۱۵ ثانیه برنامه متوقف بشه و بعدش ادامه پیدا کنه، در این حالت، دیگه لیدر نیست ولی تا iteration بعدی متوجه نمیشه که دیگه لیدر نیست. و متاسفانه دلایل زیادی وجود داره که اجرای برنامه برای مدتی متوقف بشه:خیلی از زبان‌ها garbage collector دارن و حتی اگر به طور همزمان هم اجرا بشه، به طور کامل نمی‌تونه موازی اجرا بشه. و در بدترین حالتش برنامه رو بارها متوقف می‌کنن.در VM ها، ماشین مجازی می‌تونه suspend و resume بشه.در وسایل کاربر، مثل لب‌تابش، اجرا ممکنه متوقف و ادامه پیدا کنه، مثلا وقتی در لپ‌تاپ می‌بنده.در سیستم عامل‌ها content-switch به یک thread دیگه یا hyper switch به یک VM دیگه، می‌تونه اجرا رو متوقف کنه.اگر برنامه به صورت sync با دیسک کار کنه، ممکنه thread به دلیل کندی عملیات disk I/O متوقف بشه.اگر سیستم‌عامل طوری تنظیم شده باشه که اجازه swapping به دیسک رو بده، موقع دسترسی به memory ممکنه لازم باشه که از دیسک به مموری لود بشه و thread به دلیل کندی عملیات I/O متوقف بشه.در سیستم‌های Unix ممکنه که پردازش با ارسال سیگنال SIGNSTOP متوقف بشه و تا زمانی که سیگنال SIGCONT نیاد، ادامه پیدا نمی‌کنه.یکسری سیستم هستن که اگر در زمان مشخصی و قبل ددلاین پاسخ داده نشن، مشکل جدی در کل سیستم ایجاد می‌کنن، مثل کامپیوترهای کنترل هواپیما، موشک، ربات، ماشین و... به این سیستم‌ها hard real-time می‌گیم. برای فراهم کردن یک سیستم با ضمانت real-time در همه سطح‌ها به پشتیبانی نیازه: سیستم‌عامل real-time که تخصیص CPU در زمان‌های مشخصی انجام بده، کتاب‌خونه‌هایی که بدترین حالت زمان اجراشون داکیومنت شده باشه، تخصیص داینامیک مموری محدود شده باشه و یا کلا ممنوع باشه (garbage collector real-time وجود داشته باشه ولی اپ کار زیادی براش ایجاد نکنه) و میزان زیادی تست و اندازه‌گیری که مطمئن بشیم گارانتی‌ها رو پوشش میده. برای همین زبان‌ها و کتابخونه‌ها و ابزارهای محدودی برای این کار قابل استفاده هستن، برای همین، گارانتی real-time برای بیشتر سیستم‌های سمت سرور اقتصادی و مناسب نیست.برای محدود کردن اثر garbage collection میشه این زمان‌های کوتاه از دسترس خارج شدن رو برنامه‌ریزی کرد و درخواست‌ها رو در اون زمان به نودهای دیگه ارسال کرد و صبر کرد تا GC pause تموم بشه و به این روش درخواستی درحال پردازش متوقف نمیشه. راهکار دیگه اینه که از GC فقط برای short-lived object ها استفاده کنیم که سریع کالکت میشن و هر از چندگاهی پردازش‌ها رو رستارت کنیم و توی این زمان رستارت، ترافیک به سایر نودها بره.ممکنه یک نود درخواست‌ها رو دریافت کنه، اما پاسخ‌هاش به سایر نودها نرسه و ممکنه حتی متوجه نشه که نمیرسه، و یا GC یک دقیقه متوقفش کنه و بعد تموم شدن کارش، حتی متوجه نیست که یک دقیقه در دسترس نبوده و سایر نودها اون رو مرده در نظر گرفتن. این سناریوها نشون میده که یک نود، وضعیت خودش رو نمی‌تونه مشخص کنه و در سیستم‌های distributed نمیشه به یک نود فقط اتکا کرد. برای همین quorum در نظر می‌گیریم، معمولا به این صورت که باید اکثریت بیش از نصف نودها رای بدن که یک نود مرده تا مرده در نظر گرفته بشه.در بخش‌های مختلفی از سیستم، لازمه که فقط یکی از یک‌چیزی وجود داشته باشه:فقط یک نود لیدر در پارتیشن دیتابیس باشه.فقط یک ترنزکشن یا کاربر اجازه داشته باشه که lock یک منبع یا شی رو داشته باشه.فقط یک کاربر بتونه یک نام‌کاربری خاص رو بگیره.برای این کار، quorum پاسخگو نیست، چون اگر مثلا نود لیدر متوقف شده باشه و حدنصاب نودها یک لیدر دیگه انتخاب کنه و نود لیدر قبلی بازیابی بشه و فک کنه هنوز لیدره، بازم مشکل در سیستم ایجاد می‌کنه. یا می‌تونیم client رو مجبور کنیم که lease رو قبل از دسترسی به فایل از lock server بگیره، اما اگر بازم کارش زیاد طول بکشه و lease منقضی بشه و کاربر دیگه تو این تایم lease رو بگیره و تغییرات بده، وقتی کاربر متوقف شده برگرده، فک می‌کنه lease معتبر داره و میاد در فایل می‌نویسه و فایل رو مشکل‌دار می‌کنه. برای حل این مشکل نیاز به راهکاری داریم که درخواست‌کننده بفهمه که باور &#34;اون انتخاب شده بودنش&#34; غلطه. یک راهکار ساده اسمش fencing هست. فرض کنین هروقت که lock server میاد lease میده، همراهش یک fencing token هم بده، که یک شمارشگره که هر بار لاک گرفته میشه یکی بهش اضافه میشه و هروقت کاربر درخواست نوشتن میده همراهش این توکن رو هم میفرسته و در صورتی که شماره توکن کمتر از آخرین نوشتنی باشه که انجام شده، رد میشه. اگر ZooKeeper به عنوان lock server استفاده بشه این اتفاق رو می‌تونه هندل کنه. اما مثلا برای مدیریت فایل میشه که این توکن رو توی نام فایل بیاریم.Byzantine Faultsمشکل سیستم‌های distributed سخت‌تر میشه اگر ریسک این وجود داشته باشه که یک نود دروغ بگه، ارسال پاسخ‌های معیوب. مثلا ادعا کنه که یک پیام رو دریافت کرده اما در واقع نکرده باشه. به این نوع رفتارها Byzantine fault می‌گیم و به فرآیند به نتیجه رسیدن در همچین فضای بدون اعتمادی می‌گیم Byzantine Generals Problem. زمانی این اتفاق میوفته که یک نود از پروتکل‌ها پیروی نمی‌کنه و توسط attacker ها به شبکه نفوذ شده. مثلا در محیط هوا فضا، ممکنه مموری یا CPU در اثر تشعشعات دیتاهاشون ناقص بشه. یا در سیستم‌های چند سازمانی، بعضی از اونها، ممکنه بخوان دیگران رو فریب بدن. مثل شبکه بیت‌کوین.اگر همه نودها برای سازمان خودتون و در دیتاسنتر باشه، این مشکل احتمالش کم خواهد بود اما اگر به طور کلی نودها رو صادق هم در نظر بگیریم، جا داره که سیستم رو دربرابر دروغ‌ها کوچیک با مکانیزم‌های ساده مقاوم کنیم: ممکنه بعضی از packet های نتورک ناقص بشن و از دست checksum که TCP, UDP دارن، در بره و خوبه که برای چنین مواردی چک‌سام در لایه اپلیکیشن داشته باشیم. و همینطور همه ورودی‌های کاربر رو چک کنیم که در محدوده‌ی محدودیت‌هامون باشن. یا NTP client ها رو با چندین آدرس سرور کانفیگ کنیم که از همه‌شون time range رو استعلام بگیرن و اگر سروری همگام نبود متوجه بشن.برای تعریف system model یکسری فرضیه باید در نظر بگیریم. در خصوص فرضیه‌های timing این سه سیستم مدل رو داریم:مدل Synchronous: در این مدل، تاخیر شبکه، process pause و clock error ها در یک محدوده مشخص هستن.مدل Partially synchronous: در این مدل بیشتر اوقات در محدوده هستن اما گاهی ممکنه بیشتر بشه. مدل واقع‌گرایانه اس.مدل Asynchronous: در این مدل هیچ فرضیه‌ای درباره زمان وجود نداره و حتی timeout معنایی نداره.و در مورد مشکل‌خوردن نود‌ها سه سیستم مدل متداول زیر رو داریم:مدل Crash-stop faults: یک نود که مشکل میخوره، میره و هیچوقت بر نمی‌گرده.مدل Crash-recovery faults: نود که مشکل می‌خوره، احتمالا بعد از یک زمان نامشخص شروع به پاسخگویی می‌کنه، اینطور فرض میشه که نودها یک stable storage دارن.مدل Byzantine (arbitrary) faults: نودها ممکنه هرکاری بکنن، از جمله تلاش برای فریب نودهای دیگه.دو ویژگی safety و liveness و سیستم مدل، درستی یک الگوریتم distributed رو به خوبی نشون میدن:- و safety یعنی اینکه چیز بدی اتفاق نیوفته، و liveness یعنی یک چیز خوبی &#34;در نهایت&#34; اتفاق بیوفته. اگر safety نقض بشه، می‌تونیم یک نقطه زمان پیدا کنیم که مشکل پیش اومده و یک عملیات که باعث شده رو شناسایی کنیم و بعد اتفاق افتادن دیگه نمیشه undone کردش. اما liveness معمولا نمیشه یک زمان مشخص برای اتفاق افتادنش پیدا کرد، مثلا درخواستی که ارسال شده ولی دریافت نشده، اما همیشه امید هست که در آینده درست بشه (با دریافت پاسخ).Consistency and Consensusتوی این فصل روش‌های قوی‌تر consistency model در سیستم‌های distributed رو بررسی خواهیم کرد که خب در مقایسه با روش‌های ضعیف‌تر پرفرمنس پایین‌تر و نسبت به خطاها آسیب‌پذیرتر هستن.Linearizabilityدر دیتابیس‌های eventually consistent (که یعنی اگر به میزان نامشخصی صبر کنه بلخره دیتاشون یکسان خواهد شد) اگر از دو رپلیکا یک سوال یکسان در یک زمان بپرسی، ممکنه دو پاسخ متفاوت بگیری. اگر این توهم رو ایجاد کنیم که فقط یک رپلیکا وجود داره، در نتیجه هر کلاینت یک دید یکسان به داده‌ها خواهد داشت و نگران لگ رپلیکاها نخواهد بود. این ایده پشت linearizability هست. به طور کلی تعریف ساده‌اش اینه که سیستم طوری به نظر میرسه که فقط یک کپی از داده وجود داره و همه عملیات‌ها به صورت اتومیک هستن.در خطی سازی اینطوره که اگر درخواست خوندن قبل نوشتن باشه، مقدار ثابت و برابر با مقدار اولیه متغیره. اگر درخواست خوندن بعد نوشتن باشه، مقدار ثابت و برابر با مقدار بعد نوشته شدن متغیره، اما درخواست خوندن در زمان نوشتن می‌تونه در هر لحظه مقدار قبل یا بعد نوشتن باشه که در خطی‌سازی این گارانتی باید وجود داشته باشه که یکبار که مقدار جدید نوشته و خونده شد، همه خوندن‌های بعدی، مقدار جدید نوشته شده رو ببینن تا زمانی که دوباره نوشتنی اتفاق بیوفته.کاربردش کجاس؟ فک کنید نتایج یک بازی فوتبال رو دارین چک می‌کنین، نتیجه‌ای که چند ثانیه تاریخ گذشته باشه توی این موقعیت می‌تونه مشکل‌ساز باشه.سیستم single-leader replication لازمه مطمئن بشه که فقط یک لیدر هست و split brain اتفاق نیوفتاده. یک راه انتخاب لیدر به کمک lock هست. هر نودی که شروع به کار می‌کنه، سعی می‌کنه که lock رو بگیره و تنها یکی که موفق میشه، لیدر میشه. روش پیاده‌سازی lock مهم نیست، مهمه که فقط linearizable باشه: همه نودها موافق باشن که کدوم نود lock رو داره. Apache ZooKeeper، Apache Curator و linear storage service ها کمک می‌کنن.برای گارانتی محدودیت‌ها و یکتایی‌ها نیاز به linearizability هست، مثلا وقتی به طور همزمان دو نفر می‌خوان یک فایل با یک نام ایجاد کنن.پیاده‌سازی Linearizability:در Single-leader replication این امکان وجود داره، در الگوریتم Consensus اینطور هست، در Multi-leader replication به دلیل اینکه ممکنه conflict در نوشتن بوجود بیاد و نیازمند resolution هست، امکانش وجود نداره، در Leaderless replication بستگی به چطور تعریف کردن quorum ها داره و احتمالا نیست (دو روش Last write wins و Sloppy Auorums and Hinted Handoff نیستن و در صورتی که از روش read repair به طور synchronous استفاده کنیم Linearizability خواهیم داشت اما به شرطی که بعدش last-write-wins نداشته باشیم).فرض کنیم که دو تا دیتاسنتر داریم و ارتباط بین این دو قطع شده، اگر از مولتی-لیدر دیتابیس استفاده کنیم، هر دو دیتاسنتر به طور معمول به کارشون ادامه میدن و نوشتن‌ها queue میشن و زمانی که ارتباط شبکه برقرار شد منتقل میشن. اگر از سینگل-لیدر استفاده کنیم، نوشتن‌ها و خوندن‌هایی که linearizabile هستن در دیتاسنتری که دنبال‌کننده‌اس متوقف میشه و فقط خوندن‌های stale که غیر linearizable هستن امکان‌پذیره.قضیه CAP:این مشکل فقط مختص single-leader یا multi-leader replication نیست و هر دیتابیس linearizable این مشکل رو داره و همینطور فقط مخصوص multi-datacenter نیست و در هر شبکه غیر قابل اعتماد ممکنه رخ بده:اگر برنامه شما نیاز به linearizability داره و چند رپلیکا از بقیه رپلیکاها بخاطر مشکل شبکه قطع شدن، درنتیجه تعدادی از رپلیکاها نمی‌تونن در زمان قطع بودنشون درخواست‌ها رو پردازش کنن: در نتیجه مجبورن صبر کنن تا مشکل شبکه حل بشه و یا ارور برگردونن.اگر برنامه نیازی به linearizability نداره، می‌تونه نوشتن‌هاش به طریقی انجام بشه که هر رپلیکا مستقل درخواست‌ها رو پردازش کنه، حتی اگر از بقیه رپلیکاها قطع شده. در این روش رپلیکا در دسترس باقی می‌مونه ولی عملکردش غیر linearizable هست.گاهی CAP رو به عنوان Consistency, Availability, Partition tolerance: pick 2 out of 3 معنی می‌کنن. اما وقتی شبکه به درستی کار می‌کنه، سیستم می‌تونه هر دو consistency (Lineariziblity) و total availability رو در اختیارمون بذاره، زمانی که در شبکه مشکل پیش بیاد، باید بین linearizability و total availability یکی رو انتخاب کنی، پس بنابراین بهتره که CAP رو اینطور معنی کنیم: Consistent or Available when Partioned. این یک تئوری قدیمیه که فقط درباره قطع شدن ارتباط صحبت می‌کنه و هیچی درباره تاخیر در شبکه یا نود مرده یا trade-off های دیگه چیزی نمیگه.در اکثر پیاده‌سازی‌ها یک کپی از اطلاعات در حافظه اصلی هستن و چند کپی دیگر در کش‌های مختلف برای افزایش سرعت هستن و این کپی‌ها به صورت Async به‌روز میشن، پس Linearizability از دست میره، معمولا دلیل اصلی استفاده نکردن از linearizibility پرفرمنس هست و نه fault tolerance.Ordering Guaranteesدلایل زیادی برای توجه به ترتیب وجود داره که یکی‌شون حفظ رابطه علیت هست که بارها بهش اشاره کردیم: مثل consistent prefix read که میگه انتظار داریم اول سوال رو ببینیم و بعد پاسخ. یا چند لیدری که انتظار داریم ردیف اول ایجاد بشه و بعد بخواد آپدیت بشه. و یا در snapshot isolation انتظار داریم که اگر در اسنپ‌شات پاسخ یک سوال هست، خود سوال هم باشه. یا در مورد write skew و phantoms اجازه میده سارا از شیفت مرخصی بگیره چون فک می‌کنه باب هنوز شیفت هست که البته SSI این رو شناسایی می‌کنه. یا در مورد فوتبال نگاه کردن سارا و باب، باب نتیجه تاریخ گذشته رو از سرور بعد اینکه سارا نتیجه جدید رو می‌گه، می‌گیره که به دلیل cross-channel timing dependencies هست (اطلاعات از دو کانال میره، مثلا از یک سمت از صف میاد و از یک سمت از file storage). اگر سیستم از ترتیب علیت پیروی کنه، می‌گیم که casually consistent هست.در linearizability رابطه علیت رعایت میشه ولی همونطور که قبلا صحبت شد، مسائل پرفرمنسی رو داره، اما خوشبختانه راه میانه‌ای وجود داره، causal consistency قوی‌ترین نوع consistency هست که با کند شدن شبکه کند نمیشه و در صورت قطع شدن شبکه، در دسترس باقی می‌مونه. برای این‌کار به روشی برای شرح knowledge نود در سیستم نیاز داریم، اگر نود مقدار X رو موقعی که در خواست نوشتن Y رو داده دیده باشه و رابطه این دو ممکنه از نوع علیت باشه، تحلیلگر باید بتونه بگه که آیا مقدار X رو موقعی که Y رو تولید می‌کردی مطلع بودی؟ برای مشخص کردن ترتیب علیت، دیتابیس باید بدونه که کدوم ورژن از داده توسط برنامه خونده شده، به این دلیل شماره ورژن عملیات قبلی موقع نوشتن به دیتابیس برگردونده میشه. ایده مشابه‌ای در شناسایی conflict توسط SSI استفاده میشه.Sequence Number Orderingتوی خیلی از برنامه‌ها، کاربرا کلی اطلاعات رو قبل از نوشتن چیزی، می‌خونن و شناسایی رابطه علیت یک overhead زیادی داره. راه دیگه اینه که از sequence number یا timestamp استفاده کنیم تا ترتیب رویدادها رو مشخص کنیم. timestamp نیازی نیست که از time-of-day clock بیاد و می‌تونه از logical clock بیاد که یک الگوریتم برای تولید توالی اعداد برای مشخص‌کردن عملیات‌هاس که معمولا یک increment به‌ازای هر عملیاته. در single leader replication، لیدر می‌تونه یک کانتر رو برای هر عملیات اضافه کنه و به هرکدوم در replication log مرتبط کنه. حالا اگر دنبال کننده lag هم داشته باشه، همیشه ترتیب رو خواهد دونست.اما اگر یک لیدر نباشه، multi-leader or leader-less، راه روشنی برای تضمین ترتیب اعداد برای عملیات‌ها وجود نداره. این راهکارها در عمل استفاده میشه:هر نود به طور مستقل توالی اعداد تولید می‌کنه. مثلا اگر دوتا لیدر داریم، یکی فقط اعداد فرد و دیگری فقط اعداد زوج تولید می‌کنن. می‌تونیم چند بیت در نمایش باینتری توالی اعداد رو برای unique node identifier رزرو کنیم که مطمئن بشیم هیچ توالی یکسانی تولید نخواهد شد.می‌تونیم time-of-day clock timestamp به هر عملیات الحاق کنیم. این timestamp توالی نیست اما اگر دقت خوبی داشته باشه می‌تونه بطور موثر ترتیب عملیات‌ها رو مشخص کنه. این روش برای last write wins conflict resolution استفاده میشه.میشه از قبل بلاک‌هایی از توالی اعداد رو به هر نود اختصاص داد و هر کدوم به طور مجزا از بلاکش استفاده کنه و موقعی که توالی اعداد باقی‌مونده کم شده، یک بلاک جدید بهش اختصاص داده میشه.اما هیچکدوم از این روش‌ها consistent with causality نیستن. یک روش به نام Lamport timestamp وجود داره که این گارانتی رو میده. به این صورت که هر نود یک unique identifier داره و هر کدوم از نودها یک شمارنده اعداد داره و Lamport timestamp به سادگی جفتی از (counter, node ID) است. اگر دوتا timestamp داشته باشی، اونی که conter value بزرگتری داره، بزرگتره، اگر هم مقدارشون یکسان باشه، اونی که node ID بزرگتری داره، بزرگتره. فرق این روش با شمارنده زوج/فرد اینه که هر نود حواسش به max شمارنده هست، یعنی اگر درخواستی دریافت می‌کنه که مقدار شمارنده‌اش بیشتر از شمارنده خودشه، بلافاصله مقدار کانترش رو با اون max یکی می‌کنه.Total Order Broadcastبرای پیاده‌سازی یک چیزی که نیاز به uniqueness constraint داره، مثل انتخاب نام کاربری، داشتن ترتیب عملیات‌ها کارایی لازم رو نداره و به دونستن اینکه چه زمانی اون درخواست نهایی شده هم نیاز داریم. به این مطلع بودن از اینکه چه زمانی total order نهایی شده Total order broadcast می‌گیم.اگر سیستم یک هسته CPU داشته باشه،تعریف total order عملیات‌ها آسونه، ترتیبی هست که در CPU اجرا میشن، اما در سیستم‌های distributed اینکه همه نودها روی یک ترتیب کلی موافقت کنن کمی سخته. در single-leader replication ترتیب کلی همه عملیات‌ها توسط یک نود که لیدره انجام میشه. چالش اینجاست که سیستم رو چطور scale کنیم که بتونیم توان عملیاتی بیشتری از توان یک نود رو هندل کنیم و همینطور چطور مردن لیدر رو هندل کنیم. در ادبیات سیستم‌های توزیع‌شده این مشکل با نام total order broadcast یا atomic broadcast شناخته میشه. برای این کار همیشه به این دو مورد نیاز داریم:به Reliable delivery: هیچ پیامی از دست نره: اگر پیامی به یک نود رسید، به همه نودها رسیده.و Totally ordered delivery: پیام‌ها به همه نودها با ترتیب یکسانی برسن.استفاده از total order broadcastسرویس‌های اجماعی مثل ZooKeeper و etcd درواقع این روش رو پیاده‌سازی کردن. چیزی که برای database replication دقیقا نیاز داریم همین total order broadcast هست. همینطور برای پیاده‌سازی serializable transactions می‌تونه استفاده بشه. همینطور برای پیاده‌سازی lock service که fencing token ها رو ارائه میده کاربردیه. در Total order broadcast به صورت async هست پیام‌ها گارانتی میشه که با ترتیب ثابتی برسه اما گارانتی‌ای برای اینکه کی برسه وجود نداره. اما در linearizablity گارانتی recency وجود داره که گارانتی می‌کنه که آخرین مقدار نوشته شده خونده بشه. البته که بر روی total order broadcast میشه linearizable storage رو ساخت. پیاده‌سازی total order broadcast به کمک append-only log:اضافه کردن لاگ به صورت آزمایشی که نشون بده اون نام‌کاربری رو می‌خوای.خوندن لاگ و صبر کردن برای پیامی که تو اضافه کردی تا دوباره بهت تحویل بشه.چک کردن هرپیامی که نام‌کاربری که خواستی رو بخواد، اگر اولین پیام برای درخواست اون نام‌کاربری برای خودته، یعنی موفق بوده و درخواست ثبت نام‌کاربری رو میفرستی و به کاربر تایید رو میدی.حالا اگر بخوای همین پروسه رو linearizable هم بکنی، این گزینه‌ها وجود داره:خوندن واقعی زمانی اتفاق بیوفته که پیامی که توی لاگ نوشتی، برگرده و بهت تحویل بشه. بنابراین موقعیت پیام در لاگ، زمانی که خوندن اتفاق میوفته رو تعریف می‌کنه.اگر لاگ بهت این امکان رو میده که جایگاه آخرین پیام رو به صورت linearizable بگیری، صبر کنی که تا اون جایگاه، پیام‌ها بهت تحویل بشه و بعد خوندن رو انجام بده.می‌تونی خوندنت رو از یک رپلیکا که به صورت sync آپدیت میشه انجام بدی.پیاده‌سازی total order broadcast به کمک linearizable storage:فک کن یک linearizable register که یک integer ذخیره می‌کنه داری که یک عملیات اتومیک increment-and-get رو داره. به ازای هر پیام که می‌خوای از طریق total order broadcast بفرستی، اون linearizable int رو increment-and-get می‌کنی و مقدارش رو به عنوان sequence number به پیام الحاق می‌کنی. و پیام رو به همه نودها میفرستی (اگر ناموفق شد دوباره میفرستی) و دریافت کننده ها پیام‌ها رو براساس این شماره ترتیب، مرتب می‌کنن و اعمال می‌کنن. اما مشکل زمانی پیش میاد که این نودی که شمارشگر توالی رو تولید میکنه مشکل بخوره یا شبکه‌اش تاخیر بخوره، اینجاس که باز راهکار اجماعه و باید بریم سراغ مشکل اجماع.Distributed Transactions and Consensusدر ظاهر ساده‌اس و یعنی اینکه چندتا نود روی یک چیزی توافق کنن، اما کلی سیستم مشکل‌خورده بر این باور اشتباه ساخته شدن که این مشکل رو به سادگی میشه حل کرد. موقعیت‌هایی وجود داره که مهمه نودها درباره‌اش توافق داشته باشن. به طور مثال:انتخاب Leader: در single-leader replication همه نودها باید روی یک نود به عنوان لیدر توافق داشته باشن. اگر تعدادی از نودها به دلیل مشکل در شبکه، نتونن با بقیه ارتباط برقرار کنن جایگاه رهبر باید به بحث گذاشته باشه. در این موقعیت توافق مهمه که خروجیش موقعیت split brain نشه که موجب inconsistenct و data loss میشه.در موقعیت Atomic commit: در دیتابیس وقتی که از transaction در چندین نود یا پارتیشن پشتیبانی میشه، این مشکل رو خواهیم داشت که ترنزکشن ممکنه در بعضی از نودها مشکل بخوره و در بعضی‌ها موفق باشه. اگر بخوایم Atomicity رو حفظ کنیم، باید همه نودها توافق کنن که خروجی ترنزکشن: ناموفق بوده و باید roll back بشه یا همه commit کنن. به این اجماع میگن مساله atomic commit.Atomic Commit and Two-Phase Commit (2PC)اگر در چندین نود بخوایم یک ترنزکشن رو انجام بدیم راهکار چیه؟ نمیشه به سادگی به صورت مجزا به هر نود یک ترنزکشن کامیت کرد، اگر این‌کار رو بکنیم به سادگی ممکنه در چند نود کامیت موفق باشه و در بقیه ناموفق بشه که خب این، گارانتی atomicity رو نقض می‌کنه. بعضی از نودها شاید conflict یا constraint violation بخورن و اجرای ترنزکشن رو متوقف کنن. ممکنه بعضی از درخواست‌های کامیت در شبکه از دست برن و به دلیل تایم‌اوت متوقف بشن. بعضی از نودها ممکنه قبل از اینکه کامل نوشته بشن، مشکل بخوردن و بعد ریکاوری roll back بشن. ترنزکشن بعد اینکه کامیت شده، دیگه نمیشه نامعتبر بشه و نظرمون رو عوض کنیم، برای همین فقط در صورتی باید کامیت بشه که مطمئن باشیم باقی ترنزکشن‌ها کامیت خواهند شد.Introduction to two-phase commitیک الگوریتم برای دست‌یابی به ترنزکشن اتومیک در چند نود هست که مطمئن میشه همه نودها commit و یا abort خواهند کرد. روش کارش هم به این صورته که درخواست‌های نوشتن به دو نود میره و بعد در دوفاز کامیت انجام میشه، در فاز اول استعلام prepare بودن از همه نودها گرفته میشه و اگر همه جواب مثبت دادن، در فاز بعدی درخواست commit برای همه‌شون میره.حواسمون باشه که two-phase commit و two-phase locking (برای ایجاد serializable isolation بود) که قبلا درباره‌اش صحبت کردیم، دو موضوع کاملا متفاوت هستن.در 2PC از یک کامپوننت جدید که استفاده میشه که به طور معمول در single-node transaction ها وجود نداره: coordinator که با نام transaction manager هم شناخته میشه. در صورتی که در طی درخواست prepare مشکلی پیش بیاد یا time out بشه، coordinator ترنزکشن رو abort می‌کنه، اما اگر پاسخ مثبت دادن و درخواست commit ارسال شد، دیگه هیچ راه برگشتی وجود نداره و اگر نودی با مشکل برخورد، اونقدر coordinator تلاش مجدد می‌کنه تا تایید کامیت رو بگیره. اگر نود جواب مثبت به prepare داد، و در این حین coordinator مشکل خورد یا شبکه مشکل پیدا کرد و درخواست commit به نود نرسید، نود کاری بجز صبر کردن نمی‌تونه بکنه، به این حالت in doubt یا uncertain می‌گیم. در این حالت تنها راه اینه که صبر کنه coordinator دوباره بازیابی بشه، برای همین کوردینیتور باید تصمیم commit یا abort شدن رو قبل از ارسال درخواست commit یا abort به participant ها، در transaction log بر روی دیسک بنویسه.به two-phase commit اصطلاحا blocking atomic commit protocol هم می‌گن، چون وقتی که منتظر بازیابی coordinator میشه، متوقف میشه. اما راهکار جایگزینی به نام three-phase commit هست که nonblocking هست، اما برای استفاده ازش باید بتونیم با قطعیت بگیم که نود crash کرده یا نه تا بتونه atomicity رو گارانتی کنه. اما در عمل نمیشه با قطعیت این رو گفت و ممکنه شبکه تاخیر خرده باشه و نود سالم باشه، در نتیجه همچنان از 2PC استفاده میشه.Distributed Transactions in Practiceدر عمل سعی میشه از distributed transaction ها استفاده نشه، مثلا در MySQL گزارش شده که بیش از ۱۰ برابر از single-node transaction کندتره. یک بخش خوبی از این هزینه بخاطر disk forcing در 2PC برای بازیابی از مشکله. ترنزکشن‌ها در دیتابیس‌های توزیع شده به دو صورت می‌تونن باشن:به صورت Database-internal distributed transactions: یکسری دیتابیس توزیع شده که از replication یا partitioning استاندارد خودشون استفاده می‌کنن و از ترنزکشن داخلی بین نودهای دیتابیس پشتیبانی می‌کنن. در این مدل همه نودها در ترنزکشنی که در نرم‌افزار یکسان دیتابیس درحال اجراس، شرکت می‌کنن.به صورت Heterogeneous distributed transactions: شرکت‌کننده‌ها از دو یا چند تکنولوژی متفاوت هستن و حتی سیستم‌های غیر دیتابیسی مثل message broker ها. distributed transaction در این سیستم‌ها باید از atomic commit اطمینان حاصل کنه.استاندارد XA transactions: مخفف eXtended Architecture هست و یک استاندارد برای پیاده‌سازی two-phase commit در تکنولوژی‌های ناهمگنه. این یک پروتکل شبکه نیست و فقط یک C API هست که ارتباط با transaction cordinator رو واسطه میشه.اگر coordinator مشکل بخوره و رستارت بشه باید وضعیت‌اش رو از روی لاگ بازیابی کنه و in-doubt transaction ها رو تعیین تکلیف کنه. اگر چه اگر in-doubt transaction های یتیمی وجود داشته باشه (به دلیل از دست رفتن لاگ و یا باگ نرم‌افزاری) این موارد اتومات نمی‌تونن حل بشن و تا ابد حتی با رستارت سرور دیتابیس قفل باقی می‌مونن. اینجا مسئولش باید به صورت دستی تصمیم بگیره که ترنزکشن کامیت بشه یا متوقف. برای چنین موقعیتی بیشتر XA ها یک راهکار فرار به نام heuristic decisions دارن که این اجازه رو میده که تصمیم بگیری که این ترنزکشن‌ها متوقف یا کامیت بشن.محدودیت‌های ترنزکشن‌های توزیع‌شده: باید توجه کنیم که خود coordinator هم یک نوع دیتابیسه و باید با مراقبت‌های مشابه با هر پیاده‌سازی دیتابیس دیگه‌ای باهاش رفتار بشه:اگر رپلیکا نداره، خودش به یک نقطه شکست سیستم تبدیل میشه.بیشتر اپ‌های سمت سرور به صورت stateless عمل می‌کنن ولی وقتی coordinator بخشی از اپ سرور میشه، دیگه نمیشه به راحتی اپ سرور رو کم و زیاد کرد و coordinator بخش مهمی از سیستم میشه و لاگش برای بزیابی ترنزکشن‌های in-doubt لازمه، در نتیجه همچین اپ‌هایی دیگه stateless نیستن.چون XA با تعداد زیادی از دیتا سیستم‌ها کار می‌کنه، کمترین مشترکات رو پوشش میده. مثلا نمی‌تونه deadlock ها رو در سیستم‌های مختلف شناسایی کنه و یا با SSI نمی‌تونه کار کنه چون نیاز به پروتکلی برای شناسایی conflict ها در سیستم‌های مختلف داره.Fault-Tolerant Consensusالگوریتم consensus باید موارد زیر رو ارائه بده:ویژگی Uniform agreement: هیچ دو نودی تصمیم‌های متفاوتی نگیرن.ویژگی Integrity: هیچ نودی دوبار تصمیم نگیره.ویژگی Validity: اگر نودی تصمیم v رو گرفت، تصمیم v توسط نودی پیشنهاد داده شده باشه. مثلا همیشه یک نود به مقدار null رای نده.ویژگی Termination: هر نودی که crash نکرده در نهایت روی یک مقداری تصمیم بگیره. مثلا نمیشه از روش 2PC استفاده کرد چون اگر کوردینیتور پایین بیاد، نودها تصمیمی نمی‌گیرن، یا نمیشه نودها به صورت هارد کد از تصمیم یک نود دیگه پیروی کنن، چون اگر اون نود پایین بیاد، دیگه تصمیمی گرفته نمیشه.بهترین الگورتیم شناخته شده اجماع، Viewstamped Replication, Paxos, Raft and Zab هست. شباهت‌هایی بین‌شون وجود داره اما یکی نیستن، خوبه که ایده کلی رو که بین‌شون مشترک هست رو بدونین وگرنه خودتون میرین پیاده‌سازیش می‌کنین که توصیه نمیشه چون سخته.اکثرشون اینطور که اینجا گفتیم روی یک عدد واحد اجماع نمی‌کنن و بجاش روی یک دنباله از مقدارها تصمیم‌گیری می‌کنن که تبدیلشون می‌کنه به الگوریتم‌های total order broadcast.در single-leader replication اگر leader دستی و توسط یک نفر در تیم عملیات انتخاب بشه، الگوریتم اجماع رو خواهیم داشت اما بخاطر اینکه نیاز به دخالت انسانی داره، راضی‌کننده نیست. بعضی از دیتابیس‌ها انتخاب لیدر و بازیابی از مشکل رو اتومات انجام میدن. برای این‌کار ما نیاز به اجماع داریم که split brain رخ نده و برای اجماع نیاز به الگوریتم total order broadcast داریم و برای این نیاز به اجماع داریم و... چطور حالا ازین حلقه بی‌نهایت بیایم بیرون؟همه پروتکل‌های اجماعی که تا الان صحبت کردیم درونن به یک لیدر به طریقی نیاز داشتن، اما گارانتی نمی‌کردن که لیدر یکتاست، اما گارانتی ضعیف‌تری داشتن: یک epoch number تعریف می‌کنن و گارانتی می‌کنن که در هر epoch لیدر یکتاست. هر زمان که فک می‌کنن لیدر مرده، نودها رای‌گیری می‌کنن و این رای‌گیری مقدار epoch رو یکی افزایش میده و وقتی دو لیدر با دو epoch متفاوت وجود داشته باشه، اونی که عددش بزرگتره، غالب میشه. قبل اینکه هر لیدری تصمیمی بگیره، باید قبلش چک کنه که لیدری با عدد بزرگتری وجود نداشته باشه. بعلاوه اینکه باید حدنصاب تایید رو از نودها بگیره، برای هر تصمیمی که لیدر می‌خواد بگیره، درخواستش رو به نودها میفرسته و صبر می‌کنه که حدنصاب به درخواستش پاسخ بدن. نودها درصورتی به نفعش رای میدن که لیدری با epoch بالاتر نباشه. پس دو مرحله رای‌گیری داریم.اما این الگوریتم‌های اجماع محدودیت‌هایی دارن، موقعی که شبکه مشکل پیدا کنه، بخش اکثریت نودها به کارش ادامه میده ولی اقلیت block میشه. بیشتر الگوریتم‌های اجماع تعداد نودها رو ثابت درنظر می‌گیره و این یعنی نمی‌تونی تعداد نودها رو در کلاستر کم و زیاد کنی. این الگوریتم‌ها براساس timeout مشکل خوردن نود رو شناسایی می‌کنن ولی در محیط واقعی با تاخیرهای غیرقابل پیش‌بینی و توزیع جغرافیایی، پیش میاد که به اشتباه یک نود مرده در نظر گرفته میشه و رای‌گیری پیاپی لیدر مساله عملکردی جدی ایجاد می‌کنه.Membership and Coordination Servicesپروژه‌هایی مثل ZooKeeper و etcd طراحی شدن که مقدار کمی از اطلاعات که میشه همه‌اش رو در مموری نگه‌داشت رو نگهداری کنن (البته بازم توی دیسک برای ماندگاری می‌نویسن). اون مقدار کم داده در تمام نودها به‌وسیله الگوریتم total order broadcast منتشر میشه. در ZooKeeper الگوریتم‌های دیگه‌ای هم پیاده‌سازی شده:عملیات‌های Linearizable atomic: با استفاده از عملیات atomic compare-and-set، می‌تونی یک lock رو پیاده‌سازی کنی.عملیات‌های Total ordering: توسط total ordering و دادن یک transaction ID (zxid) و version number (cversion) به هر عملیات fencing token رو پیاده‌سازی می‌کنه که از conflict جلوگیری کنه.شناسایی شکست: یک long-lived session هر کلایت برروی سرور ZooKeeper داره و سلامت هر نود چک میشه و در صورت منقضی شدن session همه lock های مرتبط با اون نشست آزاد میتونن بشن.اطلاع‌رسانی تغییرها: همه client ها می‌تونن lockها و مقادیر رو watch کنن. در نتیجه می‌تونن وضعیت نودهای دیگه و یا زمان پیوست یک client دیگه به کلاستر رو متوجه بشن.از این سرویس‌ها برای پیداکردن سرویس‌ هم استفاده میشه، چه آدرس IP ای باید وصل بشیم تا به سرویس مشخصی دسترسی پیدا کنیم.Derived Dataawk, grep, and sortBatch Processing Systemsپردازش Batch شامل گرفتن یک حجم زیاد از ورودی اطلاعات، پردازش اون و تولید یک خروجی دیتاست. این کار معمولا برنامه‌ریزی میشه که به طور دوره‌ای انجام بشه و ممکنه از چند دقیقه تا چندین روز طول بکشه. واحد سنجش اصلیش، توان عملیاتیش هست که میزان زمان لازم برای پردازش یک دیتاست ورودی با یک سایز مشخصه. همینطور قابلیت بازیابی از failure در طی انجام کار رو دارن. مثلا پردازش لاگ وب‌سرور برای تولید گزارش روزانه.MapReduceیک الگوریتم batch processing هست که اجازه پردازش موازی در چندین ماشین بدون نیاز به کدی که موازی عمل کنه رو میده. بخش‌های اصلیش ایناس:بخش Mapper: پردازش رکوردهای ورودی و تولید جفت key-value.بخش Reducer: گرفتن جفت‌های key-value از mapper و گرفتن value ها از کلیدهای یکسان و پردازش اونها.مثال: میخوایم تعداد هر کلمه در چند داکیومنت رو در بیاریم:فاز Map: هر داکیومنت رو به طور جداگانه پردازش می‌کنیم و کلمات رو با تکرارشون مشخص می‌کنیم.فاز Shuffle and Sort: تکرار هر کلمه رو گروه بندی می‌کنیم. (the, [1, 1, 1])فاز Reduce: مقدار sum برای هر کلمه رو حساب می‌کنه. (the, 3)Dataflow Systemsسیستم‌هایی کلی شده از مدل MapReduce هستن که workflow های پیچیده‌تر با پردازش real-time رو پشتیبانی می‌کنن. شامل شکستن عملکرد برنامه به مجموعه‌ای از سرویس‌هاست که باهم از طریق asynchronous message streams بجای synchronous request/response در ارتباط هستن. مزیتش اینه که در مقابل مشکل خوردن ایمن‌تر هستن چون ارتباط async هست. قابلیت رشد سیستم بیشتره چون اجازه توسعه مستقل سیستم‌ها رو میده. همینطور امکان مشخص کردن ترتیب پیام‌ها به طور موثر رو هم میده. نمونه‌هاش Apache Beam و Apache Spark هستن.Stream Processingایده کلی‌اش پردازش داده به طور پیوسته در زمان رسیدنشه. کار اصلیش هندل کردن جریان پیام‌هاست. برای دیتاهای با طول عمر کوتاه که بعد consume شدن پاک میشن، بهینه شده. عملیات‌های async رو پشتیبانی می‌کنه و مناسب سیستم‌هایی که client ها میان و میرن. به طور مثال RabbitMQ, Kafka, ActiveMQ رو میشه نام برد. اما دیتابیس برای نگهداری طولانی‌مدت داده‌ها و پشتیبانی از query ها و index های پیچیده طراحی شدن. میشه چندین consumer داشت تا پیام‌ها لود بین‌شون بالانس بشه و پردازش موازی داشته باشیم (Load Balancing). و یا میشه هر پیام رو به همه consumer ها رسوند (Fan-out). </description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 27 Oct 2026 14:31:02 +0330</pubDate></item><item><title>نقشه راه انتخاب بین مونولیتیک یا مایکروسرویس</title><link>https://virgool.ir/@hamze/نقشه-راه-انتخاب-بین-مونولیتیک-یا-مایکروسرویس-cco9cb5iqorx</link><description>معماری مونولیتیک همیشه به چند دلیل اولین انتخاب می‌تونه باشه: توسعه، تست، استقرار و رشد دادنش خیلی ساده است که این ویژگی‌ها در مراحل اولیه ایده (early-stage) خیلی مفیده.اما چطور معماری مونولیتیک می‌تونه تبدیل به جهنم بشه:پیچیدگی‌هایی که برنامه‌نویس رو پیر می‌کنه: کد بیس اونقد بزرگ بشه که دیگه واقعا نشه یک برنامه‌نویس همه‌شو به طور کامل بفهمه. اون موقع‌اس که تغییرات کند و ریسکی میشه.توسعه و استقرار کند بشه: اونقدر کدبیس بزرگ شده که، زمان توسعه و راه‌اندازی زیاد شده، در حدی که دیگه نسخه‌های جدید ماهانه بالا میان و خبری از استقرار مستمر (continuous deployment) نیست و تست‌های دستی گلوگاه (bottleneck) شدن.مشکلات رشد دادن پیش بیاد: درون یک کدبیس مونولیتیک، بین نیازهای منابع مثل CPU و RAM تداخل پیش بیاد.اطمینان به سیستم کم بشه و مدیریت خطاها سخت بشه: یک باگ توی یک بخش می‌تونه کل سیستم رو مختل کنه.قفل شدن به یک فناوری پیش میاد: اونقد کدبیس بزرگ شده که نمیشه به این سادگی‌ها فریمورک آپدیت کرد یا از تکنولوژی‌های جدید استفاده کرد.توجه داشته باشیم که Microservicef با Service Oriented Architecture متفاوته، دیتابیسش مشترکه، پروتکل ارتباطیش متفاوته و سیستم‌هایی با سایز بزرگ هستن.معایب مایکروسرویس:پیچیدگی سیستم‌های توزیع شده: Network latency, eventual consistency, debuggingچالشِ تقسیم‌بندی سیستم: ریسک تولید سیستم‌های مونولیتیکِ توزیع شده :))سربار عملیاتی: DevOps automation (orchestration, monitoring)پیچیدگی در تست: تست End-to-end سخت‌تر خواهد شد </description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sat, 20 Dec 2025 20:49:48 +0330</pubDate></item><item><title>چالش‌های معماری مایکروسرویس در سیستم‌های مالی با تراکنش بالا</title><link>https://virgool.ir/@hamze/چالش-های-معماری-مایکروسرویس-در-سیستم-های-مالی-با-تراکنش-بالا-iseamyqpr5ta</link><description>فکر کنین یک صرافی آنلاین ارزدیجیتال با هزاران تراکنش در ثانیه رو با معماری مایکروسرویس طراحی کردیم، یکی از این سرویس‌ها کیف‌پول خواهد بود که سرویس‌های زیادی باهاش در اتباط خواهند بود و این سرویس باید بتونه با دقت موجودی کاربر رو محاسبه کنه. توی این مقاله می‌خوایم چالش‌ها و راهکارهای پیاده‌سازی این سرویس رو بررسی کنیم.خوشحال میشم نظرات‌تون رو در انتها بشنوم :)چالش‌ها:وقتی چندین میکروسرویس (مثل سرویس معامله، سرویس برداشت، سرویس انتقال دارایی) به طور همزمان به سرویس کیف‌پول درخواست بزنن، چند چالش اصلی می‌تونه بوجود بیاد:چالش Lost Update و یا Race Condition:سناریو:کاربر ۱۰۰ تا موجودی داره.درخواست A برداشت ۵۰ تا: خوندن موجودی به مقدار ۱۰۰ تادرخواست B برداشت ۳۰ تا: خوندن موجودی قبل از ذخیره شدن A به مقدار ۱۰۰ تانوشتن درخواست A: مقدار ۱۰۰ منهای ۵۰ مساوی ۵۰ تانوشتن درخواست B: مقدار ۱۰۰ منهای ۳۰ مساوی ۷۰ تانتیجه: دیتابیس میگه موجودی ۷۰ تاست ولی در عمل کاربر ۸۰ تا استفاده کرده و باید موجودی ۲۰ تا باشه. صرافی پول از دست داده.چالش پرفورمنس Hot Row Contention:در صرافی‌ها برای مدیریت کیف‌پول اصلی صرافی و یا کاربرهای وال که هزاران تراکنش در ثانیه دارن، اگر به ازای هر تراکنش قفل بزنیم، دیتابیس به یک Bottleneck تبدیل میشه و CPU درگیر مدیریت قفل‌ها میشه بجای انجام پردازش‌ها.چالش Deadlock ها:سناریو:کاربر A به کاربر B پول میفرسته و کاربر B به کاربر A پول می‌فرسته.ترنزکشن ۱ میاد و A رو لاک می‌کنه و برای B صبر می‌کنه.ترنزکشن ۲ میاد و B رو لاک می‌کنه و برای A صبر کی‌کنه.نتیجه: هردو freeze میشن تا زمانی که دیتابیس time out بشه.چالش Idempotency:اطمینان از اینکه هر درخواست، فقط یکبار اجرا میشه، ممکنه کاربر یا gateway به خاطر تایم‌اوت شبکه چندین بار retry کنه. راهکار اینه که یک مکانیزم برای ایجاد Idempotency-Key یکتا برای هر عملیات مالی ایجاد کنیم و اگر جدید بود برچسب درحال‌اجرا بزنیم و بریم پردازشش کنیم، اگر درحال‌اجرا بود ردش کنیم و یا تو صف بذاریم. اگرم کامل شده بود، نتیجه رو بدون انجام پردازش مجدد براش بفرستیم.چالش Dual-Write: (Outbox Pattern)سناریو: کاربر موجودیش رو ۱۰۰تا اضافه کرده. ما اومدیم و بالانسش رو ۱۰۰ تا بالا بردیم و همزمان روی کافکا هم می‌نویسیم که موجودیش افزایش پیدا کرده که بقیه سرویس‌ها (مثل حسابداری و...) عملیات‌شون انجام بدن. حالا فرض کنین این وسط سیستم مشکل بخوره و موجودی توی ردیس افزایش پیدا کنه اما روی کافکا نوشته نشه. اینجوری atomically write غیر ممکنه.راهکارش اینه که به طور اتومیک ترنزکشن همزمان هم موجودی رو آپدیت کنیم و هم توی یک جدول به صول WAL (Write-Ahead Log) این تغییر موجودی رو به صورت pending بنویسیم و یک سرویس دیگه این جدول رو مانیتور کنه و روی کافکا بنویسه و status شو آپدیت کنه.چالش crash قبل پردازش پیام کافکا:سناریو: فرض کنین که پیام برداشت ۱۰۰ تا از حساب رو از روی کافکا خوندیم، و وسط کار پردازش کرش کرده، دو حالت ممکنه پیش بیاد:کامیت خوندن از کافکا رو قبل از ثبت دیتابیس انجام دادیم: بعد که سرویس بیاد بالا، با اینکه برداشت در دیتابیس ثبت نشده، فک می‌کنه موفق بوده.کامیت خوندن از کافکا رو بعد از ثبت دیتابیس انجام دادیم: توی دیتابیس ثبت کردیم و سیستم بدون کامیت کافکا مشکل خورده و بعد که دوباره بالا میاد، مجدد برداشت رو ثبت می‌کنه.راهکار: استفاده از Checkpointing Strategy با Idempotency یعنی در واقع کامیت رو بعد ثبت در دیتابیس انجام میدیم و به کمک Idempotency مطمئن میشیم که فقط یکبار انجام خواهد شد. چالش دیگه‌ای که وجود داره اینه که کامیت بعد هر پیام کنده که راهکارهاش قابل بررسیه.راهکارها:راهکار Database Pessimistic Locking (Select for Update):نقاط قوت: تضمین می‌کنه که race condition اتفاق نمی‌افته.نقاط ضعف: قاتل پرفرمنسه. همه درخواست‌های دیگه کاربر باید توی صف صبر کنن. اگر لاجیک ما ۱۰۰ میلی‌ثانیه زمان ببره، حداکثر throughput (توان عملیاتی) کاربر ۱۰ درخواست در ثانیه خواهد بود (10 TPS).نتیجه: برای کیف‌پولای با فرکانس پایین خوبه ولی مناسب Hot Wallet ها نیست.راهکار Optimistic Locking (Versioning):به جدول‌ها یک فیلد ورژن اضافه می‌کنیم. موقع درخواست نوشتن چک می‌کنیم که ورژن تغییر نکرده باشه.تریدآف: اگر دیتابیس گزارش بده که ۰ ردیف آپدیت شده، یعنی ورژن تغییر کرده و باید اپلیکیشن retry کنه.نتیجه: در صرافی‌های با فرکانس بالا که هزاران درخواست در ثانیه رو می‌خوان هندل کنن، ۹۹ درصد درخواست‌ها ناموفق میشن و Retry Storm بوجود میاد.راهکار In-Database Atomic Updates (The &#34;Blind Write&#34;):حساب کتاب ریاضی رو توی دیتابیس انجام میدیم: SET balance = balance - 50نقاط قوت: فوق‌العاده سریعه، دیتابیس خودش برای کمترین مقدار ممکن قفل رو مدیریت می‌کنه.نقاط ضعف: مقدار بالانس رو بدون اینکه یک درخواست دوم بزنی و بخونیش، نمی‌دونی. و سخته که لاجیک‌های پیچیده رو به این روش پیاده کرد.راهکار معماری مناسب صرافی:این معماری‌های اولیه رو اول در نظر بگیریم:معماری The Serialized Queue (Async Processing):بجای سروکله زدن با قفل‌ها، با قراردادن همه درخواست‌ها در یک صف، کلا concurency رو از بین می‌بریم.همه درخواست‌ها (خرید/فروش/برداشت/انتقال) رو در یک صف (Kafka/RabbitMQ) به ازای هر user_id قرار می‌دیم. در واقع این همون WAL (Write-Ahead Log) هست.یک Consumer (Worker) واحد به ازای هر user_id قرار میدیم که بخونه و وارد دیتابیس بکنه، چون یک پروسس واحد به ازای هر کاربر این کار رو می‌کنه، دیگه نیازی به database lock وجود نداره.معماری The In-Memory Ledger (Redis + Write-Behind):بالانس کاربر در ردیس نگه‌داری میشه (یا به هر روش دیگه‌ای داخل مموری). و با کمک دستورای (INCRBY/DECRBY) به صورت اتومیک آپدیت میشه. و چون ذاتا ردیس به ازای هر فرمان single-threaded هست، به طور پیش‌فرش atomically safe هست. و اگر ناموفق بود باید کل پروسه رو revert کرد.اما ترفند کار اینجاست که به طور همزمان ما نمیایم توی دیتابیس هم بنویسیم، میایم و لاگ ترنزکشن رو توی queue/DB به صورت Asynchronous می‌نویسیم (Write-Behind).معماری‌های HFT (High-Frequency Trading):این معماری‌ها ساده‌سازی شده که پیچیدگی فهم‌شون کمتر باشه. به عنوان مثال، راهکارهای بعضی از چالش‌هایی که در اول مقاله آوردم رو در نهایت باید بهشون اضافه کرد.معماری LMAX Architecture or CQRS with Event Sourcing:به این روش Event Sourcing pattern with In-Memory state هم می‌گن.کانسپت: ورکر (پروسسر) کارش فقط خوندن و توی دیتابیس نوشتن نیست، بلکه یک stateful microservice هست که بالانس کاربر رو توی RAM نگه می داره.درخواست وارد صف میشه &gt; ورکر ایونت رو می‌خونه &gt; رم رو بلافاصله آپدیت می‌کنه &gt; توی دیتابیس به صورت غیرهمزمان ثبت می‌کنه (background thread).وقتی درخواست بالانس می‌کنی، درخواست رو از یک مسیر خیلی سریع (high-priority channel or RPC) به ورکر می‌فرستی و می‌پرسی که موجودی کاربر در RAM چقدر است؟اگر ردیس به مشکل خورد، دوباره بالانس رو با اجرای دوباره لاگ ترنزکشن‌های ذخیره شده در دیتابیس می‌سازیم. در واقع ما میایم و هر ۱۰ دقیقه یا هر ۱۰ هزار درخواست، کل ردیس رو روی هارد Snapshots می‌گیریم و بعد Crash/Restart آخرین اسنپ‌شات رو لود می‌کنیم و WAL رو از بعد اون اجرا می‌کنیم.معماری The &#34;Pending Balance&#34; Pattern (The Optimistic approach):این روش مرسومی برای کیف‌پول‌هاس استاندارد و یا سیستم‌های بانکی کلاسیکه، ولی مناسب HFT های جدید با توجه به تاخیری که در حد میلی‌ثانیه می‌تونه ایجاد کنه نیست.داده توی ردیس نگه‌داری میشه و توسط ingest service که در واقع API Gateway Layer هست، قبل از صف مدیریت میشه.کاربر درخواست برداشت ۵۰ تا رو میزنه &gt; لایه ورودی (Ingest Layer): بالانس ردیس رو چک می‌کنه که ۱۰۰ تاست، بلافاصله مقدار موجودی ۵۰ تا و موجودی قفل شده ۵۰ تا رو توی ردیس ثبت می‌کنه و درخواست برداشت ۵۰ تا رو به کافکا می‌فرسته &gt; لایه پردازش (Processor): مقدار رو از کافکا می‌خونه، عملیات برداشت رو انجام میده و اگر موفق بود، ایونت تایید رو ارسال می‌کنه &gt; موقعی که رویداد تایید میاد، لایه ورودی مقداد locked_balance رو حذف می‌کنه. اگر برداشت مشکل خورد باید موجودی قفل شده به موجودی اصلی برگردونده بشه.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 09 Dec 2025 14:37:37 +0330</pubDate></item><item><title>همه چیز درباره برقراری ارتباط با دیگران</title><link>https://virgool.ir/@hamze/همه-چیز-درباره-برقراری-ارتباط-با-دیگران-ycnfqsuhvrm9</link><description>اینجا می‌خوام درباره یادگیری‌هام و تجربه‌های شخصی و کاری‌ام در زمینه ارتباط با دیگران صحبت کنم؛ تجربه‌ای که حاصل زندگی شخصی و حدود ۱۵ سال کار حرفه‌ایه.اول بذار صورت‌مسئله رو روشن کنم: من در کنار یک آدم دیگه قرار گرفتم که زبان مشترکی داریم و می‌خوایم با کمک ابزارهایی که در اختیار هر دو طرفه، اطلاعات رو منتقل کنیم؛ طوری که نزدیک‌ترین برداشت ممکن از واقعیت ذهنی‌مون برای هم ساخته بشه.اما خب، هنوز مثل کامپیوترها نمی‌تونیم اطلاعات رو «کپی و پیست» کنیم 😅ارتباط موثر در زندگی و کارارتباط موثر در گام اول درون سرِ ما شکل می‌گیردمثل تمامی اهداف زندگی، گام اول، توجه به نگاهی که درباره ارتباط با دیگران داریم هست. این نگاه بخش زیادی از اونچه که اتفاق خواهد افتاد رو شکل میده، پس مهمه که اول انرژی افکارمون رو توی کانال مناسب قرار بدیم:واقعیت‌ها در دسترس من نیستن: مهمه که یادم باشه یک انسانم، و تمام دریافت‌های من، بازتابی از دنیای بیرون در درون منه، یعنی چی؟ یعنی اینکه من هر برداشتی که از مشاهدات و شنیده‌هام می‌کنم، بنا به درک و تجربیات قبلی خودمه، من بدون سوگیری (درباره خطاهای شناختی می‌تونین بخونین - cognitive biases) نمی‌تونم به مسائل نگاه کنم و خب این باعث میشه که همه‌ی واقعیت، اون چیزی که من فک می‌کنم نباشه.برای فهمیدن مساله گفتگو می‌کنم: نمیرم که ثابت کنم یا از موضعی دفاع کنم. در گام اول میرم که بشنوم و بفهمم که مساله درون سر طرف مقابل چیه. هروقت که راهکاری برای حل مساله میاد تو ذهنم، کنار میذارمش و میگم الان فقط قراره مساله رو بفهمم.شاید چیزی ورای فهم من وجود داشته باشه: میرم که کشف کنم، شاید پشت این گفتگو دری وجود داره که هیچوقت ندیدم و این آدم کمک کنه که ببینمش و بازش کنم و بتونم یک رشد در نگاهم و تفکراتم ایجاد کنم. الان مساله کشفه و نه راه‌حل‌های گذشته‌ام.هر گفتگویی قرار نیست منجر به نتیجه بشه، اما اگر نتیجه‌ای قرار باشه دربیاد، باهم میسازیمش: توی گفتگوهای روزمره (و نه کاری) هیچ نیازی نیست که منجر به نتیجه‌گیری مشترک بشه، حتی می‌تونم بگم که نیاز دارم چند روز بهش فکر کنم. یا می‌تونم بگم که توی نگاه‌مون تفاوت وجود داره و خب با توجه به شرایط هر کدوم‌مون شاید هر دو نگاه درست باشه.اگر در نهایت متفاوت فکر می‌کردیم، لازم به اثبات چیزی نیست: من نیازی ندارم برای نگاهی که توی زندگی شخصیم کار می‌کنه، به دیگران اثبات کنم درسته یا نگاه اونا غلطه، ما توی گفتگو، زوایایی که می‌بینیم رو بیان می‌کنیم، اما برای هم تصمیم نمی‌گیریم. در نهایت تصمیم‌گیری یک مساله درونی و شخصیه.حدس زدن گفتگو نیست: هرجایی که وسط صحبت‌ها برداشتی کردم و حدسی زدم، تنها کاری که می‌کنم اینه که بگم من از صحبتت این برداشت رو کردم و بیانش کنم، هر اقدامی غیر این، مسیر ارتباط رو قطع می‌کنه. در نتیجه نه من درباره منظور طرف مقابل حدس میزنم و نه انتظار دارم که طرف مقابل بتونه حدس بزنه که من چی میگم یا می‌خوام.درون سر دو اصل کلیدیه که وجود داشته باشه: من برای کشف، گفتگو می‌کنم - حدس زدن بخشی از گفتگو نیست، گفتگو فقط با صحبت کردن شکل می‌گیره.و بعد از مرتب کردن درون سر، زبان‌مان هم نیاز به تمرین داردمهمه که چطور بیان می‌کنیم. همه آدم‌ها تعاریف مختلف خودشون رو دارن، کلمات کیفی مثل خوب، بد، زیبا، لذت‌بخش، آزار دهنده و... هزاران کلمه دیگه، توی سر هر کس معنی متفاوتی می‌تونن داشته باشن. پس چطور حرفم رو درست منتقل کنم؟ ساختار جمله زیر تا حد خوبی کمک می‌کنه:من + &#34;مشاهداتم&#34; + &#34;حسم&#34; + &#34;چیزی که نیازمه&#34;من وقتی جلوی غریبه‌ها بهم میگی جوجه، حسم اینه که جدیم نمی‌گیری و به بقیه این فضا رو میدی جدی نگیرنم. دوس دارم که جلوی غریبه‌ها باهام با احترام صحبت کنی (و با مثال بگی با احترام یعنی چی)من وقتی می‌بینم گزارش‌ها دیر ارسال می‌شن، حس می‌کنم تیم هماهنگ نیست. دوست دارم قبل ددلاین یه اطلاع‌رسانی کوتاه انجام بدیم تا بتونم منابع رو تنظیم کنم.چندتا بخش مهم اینطور صحبت کردن رو باهم بررسی کنیم:شروع با من: این یعنی من از سمت خودم و چیزی که تجربه کردم صحبت می‌کنم. توی ارتباط این کلیدیه - وقتی با کلمه‌ی تو شروع کنم و مثلا بگم تو بهم احترام نمی‌ذاری، اصلا ملوم نیست از چی صحبت می‌کنم و چیزی که من تجربه کردم رو بیان نمی‌کنه. از طرف دیگه، این جمله بجای اینکه تمرکزش روی مساله من باشه، بار معنایی رو میبره روی طرف مقابل، و این باعث میشه که طرف مقابل حس کنه بهش حمله شده، چون دارم طرف رو زیرسوال میبرم، و معمولا اولین واکنش طرف مقابل گارد گرفتن خواهد بود.از مشاهدات بگو: اولین چیزی که میگم، دقیقا اتفاقی هست که با حواس پنج‌گانه‌ام تجربه کردم و نه هیچ‌چیز دیگه - نه اون فکری که تو سرمه یا حسی که تجربه کردم - کلیدیه که دقیق بتونم اتفاقی که تجربه کردم رو بیان کنم تا دقیقن طرف مقابلم بدونه از چی صحبت می‌کنم.از حست بگو: خب حالا که مشاهدات رو گفتم، وقتشه بگم که درونم درباره اون اتفاق چی رو تجربه کردم، چون دریافت آدم‌ها نسبت به اتفاقات متفاوته، وقتی حسم رو می‌گم، کمک می‌کنم که طرف مقابل بتونه درک کنه که چی رو تجربه کردم و بهش این فرصت رو بدم که باهام همدلی کنه.از انتظارت بگو: آدم‌ها نیازهای متفاوتی دارن، پس اینکه حسم رو بگم کافی نیست و طرف مقابل نمی‌تونه نیازم رو حدس بزنه، پس لازمه نیازم رو واضح بگم و دربارش صحبت کنم.توی این ساختار حرفت رو بزن: من + چی مشاهده کردم + چه حسی رو تجربه کردم + چه انتظاری دارمو آن زمان که بدن نیز حرف می‌زندراستش زبان بدن به‌قدری گسترده‌ست که پرداختن کامل بهش در این مقاله ممکن نیست، اما وقتی صحبت از ارتباط میشه، نمی‌شه ازش حرفی نزد.متخصصان زبان بدن معتقدند حدود ۷۰٪ ارتباط ما با دیگران از طریق زبان بدن و تن صدامونه و تنها حدود ۳۰٪ به ساختار جملات و کلمات اختصاص داره. در ادامه چند نکته‌ی ابتدایی اما مهم رو مرور می‌کنم:تماس چشمی برقرار کن: حدود ۷۰٪ زمان مکالمه به محدوده‌ی بین دو ابرو و بینی طرف مقابل نگاه کن. نگاه مستقیم و طولانی توی چشم ممکنه طرف رو معذب کنه، و نگاه نکردن نشونه‌ی بی‌توجهی برداشت میشه.به تن صدات توجه کن: تن صدا می‌تونه هیجان، تمرکز و تأکیدت رو منتقل کنه. صحبت کردن با تن صدای یکنواخت، آروم و بی‌انرژی، حس بی‌علاقگی یا خستگی رو منتقل می‌کنه.حالت نشستن نشونه‌ی علاقه‌ست: به‌طور ساده، جهت بدن، جهت فکره. پس به سمت طرف مقابل بشین و بدنت رو کمی متمایل کن. اگه پاهات به سمت در باشه، ناخودآگاه این پیام رو می‌فرستی که منتظری زودتر بری.به حالت دست‌هات دقت کن: زبان بدن باز، نشونه‌ی پذیرا بودنه. سعی کن دست‌به‌سینه نشینی، دست‌هات رو توی هم گره نزنی و جلوی بدنت رو با کیف یا لیوان نپوشونی. دیده شدن کف دست‌ها حس اعتماد ایجاد می‌کنه.موقع گوش دادن بازخورد بده: با حرکات آروم سر می‌تونی نشون بدی که گوش می‌دی. حواست باشه تکون سریع و مکرر سر، ممکنه به‌عنوان نشونه‌ی عجله برای تموم شدن بحث برداشت بشه.گوشی و ساعت رو کنار بذار: نگاه کردن به گوشی یا ساعت نه‌تنها نشونه‌ی بی‌توجهیه، بلکه به طرف مقابل این پیام رو می‌ده که منتظری گفت‌وگو زودتر تموم بشه.بدن طرف مقابل رو بخون: زبان بدن ناخودآگاه‌تر از زبان گفتاره و برای همین اغلب، اطلاعات صادقانه‌تری منتقل می‌کنه. با تمرین می‌تونی از زبان بدن طرف مقابل به میزان صداقت، راحتی و احساسات واقعی‌ش پی ببری. این مهارت به زمان و تمرین نیاز داره و پرداختن کامل بهش جاش توی این مقاله نیست.در محیط کار، قواعد کمی متفاوت استتوی محیط کار یک تفاوت اصلی وجود داره، اینکه باید بتونیم تهش به جمع‌بندی و خروجی برسیم. برای همین علاوه بر قواعدی که دربارش صحبت کردم، چندتا قاعده دیگه می‌تونه کمک‌مون بکنه:موضوعات مهم، داکیومنت می‌خوان: معمولا برای جلسات مهم کاری جلسات ترتیب داده میشه، یادمون باشه که &#34;تصمیم‌گیری&#34; یک پروسه زمان‌بر می‌تونه باشه، برای همین، آماده کردن داکیومنت و ارسالش به افراد حاضر در جلسه، قبل از جلسه، می‌تونه کمک کنه که جلسه موثر تر پیش بره، پیشنهادم بیان این موارده: شرح مساله، شرح راه‌حل‌های پیشنهادی، مواردی که توی جلسه قراره براشون تصمیم‌گیری بشه، خروجی مورد انتظار جلسه.عددها صحبت می‌کنن: توی فضای کاری، استفاده‌نکردن از صفات کیفی مهم‌تر میشه. بجای اینکه بگیم وضعیت فروش خوب نیست، از اعداد، جدول‌ها، مقایسه‌ها، نمودارها و چارت‌ها استفاده کنیم.از همه حواس ۵ گانه استفاده کن: هرچی مساله پیچیده‌تر باشه، سعی کن حواس بیشتری رو درگیر کنی، وقتی به جلسه نمودار و چارت اضافه کنی، بینایی طرف هم درگیر کردی، اگر نمونه محصول رو بیاری تو جلسه، لامسه و حتی بویایی رو هم درگیر کردی. نقطه اتصال‌های اولیه ما برای ارتباط ایناس، هرچی بیشتر استفاده کنی، بیشتر آدما می‌تونن ارتباط بگیرن.داستان واقعیت رو بگو: وقتی از ورودی‌ها که حواس ۵گانه هستن گذر کردی، دو نقطه دیگه برای فتح باقی می‌مونه. منطق و حس (مغز و قلب) با اعداد و نمودارها و جداول مقایسه‌ای به منطق نفوذ کن و با بیان صادقانه تجربیات گذشته خودت و تجربیات مشابه دیگران به قلب‌ نفوذ کن. آدم‌ها با داستان‌های مشابه خیلی سریع درگیر میشن و می‌تونن ارتباط حسی برقرار کنن.برای ساختن برو و نه اثبات کردن: به نظرم شاید این مهم‌ترین باشه که قبل جلسه با خودت تکرار کنی که دارم میرم توی جلسه تا باهم بهترین مسیر رو بسازیم، پس میرم که خوب گوش بدم، در مورد جنبه‌های مختلف صحبت کنیم و باهم دیگه بهترین راه‌حل رو بسازیم. پس راه‌حل‌های توی سرت رو قطعی ندون.چند تکنیک برای ارتباط موثرترگوش دادن فعالانه: برای اینکه مطمئن‌شم خوب به طرف مقابل گوش دادم و منظورش رو درست متوجه شدم، بعد تموم شدنش حرفش، خلاصه حرفش رو تکرار کنم: من از حرفات این رو متوجه شدم که ...رهایی از راه‌حل‌های ذهنی: خیلی وقتا وسط صحبت کردن طرف مقابل، ذهنم درگیر پاسخ به یک جمله‌اش میشه و این باعث میشه که بقیه صحبت‌هاش نشنوم و تو فکرم فرو برم. یک دفتر همرام میبرم و یادداشت می‌کنم و برمی‌گردم به گوش کردن فعالانه، چون مطمئنم چیزی دیگه فراموش نمیشه.سوال باز بپرس: این رو قبلا گفتم و باز تکرار می‌کنم، حدس زدن جزئی از ارتباط نیست، اگر چیزی برام واضح نیست، یک سوال میپرسم که دقیق‌تر برام شرح بده. یا میگم من برداشتم اینه ... که بتونه تصحیحش کنه.گفتگو چالشی: گاهی گفتگو از فضای سازنده خارج میشه، چه زمانی؟ زمانی که حس‌هایی مثل خشم، هیجان یا هر حس شدید دیگه‌ای بر گفتگو حاکم بشه، این زمان، زمان هیچ نوع تصمیم‌گیری‌ای نیست، فقط می‌گم که: الان خیلی خشمگینم، نیاز دارم که آروم‌تر بشم (بشی) تا در این مورد تصمیم بگیریم، فردا (یک ساعت دیگه) دربارش صحبت کنیم.گاهی روی تصمیمات باید خوابید: این مورد شاید خیلی مرتبط با ارتباط نباشه، اما خواستم بهش اشاره کنم که تصمیمات بزرگ نیاز به اطمینان داره و بخشی از این اطمینان از جایی میاد که مطمئن بشیم که هیجانی نبودن، برای همین خوبه که برای تصمیمات بزرگ صبر کنم که یک روز بگذره و فردا مطمئن شم هنوز تصمیم‌ام همونه و همه جنبه‌ها رو دیدم یا نه و بعد با طرف مقابل درمیون بذارم.من حمزه قائم‌پناه هستم و این پست رو برای کامیونیتی فنی بیلدآپ نوشتم (تلگرام: build_up_team)</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 14 Oct 2025 13:54:07 +0330</pubDate></item><item><title>یک دوری با Ansible بزنیم</title><link>https://virgool.ir/@hamze/یک-دوری-با-ansible-بزنیم-spiq0c7lbrbe</link><description>انسیبل یک سری از ابزارهای رو برای Infrastructure as Code (IaC) در اختیارمون میذاره که به کمکش می‌تونیم مدیریت کانفیگ‌ها، دپلوی برنامه، orchestration و کلی کارای دیگه رو انجام بدیم.آموزش Ansibleمفهوم Playbook:به اسکریپت‌های انسیبل که به فرمت YAML هستن که برای اتومات کردن تسک‌ها می‌نویسیم‌شون، playbook می‌گیم.مفهوم Control node:ماشینی که روش Ansible CLI tools رو اجرا می‌کنیم که از طریق اون، نودها رو مدیریت می‌کنیم.ویژگی‌های Ansible:به صورت Agent-less: یعنی لازم نیست روی سرورهایی که می‌خوایم کانفیگ‌شون کنیم، چیزی نصب کنیم. بلکه فقط روی سرور اصلی یا همون لوکال‌مون نصب میشه و از طریق SSH به سرورها متصل میشه و کانفیگ‌شون می‌کنه.قابلیت رشد: خیلی راحت میشه اتومات روی کلی سرور و سرویس کلاد رشدش داد و به صورت ماژولار توسعه پیدا می‌کنه.پیش‌بینی‌پذیری: وقتی سیستم در وضعیتی هست که توی playbook تعریف کردیم، Ansible چیزی رو تغییر نمیده، حتی اگر بارها playbook اجرا بشه.شروع اتومات کردن با Ansible:اول نصب کنیم و یک پروژه ایجاد کنیم:pip install ansible&#xA;mkdir ansible_quickstart &amp;&amp; cd ansible_quickstartایجاد یک inventoryدر واقع inventory یک کانفیگ فایله که گروه‌ها و مشخصات سرورهای هر گروه رو مشخص می‌کنیم. در داخل پروژه با نام: inventory.ini (میتونه فرمت YAML هم باشه)[myhosts]&#xA;192.0.2.50&#xA;192.0.2.51&#xA;192.0.2.52برای verify و ping کردن فایل inventory: (از -u برای مشخص کردن user می‌تونیم استفاده کنیم)ansible-inventory -i inventory.ini --list&#xA;ansible myhosts -m ping -i inventory.iniتولید متا گروه: می‌تونیم چند گروه رو هم در یک گروه بزرگتر قرار بدیم.افزودن متغیر: متغیرها برای مدیریت نودهاس مثل مشخص کردن port, ssh user و غیره. که می‌تونه برای یک هاست مشخص و یا گروه تعریف بشه.نکات نام‌گذاری Inventory:اسامی یکتا و با‌معنی باشن و case sensitive هستن.بجای space از آندرلاین استفاده کنین.برمبنای what (گروه بندی هاست‌ها براساس توپولوژی: db, web, leaf)، where (گروه بندی هاست‌ها برمبنای موقعیت: datacenter, region, floor, building)، when (گروه‌بندی برمبنای محیط: test, staging, production) نام گذاری کنین.ساخت یک playbook:پلی‌بوک یک برنامه اتومیشن به فرمت YAML هست که انسیبل استفاده می‌کنه تا نودهایی که مدیریت می‌کنه رو آماده و کانفیگور کنه. پلی‌بوک لیستی از play ها است که Ansible از بالا به پایین انجام میده تا به هدف کلی برسه.مفهوم Play:یک لیست از تسک‌ها که مرتبط با نودهای مدیریت شده در inventory هست. (تعریف what, who)مفهوم Task:یک رفرنس به یک ماژول که عملیاتی که Ansible انجام میده رو تعریف می‌کنه. مفهوم Module: یک واحد کد یا باینتری که Ansible روی نود مدیریت‌شده اجرا می‌کنه. ماژول‌های Ansible در کالکشن‌هایی برای هر ماژول گروه‌بندی شدن. (فانکشن‌های از پیش نوشته شده‌ای مثل apt, yum, copy)نوشتن یک playbook که سرورها رو پینگ کنه و پیام hello world رو چاپ کنه: (فایل playbook.yaml)- name: My first play&#xA;  hosts: myhosts&#xA;  tasks:&#xA;   - name: Ping my hosts&#xA;     ansible.builtin.ping:&#xA;&#xA;   - name: Print message&#xA;     ansible.builtin.debug:&#xA;       msg: Hello worldاجرای فایل playbook:ansible-playbook -i inventory.ini playbook.yamlمفهوم Handlers:یک نوع خاص Task که فقط زمانی اجرای میشه که توسط تسک‌های قبلی فراخونی بشه که یک تغییری ایجاد کردن.مفهوم Roles:به محتواهای reusable Ansible که می‌تونه task, handler, variable, plugin و... باشه، که می‌تونیم ازشون داخل Play ها استفاده کنیم، می‌گیم Role.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sat, 11 Jan 2025 15:52:43 +0330</pubDate></item><item><title>PostgreSQL High Availability</title><link>https://virgool.ir/@hamze/postgresql-high-availability-lhlmzhvgqtey</link><description>هدف از دسترس‌پذیری بالا (HA)دسترس‌پذیری بالا تضمین می‌کنه که سیستم پایگاه داده حتی در زمان خرابی‌ها، عملیاتی باقی می‌مونه و زمان پایین بودن و از دست دادن داده‌ها رو کاهش میده.PostgreSQL High Availabilityکلمات کلیدی برای PostgreSQL HAمفهوم Replication: یعنی یک نسخه اضافه از داده‌ها رو روی یک نود دیگه نگهداریم. به دو روش اصلی می‌تونیم این کار رو انجام بدیم:تکثیر هم‌زمان Synchronous Replication: اطمینان می‌ده که داده‌ها قبل از تایید تراکنش (commit) توی سرور اصلی و سرورهای ثانویه نوشته شده. این روش اطمینان خوبی ارائه می‌ده اما ممکنه باعث ایجاد تاخیر (lag) شود.تکثیر غیرهم‌زمان Asynchronous Replication: داده‌ها رو ابتدا توی سرور اصلی می‌نویسه و بعدش به صورت غیرهم‌زمان به سرورهای ثانویه منتقل می‌کنه. این روش سریعتره اما در صورت به مشکل‌خوردن، احتمال از دست دادن داده‌ها وجود داره.مفهوم Quorum Commit: حدنصابی که برای تایید نوشته شدن در نودهای ثانویه نیازه تا بگیم موفقیت‌آمیز بوده. این روش بین سرعت و اطمینان تعادل ایجاد می‌کنه. مثلا اگر سه سرور داریم، حدنصاب مناسب می‌تونه ۲ باشه، یعنی اگر ۲ سرور تایید بدن که نوشتن انجام شده، نوشتن رو موفق در نظر می‌گیریم.مفهوم Failover: به فرایندی می‌گیم که، موقع به مشکل خوردن سرور اصلی، یک سرور آماده جایگزین سرور اصلی بشه که سیستم به مشکل نخوره. این فرایند می‌تواند:انتقال دستی Manual Failover: نیاز به مداخله انسانی داره.انتقال خودکار Automatic Failover: به طور اتومات توسط ابزارهایی مانند repmgr، Patroni یا pg_auto_failover مدیریت می‌شه.مفهوم (Load Balancing): درخواست‌های خوندن را بین سرورهای آماده توزیع می‌کند تا عملکرد بهبود پیدا کنه و بار سرور اصلی کاهش بیابه.مفهوم Connection Pooling: ابزارهایی مانند PgBouncer یا Pgpool-II که کانکشن‌های پایگاه داده رو مدیریت می‌کنن که، به طوری که یکسری کانکشن باز و آماده رو مدیریت می‌کنن تا حجم زیاد درخواست‌ها رو بتونن از طریق اونها مدیریت کنن و مساله کمبود کانکشن رو تا حدی مدیریت کنن.مفهوم Single Point of Failure: در واقع وقتی سیستم وابسته به یک سرویس در یک نقطه باشه، در  صورتی که اون نود یا سرویس با مشکل روبرو بشه و جایگزینی براش نباشه، اون رو یک نقطه شکست می‌گیم که کل سیستم رو می‌تونه دچار مشکل کنه.پیاده‌سازی PostgreSQL HA روی سه نودنمای کلی تنظیماتسرور اصلی: سرور اصلی که تمام عملیات نوشتن را مدیریت می‌کند.سرورهای رپلیکا: دو نسخه فقط خواندنی از سرور اصلی.ابزار Docker: برای کانتینری کردن PostgreSQL و اطمینان از قابلیت حمل.ابزار Repmgr: ابزاری محبوب برای مدیریت تکثیر و انتقال وظایف در PostgreSQL.نحوه عملکردنود اصلی: نوشتن‌ها رو قبول می‌کنه و داده‌ها را به نودهای آماده (replica - secondary) منتقل می‌کنه.نودهای ثانویه: عملیات خوندن می‌تونه از روی اینا انجام بشه.اگر نود اصلی خراب بشه،repmgr به طور خودکار یکی از نودهای آماده رو به عنوان نود اصلی ارتقا می‌ده.پیکربندی Docker Composeاین داکر کامپوز رو روی سه سرور بالا میاریم، توجه داشته باشیم که متغیرهای env هر سرور متفاوت هستن و باید فایل .env در کنار فایل داکر کامپوز هر یک از این سه سرور موجود باشه:version: &#39;2&#39;&#xA;&#xA;services:&#xA;  db:&#xA;    container_name: postgres_db&#xA;    image: 192.168.0.33:5000/postgresql-repmgr:17&#xA;    ports:&#xA;      - 5432:5432&#xA;    volumes:&#xA;      - db-data:/bitnami/postgresql&#xA;    environment:&#xA;      - POSTGRESQL_POSTGRES_PASSWORD=${POSTGRES_PASSWORD}&#xA;      - POSTGRESQL_USERNAME=${DB_USER}&#xA;      - POSTGRESQL_PASSWORD=${DB_PASS}&#xA;      - POSTGRESQL_DATABASE=${DB_NAME}&#xA;      - REPMGR_PASSWORD=${REPMGR_PASSWORD}&#xA;      - REPMGR_PRIMARY_HOST=${DB_MASTER_IP}&#xA;      - REPMGR_PARTNER_NODES=${DB_MASTER_IP},${DB_SLAVE1_IP},${DB_SLAVE2_IP}&#xA;      - REPMGR_NODE_NAME=${REPMGR_NODE_NAME}&#xA;      - REPMGR_NODE_NETWORK_NAME=${DB_CURRENT_SERVER_IP}&#xA;      - REPMGR_NODE_ID=${REPMGR_NODE_ID}&#xA;      - REPMGR_FAILOVER=automatic&#xA;      - REPMGR_PROMOTE_COMMAND=&#39;repmgr standby promote&#39;&#xA;      - REPMGR_FOLLOW_COMMAND=&#39;repmgr standby follow&#39;&#xA;      - POSTGRESQL_EXTRA_FLAGS=--synchronous_standby_names=&#34;2 (${DB_SLAVE1_IP}, ${DB_SLAVE2_IP})&#34;&#xA;    networks:&#xA;      - net-backend&#xA;&#xA;volumes:&#xA;  db-data:&#xA;&#xA;networks:&#xA;  net-backend:&#xA;    name: net-backend&#xA;    driver: bridgeنمونه فایل env:REDIS_MASTER_IP=192.168.0.44&#xA;REDIS_CURRENT_SLAVE_IP=192.168.0.45&#xA;&#xA;DB_CURRENT_SERVER_IP=192.168.0.1&#xA;DB_MASTER_IP=192.168.0.1&#xA;DB_SLAVE1_IP=192.168.0.2&#xA;DB_SLAVE2_IP=192.168.0.3&#xA;DB_USER=postgres&#xA;DB_PASS=pass&#xA;DB_NAME=my_db&#xA;&#xA;REPMGR_PASSWORD=repmgrpass&#xA;POSTGRES_PASSWORD=postgrespass&#xA;REPMGR_NODE_NAME=node-0        # Change for each node: node-1, node-2&#xA;REPMGR_NODE_ID=1               # Change for each node: 2, 3توضیح پیکربندیبخش Image: تصویر PostgreSQL با repmgr برای مدیریت HA مشخص شده.متغیرهای محیطی: تنظیمات PostgreSQL و repmgr مانند اطلاعات کاربری، نودهای ثانویه و رفتار انتقال وظایف را پیکربندی می‌کنه.بخش Volumes: داده‌های پایگاه داده را در برابر راه‌اندازی مجدد کانتینر پایدار نگه می‌داره.بخش Network: اطمینان از ارتباط تمام نودها در یک شبکه.بخش Synchronous Standby: اطمینان از هم‌زمان بودن دو نود آماده برای حداقل کردن از دست دادن داده.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sun, 29 Dec 2024 14:37:54 +0330</pubDate></item><item><title>نماد O بزرگ؛ زمانی که پای عملکرد الگوریتم به وسط می‌آید</title><link>https://virgool.ir/@hamze/نماد-o-بزرگ-زمانی-که-پای-عملکرد-الگوریتم-به-وسط-می-آید-otlwpds1dtz5</link><description>توی طراحی یک نرم‌افزار با عملکرد بالا، چیزی که مهمه اینه که متوجه باشیم الگوریتم‌مون از چه پیچیدگی زمانی‌ای استفاده می‌کنه.پیچیدگی O(1)مفهوم: پیچیدگی زمانی ثابت (Constant)، یعنی هر ورودی که بهش بدی، یک میزان ثابت زمان می‌بره که اجرا بشه، یعنی عملکردش وابسته به سایز ورودی نیست.مثال: دسترسی به یک المان آرایه به کمک index اون و یا اضافه یا حذف کردن یک المان از یک hash table. (جدول هش ساختار داده‌ای هست که از جنس key, value هست و به کلیدهاش به مقادیرش دسترسی پیدا می‌کنیم.)پیچیدگی O(log n)مفهوم: پیچیدگی زمانی لگاریتمی (Logarithmic)، یعنی اینکه زمان اجرا با سرعت کمتری نسبت به رشد ورودی افزایش پیدا می‌کنه. میشه اینطور در نظر گرفت که با ضریب حدودا نیم به ۱ زمان نسبت به ورودی اضافه میشه.مثال: جستجوی دودویی (binary search) روی یک آرایه مرتب و یا عملیات روی یک درخت جستجوی دودویی بالانس شده.پیچیدگی O(n)مفهوم: پیچیدگی زمانی خطی (Linear)، یعنی اینکه با نسبت ۱ به ۱ (مستقیم) براساس ورودی الگوریتم، تایم اجراش بیشتر میشه.مثال: پیدا کردن مینیموم و ماکسیموم در یک آرایه غیر مرتب.پیچیدگی O(n logn)مفهوم: پیچیدگی خطی لگاریتمی (Linearithmic)، این ترکیبی از پیچیدگی زمانی خطی و لگاریتمی هست. یعنی مثلا چیزی در حدود ۱.۵ به ۱ زمان نسبت به ورودی رشد می‌کنه.مثال: روش‌های مرتب‌کردن عملکرد بالا مثل merge sort,  quick sort, heap sort. در واقع میشه گفت یک لوپ روی ورودی دارن که درونش یک لوپ روی نصف ورودی داره.پیچیدگی O(n^2)مفهوم: پیچیدگی زمانی توان ۲ (Quadratic)، زمان اجرا به صورت تصاعدی با سایز ورودی اضافه میشه.مثال: الگوریتم‌های مرتب کردن ساده مثل bubble sort, insertion sort, selection sort. در واقع الگوریتم‌هایی که دو بار لوپ روی ورودی میزنن.پیچیدگی O(n^3)مفهوم: پیچیدگی زمانی توان ۳ (Cubic)، زمان اجرا خیلی بیشتر با هر افزایش ورودی بسیار بیشتر اضافه میشه.مثال: الگوریتم ضرب دو ماتریکس با یک الگوریتم ابتدایی، در واقع میشه گفت کدی که ۳ لوپ تو در تو روی وردی‌ها داره.پیچیدگی O(2^n)مفهوم: پیچیدگی زمانی نمایی (Exponential)، با اضافه شدن هر ورودی جدید، زمان اجرا ۲ برابر میشه، این وحشتناکه، برای درکش مثال بزنم، مثلا با ۵ ورودی ۱ ثانیه طول می‌کشه، با ۶ تا ۲ ثانیه، با ۷ تا ۴ ثانیه، می‌تونین حساب کنین با ۲۰ تا چند ثانیه؟ به نظر یه چیزی حدود ۱۲ روز میشه :)مثال: الگوریتم‌هایی که به صورت بازگشتی (recursive) مسائل رو با تقسیم کردن به چندین زیر مساله حل می‌کنن.پیچیدگی O(n!)مفهوم: پیچیدگی زمانی فاکتوریل (Factorial)، با افزایش ورودی سر به فلک می‌کشه :))مثال: الگوریتم‌هایی که همه حالت‌های ممکن رو چک می‌کنن تا به جواب درست برسن، الگوریتم‌های جای‌گشت (Permutation Algorithms). پیچیدگی O(sqrt(n))مفهوم: پیچیدگی زمانی جزر (Square root)، زمان اجرا متناسب با جزر ریشه ورودی اضافه میشه.مثال: در واقع الگوریتم‌هایی که توش یک لوپ با گام‌های جزر n داریم، مثل الگوریتم پیدا کردن اعداد اول.ترتیب عملکردهمه پیچیدگی زمانی‌هایی که آوردم به ترتیب عملکرد از کارا ترین آورده شده، فقط پیچیدگی جزری رو آخر آوردم چون متداول نیست ولی در عمل بین لگاریتمی و خطی قرار می‌گیره.با سپاس از ByteByteGo برای این اینفوگراف خوشگلش :)</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 08 Oct 2024 12:13:37 +0330</pubDate></item><item><title>دروازه API ها چیه و چه کمکی می‌تونن بکنن؟ - API Gateway</title><link>https://virgool.ir/@hamze/دروازه-api-ها-چیه-و-چه-کمکی-می-تونن-بکنن-api-gateway-qw4apiym0cm8</link><description>دروازه API ها یا API Gateway یک واسطه که بین کاربرها و سرویس‌ها قرار می‌گیره. به این صورت که همه درخواست‌های کاربرها میان به این واسط و از اونجا هر درخواست به سرویس و endpoint مرتبطش ارسال میشه. در واقع یک نقطه ورود مرکزی برای همه کاربرهاست.چرا بهش احتیاج داریم؟جواب ساده اینکه اگر فکر می‌کنین که بهش احتیاجی ندارین و کار بدون این انجام میشه، خب بهش فعلا حداقل احتیاجی ندارین :)اما این کارا رو به کمکش می‌تونین انجام بدین:اعتبار سنجی پارامترها (Parameter Validations): چک کردن پارامترهایی که همراه درخواست اومده و برگشت زدن درخواست در صورت اشتباه بودن‌شون.چک کردن اجازه دسترسی (Whitelist Verification): اجازه دسترسی به کل سرویس‌ها رو بر اساس IP یا User Agent و یا هرچیز دیگه‌ای مدیریت می‌کنه و کاربرد امنیتی داره.اعتبارسنجی و بررسی دسترسی کاربر (Authentication and Authorization): وضعیت لاگین و اجازه دسترسی به بخشی که درخواست کردن رو چک می‌کنه.محدودیت نرخ درخواست (Rate Limiting): نرخ درخواست‌ها به سرویس‌ها رو چک می‌کنه و می‌تونه محدودیت ایجاد کنه تا سرویس‌ها زیر بار غیر استاندارد نرن و مشکل نخورن.مسیریابی (Routing): درخواست برای رسیدن به سرویس مناسب هدایت می‌کنه. اینجا ممکنه Load Balance هم اتفاق بیوفته که ترافیک بین نمونه‌های یکسان یک سرویس توزیع بشن تا عملکرد سریع‌شون حفظ بشه.پیدا کردن سرور سرویس (Service Discovery): یک مکانیزمه که این امکان رو میده که متوجه اضافه شدن یا حذف شدن یک سرویس بشه. به این صورت به طور داینامیک می‌تونن سرویس‌ها رشد کنن.ایجاد تغییر در درخواست (Request Transformation): ایجاد تغییر در درخواستی که اومده، مثلا header, body و یا method اش رو تغییر بده.و در همه این موارد هم می‌تونیم کش، لاگ و مدیریت خطاها رو داشته باشیم و روی سرویسی مثل Elastic Search و Kibana بفرستیم که بتونیم این دیتاها رو بررسی کنیم.چطور راه بندازیمش؟سرویس‌های ابری مطرح مثل Amazon, Azure, Google Cloud این سرویس رو در اختیار می‌گذارن اما راهکارهای Open-source ای مثل Kong هست. چیزی که من متداول‌تر توی شرکت‌هایی که کار کردم دیدم استفاده از HAProxy و در صورت نیاز ‌Nginx برای این کار بوده.مرسی از ByteByteGo برای این اینفوگراف‌های خوشگلش :)</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sat, 05 Oct 2024 16:02:01 +0330</pubDate></item><item><title>کتاب انسان در جستجوی معنا</title><link>https://virgool.ir/@hamze/کتاب-انسان-در-جستجوی-معنا-b8mg0rfxfsa0</link><description>کتاب انسان در جستجوی معنا، اثر ویکتور فرانکل هست که یک پزشک متخصص مغز یهودی بوده که در زمان جنگ‌جهانی در اردوگاه‌های کار اجباری نازی‌ها به‌سربرده و از خاطرات اونجا و معنایی که در میان اون سختی‌ها برای زندگی پیدا کرده می‌گه. این متن، خلاصه کتاب نیست، بلکه برداشت‌ها و یا نقل‌قول‌های کلیدی از نگاه منه که اینجا نگه‌شون داشتم :)به فردی که اجباری نداشت روزی ۱۲ ساعت روی زمین باتلاقی کار کنه حسادت داشتیم، به اینکه لازم نبود بیل بزنه تا لوله‌ها رو توی زمین بذاره قبطه می‌خوردیم.کسی نمی‌تونه تصور کنه صدای منحوس آژیر خطر هم می‌تونه خوشایند باشه وقتی که اگر به صدا نمیومد از شدت خستگی میمردی.ما هیچ چیزی را مالک نبودیم، تنها خوش شانسی ما این بود که تن ضعیف‌مون رو مالک بودیم.در نهایت آنچه به سقوط حالت روحی و درونی زندانی منجر میشد، آنچنان به شرایط روانی، جسمی و محیط زندان وابسته نبود، بلکه نتیجه تصمیم زندانی بود.تنها کسانی قربانی آثار ویران کننده زندگی اردوگاهی شده‌اند که اجازه کم شدن سیرت معنوی و اخلاقی خود را داده‌اند. - با مذهبی متفاوته :)زندگی موقتِ نامحدود.دور کردن واقعیت از شرایط موجود، همه چیز رو بی‌معنا می‌کنه. این شرایط مجالی میده که از نظر روحی فراسوی خود بری. این شرایط اومدن تا سد راه بشن و بتونی شیوه برداشتنشون رو یاد بگیری، اما بعضی‌ها بجاش سختی‌ها رو کوچک میشمردن، به عنوان چیزی که نتیجه‌ای با خودش نداره، و غرق شدن در زندگی گذشته رو برتری می‌دادند. که این باعث میشد زندگی براشون بی‌معنی بشه.زندگی مثل رفتن به دندون‌پزشکیه، چون انسان همواره در انتظار دردناک‌ترین لحظه‌هاست، با اینکه اون لحظات رو پشت سر گذاشته.با نشون دادن هدف‌های آینده، دریچه امیدی براش باز بشه.کسی که چرایی برای زندگی‌کردن داره، از عهده چگونگی اون برخواهد آمد.زمانی که آدمی آگاه می‌شه که سرنوشتش رنج بردنه، به اجبار رنجش را به عنوان وظیفه‌ای منحصر به فرد می‌پذیرده و می‌پذیره که هیچ‌کس نمی‌تونه بجاش رنج بکشه و از سختی‌ها رهاش کنه. نحوه تحمل و مواجهه با مشکلات تنها فرصتیه که داره.تحمل رنج ایجاد کننده فرصت‌هایی است که به کمک آن به کمال می‌رسیم.بی‌همتایی و یگانگی که هر فرد را از دیگری متمایز می‌کنه و هرکس مسئولیتی داره و قابل جایگزینی نیست یعنی کسی به جای اون نمی‌تونه انجامش بده، در مقابل کاری ناتمام یا انتظار کشیدن دیگران برای او، معنی خودکشی از بین می‌ره.معنا درمانی بر این اصل تکیه داره که پذیرش مسئولیت بسیار مهمه. چنان زندگی کن که انگار بار دومه توی این جهانی و امروز در حال برطرف کردن اشتباهاتی هستی که بار اول انجام دادی. ❤خودت انتخاب کن در برابر چه چیز و چه کس چقدر مسئولی.انتخاب اینکه در برابر وظیفه‌ای که زندگی بر دوشش گذاشته، به افرادی ویژه پاسخگو باشه.هدایت گاهی به سمت چیزیست که باعث می‌شود آدمی خود را کنار بگذارد و با قبول هر نوع فداکاری و ایثاری به انسان بودن واقعی خود نزدیک شود.معنا درمانی معنا را به سه روش تعریف می‌کند:با انجام دادن کاری ارزشمند و یا خلق و آفرینش آن.به دست آوردن تجربه‌ای حاصل از آشنا شدن با کسی یا چیزی.با تاب آوردن رنج و درد و رویه‌ای که برای آن انتخاب می‌کند.داستان مردی که همسرش مرده بود و مدت‌ها غمناک بود. ازش پرسید اگر تو زودتر مرده بودی، همسرت چه حسی داشت؟یافتن معنایی برای رنج‌ها.به نظر می‌رسه رنج و فنا و پریشانی و مرگ، دزدان معنای زندگی اند، اما چیزی که از دست میره، توانایی‌ها و امکانات هستن. وقتی شکوفا شوند به گذشته‌ای فنا ناپذیر متصل می‌شن، احساس مسئولیت و وظیفه. درس ما از گذرا بودن امکانات و چگونه انتخاب کردن ما: کدام باید شکوفا شود و کدام به فنا برود؟ اثر ماندگار او در این دنیا زودگذر چه خواهد بود؟تقویم اون از نکرده‌ها به کرده‌ها و شده‌ها تبدیل میشه. با افتخار به این برگ‌ها و زندگی پر از فعالیت خود نگاه می‌کند.با جدیت و پشتکار نقش خود را در زندگی بازی کرده است.من بجای امکانات، واقعیات را در کنارم دارم، لبریز از عشق و محبت ورزیده شده و دردها و رنج‌هایی که با دلیری در برابرشون ایستادگی کردم و امروز بیشتر از هرچیزی بهشون افتخار می‌کنم و اجازه نمی‌دم حسرت‌ها در وجودم ریشه کنه.پدری که امکاناتی که خودش فراهم کرده و در اختیار نوجوان‌ها قرار داده.روش متضاد: اتفاقی که ازش ترس داره، با قصد فراوان بخوایش. بر دو پایه استواره:ترس باعث میشه تا همون اتفاقی که ازش می‌ترسیم بیفته.فشار توجه بالا منجر به دور شدن از آنچه آرزو داریم.مثلا آدمی که زیاد عرق می‌کنه و از پیش اومدنش می‌ترسه، هروقت حس کرد عرق می‌کنه، بجای شرمندگی از اطرافیان بخواد ببینن که چقد عرق کرده.حسابداری که از بدخطیش می‌ترسه: بذار همه ببینن چقد خرچنگ غورباقه می‌نویسم، همه باید بدونن.بچه‌ای که لکنت داشت و توی قطار بخاطر بلیط نداشتن، گرفته بودنش و خواسته از لکنتش استفاده کنه. اما اون موقع کاملا واضح صحبت کرده.مگر اینکه خلا وجودی خودنمایی کنه و مجدداً سنتوم‌ها رو فعال کنه.اما شاید آزادی در انتخاب موقعیت‌ها رو گاهی نداشته باشیم. ما آزادی چگونه مواجه شدن با موقعیت‌ها رو داریم.آزادی در کنار مسئولیت پذیری باید باشد.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sun, 18 Aug 2024 10:11:56 +0330</pubDate></item><item><title>همه چیز درباره سیستم اعتبارسنجی جنگو</title><link>https://virgool.ir/@hamze/همه-چیز-درباره-سیستم-اعتبارسنجی-جنگو-lzytwzdywg9g</link><description>به روش‌های متفاوتی میشه توی جنگو پرمیژن‌ها رو تعریف کرد، دو روش اصلی رو اینجا بررسی می‌کنیم:پرمیژن‌ها می‌تونن در سطح model باشن (add, change, delete , view) که اینها به طور پیش‌فرض توسط خود جنگو تعریف میشن و ما می‌تونیم سطح‌های کاستوم دیگه‌ای تعریف کنیم. و در پنل django admin میشه گروه‌بندی تعریف کرد و این سطح دسترسی‌ها رو به گروه‌ها و یا مستقیم به کاربرها داد.خود Django REST Framework هم کلاس‌های پرمیژن خودش رو داره (مثل IsAuthenticated, IsAdminUser) و همینطور ما می‌تونیم کلاس‌های پرمیژن کاستوم DRF رو تعریف کنیم.ساخت سوپر یوزر با کامند:python manage.py createsuperuser --username=joe --email=joe@example.comاحراز هویت کاربر:با نام‌کاربری و پسورد لاگین میشه و یوزر برمی‌گردونه و اگر وجود نداشت None میده:user = authenticate(username=&#34;john&#34;, password=&#34;secret&#34;)&#xA;# Asynchronous version: aauthenticate()سطح دسترسی:جنگو یک سیستم پرمیژن داخلی داره که باهاش میشه به کاربرها یا گروهی از کاربرها، یک دسترسی خاص داد که از طریق Django admin site انجام میشه. که به این صورته:دسترسی به دیدن ویوها محدود به کاربرهایی که دسترسی view یا change برای اون object ها رو دارن.دسترسی به ویو افزودن فقط به کاربرایی داده میشه که این دسترسی add رو دارن.دسترسی ویرایش، محدود به کاربرایی هست که دسترسی change رو برای اون object ها رو دارن.دسترسی‌ها می‌تونه محدود به ازای هرنوع object نباشه و به ازای یک object خاص باشه. که به کمک متد های زیر انجام میشه:has_view_permission(), has_add_permission(), has_change_permission() and has_delete_permission()مشابه همه جنگو مدل‌ها، از طریق شی کاربر میشه با پرمیژن‌هاش کار کرد:myuser.groups.set([group_list])&#xA;myuser.groups.add(group, group, ...)&#xA;myuser.groups.remove(group, group, ...)&#xA;myuser.groups.clear()&#xA;myuser.user_permissions.set([permission_list])&#xA;myuser.user_permissions.add(permission, permission, ...)&#xA;myuser.user_permissions.remove(permission, permission, ...)&#xA;myuser.user_permissions.clear()پرمیژن‌ها بعد اولین بار که فراخوانی میشن، کش میشن، و این در بیشتر مواقع اوکیه، اما اگر بخوایم بلافاصله پرمیژن‌های جدید رو بگیریم و کش رو به روز کنیم، آسون‌ترین راه‌حل اینه که دوباره user رو از دیتابیس بگیریم.درباره Proxy model ها:این مدل‌ها دقیقا مشابه مدل معمولی هستن، با این تفاوت که پرمیژن‌هاشون برای خودشونه و ارث نمیبرن:class Person(models.Model):&#xA;    class Meta:&#xA;        permissions = [(&#34;can_eat_pizzas&#34;, &#34;Can eat pizzas&#34;)]&#xA;&#xA;&#xA;class Student(Person):&#xA;    class Meta:&#xA;        proxy = True&#xA;        permissions = [(&#34;can_deliver_pizzas&#34;, &#34;Can deliver pizzas&#34;)]&#xA;&#xA;&gt;&gt;&gt; content_type = ContentType.objects.get_for_model(Student, for_concrete_model=False)&#xA;&gt;&gt;&gt; student_permissions = Permission.objects.filter(content_type=content_type)چک لاگین بودن کاربر:اگر لاگین نباشه، request.user یک شی از AnonymousUser برمی‌گردونه.if request.user.is_authenticated:&#xA;# user = await request.auser() for asyncمحدود کردن بخش‌ها به ورود کاربر:میشه از متد is_authenticated که قبلا گفتیم استفاده کرد.میشه از دکوریتور @login_required استفاده کرد. که اگر لاگین نباشه میفرسته صفحه لاگین.این دکوریتورهم جایی شاید به کارتون بیاد: @staff_member_requiredاستفاده از mixin به نام LoginRequiredMixin در class-based viewsاستفاده از دکوریتور user_passes_test که به عنوان پارامتر اسم یک متد رو میگیره و اگر False برگردونه، کاربر رو می‌فرسته صفحه ریدایرکتی که بهش دادیماستفاده از دکوریتور permission_required که به عنوان پارامتر اسم پرمیژن رو می‌گیره.استفاده از mixin به نام PermissionRequiredMixin برای class-based viewsنامعتبر کردن session بعد از تغییر رمزعبور:به طور معمول در جنگو یک hash در session وجود داره که با هر درخواست کاربر این hash چک میشه که یکسان باشه و اینه که این امکان رو میده که کاربر با تغییر پسوردش، همه از همه session هاش log out بشه. اگر صفجه تغییر پسورد خودتون رو دارین با فراخونی متد زیر، بعد از تغییر پسورد، دیگه کاربر خودش رو لاگ‌اوت نمی‌کنه:update_session_auth_hash(request, form.user)Django REST Framework Permissionsپرمیژن‌ها در REST framework همیشه به صورت لیستی از پرمیژن‌های کلاس تعریف میشن. قبل از اجرای بدنه اصلی view هر پرمیژن توی لیست چک میشه و اگر پرمیژنی ناموفق بشه اکسپشن exceptions.PermissionDenied or exceptions.NotAuthenticated  برمی‌گردونه و کد ویو اجرا نمیشه.دسترسی سطح objectهمینطور دسترسی سطح object هم پشتیبانی میشه که به این صورت میشه چک کردش:obj = get_object_or_404(self.get_queryset(), pk=self.kwargs[&#34;pk&#34;])&#xA;self.check_object_permissions(self.request, obj)مشخص کردن سطح دسترسیمیشه با مشخص کردن DEFAULT_PERMISSION_CLASSES در setting مشخص کرد که سطح پیش‌فرض دسترسی چی باشه.و می‌تونیم سطح دسترسی class-based view ها رو به ازای هر view یا viewset مشخص کنیم:permission_classes = [IsAuthenticated]و یا برای function-based view ها:@permission_classes([IsAuthenticated])در عمل چطور پیاده کنم؟ (روش پیشنهادی)۱- توی پنل ادمین جنگو گروه مورد نظرت رو تعریف کنین و کاربری رو که می‌خوای به این گروه اد کن.۲- کلاس کاستوم پرمیژن رو تعریف کن:from rest_framework.permissions import BasePermission&#xA;from django.contrib.auth.models import Group&#xA;&#xA;class IsInGroup(BasePermission):&#xA;    group_name = None&#xA;&#xA;    def has_permission(self, request, view):&#xA;        return request.user and request.user.groups.filter(name=self.group_name).exists()&#xA;&#xA;class IsInAdminGroup(IsInGroup):&#xA;    group_name = &#39;admin&#39;&#xA;&#xA;class IsInEditorGroup(IsInGroup):&#xA;    group_name = &#39;editor&#39;&#xA;&#xA;class IsInViewerGroup(IsInGroup):&#xA;    group_name = &#39;viewer&#39;۳- توی ویو Group-based پرمیژن رو تعریف کن:from rest_framework.views import APIView&#xA;from rest_framework.permissions import IsAuthenticated, AllowAny&#xA;&#xA;class MyCustomView(APIView):&#xA;    permission_classes_by_action = {&#xA;        &#39;default&#39;: [IsAuthenticated],&#xA;        &#39;retrieve&#39;: [IsInViewerGroup],&#xA;        ....&#xA;    }&#xA;&#xA;    def get_permissions(self):&#xA;        try:&#xA;            # Return the permission classes based on the action&#xA;            return [permission() for permission in self.permission_classes_by_action[self.request.method.lower()]]&#xA;       except KeyError:&#xA;            # Return the default permission classes if the action is not explicitly specified&#xA;            return [permission() for permission in self.permission_classes_by_action[&#39;default&#39;]]&#xA;&#xA;    def get(self, request, *args, **kwargs):&#xA;        # Your GET logic here&#xA;        passمنابع:https://docs.djangoproject.com/en/5.0/topics/auth/default/https://www.django-rest-framework.org/api-guide/permissions/#django-rest-framework-role-filters</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Wed, 10 Jul 2024 11:14:54 +0330</pubDate></item><item><title>همه چی درباره JWT</title><link>https://virgool.ir/@hamze/همه-چی-درباره-jwt-hotgf8lkk4mo</link><description>یک نوع توکن اعتبارسنجی که به طور گسترده برای اشتراک اطلاعات بین کلاینت و سرور استفاده میشه. مهمه که توجه داشته باشیم که JWT گارانتی نمی‌کنه که دیتا رمزنگاری شده باشه بلکه encode شده و دیتا توسط هرکسی که رهگیریش کنه، قابل دیدنه، به همین دلیل HTTPS همراه با JWT بسیار پیشنهاد میشه.به یاد داشته باشیم که JWT یک توکن sign شده است نه رمزنگاری شده. پس اطلاعات حساس رو داخلش قرار ندیم.در واقع JWT مخفف JSON Web Token است که یک استاندارد بازه که انتقال اطلاعات JSON، بین دو موجودیت رو امن می‌کنه.یک JWT که sign شده، می‌تونه یکپارچگی اطلاعات داخلش رو ادعا کنه، درحالی‌که یک توکن encrypt شده، می‌تونه مخفی بودن اون اطلاعات رو ادعا کنه. امضای روی توکن امضا شده با public/private key گواهی می‌کنه که فقط کسی که کلید خصوصی رو داره، توکن رو امضا کرده.در پروتوکل‌های stateless، چیزی در مورد درخواست قبلی نمی‌دونه و باید با هر درخواست خودت رو معرفی کنی. اینکه بخوای از session استفاده کنی و کاربر درخواست لاگین بده و بعد یک session id بگیره و با هر درخواست بعدیش بفرسته، راهکار مناسبی نیست، چون این داده session روی سرور ذخیره میشه و حافظه می‌گیره، مدیریت باید بکنیش و session های غیرفعال رو منقضی کنی. هربار باید چک کنی که session معتبر هست یا نه و این کار سربار داره.اما JWT از session استفاده نمی‌کنه، سرور بجای ایجاد session، وقتی کاربر اطلاعات شناسایی شو می‌فرسته، یک JSON Web Token برمی‌گردونه، که باهاش همه کارهایی که نیاز به دسترسی داره رو می‌تونی انجام بدی.مشابه کارت هتله که بعد از رجیستر کردن بهت میدن و باهاش به اتاقت و کافه و استخر و بقیه بخش‌ها دسترسی داری اما به بقیه اتاق‌ها و بخش‌های مدیریتی دسترسی نداری و تاریخ انقضا هم داره.چه زمانی از JWT استفاده کنیم؟اول Authorization: استفاده مرسومش برای اعتبارسنجیه، بعد از لاگین، همه درخواست‌ها با خودشون JWT رو دارن و به کاربر بنا به دسترسی‌هاش، اجازه دسترسی رو میده.دوم انتقال اطلاعات: انتقال امن اطلاعات بین بخش‌ها رو تسهیل می‌کنه. ارسال کننده JWT با امضایی که انجام میده، می‌تونه مشخص بشه، به طور مثال با استفاده از کلید خصوصی و عمومی. همینطور، به دلیل اینکه امضا براساس header و payload محاسبه میشه، می‌تونم تایید کنیم که اطلاعات تغییری داده نشده.مزیت‌های JWTسبک بودنش. از XML encode شده، فضای کمتری نیاز داره.به session نیازی نداره.به طور داخلی expiration داره.به cookie نیازی نداره.ساختار JWTسه بخش اصلی داره که به این شکله: &lt;&lt;header&gt;&gt;.&lt;&lt;payload&gt;&gt;.&lt;&lt;signature&gt;&gt;بخش Header:معمولا دو بخش توی header هست، نوع توکن (JWT) و الگوریتمی که برای امضا شدنش استفاده شده (RSA, HMAC).بخش Payload:این بخش شامل اطلاعاتی که ادعا میشه از سرور اومده. می‌تونه اطلاعات مرتبط با یک موجودیت یا هر اطلاعاتی باشه. ما سه نوع claim داریم که در این قسمت می‌تونیم استفاده کنیم:نوع Registered Claims: از قبل تعریف شده هستن، اجباری نیستن اما امنیت رو بالا می‌برن.iss: issuersub: subjectexp: expiration timenbf: not beforeiat: issued atنوع Public Claims: چیزایی که خود کاربر تعریف می‌کنه و متناسب با اپ هست.نوع Private Claims: مواردی که برای اشتراک اطلاعات بین بخش‌هاس و روش توافق شده و اسامیش توسط توافق طرفین انتخاب شده.بخش SIgnature:برای تولید امضا، لازمه که encoded header و encoded payload و secret رو با الگوریتمی که در header مشخص کردیم sign کنیم.در نهایت از ترکیب این سه بخش، JWT token ما ایجاد میشه.encode64({&#xA;   &#34;alg&#34;: &#34;HS256&#34;,&#xA;   &#34;typ&#34;: &#34;JWT&#34;&#xA; }).&#xA;encode64({&#xA;   &#34;sub&#34;: &#34;1234567890&#34;,&#xA;   &#34;name&#34;: &#34;John Doe&#34;,&#xA;   &#34;admin&#34;: true,&#xA;   &#34;iat&#34;: 1516239022&#xA; }).&#xA;HMACSHA256(&#xA;   base64UrlEncode(header)&#xA;    + &#34;.&#34; +&#xA;   base64UrlEncode(payload),&#xA;   your-256-bit-secret&#xA;)آسیب‌پذیری‌های معمول JWTدیتای توکن رو تغییر بده و الگوریتم رو  none بزنه (انواع variation کلمه none).کوتاه بودن shared secret برای اینکه سریع باشه، اما باعث میشه حملات brute force آسون‌تر بشه. طول shared secret HS256/384/512  ضروریه.ممکنه اطلاعات payload رو تغییر بدی و به اندپویت بفرستی و در exception برگشتی امضا درست که انتظار داشته رو برگردونه!از طریق پارامتر kid می‌تونه SQL Injection, Path traversal, command injection انجام بده.دستکاری آدرس URL و الگوریتم به روش JWKS Injection/JWKS Spoofingاستانداردهای پیاده‌سازی Best Practicesنوع الگوریتم رو بررسی کنین که None نیاد.ورودی‌های کریپتوگرافیک رو همیشه validate کنین.از کلیدهای رمز قوی استفاده کنین.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Wed, 05 Jun 2024 11:51:56 +0330</pubDate></item><item><title>در پایتون GIL چیست؟</title><link>https://virgool.ir/@hamze/در-پایتون-gil-چیست-gzqp5f2covel</link><description>فارسیش میشه قفل مترجم جهانی - Global Interpreter Lock - که یک مکانیزم در CPython (پیاده‌سازی پیش‌فرض و پراستفاده‌ترین پایتون) هست که مانع اجرای همزمان بایت کدهای پایتون توسط چندین native thread میشه. برای ساده‌سازی مدیریت حافظه در حضور برنامه‌های چند thread ای در پایتون طراحی شده. برای پیاده‌سازی concurrent programming در پایتون هست.و GIL فقط به یک thread اجازه اجرای بایت‌کد پایتون رو در هر زمان در یک پردازش میده. این یعنی اینکه multiple threads در پایتون نمی‌تونه کد پایتون رو به طور موازی اجرا کنه. (دلیلش اینه که مدیریت حافظه آسون‌تر بشه، از race condition جلوگیری بشه، تغییرات در کد موجود حداقل بشه و...)و GIL در مدیریت دسترسی به آبجکت‌های پایتون و ساختارداده‌ها کمک می‌کنه و از race condition جلوگیری می‌کنه که ممکنه به دلیل اینکه چندین thread بخوان یک دیتای یکسان رو همزمان تغییر بدن ایجاد میشه.و GIL در Jython و ironPython وجود نداره.نمونه کد:import threading&#xA;&#xA;counter = 0&#xA;&#xA;def increment():&#xA;    global counter&#xA;    for _ in range(1000000):&#xA;        counter += 1&#xA;&#xA;# Create two threads to increment the counter&#xA;thread1 = threading.Thread(target=increment)&#xA;thread2 = threading.Thread(target=increment)&#xA;# Start both threads&#xA;thread1.start()&#xA;thread2.start()&#xA;&#xA;# Wait for both threads to finish&#xA;thread1.join()&#xA;thread2.join()&#xA;&#xA;# Expected result: The counter will be less than 2,000,000 due to GIL contention&#xA;print(&#34;Counter:&#34;, counter)در این کد دو thread سعی می‌کن که یک شمارشگر مشترک رو اضافه کنن. با توجه به GIL، عملیات افزایش اتمی نیست (چون شامل گام‌های خوندن، افزودن و نوشتن میشه که ممکنه وسطش thread رو سوییچ کنه) و ممکنه منجر به این بشه که مقدار شمارشگر کمتر از مقدار مورد انتظار ۲ میلیون بشه. با کد زیر میشه عملیات افزایش رو اتومیک کرد: از RLock یا Semaphore هم میشه استفاده کرد.with counter_lock:&#xA;        # Incrementing the counter within the lock ensures atomicity&#xA;        counter += 1باید نسبت به محدودیت GIL آگاه بود. در سناریوهای مرتبط با CPU از مدل‌های همزمانی جایگزین مثل multiprocessing در زمانی که همزمانی واقعی نیاز داریم رو در نظر بگیریم.در سناریوهای مرتبط با I/O، بیایم asynchronous programming رو مدنظر قرار بدیم و از چارچوب‌هایی مثل asyncio برای همزمانی بدون محدودیت‌های GIL استفاده کنیم.در یکسری از کتابخونه‌ها آزاد کردن GIL در سناریوهای مختلف در نظر گرفته شده تا threadهای دیگه بتونن اجرا بشن. این کتابخونه‌ها رو مدنظر قرار بدین.و در نهایت اگر موازی‌سازی براتون حیاتیه، پیاده‌سازی‌های جایگزین پایتون رو مثل Jython و ironPython رو در نظر بگیرین که GIL رو ندارن.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 28 Nov 2023 17:07:09 +0330</pubDate></item><item><title>استقرار جنگو - Django Deployment</title><link>https://virgool.ir/@hamze/استقرار-جنگو-django-deployment-aulhqiy4wkwf</link><description>جنگو، یک فریمورک وب هست، پس نیاز به وب‌سرور برای عمل کردن داره و چون اکثر وب‌سرورها به طور نیتیو پایتون نمی‌فهمن، ما نیاز به یک رابط برای ارتباط نیاز داریم.استقرارجنگو در حال حاضر دو رابط رو پشتیبانی می‌کنه:رابط WSGI - web server gateway interface: که استاندارد پایتون برای ارتباط بین وب‌سرور و برنامه‌است. اما فقط کد synchronous رو پشتیبانی می‌کنه. - Gunicorn - Green Unicorn: کار باهاش ساده‌است، از چندین پردازش ورکر پشتیبانی می‌کنه. - uWSGI: پیچیده‌تره و از تنظیمات و کانفیگ‌های مختلفی پشتیبانی می‌کنه و با وب‌سرورهای مختلفی میشه استفاده‌اش کرد.رابط ASGI - Asynchronous Server Gateway Interface: استاندارد جدید asynchronous-friendly هست که به سایت جنگویی امکان استفاده از امکانات غیرهمزمان پایتون و جنگویی که پیاده شده رو میده. موقع استفاده از وب‌سوکت از این رابط‌ها استفاده می‌کنیم. - Daphne: به طور خاص برای اپ جنگو طراحی شده و اگر از channel ها در جنگو استفاده می‌کنین انتخاب مناسبیه. - Hypercorn:  - Uvicorn: معمولا با FastAPI استفاده میشه.شما باید همینطور در نظر بگیرین که چطور فایل‌های static برنامه رو می‌خواین دپلوی کنین و همینطور چطور می‌خواین error reporting رو مدیریت کنین.مدیریت همزمانی - Concurrency - در وب سرورها:رویکرد Eventletدر واقع Eventlet یک concurrent networking library پایتون هست که از  coroutines و greenlets برای دستیابی به همزمانی استفاده می‌کنه. این امکان رو میده که عملیات‌های non-blocking I/O در محیط single-threaded اجرا بشن. درواقع امکان اجرای تعداد زیادی کانکشن به طور همزمان بدون نیاز به multi-threading یا multi-processing رو ایجاد می‌کنه. بر اساس مدل event-driven هست. انتخاب مناسب برای برنامه‌هایی که نیازمند همزمانی بالا و کارایی در هندل کردن تعداد زیادی کانکشن هستن اما از پیچیدگی multi-threading یا multi-processing اجتناب می‌خوایم بکنیم.رویکرد Pre-forkingاین رویکرد شامل ایجاد چندین پروسس قبل از نیاز بهشون برای هندل کردن ریکوئست‌های ورودی میشه. هر پروسس یک نسخه از کد برنامه و منابع برای خودش داره و مستقل عمل می‌کنه. درخواست‌ها بین پروسس‌های در دسترس تقسیم می‌شن و امکان هندل کردن همزمان چندین کانکشن رو ایجاد می‌کنن. ریسک shared state issues وجود نداره، چون هر کدوم منابع مستقل خودشون رو دارن.رویکرد Multi-threadingدر این رویکرد هر کانکشن و درخواست توسط یک thread مجزا هندل میشه. ترد ها به صورت همزمان در یک پروسس اجرا میشن. GIL می‌تونه محدودیت تسک‌های CPU-bound باشه و اما برای تسک‌های I/O-bound کمتر مشکل ایجاد می‌کنه.رویکرد Multi-processingمشابه pre-forking، شامل ایجاد چندین پروسس و هندل کردن به طور همزمان کانکشن‌هاست.رویکرد Asyncioیک کتابخونه پایتون برای نوشتن کد single-threaded concurrent بوسیله coroutines, tasks, and event loops هست. یک چارجوب برنامه نویسی غیرهمزمان میده که اجازه هندل کردن تسک‌های I/O-bound رو بدون استفاده از threads یا processes رو میده.رویکرد Thread Poolingبجای ایجاد یک thread جدید برای هر کانکشن، یک استخر با سایز مشخص از thread ها نگهداری میشه تا درخواست‌های ورودی رو هندل کنن. با این روش سربار ایجاد تعداد زیاد thread برداشته میشه.رویکرد Coroutine Libraries (e.g., Trio, Curio)مشابه asyncio، این کتابخونه‌ها ابزارهایی برای نوشتن asynchronous و concurrent کد بوسیله coroutines در اختیارمون میذارن.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 28 Nov 2023 17:02:43 +0330</pubDate></item><item><title>ساختار داده Heap</title><link>https://virgool.ir/@hamze/ساختار-داده-heap-rbmooy0uepyd</link><description>ساختار داده هیپ یک ساختار داده بر مبنای درخت که به صورت دودویی هست ایجاد شده. یعنی به صورت باینری پیاده سازی میشه در نتیجه پیچیدگی زمانی ایجادش O(log n) خواهد بود. و تمامی شاخه‌ها بجز ردیف آخر که از سمت چپ شروع به پر کردن می‌کنیم، دو فرزند خواهند داشت. کاربرد اصلیش برای پیدا کردن ماکزیمم و مینیموم در لحظه است که پیچیدگی خوندن ماکس و مین با این روش O(1) خواهد بود.دونوع Heap داریم:نوع Max-Heap: کلیدی که در نود روت قرار داره، بزرگترین از بین همه بچه‌ها خواهد بود و همه بچه‌ها کوچکتر و یا مساوی خواهند بود و این قاعده به طور بازگشتی برای همه زیر درخت‌ها برقراره.نوع Min-Heap: کلیدی که در نود روت قرار داره، از همه بچه‌ها کوچکتر یا مساوی خواهد بود و این قاعده برای همه زیر درخت‌ها هم بر قراره.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Fri, 24 Nov 2023 12:20:55 +0330</pubDate></item><item><title>زبان‌های برنامه‌نویسی از نگاه اجرا، سطح و پردازش</title><link>https://virgool.ir/codenevis/زبان-های-برنامه-نویسی-از-نگاه-اجرا-سطح-و-پردازش-vnfqyvk2we65</link><description>از جنبه اجرازبان‌های تفسیری (interpreted)در زبان‌های تفسیری (interpreted)، سورس کد مستقیما توسط مفسر اجرا میشه. مفسر کد رو خط به خط می‌خونه و به کد ماشین یا کد میانی تفسیرش می‌کنه و اجراش می‌کنه. هیچ مرحله کامپایل جداگانه‌ای وجود نداره که فایل اجرایی مستقلی ایجاد بشه.زبان‌هایی مثل پایتون، جاوا اسکریپت و روبی، تفسیری هستن.این زبان‌ها به طور کلی انعطاف‌پذیری بیشتری دارن و به پلتفرم وابسته نیستن اما ممکنه در مقایسه با زبان‌های کامپایلری زمان اجرا کندتری داشته باشن.زبان‌های کامپایلری (compiled)در زبان‌های کامپایلری، سورس کد به زبان ماشین یا یک زبان میانی توسط کامپایلر قبل از اجرا ترجمه میشه. کامپایلر کل کد رو آنالیز می‌کنه و یک فایل قابل اجرا که می‌تونه مستقلا اجرا بشه از سورس کد اصلی تولید می‌کنه.زبان‌هایی مثل C, C++, Go و جاوا از این دسته هستن.این زبان‌ها اغلب عملکرد بهتری ارائه میدن چون کد قبل از اجرا بهینه شده. اگرچه گام کامپایل کردن، یک فاز به مراحل توسعه اضافه می‌کنه و توسعه رو می‌تونه کندتر بکنه.زبان‌های کامپایلری اغلب برای هر پلتفرم متفاوت نیازه که دوباره کامپایل بشن تا عملکرد بهینه رو داشته باشن.از جنبه سطحاین مبحث مرتبط با سطح abstraction از سخت‌افزار و میزان خوانایی برای انسان در زبان‌های برنامه‌نویسیه.زبان سطح بالا (High-Level Programming Languages)این زبان‌ها به زبان انسان نزدیک‌تر هستن و abstraction بیشتری از سخت‌افزار دارن که باعث میشه کدهاشون قابل فهم‌تر و خواناتر باشه. معمولا برنامه‌هایی که با زبان‌های سطح بالا نوشته میشن قابل انتقال بیشتر بین پلتفرم‌های متفاوت هستن چون با معماری کامپیتور خاصی گره خورده نیستن.زبان سطح پایین (Low-Level Programming Languages)این زبان‌ها به سخت‌افزار نزدیک‌تر هستن و سطح پایین‌تری از abstraction دارن. در نتیجه نیاز به درک بیشتری از منابع کامپیوتر و معماری‌شون دارن. و همینطور خوانایی‌شون سخت‌تره. اما کنترل بیشتری روی سخت‌افزار دارن و امکان نوشتن کدهای بسیار بهینه و کارا رو میدن، اگرچه به قیمت افزایش پیچیدگی.از جنبه همزمانیاین جنبه اشاره به روشی که زبان برنامه‌نویسی یا فریمورک، اجرای چند تسک یا پردازش رو به طور همزمان مدیریت می‌کنه اشاره داره. یعنی توانایی یک سیستم برای اجرای چندین تسک مستقل به طور همزمان.نوع Sequential Programmingدر این مدل، تسک‌ها یکی پس از دیگری به روش خطی اجرا میشن. یک thread اجرا هر تسک رو به طور متوالی            پردازش می‌کنه.نوع Parallel Programmingدر برنامه‌نویسی موازی، چندین تسک به طور همزمان در لحظه توسط چندین پردازشگر یا هسته اجرا میشن. هر تسک به طور مستقل اجرا میشه.نوع Concurrent Programmingبرنامه‌نویسی همزمان، شامل مدیریت تسک‌های متعدده که ممکنه در دوره‌های زمانی با هم تداخل شروع، اجرا و کامل شدن داشته باشن. این روش برای افزایش پاسخگویی و کارایی در سیستم با چندین تسک و حتی با یک پردازشگر استفاده میشه.نوع Coroutine-Based Concurrencyکوروتین‌ها واحدهای همزمانی سبک و مشارکتی هستن که به تسک‌ها اجازه توقف و از سرگیری رو میدن. بدون سربار thread ها، کانکارنسی رو در اختیارمون می‌ذارن.نوع Thread-Based Concurrency  در این نوع، Threadها واحدهای اجرایی مستقل و سبک در یک پردازش هستن. در این روش، چندین thread به طور همزمان در یک پردازش اجرا میشن. thread ها با هم فضای memory رو به اشتراک می‌ذارن که می‌تونه چالش data synchronization  ایجاد بکنه.نوع Actor-Based Concurrencyهمزمانی مبتنی بر Actor از Actor model الهام گرفته. جایی که اکتور ها موجودیت‌های مستقلی هستن که از طریق ارسال پیام به یکدیگر با هم ارتباط برقرار می‌کنن. هر اکتور وضعیت خاص خودش رو داره و پیام‌ها رو به صورت ناهمزمان پردازش می‌کنه که امکان همزمانی سطح بالایی رو فراهم می‌کنه.نوع Event-Driven Concurrencyبرنامه‌نویسی برمبنای رویداد مبتنی بر اجرای یک رویداد بر اساس trigger شدن اجرای یک تسکه. تسک‌ها معمولا به عنوان یک ایونت هندلر تعریف می‌شن که به یک رویداد خاص پاسخ بدن.در اجراهای همزمان (concurrent) سوییچ کردن بین تسک‌ها در زمان‌های خاصی که به نام context switches اتفاق می‌افته. در این زمان‌ها سیستم‌عامل تصمیم می‌گیره که اجرا رو متوقف کنه و اجرای یک تسک دیگه رو شروع کنه یا ادامه بده. این که دقیقا چه زمانی اتفاق می‌افته مرتبط با مدل همزمانی و سیستم‌عامله. در ادامه یکسری سناریو متداولش رو میگم:زمانی که یک تسک بلاک عملیاتی مثل I/O میشه (خوندن از فایل، صبر برای شبکه) یا به خواب میره.در این مدل هر پردازش زمان مشخصی برای اجرا داره و وقتی که تموم بشه، سیستم‌عامل می‌تونه سوییچ کنه.معماری‌های همزمانی و کتابخونه‌هاش می‌تونن این موضوع رو مدیریت کنن، مثلا در برنامه نویسی مبتنی بر رویداد، این تغییر کانتکست می‌تونه موقع هندل کردن یک رویداد اتفاق بیوفته.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Mon, 13 Nov 2023 21:30:13 +0330</pubDate></item><item><title>کوبر Kubernetes</title><link>https://virgool.ir/@hamze/کوبر-kubernetes-nz7ysnecrd0v</link><description>کوبر چیه؟یک ابزار اپن‌سورس ارکستریشن (orchestration) هست که توسط گوگل توسعه پیدا کرده و کمک می‌کنه تعداد زیادی اپ کانتینرایز شده (containerized) رو در محیط‌های (environment) مختلف مدیریت کنیم.مشکلی که حل می‌کنه اینه که وقتی از سمت منولوتیک به سمت میکروسرویس میری، به راهکاری نیاز داری که به روش درست و مناسبی بتونی صدها کانتینر رو مدیریت کنی، به این کار میکنم ارکستریشن.کاری که برات میکنه اینه که:بهت high availability رو میده و کمک می‌کنه downtime نداشته باشی.بهت عملکرد بالا و scalibility رو میده. موقع نیاز، زیرساخت رو scale می‌کنه و هر زمان لازم نبود دوباره کاهشش می‌ده.بازیابی فاجعه‌ها: اگر هر مشکلی برای زیرساخت یا دیتاسنتر پیش بیاد، سازکاری برای بک‌آپ و بازیابی اطلاعات داره.معماری کوبر:حداقل یک Master Node که بهش Control Plane می‌گیم و چندین Worker Node که بهشون Nodes می‌گیم، تشکیل شده.هر نود روش kubelet process ران شده، که کارش اینه که امکان ارتباط کلاستر با هم رو ایجاد کنه و یکسری تسک مثل اجرای برنامه رو انجام بده. هر ورکر نود روش چندین کانتینر اپ دپلوی شده و اینجا جایی که برنامه‌تون داره اجرا میشه.روی مسترنود برنامه‌هایی که برای اجرای درست و مدیریت کلاستر لازمه اجرا میشه. که شامل این موارده:بخش API Server: نقطه ورود به کلاستر کوبر، داشبورد و API و کامندلاین با این قسمت مرتبط میشه.بخش Controll Manager: کلیت اینی که در کلاستر وضعیت چیه رو نگه میداره. اینکه یک کانتینر مرده و نیازه بازیابی بشه و... بخش Scheduler: این بخش تصمیم می‌گیره که روی کدوم نود، پاد (کانتینر) جدید باید بالا بیاد بر اساس منابعی که اون کانتینر نیاز داره و منابعی که در اون نود موجوده.بخش etcd: استوریج key value، وضعیت هر لحظه کلاستر کوبر رو نگه میداره. همه دیتای تنظیمات رو داره. بخش بک‌آپ و بازیابی که صحبت کردیم هم براساس اسنپ‌شات‌هات این قسمت انجام میشه. بخش VIrtual Network: کارش اینه که همه نودها رو تبدیل به یک ماشین unified بکنه و امکان صحبت کردن مستر نود و ورکر نودها رو فراهم کنه. توجه داشته باشیم که لود اصلی روی ورکرهاست، پس منابع بیشتری دارن. مستر نود هم چون همه کارهای مدیریتی رو انجام میده و اگر مشکل بخوره، کل کلاستر غیر قابل استفاده میشه، لازمه که حداقل یک سرور بک‌آپ ازش داشته باشیم.کامپوننت‌های اصلی کوبر:بخش node: یک سرور فیزیکی یا مجازیبخش pod: کوچک‌ترین کامپوننت کوبر هست. معمولا شامل یک کانتینر میشه. هر پاد یک ip آدرس داره و می‌تونن باهم صحبت کنن که این آدرس داخلیه و از طریق virtual network در اختیارشون قرار گرفته. پادها ephemeral هستن، این یعنی می‌تونن خیلی راحت بمیرن، و اگر این اتفاق بیوفته، یک جدیدش ایجاد و جایگزین میشه و ip آدرس جدید می‌گیره، اگر برای ارتباط برنامه با دیتابیس از ip استفاده کرده باشیم به مشکل می‌خوریم برای همین از کامپوننت دیگه‌ای به نام service استفاده می‌کنیم.بخش Service: سرویس یک ip آدرس ثابته که می‌تونه به هر پاد تخصیص پیدا کنه. برای اینکه بخوایم اپ رو توی مرورگر باز کنیم نیاز به اکسترنال سرویس داریم، اکسترنال سرویس امکان دسترسی از طریق سرویس‌های بیرونی رو بهمون میده. اما برای دیتابیس نمی‌خوایم این دسترسی وجود داشته باشه، پس از اینترنال سرویس استفاده می‌کنیم. بخش Ingress: وقتی بخوایم از اکسترنال سرویس استفاده کنیم، باید ip نود و پورتش رو بزنیم، اما ما می‌خوایم که از یک پروتکل امن و نام دامنه استفاده کنیم. این کار رو Ingress برامون می‌کنه. درخواست اول به اینگرس میره و بعد از اونجا فوروارد میشه به سرویس. کامپوننت ConfigMap: فرض کنین که آدرس دیتابیس عوض شده، باید بری تو کدت عوض کنی، بعد build بگیری و بعد پوش کنی روی repo و بعد روی پاد pull بگیری! این برای همچین تغییر کوچیکی زیاده. در واقع کانفیگ‌مپ، تنظیمات بیرونی اپلیکیشن‌تونه که کانفیگ‌ها رو نگه میداره و پاد می‌تونه دیتا رو ازش بگیره. توجه داشته باشین که در کانفیگ‌مپ فقط دیتای غیرامنیتی رو بذارین.کامپوننت Secret: کامل مشابه ConfigMap هست با این تفاوت که برای نگهداری دیتاهای امنیتی مثل پسوردها هست و به صورت encode base 64 نگهداری میشه. به طور پیشفرض رمزگذاری نشده و باید خودتون کانفیگ کنینش.کامپوننت Volume: با رستارت پاد دیتابیس دیتاش میپره و این چیزی که ما می‌خوایم نیست. با استفاده از volume بخشی از حافظه فیزیکی روی هارد به پاد اختصاص داده میشه که می‌تونه روی لوکال ماشین و یا خارج از کلاستر کوبر باشه. کوبر کار مدیریت ماندگاری دیتا رو انجام نمیده، ما مسئول اینیم که مطمئن بشیم که رپلیکا و بکاپ و... برای اون دیتا داریم. در کوبر همه چیز replica دارن، یعنی یک نسخه دیگه ازشون موجوده، اینطور وقتی اپ به هر دلیلی بیاد پایین، کاربر با downtime روبرو نمیشه. رپلیکا ها به یک سرویس متصل هستن، سرویس همینطور یک لودبالانسر هم هست، این یعنی درخواست رو دریافت می‌کنه و پادی میفرسته که کمتر سرش شلوغه. برای این که رپلیکا داشته باشیم باید یک blueprint برات پادها تعریف کنیم که توش مشخص کنیم که چه تعداد رپلیکا می‌خوایم داشته باشیم. به این بلو پرینت میگیم deployment.کامپوننت deployment: در واقع abstraction ای از پاد هاست که رپلیکا کردن و تنظیمات دیگه‌ای مثل scale up یا scale down کردنشون رو انجام میده. در عمل ما غالبا با دپلویمنت کار می‌کنیم و نه پادها.  اما ما دیتابیس رو با دپلویمنت نمی‌تونیم مدیریت کنیم چون state داره، یعنی اینکه نیاز به دسترسی به یک دیتا استوریج مشترک داره. و نیاز به یک مکانیزم داریم که مشخص کنه کدوم پاد داره می‌نویسه و کدوم پاد داره ازش می‌خونه تا از ناهماهنگی داده جلوگیری کنه. این مکانیزم همراه با قابلیت رپلیکا توسط Statefulset در اختیارمون قرار می‌گیره.کامپوننت statefulset: این کامپوننت برای اپ‌های stateFUL ای مثل دیتابیس‌ها به کار میره. دپلوی statefulset ها آسون نیست، برای همین  یک پرکتیس معمول اینه که دیتابیس رو خارج از کلاستر کوبر هاست می‌کنن.کانفیگ کوبراز طریق API Server که قبلا صحبت شد، ما کانفیگ‌ها رو در فرمت YAML و یا Json به کوبر میدیم. سه بخش اصلی این فایل کانفیگ داره:متادیتا (metadata): که نام کامپوننت هستمشخصات (specification): همه مشخصاتی که می‌خوایم روی اون کامپوننت اعمال بشه. وضعیت (status): این بخش اتومات توسط کوبر ایجاد و اضافه میشه. کوبر همواره چک می‌کنه که وضعیت موجود چیه و وضعیت مورد نظر ما چیه، اگر یکی نباشن، کوبر میره و حلش می‌کنه. اطلاعات وضعیت موجود از etcd میاد.راه اندازی لوکالو minikube چیه؟اگر ما بخوایم کل کلاستر پروداکشن رو روی ماشین لوکال‌مون ران کنیم، سیستم‌مون قاعدتا جواب نمیده. برای همین از minikube استفاده می‌کنیم. این‌طور یک نود داریم که روش هم Master processes و هم Worker Processes ران میشه و روش داکر کانتینر نصبه. خب برای راه‌اندازیش ما از kubectl استفاده می‌کنیم.و kubectl چیه؟ابراز کامندلاین (CLI) برای کلاستر کوبر هست. توجه داشته باشین که kubectl فقط برای کار با Minikube cluster نیست و با همین ابزار با همه نوع کلاستر کوبر میشه کار کرد. از سایت minikube نصبش می‌کنیم. اتفاقی که می‌افته ما خود minikube رو با داکر ران می‌کنیم و داخل خودش باز با داکر ران میشه اپ (آپشن ران با virtual machine رو هم داریم). پس اگر داکر ندارین، اون رو هم نصب کنین.ران کردن کوبر با درایور داکر:minikube start --driver dockerچک وضعیت کلاستر:minikube statusنمایش همه نود های داخل کلاستر:kubectl get nodeنمایش همه پادها:kubectl get podدستور اعمال فایل‌های yaml پیکربندی:kubectl apply -f [mongo-config.yaml]گرفتن تمام کامپوننت‌های داخل کلاستر:kubectl get allگرفتن  کانفیگ‌مپ و سکرت‌های کوبر:kubectl get secret&#xA;kubectl get configmapجزئیات یک کمپوننت:kuectl describe pod [pod-name]چک کردن لاگ‌های یک پاد:kubectl logs [pod-name] -fگرفتن minikube IP:minikube ipنمونه کانفیگ یک پروژه کوبر ساده:https://gitlab.com/nanuchi/k8s-in-1-hour</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Fri, 10 Nov 2023 16:22:25 +0330</pubDate></item><item><title>دیزاین پترن‌های مرتبط با دیتابیس</title><link>https://virgool.ir/@hamze/دیزاین-پترن-های-مرتبط-با-دیتابیس-dvpqdigwwx2r</link><description>در ارتباط با سروکله زدن با دیتابیس، الگوهای متفاوتی وجود داره که چند مورد اصلیش رو بررسی می‌کنیم:الگو اکتیو رکورد (Active Record Pattern):روش ساده‌ای که در اکثر فریمورک‌ها می‌بینیم، به این صورت که رکورد دیتابیس رو به صورت آبجکت در اختیار ما می‌گذاره و به کمک متدها می‌تونید باهاش کار کنیم. کار کردن باهاش ساده‌ است و پیاده‌سازی ساده‌ای هم داره.  اما ضعفش اینه که دیتاها و لاجیک درهم‌آمیخته (coupling) میشن.new_user = User(None, &#39;john_doe&#39;, &#39;john@example.com&#39;)&#xA;new_user.save()الگو ریپوزیتوری (Repository Pattern):این الگو، منطق دسترسی به داده‌ها رو از بقیه اپلیکیشن جدا می‌کنه و در سطح بالاتری کار با دیتابیس رو ارائه میده. به این صورت پیاده میشه که کار با دیتابیس رو ابسترکت (abstracts) میکنه و به صورت یک اینترفیس (interface) تعریف میشه و اون اینترفیس پیاده سازی و ازش استفاده میشه. با این کار تست‌پذیری، انعطاف‌پذیری و امکان راحت استفاده از دیتابیس‌ها متفاوت رو پیدا می‌کنیم. اما از طرفی پیچیدگی پیاده‌سازی رو بیشتر می‌کنه. در واقع لاجیک بیزینس کاری نداره که اون سمت دیتابیس چه دیتابیسی قرار داره و چطور کار می‌کنه و فقط با ابسترکتش سر و کار داره.الگو دیتا مپر (Data Mapper Pattern):این الگو یک لایه برای مجزا کردن آبجکت توی مموری (که نمایانگر داده دیتابیسته، مثلا آبجکت User) از الگو دیتابیس  مربوطه‌اشه (که شامل جداول و ستون‌ها و ارتباط‌های جداول و محدودیت‌هاشونه). استفاده اصلیش اینه که دسترسی به داده و ارتباطش با دیتابیس رو از لاجیک مربوط به برنامه جدا کنه. این الگو باعث میشه لاجیک بیزینس از کد دسترسی دیتابیس جدا کرد. اما خب پیچیدگی پیاده‌سازی رو بیشتر می‌کنه. مثالش:class UserMapper {&#xA;    public function findById($id) {&#xA;        // Database query and mapping logic here&#xA;    }&#xA;    &#xA;    public function save(User $user) {&#xA;        // Database save logic here&#xA;    }&#xA;}الگو یونیت آف ورک (Unit of Work Pattern):این الگو معمولا در مورد چارچوب object-relational mapping (ORM) به کار میره، کمک می‌کنه که تراکنش‌ها (transactions) و عملیات‌های مرتبط با چرخه‌حیات دیتابیس رو  به صورتی مدیریت کنیم که مطمئن بشیم دیتا بی‌نقص و کامله، هدف اصلیش اینه که چندین عملیات دیتابیس در یک تراکنش انجام بشه و مطمئن بشیم که همه تغییرات یا کامیت میشه و یا همه به حالت قبل برمی‌گردن (roll back).class UnitOfWork:&#xA;    def __init__(self, database):&#xA;        self.database = database&#xA;        self.connection = None&#xA;&#xA;    def __enter__(self):&#xA;        self.connection = sqlite3.connect(self.database)&#xA;        return self&#xA;&#xA;    def __exit__(self, exc_type, exc_value, traceback):&#xA;        if exc_type is None:&#xA;            self.connection.commit()&#xA;        else:&#xA;            self.connection.rollback()&#xA;        self.connection.close()&#xA;&#xA;# Example usage&#xA;with UnitOfWork(&#39;mydb.db&#39;) as uow:&#xA;    cursor = uow.connection.cursor()&#xA;    cursor.execute(&#39;INSERT INTO users (name, email) VALUES (?, ?)&#39;, (&#39;John Doe&#39;, &#xA;john@example.com&#39;))&#xA;    cursor.execute(&#39;UPDATE users SET name = ? WHERE id = ?&#39;, (&#39;Updated Name&#39;, 1))الگو کوئری آبجکت (Query Object Pattern):از این الگو برای این استفاده میشه که یک کوئری رو در یک آبجکت قرار بدیم که یکسری پارامتر بگیره و به طور مستقل اجرا بشه. با این کار یکسری کوئری قابل استفاده در جاهای متفاوت داری که نگهداری و تغییرات آینده رو هم راحت‌تر می‌کنه.class UserQuery:&#xA;    def __init__(self, db):&#xA;        self.db = db&#xA;        self.query = &#34;SELECT * FROM users WHERE age = :age&#34;&#xA;&#xA;    def execute(self, age):&#xA;        # Execute the query with the provided age parameter&#xA;        cursor = self.db.cursor()&#xA;        cursor.execute(self.query, {&#39;age&#39;: age})&#xA;        return cursor.fetchall()الگو تیبل دیتا گت‌اوی (Table Data Gateway Pattern):توی این الگو میایم و به ازای هر جدول دیتابیس یک کلاس مشخص تعریف می‌کنیم و عملیات‌های دسترسی به داده‌اش رو تعریف می‌کنیم.class UserGateway:&#xA;    def __init__(self, db_connection):&#xA;        self.db_connection = db_connection&#xA;&#xA;    def find_by_id(self, user_id):&#xA;        pass&#xA;&#xA;    def create_user(self, name, email):&#xA;        pass&#xA;&#xA;    def update_user(self, user_id, name, email):&#xA;        pass&#xA;&#xA;    def delete_user(self, user_id):&#xA;        passالگوی آبجکت دسترسی به داده (Data Access Object (DAO) Pattern):این الگو هم میاد و یک اینترفیس (interface) برای عملیات‌های دیتابیس تعریف می‌کنه. درواقع میشه گفت ترکیبی از الگوهایی مثل Repository و Unit of Work می‌تونه باشه. ولی Repository معمولا سطح بالاتره و با نگاه به منطق بیزینس تعریف میشه، مثلا متدهایی مثل findUserById, getProductByName, or saveOrder اما در DAO می‌تونه سطح پایین‌تر و در ارتباط نزدیک‌تر با دیتابیس و همینطور متدهای کلی‌تر مثل executeQuery, insertRecord, or updateData باشه.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 07 Nov 2023 02:22:26 +0330</pubDate></item><item><title>مفاهیم امنیت و دستکاری داده نرم‌افزار که باید فرقشون بدونیم</title><link>https://virgool.ir/@hamze/مفاهیم-امنیت-و-دستکاری-داده-نرم-افزار-که-باید-فرقشون-بدونیم-kvxxgh3xmdn0</link><description>معنی Encode کردن:به پروسه تبدیل کردن داده از یک فرمت به فرمت دیگه می‌گن. موقع encode کردن دنبال امنیت نیستیم چون غالبا یک فرآیند برگشت‌پذیره، یعنی می‌تونی decode بکنیش تا به فرم اصلیش برگرده. اسکیماهای متداول encode کردن این موارده: Base64, URL encoding, HTML encodingمعنی Encrypt کردن:به فرآیند تغییر داده به فرمت امن و غیرقابل خوندن به کمک الگوریتم encryption و یک key می‌گن. دلیل اصلیش، حفاظت از داده‌های محرمانه است. فقط کسانی که به کلید decryption دسترسی دارن، می‌تونن به داده اصلی دسترسی پیدا کنن. الگوریتم‌های encryption شامل AES, RSA, DES میشه و ازشون معمولا برای امن کردن دیتاهای حساس مثل اطلاعات کارت اعتباری و داده‌های شخصی در طی انتقال داده یا ذخیره‌سازی استفاده میشه.معنی Hash کردن:هش کردن پروسه گرفتن یک ورودی یا پیام و برگردوندن یک رشته از کاراکترهای با طول مشخص هست که معمولا اعداد هگزادسیمال هستن. عملیات هش یک عملیات یک طرفه است، یعنی به سادگی نمیشه پروسه رو معکوس کرد تا به ورودی اولیه هش برسی. هش معمولا برای اعتبارسنجی و تایید صحت اطلاعات استفاده میشه. برای بررسی اینکه آیا داده‌ها دستکاری شده‌ان مفیدن چون هر تغییری در ورودی منجر به تولید یک هش کاملا متفاوت میشه. فانکشن‌های هش مرسوم: MD5, SHA-1, SHA-256 هستن.معنی Compression کردن:به معنی پروسه کاهش دادن سایز داده برای اینکه فضای ذخیره سازی کمتری بگیره و یا کارایی انتقال داده رو افزایش بده. این معمولا یک فرآیند برگشت پذیره و می‌تونی داده رو Decompress کنی تا به حالت اولش برگرده.معنی Salting کردن:سلتینگ تکنیکی که در هش کردن استفاده میشه تا امنیت پسوردهای هش شده رو افزایش بده. یک مقدار رندوم (salt) به پسورد قبل هش کرد اضافه میشه که باعث میشه چالش بیشتری برای اتکرها ایجاد بشه که از جدول‌های از پیش‌محاسبه شده (rainbow tables) برای شکستن پسوردها استفاده کنن. به طور مثال برای هش کردن پسورد کاربران به ازای هر یوزر باید از salt یکتایی استفاده کرد.معنی Tokenization کردن:به پروسه جایگزین کردن اطلاعات حساس با شناسه منحصر به فرد (unique identifier or token) می‌گن. دیتای اصلی امن نگهداری میشه و فقط توکین در سیستمی که به صورت پابلیک هست استفاده میشه تا دردسترس بودن اطلاعات حساس رو کاهش بده.در ادامه میخوام درباره مفاهیمی که در مواردی که صحبت کردیم به کار میره کمی بیشتر دقیق بشم:کلید (key) چیه؟کلید بخشی از اطلاعاته که در الگوریتم‌های رمزنگاری (cryptographic) استفاده میشه تا پروسه رمزگذاری و رمزگشایی رو کنترل کنه و برای این پروسه وجودش ضروریه. دو نوع داره:متقارن (symmetric): در رمزنگاری متقارن، یک کلید واحد برای رمزنگاری و رمزگشایی استفاده میشه.نامتقارن (asymmetric): در رمزنگاری نامتقارن، معمولا یک جفت کلید وجود داره، یکی برای رمزنگاری (public key) و دیگری برای رمزگشایی (private key).نمک (salt) چیه؟رندوم دیتایی که تولید و اضافه میشه به ورودی برای توابع رمزگذاری هش، معمولا در کانتکس هش‌کردن پسورد به کار میره تا امنیت پسورد هش شده رو افزایش بده. با salt مطمئن میشیم که حتی اگر دو کاربر رمز یکسانی رو به‌کار ببرن، مقدار هش‌شده شون یکی نخواهد بود.فلفل (pepper) چیه؟مشابه salt و یک دیتای رندومه اما به صورت متفاوتی استفاده میشه، معمولا یک مقدار محرمانه‌ است که جدا از دیتای هش‌شده نگهداری میشه. به داده قبل از هش شدن اضافه میشه اما در دیتابیسی که مقدار هش‌شده نگهداری میشه، نگهداری نمیشه. هدفش اینه که یک لایه دیگه امنیت اضافه کنه و اگر مهاجم‌ها به داده هش‌شده دسترسی پیدا کردن، کار رو براشون سخت‌تر کنه.</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sun, 05 Nov 2023 16:49:21 +0330</pubDate></item><item><title>رویارویی با الگوریتم‌های گراف</title><link>https://virgool.ir/@hamze/رویارویی-با-الگوریتم-های-گراف-pg9g0xr0jap9</link><description>الگوریتم‌های گراف جدا از این که در محاسبه‌های الگوریتمی چالش می‌تونن ایجاد کنن، در خیلی از کسب و کارها مثل مسیریابی، شبکه‌های اجتماعی و... کاربردی هستن. با یک رویکرد درست و فهم کانسپت‌های اصلی میشه از پسشون براومد. در ادامه درباره گام‌های حل مسائل گراف رو بررسی می‌کنیم:(این مقاله به مرور کامل‌ خواهد شد)۱- فهم مساله:اول سعی کنیم مساله رو به درستی متوجه بشیم.محدودیت‌های مساله، فرمت ورودی و فرمت مورد انتظار خروجی رو مشخص کنیم.۲- مساله رو به صورت گراف مدل‌سازی کنیم:مشخص کنیم که آیا مساله می‌تونه به صورت گراف نمایش داده بشه. خیلی از مسائل دنیای واقعی رو میشه به صورت گراف، مپ کرد.شناسایی نودها (nodes - vertices) و لبه‌ها (edges) که مرتبط با مساله هستنمشخص کردن نوع گراف: جهت‌دار یا بدون جهت (directed or undirected)ُ، وزن‌دار یا بدون‌وزن (weighted or unweighted)ُ، چرخه‌دار یا بدون چرخه (cyclic or acyclic)۳- انتخاب الگوریتم گراف مناسب:براساس مساله، لازمه که از الگوریتم مناسب برای حل استفاده کنیم:Depth-First Search Breadth-First Search Dijstra&#39;s Algorithm (Shortest Path) Bellman-Ford Algorithm (shortest path with negative weights)Kruskal&#39;s Algorithm (minimum spanning tree)Prim&#39;s Algorithm (minimum spanning tree)Floyd-Warshall Algorithm (all pairs shortest paths)Topological Sort (for directed acyclic graphs)Strongly Connected Components (SCC)۴- پیاده‌سازی الگوریتم و تستش با ورودی‌های مختلف و بهینه‌سازی در صورت نیاز۵- بررسی پیچیدگی زمانی و فضایی (Time and Space Complexity)۶- فهم انواع سوالات مثل: finding paths, cycles, minimum spanning trees, and topological sorting۷- آشنایی با مفاهیم مشترک گراف‌ها مثل: vertices, edges, adjacency matrices, and adjacency lists۸- آشنایی با ساختار داده‌های مرتبط: queues, stacks, and priority queues</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Tue, 24 Oct 2023 16:03:36 +0330</pubDate></item><item><title>حل مسائل داینامیک پروگرمینگ Dynamic Programming</title><link>https://virgool.ir/@hamze/حل-مسائل-داینامیک-پروگرمینگ-dynamic-programming-ec9epf62locj</link><description>داینامیک پروگرمینگ به طور عمده، بهینه سازی مثایل بازگشتیه (Recursion). راه‌حل‌های بازگشتی رو به این روش ما می‌تونیم بهینه کنیم. ایده به این صورته که نتیجه زیرمساله‌ها رو یک جایی ذخیره کنیم، در نتیجه بعدا که بهشون نیاز داریم، لازم نیست که دوباره حساب‌شون کنیم. این راهکار، پیچیدگی زمانی رو از تصاعدی به چندجمله‌ای (exponential to polynomial) تبدیل می‌کنه.تکنینک‌های حل مسائل داینامیک پروگرمینگ:۱- رویکرد بالا به پایین (Top-Down): این رویکرد از تکنیک memoization استفاده می‌کنه که شامل recusrsion و caching هست. بازگشتی، فرآینده فراخوانی پی‌درپی تابع‌هاست و کش کردن، پروسه ذخیره نتایج میانی هست.۲- رویکرد پایین به بالا (Bottom-Up): این رویکرد از تکنیک tabulation استفاده می‌کنه که راهکار داینامیک پروگرمینگ رو پیاده‌سازی می‌کنه. به روش قبلی مساله رو حل می‌کنه ولی بدون بازگشتی (recursion). در این روش حلقه جایگزین بازگشتی میشه. نمونه کد حل مساله فیبوناچی به روش memoization:def fibonacci(n, cache={}):&#xA;    if n in cache:&#xA;        return cache[n]&#xA;    if n == 0:&#xA;        result = 0&#xA;    elif n == 1:&#xA;        result = 1&#xA;    else:&#xA;        result = fibonacci(n-1) + fibonacci(n-2)&#xA;    cache[n] = result&#xA;    return resultنمونه کد حل مساله فیبوناچی به روش tabulation:int fibonacci(int n) {&#xA;    if (n == 0) {&#xA;        return 0;&#xA;    } else if (n == 1) {&#xA;        return 1;&#xA;    } else {&#xA;        std::vector&lt;int&gt; table(n + 1, 0);&#xA;        table[0] = 0;&#xA;        table[1] = 1;&#xA;        for (int i = 2; i &lt;= n; i++) {&#xA;            table[i] = table[i-1] + table[i-2];&#xA;        }&#xA;        return table[n];&#xA;    }&#xA;}</description><category>حمزه قائم پناه</category><author>حمزه قائم پناه</author><pubDate>Sun, 22 Oct 2023 16:05:32 +0330</pubDate></item></channel></rss>