<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های معین کولیوند</title>
        <link>https://virgool.io/feed/@moeinkolivand97</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-07-06 11:52:42</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/4287817/avatar/Sdo6lb.jpg?height=120&amp;width=120</url>
            <title>معین کولیوند</title>
            <link>https://virgool.io/@moeinkolivand97</link>
        </image>

                    <item>
                <title>وقتی تراکنش‌ها هم‌زمان می‌شوند: مقابله با Race Condition و Double Spending</title>
                <link>https://virgool.io/@moeinkolivand97/%D9%88%D9%82%D8%AA%DB%8C-%D8%AA%D8%B1%D8%A7%DA%A9%D9%86%D8%B4-%D9%87%D8%A7-%D9%87%D9%85-%D8%B2%D9%85%D8%A7%D9%86-%D9%85%DB%8C-%D8%B4%D9%88%D9%86%D8%AF-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D9%87-%D8%A8%D8%A7-race-condition-%D9%88-double-spending-ms7uefvmnwrn</link>
                <description>در یک سرویس انتقال پول ، وقتی دو درخواست برداشت از یک حساب تقریباً همزمان میرسن، هر دو درخواست دقیقاً در یک لحظه موجودی رو می‌خونن. هر دو می‌بینن که موجودی کافیه، پس هر دو تراکنش تایید و با موفقیت اجرا میشن. نتیجه این میشه که بیشتر از پول موجود برداشت میشه: موجودی واقعی منفی میشه، اما دیتابیس هنوز یک مقدار مثبت نشون میده. ۲. مشکلاین دقیقاً همان Race Condition و Double Spending بود. سیستم هیچ مکانیزمی برای جلوگیری از تراکنش‌های هم‌زمان روی یک حساب نداشت و عملیات مالی به صورت اتمیک اجرا نمی‌شد.۳. چرا این مشکل رخ می‌دهد؟عملیات انتقال پول شامل سه مرحله است:خواندن موجودی فعلیمحاسبه موجودی جدیدنوشتن موجودی جدیدوقتی دو thread یا process هم‌زمان این سه مرحله را اجرا کنند، Race Condition ایجاد می‌شود. به عبارتی، نیاز داریم مکانیزمی که بگوید: فقط یکی از شما می‌تواند در یک زمان روی این حساب کار کند.۴. راه‌حل: Distributed Lock با Redisبرای جلوگیری از مشکل، از Distributed Lock استفاده می‌کنیم.نحوه کار:قبل از عملیات روی حساب، سعی می‌کنیم یک قفل روی آن حساب بگیریم.اگر قفل گرفته شد، عملیات اجرا می‌شود و بعد قفل آزاد می‌شود.اگر نتوانستیم قفل بگیریم، باید صبر کنیم تا process دیگری کارش تمام شود.دستور Redis برای قفل:SET key value NX PX milliseconds
NX: فقط اگر key وجود ندارد، تنظیم شود.PX milliseconds: مدت زمان انقضای قفل؛ اگر process کرش کند، قفل خودکار آزاد می‌شود.سناریو درخواست و نحوه بررسی:درخواست یک تلاش می‌کند قفل حساب را بگیرد و موفق می‌شود.درخواست دو تلاش می‌کند قفل را بگیرد، اما به دلیل اینکه قفل در اختیار موبایل است، ناموفق می‌ماند و منتظر می‌ماند.درخواست یک حساب را می‌خواند ( 1000 دلار) و بررسی می‌کند که برداشت ۶۰۰ دلار امکان‌پذیر است . مبلغ ۶۰۰ دلار از موجودی کم می‌شود و موجودی جدید ۴۰۰ دلار ثبت می‌شود .درخواست یک قفل را آزاد می‌کند .در این نقطه، درخواست دوم می‌تواند دوباره تلاش کند و درخواست دوم موفق می‌شود قفل را بگیرد.موجودی را می‌خواند ( 400 دلار) و بررسی می‌کند که آیا می‌تواند ۷۰۰ دلار برداشت کند . چون موجودی کافی نیست، تراکنش رد می‌شود. در نهایت، قفل توسط درخواست دوم رها می‌شود.۵. نکات مهم در پیاده‌سازیاستفاده از Token منحصر به فرد: برای آزادسازی امن قفل، معمولاً از UUID استفاده می‌کنیم.تعیین TTL مناسب: طول قفل باید به اندازه کافی باشد تا عملیات تمام شود، اما نه طولانی که سیستم قفل بماند.Retry با Exponential Backoff: اگر قفل در دسترس نبود، با تأخیر و افزایش تدریجی دوباره تلاش می‌کنیم.جلوگیری از Deadlock: هنگام گرفتن چند قفل، همیشه ترتیب مشخص داشته باشیم.گارانتی Idempotency: هر عملیات یک شناسه یکتا داشته باشد تا درخواست‌های تکراری دوباره اجرا نشوند.Atomic Operations در Redis: برای update موجودی، می‌توان از WATCH/MULTI/EXEC یا Lua script استفاده کرد.۶. نتیجه‌گیریRace Condition و Double Spending یکی از چالش‌های اصلی در عملیات مالی است. استفاده از Redis Distributed Lock یک راه‌حل ساده و قدرتمند است که تراکنش‌ها را ایمن می‌کند و هم‌زمانی را کنترل می‌کند.۷. کد کامل پروژهپیاده‌سازی کامل سیستم، شامل تست‌های race condition و سناریوهای مختلف، در GitHub موجود است:https://github.com/your-username/distributed-wallet-redis-lockدر این repository می‌توانید ببینید:پیاده‌سازی کامل Redis Lockسیستم کیف پول با Kafka و FastStreamتست‌های مختلف race conditionسناریوهای Deadlock و Idempotencyمطالب مرتبطبرای درک بهتر مفاهیم Redis و مدیریت تراکنش‌ها، می‌توانید دو پست قبلی من را هم مطالعه کنید:داستان Watch و Pipeline در RedisTransaction در Redis</description>
                <category>معین کولیوند</category>
                <author>معین کولیوند</author>
                <pubDate>Tue, 25 Nov 2025 23:20:43 +0330</pubDate>
            </item>
                    <item>
                <title>داستان WATCH و Pipeline در Redis</title>
                <link>https://virgool.io/@moeinkolivand97/%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-watch-%D9%88-pipeline-%D8%AF%D8%B1-redis-fmwctoi8lwzo</link>
                <description>در قسمت اول با هم درباره Transaction در Redis و نحوه کار MULTI و EXEC صحبت کردیم. امروز می‌خواهیم قدم بعدی را برداریم و دو مفهوم خیلی مهم‌تر را بررسی کنیم که توی دنیای واقعی کاربرد زیادی دارند.یک مشکل واقعی: فروش بیش از حدتصور کنید یک فروشگاه آنلاین دارید. آخرین محصول محبوب شما (مثلاً آیفون جدید) فقط یک عدد در انبار مونده. حالا دو کاربر دقیقاً همزمان دکمه «خرید» رو می‌زنن. چی اتفاق می‌افته؟بدون مدیریت درست، ممکنه هر دو نفر بتونن محصول رو بخرن! یعنی شما یک محصول رو به دو نفر فروختید. این یعنی سردرگمی، شکایت مشتری، و مشکلات بزرگ.این مشکل رو Race Condition می‌گیم - وقتی چند نفر همزمان می‌خوان یک چیز رو تغییر بدن و نتیجه‌ش پیش‌بینی‌پذیر نیست.چرا MULTI/EXEC کافی نیست؟خب، توی قسمت قبل یاد گرفتیم که MULTI و EXEC چطوری چند دستور رو اتمیک اجرا می‌کنن. اما یک مشکل داره:شما نمی‌تونید قبل از شروع Transaction تصمیم بگیرید!مثلاً باید اول موجودی رو چک کنید، ببینید کافیه یا نه، بعد تصمیم بگیرید که آیا بخرید یا نه. اما بین زمانی که موجودی رو چک می‌کنید تا زمانی که Transaction رو شروع می‌کنید، ممکنه کاربر دیگه‌ای محصول رو خریده باشه!اینجاست که WATCH وارد میدون میشه.WATCH: نگهبان داده‌های شماWATCH مثل یک نگهبان عمل می‌کنه. بهش می‌گید: &quot;این کلید رو زیر نظر بگیر. اگه کسی دست زد بهش، من رو خبر کن!&quot;مفهوم Optimistic Lockingاسم فانتزیش اینه: قفل خوش‌بینانه (Optimistic Locking).چرا خوش‌بینانه؟ چون فرض می‌کنیم که احتمال تضاد کمه. یعنی نمی‌ریم از اول کلید رو قفل کنیم (Pessimistic Locking)، بلکه می‌گیم &quot;احتمالاً کسی دیگه الان داره با این کار نمی‌کنه، ولی اگه کار کرد، ما می‌فهمیم.&quot;داستان دو مشتریبیایید داستانی واقعی‌تر تعریف کنم:کاربر الف و کاربر ب هر دو می‌خوان آخرین محصول رو بخرن.بدون WATCH:کاربر الف موجودی رو می‌خونه → ۱ عدد ✓کاربر ب موجودی رو می‌خونه → ۱ عدد ✓کاربر الف موجودی رو کم می‌کنه → ۰ عددکاربر ب موجودی رو کم می‌کنه → ۱- عددنتیجه؟ موجودی منفی شده! فاجعه!با WATCH:کاربر الف می‌گه: &quot;WATCH این محصول رو&quot;کاربر ب می‌گه: &quot;WATCH این محصول رو&quot;کاربر الف موجودی رو می‌خونه → ۱ عددکاربر ب موجودی رو می‌خونه → ۱ عددکاربر الف Transaction رو شروع می‌کنه (MULTI)کاربر الف موجودی رو ۱ واحد کم می‌کنهکاربر الف Transaction رو تموم می‌کنه (EXEC) → موفق! ✓کاربر ب Transaction رو شروع می‌کنه (MULTI)کاربر ب موجودی رو ۱ واحد کم می‌کنهکاربر ب Transaction رو تموم می‌کنه (EXEC) → شکست!کاربر ب متوجه میشه که بین زمان WATCH و EXEC، داده تغییر کرده. Redis بهش می‌گه: &quot;ببخشید، کسی دیگه جلوتر از تو رفته. دوباره امتحان کن.&quot;این همون چیزیه که ما می‌خواستیم! سیستم خودش مشکل رو تشخیص داده و جلوی فروش بیش از حد رو گرفته.چطوری توی پروژه استفاده می‌کنیم؟در پروژه‌ای که گذاشتم روی گیت‌هاب، یک سیستم مدیریت موجودی ساختم. وقتی کاربر می‌خواد محصول بخره، این مراحل رو طی می‌کنیم:WATCH کردن کلیدهای مهم - محصول و موجودی کاربر رو زیر نظر می‌گیریمخوندن وضعیت فعلی - موجودی الان چقدره؟بررسی شرایط - آیا موجودی کافیه؟محاسبات - اگه کافی بود، محاسبه می‌کنیم موجودی جدید چقدر میشهاجرای اتمیک - با MULTI/EXEC همه چیز رو یکجا اعمال می‌کنیماگه بین مرحله ۲ و ۵ کسی داده رو تغییر بده، Redis Transaction رو لغو می‌کنه و ما دوباره امتحان می‌کنیم.دستور UNWATCH:گاهی اوقات وسط کار متوجه میشیم که نیازی به ادامه Transaction نیست. مثلاً موجودی صفره و کاربر نمی‌تونه بخره.در این حالت، باید UNWATCH رو صدا بزنیم تا Redis بدونه دیگه نیازی به نظارت روی اون کلیدها نداریم. این کار باعث میشه منابع آزاد بشن و عملکرد بهتر بشه.حالا بیایم سراغ موضوع دوم: Pipeline.مشکل: شبکه کنده!فرض کنید می‌خواید ۱۰۰ تا محصول رو به Redis اضافه کنید. هر بار یک دستور SET می‌فرستید.هر دستور Redis این مسیر رو طی می‌کنه:برنامه شما دستور رو می‌فرسته به RedisRedis دستور رو اجرا می‌کنهRedis جواب رو برمی‌گردونه به برنامه شمااگه هر بار این رفت‌وبرگشت ۱ میلی‌ثانیه طول بکشه، برای ۱۰۰ تا دستور چقدر زمان می‌بره؟۱۰۰ میلی‌ثانیه!حالا اگه بگم می‌تونید همین کار رو توی ۱ میلی‌ثانیه انجام بدید، چی؟راه‌حل: PipelinePipeline یعنی &quot;لوله&quot;. تصور کنید یک لوله دارید که همه دستوراتتون رو توش می‌ریزید و یکجا می‌فرستید به Redis. بعدش Redis هم یکجا همه رو اجرا می‌کنه و جواب‌ها رو برمی‌گردونه.نتیجه؟ به جای ۱۰۰ بار رفت‌وبرگشت، فقط یک بار رفت‌وبرگشت داریم!تفاوت Pipeline با Transactionیک سوال مهم: آیا Pipeline همون Transaction هست؟جواب: نه!Pipeline دو حالت داره:1. Pipeline بدون Transactionوقتی Pipeline رو بدون Transaction استفاده می‌کنید، دستورات شما سریع اجرا میشن ولی اتمیک نیستن.یعنی اگه وسط کار یکی از دستورات خطا بده، بقیه دستورات همچنان اجرا میشن.این حالت برای کارهایی مثل Seeding عالیه - وقتی می‌خواید انبوه داده رو یکجا بریزید توی Redis.در پروژه من، وقتی اولین بار برنامه رو اجرا می‌کنید، یک سری داده‌های اولیه (محصولات، کاربران) رو به Redis اضافه می‌کنم. اگه این کار رو بدون Pipeline بکنم، خیلی کنده میشه. ولی با Pipeline، همه چی یکجا و سریع لود میشه.2. Pipeline با Transactionوقتی Pipeline رو با Transaction ترکیب می‌کنید، هم سرعت Pipeline رو دارید هم ایمنی Transaction رو.یعنی دستورات شما سریع اجرا میشن و اگه مشکلی پیش بیاد، همه لغو میشن.این حالت برای کارهایی مثل خرید محصول عالیه - می‌خواید سریع باشه و همزمان ایمن.مثال واقعی از پروژهدر پروژه من، یک تابع seed_products دارم که محصولات اولیه رو به Redis اضافه می‌کنه.اگه بدون Pipeline بود:برای هر محصول یک بار رفت‌وبرگشت → کند!زمان کل: حدود ۱۵۰ میلی‌ثانیهبا Pipeline:همه محصولات یکجا → سریع!زمان کل: حدود ۵ میلی‌ثانیه۳۰ برابر سریع‌تر!ترکیب WATCH و Pipeline:حالا وقتشه که این دو رو باهم ترکیب کنیم.تصور کنید می‌خواید یک سیستم سبد خرید بسازید:کاربر چند تا محصول رو به سبدش اضافه می‌کنهوقتی روی &quot;تسویه حساب&quot; می‌زنه، باید همه محصولات رو یکجا بخرهاین کار پیچیدگی‌هاش زیاده:باید همه محصولات رو چک کنید (موجودی کافیه؟)باید همه تغییرات رو یکجا اعمال کنید (اتمیک باشه)باید سریع باشه (کاربر منتظر نمونه)راه‌حل:با WATCH همه محصولات سبد رو زیر نظر بگیریدموجودی همه رو چک کنیداگه همه چی اوکی بود، با Pipeline + Transaction همه رو یکجا خرید کنیداگه بین چک کردن و خرید، کسی یکی از محصولات رو خریده باشه، Transaction شکست می‌خوره و کاربر متوجه میشه.عملکرد: اعداد واقعیبیایید نگاهی به اعداد واقعی بندازیم.تست ۱: ذخیره ۱۰۰ محصولبدون Pipeline:زمان: ۱۵۰ میلی‌ثانیهتعداد رفت‌وبرگشت شبکه: ۱۰۰ باربا Pipeline:زمان: ۵ میلی‌ثانیهتعداد رفت‌وبرگشت شبکه: ۱ باربهبود: ۳۰ برابر سریع‌تر!تست ۲: خرید همزمان (Race Condition)بدون WATCH:۲ کاربر همزمان خرید می‌کننموجودی منفی میشهفروش بیش از حد اتفاق می‌افتهبا WATCH:۲ کاربر همزمان خرید می‌کننیکی موفق میشه ✓دومی شکست می‌خوره و دوباره امتحان می‌کنه ✓موجودی همیشه درسته ✓تست‌های واقعیدر پروژه، چند تست Integration نوشتم که تضمین می‌کنه همه چی درست کار می‌کنه:تست خرید موفقیک کاربر یک محصول می‌خره. بررسی می‌کنیم که:موجودی محصول کم شده؟ ✓موجودی کاربر زیاد شده؟ ✓تست جلوگیری از فروش بیش از حدموجودی رو روی ۲ تا می‌ذاریم. سه بار می‌خواییم بخریم. نتیجه:دو بار اول موفق ✓بار سوم شکست می‌خوره ✓موجودی صفر میشه (نه منفی!) ✓تست کاربران مختلفدو کاربر مختلف محصول می‌خرن. بررسی می‌کنیم:هر کاربر موجودی جداگانه داره ✓موجودی کل محصول درست کم شده ✓همه این تست‌ها رو می‌تونید خودتون اجرا کنید و ببینید چطوری کار می‌کنن.نتیجه‌گیریدر این قسمت یاد گرفتیم که:WATCH چطوری با &quot;قفل خوش‌بینانه&quot; از Race Condition جلوگیری می‌کنهPipeline چطوری با کاهش رفت‌وبرگشت شبکه، عملکرد رو تا ۱۰۰ برابر بهبود می‌دهچطوری این دو رو با هم ترکیب کنیم برای سیستم‌های حرفه‌ایچه موقع از کدوم استفاده کنیم و چه موقع نهاین مفاهیم توی پروژه‌های واقعی خیلی کاربردی هستن. از e-commerce گرفته تا سیستم‌های مالی، همه جا بهشون احتیاج دارید.دسترسی به پروژهکل کد پروژه رو می‌تونید از گیت‌هاب من دانلود کنید:git clone https://github.com/moeinkolivand/redis-lock-inventory
docker-compose up -d
docker compose run --rm app pytest tests.py -v
همه تست‌ها رو اجرا کنید و ببینید چطور کار می‌کنندر قسمت بعدی می‌ریم سراغ موضوعات پیشرفته‌تر مثل Distributed Locks , انواع Lock در ردیس.</description>
                <category>معین کولیوند</category>
                <author>معین کولیوند</author>
                <pubDate>Fri, 21 Nov 2025 12:27:45 +0330</pubDate>
            </item>
                    <item>
                <title>Transaction در Redis</title>
                <link>https://virgool.io/@moeinkolivand97/transaction-%D8%AF%D8%B1-redis-yuv0rc6iybpa</link>
                <description>فرض کن مسئول یک سیستم ثبت سفارش برای یک فروشگاه اینترنتی هستی. وقتی کاربر دکمه «ثبت سفارش» را می‌زند، چند مرحله پشت‌سرهم باید انجام شود:کم کردن موجودی کالامسدود کردن مبلغ سفارش در کیف‌پول کاربرایجاد شماره سفارشحالا تصور کن سیستم دقیقاً بعد از کم کردن موجودی ولی قبل از مسدود کردن مبلغ کرش کند!نتیجه؟موجودی کم شدهمبلغ مسدود نشدهسفارش ناقصدیتای ناسازگار       و یک دردسر بزرگ! اینجاست که Transaction وارد بازی می‌شود.Redis به کمک Transaction این امکان را می‌دهد که مجموعه‌ای از عملیات را یا کاملاً انجام دهیم یا اصلاً انجام ندهیم — یعنی Atomic.به‌این‌ترتیب از مشکلات ناشی از خطاها و عملیات هم‌زمان (Concurrent Operations) جلوگیری می‌کند.در این مقاله، قدم‌به‌قدم Redis Transaction را بررسی می‌کنیم و اینکه چگونه کار می‌کند و چطور می‌توانید از آن در پروژه‌های واقعی خود استفاده کنید. Transaction در Redis چیست؟Transaction در Redis اجازه می‌دهد چندین Command (مثل SET, INCR, DECR, …) را به صورت یک واحد Atomic اجرا کنید.به این معنا که:همهٔ دستورات داخل تراکنش به‌طور کامل اجرا می‌شوند یا هیچ‌کدام اجرا نمی‌شوند.اما توجه کنید:Redis Transaction ≠ ACID TransactionRedis فقط تضمین می‌کند:AtomicityIsolationاما موارد زیر تضمین نمی‌شود:ConsistencyDurabilityدر مقابل، دیتابیس‌های رابطه‌ای (مثل PostgreSQL, MySQL) تمامی این ویژگی‌ها را گارانتی می‌دهند.بنابراین اگر سرور Redis بلافاصله بعد از دستور EXEC کرش کند، بسته به تنظیمات (مثلاً AOF یا Snapshot)، ممکن است بخشی از داده از دست برود. چگونه یک Transaction در Redis بسازیم؟ساخت تراکنش در Redis بسیار ساده است.1 -  شروع تراکنش — MULTIهر دستوری که پس از MULTI اجرا شود، وارد یک صف (Queue) می‌شود.2 -  اجرای تراکنش — EXECبا اجرای EXEC، تمام دستورات موجود در صف به ترتیب اجرا می‌شوند.اگر هر کدام از دستورات هنگام اجرا خطا داشته باشند، کل تراکنش از بین می‌رود.3 - لغو تراکنش — DISCARDاگر بخواهیم تراکنش را لغو کنیم، کافی است دستور DISCARD را اجرا کنیم.این کار تنها صف را خالی می‌کند. مثال ساده از اجرای یک TransactionECHO &quot;Start Transaction With Command MULTI&quot;
MULTI
SET AGE 10
GET AGE
ECHO &quot;Execute The Transaction With Command EXEC&quot;
EXEC
در این مثال:دستور MULTI تراکنش را آغاز می‌کند.دو دستور SET و GET وارد صف می‌شوند.با EXEC، دستورات موجود در صف اجرا می‌شوند.اگر به خروجی نگاه کنید، می‌بینید که هیچ دستوری قبل از EXEC اجرا نمی‌شود — همه چیز Queue می‌شود. نکات مهم درباره Transaction در Redisبرای بهبود خوانایی و درک بهتر، این بخش را به صورت نکات کلیدی می‌آورم:Redis تراکنش را خط‌به‌خط اجرا نمی‌کند؛ همه دستورات بعد از EXEC اجرا می‌شوند.تراکنش در Redis Rollback واقعی ندارد. اگر یکی از دستورات اشتباه باشد، فقط قبل از EXEC خطا می‌گیرید.اگر زمانی که EXEC اجرا می‌شود Redis کرش کند، ممکن است بخشی از داده بسته به تنظیمات از دست برود.تراکنش‌ها در Redis از نوع Optimistic هستند، به همین دلیل دستورات WATCH و مفهوم Optimistic Locking اهمیت زیادی دارند (در مقاله بعدی بررسی می‌کنیم).جمع‌بندیدر این مقاله سعی کردم با یک مثال واقعی (سیستم ثبت سفارش فروشگاه آنلاین) به صورت ساده و قابل‌فهم مفهوم Redis Transaction را توضیح دهم. حالا می‌دانید:تراکنش‌ها در Redis چگونه کار می‌کنندچه چیزهایی تضمین می‌شود و چه چیزهایی نهتفاوت آن با ACID واقعی چیستو چگونه یک Transaction ساده بسازیددر مقاله بعدی، می‌رویم سراغ WATCH و Optimistic Locking در Redis و اینکه چطور از Race Condition جلوگیری می‌کند.</description>
                <category>معین کولیوند</category>
                <author>معین کولیوند</author>
                <pubDate>Sun, 16 Nov 2025 20:39:32 +0330</pubDate>
            </item>
            </channel>
</rss>