<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های امیرحسین عنایتی</title><link>https://virgool.ir/feed/@enayati.amirh</link><description></description><language>fa</language><pubDate>2026-11-12 01:38:06</pubDate><image><url>https://files.virgool.ir/upload/users/4933658/avatar/e3CVTb.jpg</url><title>امیرحسین عنایتی</title><link>https://virgool.ir/@enayati.amirh</link></image><item><title>Dropbox چطور میلیون‌ها تغییر فایل را بین دستگاه‌ها همگام می‌کند، بدون اینکه هر بار کل فایل را منتقل کند؟</title><link>https://virgool.ir/@enayati.amirh/dropbox-چطور-میلیون-ها-تغییر-فایل-را-بین-دستگاه-ها-همگام-می-کند-بدون-اینکه-هر-بار-کل-فایل-را-منتقل-کند-ggwaoov8betd</link><description>در این مقاله به سه سؤال اصلی پاسخ می‌دهیم:کلاینت چطور متوجه می‌شود که یک فایل تغییر کرده است؟تغییرات یک فایل بزرگ چطور به سرور منتقل می‌شوند؟کلاینت‌های دیگر چطور از این تغییر مطلع می‌شوند و فرایند Sync چگونه انجام می‌شود؟این مقاله، قسمت اول از یک مجموعه ۳۰ قسمتی است که در آن سیستم‌های واقعی را یکی‌یکی بررسی می‌کنیم. برای شروع، Dropbox را انتخاب کردم، چون چند اصل مهم در طراحی سیستم را به شکلی بسیار ملموس و قابل‌درک نشان می‌دهد.یک نکته قبل از شروع: معماری‌ای که در این مقاله می‌بینید، معماری رایجی است که در منابع System Design و مصاحبه‌های فنی برای مسئله‌ای مانند Dropbox بررسی می‌شود و لزوماً نقشه‌ی دقیق زیرساخت داخلی Dropbox نیست. برای مثال، Dropbox سال‌ها پیش سیستم ذخیره‌سازی اختصاصی خودش به نام Magic Pocket را ساخت و به‌جای Amazon S3 از آن استفاده کرد. اما منطق اصلی سیستم همچنان مشابه است.در این مجموعه می‌خواهم سیستم‌هایی را بررسی کنم که هر روز از آن‌ها استفاده می‌کنیم، اما پشت یک تجربه‌ی کاربری ساده، معماری پیچیده‌ای قرار دارد.برای اولین مقاله، انتخاب من یک سیستم اشتراک‌گذاری و همگام‌سازی فایل است: Dropbox.در نگاه اول مسئله ساده به نظر می‌رسد:یک فایل روی لپ‌تاپ تغییر می‌کند و چند لحظه بعد همان تغییر باید روی لپ‌تاپ دیگر، موبایل کاربر و دستگاه همکارانی که به فایل دسترسی دارند، دیده شود.اما وقتی تعداد کاربران، دستگاه‌ها و فایل‌ها زیاد می‌شود، سؤال اصلی دیگر این نیست:چطور یک فایل را Upload کنیم؟سؤال اصلی این است:چطور نسخه‌ی صحیح فایل را نگه داریم و تغییرات آن را با کمترین تأخیر به سایر دستگاه‌ها برسانیم؟معماری Dropbox وقتی از این زاویه به آن نگاه کنیم، منطقی می‌شود.چرا آپلود کل فایل ایده‌ی خوبی نیست؟فرض کنید یک فایل چند گیگابایتی دارید و فقط بخش کوچکی از آن تغییر کرده است.اگر سیستم هر بار کل فایل را دوباره Upload کند، با سه مشکل مواجه می‌شویم:مصرف پهنای باند افزایش پیدا می‌کند.زمان Sync بیشتر می‌شود.Backend مجبور می‌شود حجم زیادی از داده‌ی تکراری را دوباره پردازش و ذخیره کند.راه‌حل این است که فایل را قبل از Upload به قسمت‌های کوچک‌تری به نام Chunk تقسیم کنیم.برای هر Chunk نیز یک Hash تولید می‌کنیم.در نتیجه، به‌جای اینکه سرور بپرسد:آیا این فایل تغییر کرده است؟می‌تواند بپرسد:کدام Chunkهای این فایل تغییر کرده‌اند؟همین تغییر کوچک، اساس کل معماری Sync را تغییر می‌دهد.مراحلمرحله ۱: File Watcher تغییر را تشخیص می‌دهدروی Client A یک File Watcher در پس‌زمینه اجرا می‌شود و فایل‌سیستم را زیر نظر دارد.کاربر نیازی ندارد دکمه‌ی Sync را فشار دهد.به‌محض اینکه فایلی ایجاد، حذف یا ویرایش شود، Watcher این تغییر را تشخیص می‌دهد.مرحله ۲: مسیر فایل به Local Sync Service داده می‌شودWatcher نباید خودش مسئول Upload فایل باشد.وظیفه‌ی آن فقط تشخیص تغییر است.بنابراین مسیر فایل تغییرکرده را به Sync Service روی همان Client می‌دهد.این جداسازی اهمیت زیادی دارد. هر کامپوننت یک مسئولیت مشخص دارد:Watcher: پیدا کردن تغییرSync Service: تصمیم‌گیری درباره اینکه چه چیزی باید Sync شودمرحله ۳: فایل Chunk و Hash می‌شودSync Service فایل را به Chunkهای کوچک‌تر تقسیم می‌کند.برای هر Chunk یک Hash محاسبه می‌شود و اطلاعاتی شبیه این در Local Chunk DB ذخیره می‌شود:{ path, index, hash }در نتیجه Client می‌داند:این Chunk متعلق به کدام فایل است.مربوط به کدام قسمت فایل است.Hash محتوای فعلی آن چیست.حالا برای تشخیص تغییر، Client و Server دیگر لازم نیست کل فایل را با هم مقایسه کنند.مقایسه‌ی Hashها کافی است.همین ایده یکی از پایه‌های Deduplication نیز محسوب می‌شود و باعث می‌شود Chunkهایی که از قبل وجود دارند، دوباره Upload نشوند.مرحله ۴: Client فقط Hashها را به Server می‌فرستداینجا یکی از مهم‌ترین تصمیم‌های طراحی اتفاق می‌افتد.وقتی Sync شروع می‌شود، Client خود فایل را به Backend ارسال نمی‌کند.در ابتدا فقط لیست Hash تمام Chunkها را می‌فرستد.چرا؟چون Server می‌تواند با استفاده از Metadata موجود تشخیص دهد:کدام Chunkها را از قبل دارد.کدام Chunkها را ندارد.به عبارت دیگر، قبل از اینکه داده‌ی سنگین منتقل شود، یک مذاکره‌ی سبک روی Metadata انجام می‌شود.مرحله ۵: Load Balancer درخواست را مسیریابی می‌کنددر سمت Server، درخواست ابتدا به Load Balancer می‌رسد.وقتی تعداد Clientها زیاد باشد، نمی‌توان تمام درخواست‌ها را به یک Sync Service ارسال کرد.Load Balancer درخواست‌ها را بین چندین Instance تقسیم می‌کند.در نتیجه Sync Layer می‌تواند به‌صورت Horizontally Scalable توسعه پیدا کند؛ یعنی با اضافه‌کردن Instanceهای بیشتر، ظرفیت سیستم افزایش پیدا کند.مرحله ۶: Server یک Diff ایجاد می‌کندحالا Server-side Sync Service Hashهایی که Client ارسال کرده را با اطلاعات موجود در Metadata RDB مقایسه می‌کند.خروجی چیزی شبیه این خواهد بود:این Chunk را دارم.این یکی را هم دارم.این سه Chunk جدید هستند.بنابراین فقط Chunkهای گمشده باید Upload شوند.این قلب اصلی سیستم است:هزینه‌ی Sync باید تا حد امکان متناسب با اندازه‌ی تغییرات باشد، نه اندازه‌ی کل فایل.چرا Server خود فایل را دریافت نمی‌کند؟تصور کنید میلیون‌ها Client فایل‌هایی با حجم صدها مگابایت یا چند گیگابایت را از طریق Sync Service ارسال کنند.در این حالت Sync Service به یک Network و CPU Bottleneck تبدیل می‌شود.به همین دلیل، Control Plane را از Data Plane جدا می‌کنیم.اطلاعات Metadata و تصمیم‌گیری‌ها از طریق Backend Serviceها انجام می‌شوند.اما خود Byteهای فایل مستقیماً به Blob Storage منتقل می‌شوند.برای انجام این کار، Server می‌تواند برای هر Chunk گمشده یک Pre-signed URL ایجاد کند.این URL به Client اجازه‌ی موقت می‌دهد که Chunk مشخص‌شده را مستقیماً در Blob Storage Upload کند.در نتیجه، Application Server مجبور نیست تمام Byteهای فایل را از خودش عبور دهد.مرحله ۷: Chunkها مستقیماً به Blob Storage می‌روندClient A حالا Chunkهای گمشده را با استفاده از Pre-signed URL مستقیماً در Blob Storage Upload می‌کند.مزیت اصلی این طراحی این است که Sync Service دیگر در مسیر انتقال داده‌های حجیم قرار ندارد.وظیفه‌ی آن هماهنگی و Coordination است، نه جابه‌جایی چندین گیگابایت داده.مرحله ۸: Blob Storage پایان Upload را اعلام می‌کندبعد از اینکه Chunkها با موفقیت در Storage ذخیره شدند، Backend باید متوجه شود که Upload به پایان رسیده است.در این معماری، Blob Storage به File Service اعلام می‌کند که Upload کامل شده است.این مرحله مهم است، چون صرفاً ذخیره‌شدن Byteها به این معنی نیست که نسخه‌ی جدید فایل منتشر شده است.Metadata نیز باید Commit شود.مرحله ۹: نسخه‌ی جدید در Metadata DB ثبت می‌شودFile Service نسخه‌ی جدید فایل را در Metadata RDB ثبت می‌کند.اینجا تفاوت بین دو نوع داده کاملاً مشخص می‌شود:File Contentخود Byteهای فایل که در قالب Chunk ذخیره شده‌اند.File Metadataاطلاعاتی مانند:مسیر فایلVersionChunkهاOwnerوضعیت Syncبنابراین:Metadata در Database قرار می‌گیرد و File Content در Object Storage.مرحله ۱۰: یک Change Event منتشر می‌شودتا اینجا Client A فایل را تغییر داده و نسخه‌ی جدید روی Server ثبت شده است.اما Client B هنوز از این تغییر اطلاعی ندارد.یک روش ساده این است که Client B دائماً از Server بپرسد:آیا چیزی تغییر کرده است؟اما اگر میلیون‌ها Client هر چند ثانیه این سؤال را تکرار کنند، تعداد بسیار زیادی Request بی‌فایده ایجاد خواهد شد.راه‌حل بهتر، استفاده از Event-Driven Architecture است.بعد از Commit شدن نسخه‌ی جدید، File Service یک Change Event در Message Broker منتشر می‌کند.مرحله ۱۱: Client B از تغییر مطلع می‌شودMessage Broker به Sync Service روی Client B اطلاع می‌دهد که فایلی که Client به آن Subscribe شده، تغییر کرده است.نکته‌ی مهم این است که:Message Broker خود فایل را منتقل نمی‌کند.فقط می‌گوید:چیزی تغییر کرده است.این موضوع باعث می‌شود مسیر Notification سبک باقی بماند.محتوای واقعی فایل بعداً از مسیر مناسب دریافت می‌شود.مرحله ۱۲: Client B درخواست Diff می‌کندحالا Client B می‌داند که نسخه‌ی جدیدی وجود دارد.اما دوباره کل فایل را Download نمی‌کند.Local Sync Service ابتدا وضعیت فعلی خودش را بررسی می‌کند و سپس از Server می‌خواهد تفاوت بین نسخه‌ی محلی و نسخه‌ی جدید را مشخص کند.مرحله ۱۳: Hashهای Client B ارسال می‌شوندClient B Hash مربوط به Chunkهایی را که در حال حاضر روی دستگاه دارد، برای Server ارسال می‌کند.Server می‌تواند مشخص کند:کدام Chunkها از قبل روی Client وجود دارند.کدام Chunkها هنوز معتبر هستند.دقیقاً کدام Chunkها برای ساخت نسخه‌ی جدید موردنیاز هستند.همان اصل دوباره تکرار می‌شود:ابتدا Metadata را مقایسه کن، سپس فقط داده‌ی موردنیاز را منتقل کن.مرحله ۱۴: مشابه مرحله ۵در این مرحله درخواست از طریق Load Balancer به یکی از Instanceهای Sync Service هدایت می‌شود.مرحله ۱۵: مشابه مرحله ۶Server Hashهای Client را با Metadata موجود مقایسه می‌کند و Diff را محاسبه می‌کند.مرحله ۱۶: Server لینک دانلود Chunkهای گمشده را برمی‌گرداندServer Diff را محاسبه کرده و برای هر Chunk موردنیاز یک Pre-signed Download URL برمی‌گرداند.بنابراین Client B لازم نیست کل فایل را Download کند.فقط Chunkهایی را دریافت می‌کند که برای رسیدن به نسخه‌ی جدید نیاز دارد.مرحله ۱۷: دانلود از Blob Storage یا CDNClient B می‌تواند Chunkها را مستقیماً از Blob Storage دریافت کند.در یک معماری مناسب، این داده‌ها می‌توانند از طریق CDN نیز ارائه شوند.CDN داده را از نقطه‌ای نزدیک‌تر به کاربر سرو می‌کند و بار مستقیم روی Storage اصلی را کاهش می‌دهد.بعد از دریافت Chunkها، Client آن‌ها را طبق Metadata کنار هم قرار می‌دهد و نسخه‌ی جدید فایل را بازسازی می‌کند.در نهایت Local Chunk DB نیز با وضعیت جدید به‌روزرسانی می‌شود.Sync کامل شد.پس چرا این معماری مقیاس‌پذیر است؟نکته‌ی اصلی این است:Dropbox یک کامپوننت جادویی ندارد.این سیستم مجموعه‌ای از تصمیم‌های کوچک است که باعث می‌شوند بار از مسیرهای گران و حساس دور شود.۱. کل فایل منتقل نمی‌شودChunking باعث می‌شود فقط بخش‌هایی که تغییر کرده‌اند منتقل شوند.۲. قبل از انتقال Byteها، Hashها مقایسه می‌شوندسیستم ابتدا متوجه می‌شود دقیقاً چه چیزی موردنیاز است.۳. Application Server فایل‌های بزرگ را Proxy نمی‌کندPre-signed URL اجازه می‌دهد داده مستقیماً بین Client و Blob Storage منتقل شود.۴. Metadata از File Content جدا استDatabase برای Query و Versioning استفاده می‌شود و Object Storage مسئول نگهداری Byteهای فایل است.۵. تغییرات به‌صورت Asynchronous منتشر می‌شوندMessage Broker اجزای مختلف سیستم را از یکدیگر جدا می‌کند و به سایر Clientها اطلاع می‌دهد که تغییری رخ داده است.۶. Backend می‌تواند به‌صورت افقی Scale شودLoad Balancer درخواست‌ها را بین چندین Sync Service تقسیم می‌کند.۷. دانلود می‌تواند از CDN انجام شوداین کار Latency را کاهش می‌دهد و بار روی Origin Storage را کمتر می‌کند.قانون طلاییدر سیستم‌های بزرگ، Scaling همیشه به معنی سریع‌تر منتقل‌کردن داده نیست.خیلی وقت‌ها بهترین طراحی این است که:داده‌ای را که اصلاً نیازی به انتقال نداشته، از ابتدا منتقل نکنیم.این همان ایده‌ای است که در معماری Dropbox بارها تکرار می‌شود:Compare first. Move later.این اولین قسمت از یک مجموعه ۳۰ قسمتی بوددر قسمت‌های بعدی، سیستم‌های مختلف را با همین رویکرد بررسی می‌کنیم:از Requirements و Bottlenecksتا Database، Cache، Queue و Consistencyو در نهایت Trade-offهای واقعی معماریو در پایان، دو نکته‌ی کوتاه:سعی کردم دیاگرام را طوری طراحی کنم که کسل‌کننده نباشد.برای این مقاله از منابع زیر استفاده کردم:Hello Interview — Design a File Storage Service Like DropboxSantosh P. — System Design of DropboxEducative — Grokking the System Design InterviewEducative — Grokking the Low Level Design Interview Using OOD PrinciplesSystem Design &amp; Software Architecture: AWS + MicroservicesMastering System Design: From Basics to Cracking Interviews#SystemDesign #SoftwareArchitecture #DistributedSystems #BackendEngineering #DropboxArchitecture</description><category>امیرحسین عنایتی</category><author>امیرحسین عنایتی</author><pubDate>Fri, 11 Sep 2026 15:02:02 +0330</pubDate></item></channel></rss>