پست

الگوی مدیریت Blobهای بزرگ در مصاحبه‌های طراحی سیستم

نحوه آپلود و دانلود مستقیم فایل‌های بزرگ با presigned URL، آپلود چندبخشی و قابل ازسرگیری، همگام‌سازی state بین پایگاه داده و Blob Storage، رویدادهای ذخیره‌سازی و CDN

الگوی مدیریت Blobهای بزرگ در مصاحبه‌های طراحی سیستم

مدیریت Blobهای بزرگ

در این فصل یاد می‌گیریم در مصاحبه طراحی سیستم چطور Blobهای بزرگ را مدیریت کنیم.

فایل‌های بزرگ مثل ویدیو، تصویر و سند در سیستم‌های توزیع‌شده به مدیریت ویژه نیاز دارند. این الگو به‌جای عبور دادن گیگابایت‌ها داده از سرورهای شما، از presigned URL استفاده می‌کند تا کلاینت‌ها بتوانند مستقیم در Blob Storage آپلود کنند و از CDN دانلود کنند. همچنین آپلود قابل ازسرگیری، انتقال موازی و ردیابی پیشرفت را می‌گیرید، چیزهایی که سیستم‌های واقعی را از پروژه‌های اسباب‌بازی جدا می‌کنند.

مسئله

اگر برای مصاحبه‌های طراحی سیستم مطالعه کرده باشید، می‌دانید فایل‌های بزرگ در Blob Storage مثل S3 ذخیره می‌شوند، نه در پایگاه داده. این جداسازی اجازه می‌دهد ذخیره‌سازی مستقل از محاسبات مقیاس بگیرد و کارایی پایگاه داده سریع بماند.

چرا Blob Storage؟ پایگاه‌های داده در داده ساخت‌یافته با کوئری‌های پیچیده عالی هستند، اما با اشیای باینری بزرگ افتضاح‌اند. فایل ۱۰۰ مگابایتی که به‌صورت BLOB ذخیره شود، کارایی کوئری، زمان پشتیبان‌گیری و replication را خراب می‌کند. object storeهایی مثل S3 دقیقاً برای این کار ساخته شده‌اند: ظرفیت نامحدود، دوام ۹۹٫۹۹۹۹۹۹۹۹۹۹۹٪ (یازده نه) و قیمت‌گذاری به ازای هر شیء. قاعده سرانگشتی این است که اگر فایلی بیش از ۱۰ مگابایت است و به کوئری SQL نیاز ندارد، احتمالاً باید در Blob Storage باشد.

Blob Storage مسئله ذخیره‌سازی را حل کرد، اما مسئله انتقال داده را نه. رویکرد استاندارد بایت‌های فایل را از سرورهای اپلیکیشن شما عبور می‌دهد. کلاینت ویدیوی ۲ گیگابایتی آپلود می‌کند، سرور API آن را می‌گیرد و بعد به Blob Storage می‌فرستد. برای دانلود هم همین‌طور است. Blob Storage برای سرور API می‌فرستد و سرور برای کلاینت. این برای فایل‌های کوچک کار می‌کند، اما با بزرگ‌تر شدن فایل‌ها از هم می‌پاشد.

سرورهای اپلیکیشن را وادار کرده‌ایم نقش بی‌مغز (dumb) را بازی کنند. آن‌ها هیچ ارزشی به انتقال اضافه نمی‌کنند و فقط تأخیر و هزینه می‌آورند. ارائه‌دهندگان ابر از قبل زیرساخت جهانی، قابلیت resume و پهنای باند عظیم دارند. با این حال، سرورهای اپلیکیشن محدود خودمان را به‌عنوان واسطه وارد می‌کنیم و جایی که لازم نیست، گلوگاه می‌سازیم.

پس (هشدار اسپویلر) از آپلود از طریق سرورهایمان به آپلود مستقیم از کلاینت به Blob Storage می‌رویم. دانلودها هم از CDNهای جهانی مثل CloudFront انجام می‌شود. سرورهای شما هرگز بایت‌های واقعی را لمس نمی‌کنند. این گلوگاه پروکسی را حل می‌کند، اما به‌تنهایی کافی نیست، چون مشکل‌های بیشتری ظاهر می‌شوند.

آپلود ۲۵ گیگابایتی که در ۹۹٪ شکست بخورد یعنی شروع دوباره از صفر. دید خود را روی پیشرفت آپلود از دست می‌دهید، چون سرورهایتان کاملاً دور زده می‌شوند. پایگاه داده شما «در انتظار» (pending) نشان می‌دهد، در حالی که S3 ممکن است همین حالا فایل کامل را داشته باشد (یا شاید نداشته باشد، و بدون بررسی نمی‌توانید بگویید).

عمل ظاهراً ساده جابه‌جایی بایت‌ها به مسئله سیستم توزیع‌شده با چالش‌های سازگاری نهایی، ردیابی پیشرفت و هماهنگ‌سازی state تبدیل شده است، مسائلی که رویکرد پروکسی هرگز با آن‌ها روبه‌رو نشد.

تصویر را دیدید. مدیریت فایل‌های بزرگ در سیستم‌های توزیع‌شده سخت است. پس کدام الگوها به ما کمک می‌کنند؟

راه‌حل

به‌جای پروکسی کردن داده از سرورهایتان، به کلاینت‌ها اعتبارنامه موقت و محدودشده (scoped) می‌دهید تا مستقیم با ذخیره‌سازی تعامل کنند. نقش سرور اپلیکیشن شما از انتقال داده به کنترل دسترسی تغییر می‌کند. درخواست را تأیید می‌کند، اعتبارنامه تولید می‌کند و از مسیر کنار می‌رود.

فرقی نمی‌کند از AWS S3، Google Cloud Storage یا Azure Blob Storage استفاده کنید، همه از URLهای موقتی پشتیبانی می‌کنند که مجوز آپلود یا دانلود با زمان محدود می‌دهند. CDNها هم می‌توانند قبل از ارائه محتوا، توکن‌های امضاشده را تأیید کنند.

سرور اپلیکیشن را مثل باجه بلیت در سالن کنسرت ببینید. وقتی کسی با بلیت معتبر می‌رسد، شخصاً او را تا صندلی‌اش همراهی نمی‌کنید. هویتش را تأیید می‌کنید، یک مچ‌بند به او می‌دهید و می‌گذارید راهش را خودش پیدا کند. اینجا هم همین‌طور است. یک بار مجوز را تأیید می‌کنیم و بعد به کلاینت‌ها اجازه می‌دهیم مستقیم با ذخیره‌سازی تعامل کنند.

آپلود مستقیم ساده

وقتی کلاینت می‌خواهد فایلی آپلود کند، به‌جای دریافت خود فایل، سرور شما فقط درخواست مجوز آپلود را دریافت می‌کند. کاربر را تأیید می‌کنید، سهمیه‌ها را بررسی می‌کنید و بعد یک URL آپلود موقت می‌سازید. این URL مجوز آپلود یک فایل مشخص در مکان مشخص را برای مدت محدود، معمولاً ۱۵ دقیقه تا ۱ ساعت، رمزگذاری می‌کند. به آن presigned URL می‌گویند.

ساختن presigned URL کاملاً در حافظه اپلیکیشن شما انجام می‌شود و تماس شبکه‌ای با Blob Storage لازم ندارد. سرور شما از اعتبارنامه ابری خودش برای ساختن امضایی استفاده می‌کند که سرویس ذخیره‌سازی بتواند بعداً تأییدش کند. یک presigned URL در واقع این‌طور به نظر می‌رسد:

1
2
3
4
5
6
7
https://mybucket.s3.amazonaws.com/uploads/user123/video.mp4
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20240115%2Fus-east-1%2Fs3%2Faws4_request
&X-Amz-Date=20240115T000000Z
&X-Amz-Expires=900
&X-Amz-SignedHeaders=host
&X-Amz-Signature=b2754f5b1c9d7c4b8d4f6e9a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

امضا یک hash رمزنگاری‌شده از جزئیات درخواست (متد HTTP، مسیر منبع، زمان انقضا) همراه با کلید مخفی شماست. وقتی کلاینت روی این URL آپلود می‌کند، سرویس ذخیره‌سازی با استفاده از نسخه‌ای از اعتبارنامه شما همان hash را دوباره حساب می‌کند. اگر امضاها یکی باشند و URL منقضی نشده باشد، آپلود ادامه پیدا می‌کند.

چون هر کسی که URL را داشته باشد می‌تواند از آن استفاده کند، هنگام ساختن آن‌ها باید محدودیت‌ها را در آن بگذارید. سرویس‌های ذخیره‌سازی اجازه می‌دهند شرط‌هایی اضافه کنید که هنگام آپلود بررسی می‌شوند.

  • محدوده Content-Length: حداقل و حداکثر اندازه فایل را تعیین کنید تا جلوی آپلود ۱۰ گیگابایتی را بگیرید، وقتی انتظار ۱۰ مگابایت دارید.
  • نوع محتوا (Content Type): مطمئن شوید endpoint عکس پروفایل فقط تصویر می‌پذیرد، نه ویدیو.

این محدودیت‌ها در امضا گنجانده می‌شوند. اگر کسی بخواهد با URLای که برای آپلود تصویر ۵ مگابایتی ساخته شده، ویدیوی ۵۰۰ مگابایتی آپلود کند، رد می‌شود.

کلاینت یک HTTP PUT ساده با فایل در بدنه درخواست به این URL می‌فرستد. از دید او، مستقیم در ذخیره‌سازی آپلود می‌کند. زیرساخت شما هرگز بایت‌ها را لمس نمی‌کند. کاربری در سیدنی با سرعت کامل در ریجن Blob Storage سیدنی آپلود می‌کند، در حالی که سرورهای شما در ویرجینیا درخواست‌های دیگر را رسیدگی می‌کنند.

دانلود مستقیم ساده

دانلود به همین شکل کار می‌کند، یا مستقیم از Blob Storage یا از طریق CDN. URLهای امضاشده‌ای می‌سازید که به فایل‌های مشخص دسترسی خواندن موقت می‌دهند. دسترسی مستقیم به Blob Storage برای دانلودهای نادر ساده‌تر و ارزان‌تر است. توزیع از طریق CDN هزینه بیشتری دارد، اما با توزیع جغرافیایی و کش، کارایی بهتری برای فایل‌هایی که زیاد دسترسی می‌شوند می‌دهد.

برای تحویل از طریق CDN، URLهای Blob Storage را امضا نمی‌کنید. در عوض، signed URL یا signed cookie می‌سازید که CDN شما آن را تأیید می‌کند. این نمونه نحوه مدیریت آن در CloudFront است:

1
2
3
4
https://d123456.cloudfront.net/videos/lecture.mp4
?Expires=1705305600
&Signature=j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b9c0d1e2f3g4h5i6j7k8l9m0
&Key-Pair-Id=APKAIOSFODNN7EXAMPLE

هر دو رویکرد از امضا استفاده می‌کنند، اما تفاوت اصلی در این است که چه کسی آن‌ها را تأیید می‌کند.

  • امضاهای Blob Storage (مثل presigned URL در S3) را سرویس ذخیره‌سازی با اعتبارنامه ابری شما تأیید می‌کند. سرویس ذخیره‌سازی کلید مخفی شما را دارد و می‌تواند تأیید کند شما امضا را ساخته‌اید.
  • امضاهای CDN (مثل signed URL در CloudFront) را سرورهای edge در CDN با رمزنگاری کلید عمومی و خصوصی تأیید می‌کنند. کلید خصوصی پیش شماست و URL را امضا می‌کنید. CDN کلید عمومی مربوط را دارد و امضاها را در edge locationهای سراسر دنیا تأیید می‌کند. لازم نیست دوباره با سرورهای شما یا سرویس ذخیره‌سازی origin تماس بگیرد.

آپلود قابل ازسرگیری برای فایل‌های بزرگ

ببینید هنگام آپلود فایل‌های واقعاً بزرگ چه می‌شود. مثلاً ویدیوی ۵ گیگابایتی. با اتصال شبکه معمولی ۱۰۰ مگابیت بر ثانیه، آپلود آن ۴۲۹ ثانیه طول می‌کشد، یعنی ۷ دقیقه و ۹ ثانیه. اگر اتصال در ۹۹٪ قطع شود، باید از صفر شروع کنید.

خوشبختانه همه ارائه‌دهندگان بزرگ ابر این مشکل را با APIهای آپلود تکه‌تکه (chunked upload) حل می‌کنند. AWS S3 از آپلود چندبخشی (multipart upload) استفاده می‌کند که در آن هر بخش ۵ مگابایتی URL اختصاصی خودش را می‌گیرد. Google Cloud Storage و Azure از URL تک‌نشستی استفاده می‌کنند که در آن بخش‌ها را با هدرهای Range روی همان endpoint آپلود می‌کنید.

همه رویکردها اجازه می‌دهند بخش‌های شکست‌خورده را از سر بگیرید، اما ساختار URL فرق می‌کند. اگر مشکلی پیش آمد، فقط باید بخش‌هایی را که شکست خورده‌اند دوباره آپلود کنید.

کلاینت این بخش‌ها را آپلود می‌کند و checksumهای کامل‌شده‌ای را که سرویس ذخیره‌سازی برمی‌گرداند ردیابی می‌کند. این‌ها فقط hash داده آپلودشده‌اند، پس محاسبه و تأییدشان ارزان است. اگر بعد از آپلود ۶۰ بخش از ۱۰۰ بخش اتصال قطع شود، کلاینت از صفر شروع نمی‌کند. در عوض از API ذخیره‌سازی می‌پرسد کدام بخش‌ها قبلاً با موفقیت آپلود شده‌اند و از بخش ۶۱ ادامه می‌دهد. سرویس ذخیره‌سازی این state را با شناسه نشست (upload ID در S3، resumable upload URL در GCS و مانند آن) نگه می‌دارد.

ردیابی پیشرفت به‌طور طبیعی با آپلود تکه‌تکه می‌آید. با کامل شدن هر بخش، درصد تکمیل را می‌دانید. محاسبه ساده در سمت کلاینت نوار پیشرفت خوبی برای نمایش به کاربر می‌دهد.

در نهایت، بعد از آپلود همه بخش‌ها، کلاینت باید endpoint ای را با فهرست شماره بخش‌ها و checksumهایشان فراخوانی کند. بعد سرویس ذخیره‌سازی این بخش‌ها را در شیء نهایی سرهم می‌کند. تا وقتی این تکمیل موفق نشود، بخش‌هایی در ذخیره‌سازی دارید اما فایل قابل‌دسترسی ندارید. آپلودهای چندبخشی ناقص هزینه دارند، پس باید lifecycle rule داشته باشید که بعد از ۲۴ تا ۴۸ ساعت پاکشان کند.

بعد از تکمیل آپلود چندبخشی، «بخش‌ها» از دید سرویس ذخیره‌سازی دیگر وجود ندارند. همه در یک شیء واحد ادغام شده‌اند، انگار کل فایل را یک‌جا آپلود کرده‌اید.

چالش‌های همگام‌سازی state

بیرون بردن سرورها از مسیر بحرانی گلوگاه را حل می‌کند، اما دسته جدیدی از مشکلات را درباره مدیریت state توزیع‌شده می‌آورد.

الگوی رایج این است که metadata فایل را در پایگاه داده نگه دارید، در حالی که فایل واقعی در Blob Storage می‌ماند. این بهترین هر دو دنیا را می‌دهد: کوئری سریع روی metadata و ذخیره‌سازی نامحدود برای خود فایل‌ها.

سیستم ذخیره فایل مثل Dropbox را در نظر بگیرید. جدول metadata شما ممکن است این‌طور باشد:

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE files (
    id              UUID PRIMARY KEY,
    user_id         UUID NOT NULL,
    filename        VARCHAR(255),
    size_bytes      BIGINT,
    content_type    VARCHAR(100),
    storage_key     VARCHAR(500),  -- s3://bucket/user123/files/abc-123.pdf
    status          VARCHAR(50),   -- 'pending', 'uploading', 'completed', 'failed'
    created_at      TIMESTAMP,
    updated_at      TIMESTAMP
);

ستون status می‌گوید فایل برای دانلود آماده است یا هنوز در حال آپلود. اما با آپلود مستقیم، هماهنگ نگه داشتن آن سخت می‌شود. پایگاه داده و Blob Storage دو سیستم جدا هستند که در زمان‌های مختلف به‌روز می‌شوند.

ساده‌ترین روش اعتماد به کلاینت است. بعد از آپلود در ذخیره‌سازی، کلاینت API شما را صدا می‌زند و می‌گوید «آپلود کامل شد!» و شما وضعیت پایگاه داده را به‌روز می‌کنید. اما این چند مشکل می‌سازد.

  • race condition: ممکن است پایگاه داده «کامل شده» را نشان دهد، قبل از این‌که فایل واقعاً در ذخیره‌سازی وجود داشته باشد.
  • فایل‌های یتیم (orphan): ممکن است کلاینت بعد از آپلود اما قبل از اطلاع دادن به شما از کار بیفتد و فایل بدون رکورد پایگاه داده در ذخیره‌سازی بماند.
  • کلاینت‌های مخرب: کاربران می‌توانند آپلود را کامل علامت بزنند بدون این‌که واقعاً چیزی آپلود کرده باشند.
  • مشکلات شبکه: ممکن است اعلان تکمیل به سرورهای شما نرسد.

بیشتر سرویس‌های Blob Storage این مشکل را با اعلان رویداد (event notification) حل می‌کنند. وقتی S3 فایلی را دریافت می‌کند، رویدادها را از طریق سرویس‌های پیام‌رسانی (مثل SNS یا SQS) با جزئیات آنچه آپلود شده منتشر می‌کند. این رویداد شامل کلید شیء است، یعنی همان storage_key که هنگام ساختن presigned URL در پایگاه داده ذخیره کرده بودید. این کار اجازه می‌دهد سطر دقیق را برای به‌روزرسانی پیدا کنید.

حالا خود سرویس ذخیره‌سازی تأیید می‌کند چه چیزی وجود دارد و کلاینت از معادله اعتماد حذف می‌شود. اما رویدادها هم می‌توانند شکست بخورند. مشکل شبکه، قطعی سرویس یا خطای پردازش ممکن است باعث تأخیر یا گم شدن رویداد شود. به همین دلیل سیستم‌های production یک شبکه ایمنی به‌نام reconciliation اضافه می‌کنند. یک کار دوره‌ای فایل‌هایی را که در وضعیت pending گیر کرده‌اند بررسی می‌کند و آن‌ها را با ذخیره‌سازی تطبیق می‌دهد.

با رویدادها به‌عنوان مکانیزم اصلی به‌روزرسانی و reconciler به‌عنوان پشتیبان، سازگاری را بدون از دست دادن مزایای کارایی آپلود مستقیم حفظ می‌کنید. تأخیر اندک در به‌روزرسانی وضعیت، مصالحه معقولی برای پروکسی نکردن گیگابایت‌ها از سرورهای شماست.

اصطلاحات ارائه‌دهندگان ابر

هر ارائه‌دهنده برای مفاهیم مشابه نام‌های خودش را دارد. نام دقیق توابع و پارامترهای SDK بین ارائه‌دهندگان فرق می‌کند و تا وقتی ایده اصلی را بفهمید، معمولاً برای مصاحبه لازم نیست آن‌ها را بدانید. با این حال اغلب از من می‌پرسند «معادل این در ارائه‌دهنده X چیست؟»، پس به نظر رسید این جدول ارزش گنجاندن دارد.

ویژگیAWSGoogle CloudAzure
URL آپلود موقتPresigned URL (با PUT یا POST)Signed URL (قابل‌ازسرگیری یا ساده)SAS Token (Shared Access Signature)
آپلود چندبخشیMultipart Upload API (بخش‌های ۵ مگابایت تا ۵ گیگابایت)Resumable Upload (اندازه تکه انعطاف‌پذیر)Block Blob (معادل چندبخشی، بلوک‌های ۴ تا ۱۰۰ مگابایت)
اعلان‌هاS3 Event Notifications (به Lambda، SQS و SNS)Cloud Storage Pub/Sub Notifications (به Cloud Functions)Event Grid
امضای CDNCloudFront Signed URL و Signed CookieCloud CDN Signed URLAzure CDN (SAS Token)
سیاست پاک‌سازیLifecycle RulesLifecycle ManagementLifecycle Management Policies

زمان استفاده در مصاحبه

قاعده ساده است و زیاد لازم نیست فکر کنید. اگر فایل‌های بزرگ‌تر از ۱۰۰ مگابایت را از طریق API خودتان جابه‌جا می‌کنید، باید فوراً به این الگو فکر کنید. آستانه دقیق به زیرساخت شما بستگی دارد، اما ۱۰۰ مگابایت جایی است که درد واقعی شروع می‌شود.

سناریوهای رایج مصاحبه

  • YouTube و پلتفرم‌های ویدیو: آپلود ویدیو بهترین حالت استفاده است. کاربر فایل ویدیوی 4K ‏۲ گیگابایتی آپلود می‌کند. اپلیکیشن وب presigned URL مربوط به S3 را می‌سازد، کلاینت مستقیم در S3 آپلود می‌کند (اغلب با multipart برای قابلیت resume) و رویدادهای S3 workflowهای encoding را شروع می‌کنند. دانلود هم مشابه است. CloudFront بخش‌های ویدیو را با signed URL ارائه می‌دهد و پخش با نرخ بیت تطبیقی (adaptive bitrate) را ممکن می‌کند، بدون این‌که سرورهای شما هرگز بایت‌های ویدیو را لمس کنند.
  • Instagram و اشتراک‌گذاری عکس: آپلود عکس کاملاً سرورهای API را دور می‌زند. اپلیکیشن موبایل برای تصاویر اصلی presigned URL می‌گیرد (که با دوربین‌های مدرن می‌تواند ۵۰ مگابایت یا بیشتر باشد)، مستقیم در S3 آپلود می‌کند و بعد رویدادهای S3 workerهای ناهمزمان را برای ساخت thumbnail، اعمال فیلتر و استخراج metadata راه می‌اندازند. فید تصاویر را از CloudFront با signed URL ارائه می‌کند تا از hotlinking جلوگیری شود.
  • Dropbox و همگام‌سازی فایل: آپلود و دانلود فایل‌ها دسترسی به داده serverless خالص است. وقتی ویدیوی ۵۰۰ مگابایتی را به Dropbox می‌کشید، اپلیکیشن برای آپلود تکه‌تکه presigned URL می‌گیرد، مستقیم در Blob Storage آپلود می‌کند و بعد workflowهای همگام‌سازی را راه می‌اندازد. اشتراک‌گذاری فایل، signed URL با زمان محدود می‌سازد که برای گیرنده بدون حساب Dropbox هم کار می‌کند.
  • WhatsApp و اپلیکیشن‌های چت: اشتراک‌گذاری رسانه (عکس، ویدیو و سند) از presigned URL استفاده می‌کند. وقتی ویدیو را در WhatsApp می‌فرستید، مستقیم در ذخیره‌سازی آپلود می‌شود و بعد سیستم چت فقط یک ارجاع به فایل را رد و بدل می‌کند. گیرنده‌ها signed URL دانلود می‌گیرند که بعد از مدتی منقضی می‌شود و تضمین می‌کند رسانه را نمی‌توان نامحدود دسترسی کرد.

چه زمانی در مصاحبه استفاده نکنیم

این الگو پیچیدگی اضافه می‌کند که فقط وقتی ارزشش را دارد که با مقیاس یا اندازه واقعی سروکار داشته باشید.

  • فایل‌های کوچک به آن نیاز ندارند. هر چیزی کمتر از ۱۰ مگابایت، مثل payloadهای JSON، فرم‌ها و thumbnailها، باید از endpointهای معمولی API استفاده کند. رقص دومرحله‌ای بدون سود واقعی تأخیر و پیچیدگی اضافه می‌کند. سرورهای شما می‌توانند هزاران درخواست کوچک را بدون سربار ساخت presigned URL مدیریت کنند.
  • الزامات اعتبارسنجی هم‌زمان. اگر باید قبل از پذیرش آپلود، داده نامعتبر را رد کنید، باید آن را پروکسی کنید. مثلاً import کردن CSV که باید هدرها و نوع داده‌ها را قبل از تأیید آپلود اعتبارسنجی کنید. باز هم می‌توانید داده را بعد از آپلود ناهمزمان پردازش کنید، اما اعتبارسنجی بلادرنگ حین آپلود نیازمند دیدن بایت‌ها هنگام عبور است.
  • انطباق و بازرسی داده. بعضی چارچوب‌های نظارتی ایجاب می‌کنند داده از سیستم‌های تأییدشده عبور کند یا قبل از ذخیره‌سازی اسکن شود. اسکن سرویس‌های مالی برای شماره کارت اعتباری، سیستم‌های سلامت که الزامات HIPAA را اجرا می‌کنند، یا هر سیستمی که باید داده را به دلایل انطباق بازرسی کند، به رویکرد سنتی پروکسی نیاز دارد. در این موارد محدود ارزش دارد که داده را از سرورهای خودتان پروکسی کنید (البته باز هم به‌صورت تکه‌تکه).
  • وقتی تجربه کاربری پاسخ فوری می‌خواهد. اگر کاربران انتظار بازخورد فوری بر اساس محتوای فایل دارند (مثل عکس پروفایل که بلافاصله با تشخیص چهره ظاهر می‌شود، یا پیش‌نمایش سندی که حین آپلود ساخته می‌شود)، ماهیت ناهمزمان این الگو تجربه کاربری را خراب می‌کند. رفت‌وبرگشت آپلود، پردازش و اطلاع‌رسانی برای قابلیت‌های واقعاً تعاملی زیادی طولانی است.

بررسی‌های عمیق رایج

این‌ها رایج‌ترین بررسی‌های عمیقی هستند که هنگام کار با Blobهای بزرگ پیش می‌آیند و می‌توانید این‌طور به آن‌ها پاسخ بدهید.

«اگر آپلود در ۹۹٪ شکست بخورد چه؟»

این سوال می‌سنجد آیا قابلیت‌های آپلود تکه‌تکه را که همه ارائه‌دهندگان بزرگ ابر دارند می‌فهمید یا نه. وقتی فایل‌ها از ۱۰۰ مگابایت بزرگ‌تر می‌شوند، باید از آپلود تکه‌تکه استفاده کنید: آپلود چندبخشی S3 (بخش‌های بیش از ۵ مگابایت)، resumable upload در GCS (هر اندازه تکه) یا block blob در Azure (بلوک‌های بیش از ۴ مگابایت). سرویس ذخیره‌سازی بخش‌هایی را که با موفقیت آپلود شده‌اند ردیابی می‌کند.

وقتی اتصال قطع می‌شود، کلاینت از صفر شروع نمی‌کند. در عوض می‌پرسد کدام بخش‌ها قبلاً آپلود شده‌اند. با ListParts در S3، چک کردن وضعیت resumable session در GCS، یا فهرست کردن بلوک‌های commit شده در Azure. اگر بخش‌های ۱ تا ۱۹ موفق بودند اما بخش ۲۰ شکست خورد، کلاینت از بخش ۲۰ ادامه می‌دهد. این برای کاربران موبایل با اتصال ناپایدار و فایل‌های بزرگی که آپلودشان ساعت‌ها طول می‌کشد بسیار مهم است.

پیاده‌سازی لازم می‌کند کلاینت شناسه نشست آپلود را ردیابی کند. این شناسه، upload ID در S3، resumable upload URL در GCS یا URL مربوط به block blob در Azure است. بعضی تیم‌ها آن را در localStorage ذخیره می‌کنند تا آپلود حتی بعد از راه‌اندازی مجدد اپلیکیشن ادامه پیدا کند. یادتان باشد آپلودهای ناقص هزینه دارند، چون ارائه‌دهندگان ابر برای بخش‌های ذخیره‌شده پول می‌گیرند. پس lifecycle policy تنظیم کنید که بعد از ۱ تا ۲ روز پاکشان کند.

«چطور جلوی سوءاستفاده را می‌گیرید؟»

مؤثرترین پیشگیری از سوءاستفاده ساده است: نگذارید کاربران بلافاصله به چیزی که آپلود می‌کنند دسترسی داشته باشند.

یک خط لوله پردازش پیاده کنید که در آن آپلودها اول در یک bucket قرنطینه (quarantine) قرار می‌گیرند. قبل از انتقال فایل‌ها به bucket عمومی، اسکن ویروس، اعتبارسنجی محتوا و بررسی‌های دیگر را اجرا کنید. این کار جلوی آپلود محتوای مخرب و به‌اشتراک‌گذاری فوری لینک آن توسط کسی را می‌گیرد.

تحلیل خودکار محتوا را راه بیندازید: تشخیص تصویر برای شناسایی محتوای نامناسب، اعتبارسنجی نوع فایل برای اطمینان از این‌که «عکس» واقعاً فایل اجرایی نیست، و کنترل اندازه برای جلوگیری از storage bomb. فقط بعد از این بررسی‌ها فایل را به مکان نهایی منتقل می‌کنید و وضعیت پایگاه داده را به «در دسترس» به‌روز می‌کنید.

این رویکرد خیلی قوی‌تر از تلاش برای شناسایی الگوهای سوءاستفاده به‌صورت بلادرنگ است. حتی اگر کسی rate limit شما را دور بزند و محتوای مخرب آپلود کند، تا وقتی سیستم شما آن را تأیید نکند، نمی‌تواند از آن استفاده کند.

تأخیر پردازش هم به‌طور طبیعی سوءاستفاده را کم می‌کند. مهاجم‌ها نمی‌توانند بلافاصله ببینند آپلودشان کار کرد یا نه و این اتوماسیون را سخت‌تر می‌کند.

همیشه محدودیت اندازه فایل را در شرط‌های presigned URL بگذارید. بدون آن، کسی می‌تواند ترابایت‌ها داده روی URLای آپلود کند که برای تصویر کوچک ساخته شده و هزینه ذخیره‌سازی شما را بالا ببرد.

«metadata را چطور مدیریت می‌کنید؟»

فایل‌ها تقریباً همیشه metadata مرتبط دارند: چه کسی آپلودشان کرده، برای چه است، دستورالعمل‌های پردازش و مانند آن. اما presigned URL بایت‌های خام را در object storage آپلود می‌کند. مصاحبه‌گرها می‌خواهند بدانند metadata را چطور به آپلودها مرتبط می‌کنید.

همان‌طور که قبلاً گفتیم، metadata شما باید جدا از خود فایل در پایگاه داده اصلی‌تان باشد. چالش همگام نگه داشتن آن‌هاست. وقتی presigned URL می‌سازید، بلافاصله رکورد پایگاه داده را با وضعیت «در انتظار» (pending) بسازید. این رکورد شامل کلید ذخیره‌سازی‌ای است که برای آپلود استفاده می‌کنید. حالا حتی قبل از وجود فایل، یک مرجع دارید.

بعضی تیم‌ها سعی می‌کنند metadata را با استفاده از تگ یا هدرهای سفارشی در خود شیء ذخیره‌سازی جا بدهند. S3 اجازه می‌دهد تا ۱۰ تگ با ۲۵۶ کاراکتر وصل کنید. اما این محدود است و کوئری را سخت می‌کند. با اسکن تگ‌های اشیا نمی‌توانید «همه فایل‌های PDF آپلودشده توسط کاربر X در هفته گذشته» را کارآمد پیدا کنید. metadata غنی را در پایگاه داده، جایی که به آن تعلق دارد، نگه دارید.

کلید ذخیره‌سازی نقطه اتصال شماست. از الگوی ثابتی مثل uploads/{user_id}/{timestamp}/{uuid} استفاده کنید که اطلاعات مفید دارد اما جلوی برخورد (collision) را می‌گیرد. وقتی رویدادهای ذخیره‌سازی فعال می‌شوند، این کلید را شامل می‌شوند و اجازه می‌دهند سطر دقیق پایگاه داده را برای به‌روزرسانی پیدا کنید. هرگز اجازه ندهید کلاینت‌ها کلیدهای خودشان را تعیین کنند. این درخواستی برای بازنویسی و مشکلات امنیتی است.

برای metadata مهمی که باید همراه آپلود به‌صورت اتمیک باشد، می‌توانید از آپلود شرطی استفاده کنید. presigned URLهایی بسازید که به هدرهای metadata مشخصی نیاز دارند. اگر کلاینت آن‌ها را نفرستد، آپلود شکست می‌خورد. اما این پیچیدگی اضافه می‌کند و بیشتر موارد استفاده به این سطح از سخت‌گیری نیاز ندارند.

«چطور سرعت دانلود را تضمین می‌کنید؟»

دانلود مستقیم از Blob Storage کار می‌کند، اما فرصت‌های بزرگ بهینه‌سازی را از دست می‌دهد. کاربر سیدنی که از Blob Storage ویرجینیا دانلود می‌کند، برای هر درخواست هنوز ۲۰۰ میلی‌ثانیه تأخیر دارد. برای فایل ۱۰۰ مگابایتی ممکن است قابل‌قبول باشد. برای هزاران فایل کوچک یا اپلیکیشن‌های تعاملی نیست.

CDNها مسئله جغرافیا را با کش کردن محتوا در edge locationهای سراسر دنیا حل می‌کنند. وقتی signed URL مربوط به CloudFront می‌سازید، اولین کاربر به origin می‌رسد، اما کاربران بعدی در آن ریجن نسخه کش‌شده را با تأخیر تک‌رقمی میلی‌ثانیه می‌گیرند. تفاوت بین ۲۰۰ و ۵ میلی‌ثانیه شاید کوچک به نظر برسد، تا وقتی که آن را در صدها درخواست ضرب کنید.

اما CDN به‌تنهایی به دانلود فایل‌های بزرگ کمک نمی‌کند. دانلود فایل ۵ گیگابایتی هنوز چند دقیقه طول می‌کشد و اگر اتصال قطع شود، کاربران از صفر شروع می‌کنند. راه‌حل range request است، یعنی توانایی HTTP برای دانلود بازه‌های بایتی مشخص از فایل. به‌جای درخواست GET برای کل فایل، کلاینت تکه‌تکه درخواست می‌کند:

1
2
GET /large-file.zip
Range: bytes=0-10485759  (first 10MB)

این کار امکان دانلود قابل‌ازسرگیری را می‌دهد. بازه‌های کامل‌شده را ردیابی کنید و بعد از اتصال مجدد فقط تکه‌های گمشده را درخواست کنید. مرورگرهای مدرن و download managerها اگر ذخیره‌سازی و CDN شما range request را پشتیبانی کنند (همه می‌کنند) این کار را خودکار انجام می‌دهند. فقط باید مطمئن شوید signed URLهای شما فعل‌ها یا هدرهای HTTP لازم را محدود نمی‌کنند.

برای موارد افراطی مثل توزیع مجموعه داده‌های عظیم یا دارایی‌های بازی، بعضی تیم‌ها دانلود تکه‌ای موازی پیاده می‌کنند. فایل را به تکه‌ها تقسیم کنید، ۴ تا ۶ تکه را هم‌زمان دانلود کنید و بعد سمت کلاینت دوباره سرهم کنید. این کار با دور زدن محدودیت هر اتصال می‌تواند سرعت دانلود را ۳ تا ۴ برابر کند. اما این پیچیدگی به‌ندرت ارزشش را دارد، چون بیشتر کاربران محدودیت پهنای باند کل دارند، نه محدودیت هر اتصال.

رویکرد عملی این است: همه‌چیز را از طریق CDN با هدرهای کش مناسب ارائه دهید. مطمئن شوید range request برای فایل‌های بزرگ کار می‌کند. بگذارید CDN و مرورگر بهینه‌سازی را انجام دهند. فقط وقتی سراغ رویکردهای عجیب مثل دانلود موازی بروید که فایل‌های چندگیگابایتی توزیع می‌کنید و کاربران با وجود اتصال خوب از سرعت شکایت دارند.

جمع‌بندی

مدیریت Blobهای بزرگ الگویی است که تقریباً در هر مصاحبه طراحی سیستمی که محتوای تولیدشده توسط کاربر دارد ظاهر می‌شود. وقتی «آپلود ویدیو»، «اشتراک‌گذاری فایل» یا «ذخیره عکس» را می‌شنوید، فوراً به دور زدن سرورهایتان برای انتقال واقعی داده فکر کنید. بینش کلیدی این است که تمرکز را از جابه‌جایی بایت‌ها در زیرساخت به هماهنگ کردن مجوزهای دسترسی و مدیریت state توزیع‌شده ببرید.

در مصاحبه نشان دهید هم مزایای کارایی و هم مصالحه‌های پیچیدگی را می‌فهمید. نشان دهید می‌دانید کی از آپلود مستقیم استفاده کنید (هر چیزی بیش از ۱۰۰ مگابایت) و کی نکنید (فایل‌های کوچک، الزامات انطباق). در نهایت چالش‌های همگام‌سازی state را فراموش نکنید. اینجاست که بسیاری از کاندیداها به مشکل می‌خورند.