معماری داده‌های توییتر، یا چگونه توییتر این حجم از داده‌ها و ترافیک سنگین را تحمل می‌کند؟

چند وقت پیش در حال خواندن کتاب Designing Data Intensive Application از آقای Martin Kleppmann بودم. مثال کاربردی که در فصل اول این کتاب آورده شده بود، برای خود من جالب و خیلی با تجربیاتم از تحمل ترافیک و داده‌های سنگین هم راستا بود. کاری که بعضا در سامانه خبرفارسی هم انجام داده بودیم. گفتم این نوشته را با شما هم به اشتراک بگذارم.

فرض کنید می‌خواهید نرم افزاری مانند توییتر بسازید. اگر بخواهیم ساده تصور کنیم، کار اصلی توییتر این است که هر کاربر می‌تواند توییت‌های مختلفی انجام دهد و کاربرانی که او را دنبال کرده باشند، می‌توانند توییت‌های یک شخص را در Time Line خود یعنی همان صفحه خانگی توییتر خود مشاهده کنند. تا اینجای کار به نظر ساده می‌رسد. از دید پایگاه داده‌های رابطه ای(مثل MySQL) یک جدول کاربران داریم، یک جدول توییت‌ها که به کاربر ارسال کننده کلید خارجی دارد و یک جدول جدا برای دنبال‌کننده‌ها که چه کاربری چه کاربر دیگری دنبال کرده است که دو کلید خارجی به جدول کاربران دارد. مانند شکل زیر:

حال اگر یک کاربر بخواهد Time Line خود را مشاهده کند، توییتر با انجام یک Query که دارای Join است، می‌توان از جدول توییت‌ها، آن توییت‌هایی را برای یک کاربر آورد که این کاربر، توییت کننده اصلی را دنبال کرده باشد.

این کار برای حجم پایین داده‌ها انجام شدنی است، ولی برای حجم بسیار بالای توییت ها و کاربران دنبال کننده چه؟ آیا این روش جواب می‌دهد؟ پاسخ منفی است.

اگر نگاهی به آمار ارائه شده توسط توییتر در سال ۲۰۱۲ داشته باشیم، در هر ثانیه نزدیک به ۳۰۰هزار مرتبه کاربران، Time Line خود را مشاهده می‌کنند. این در حالی است که در هر ثانیه به طور میانگین کمتر از ۵هزار توییت ارسال می شود. این بدان معناست که توییتر باید Queryگفته شده در بالا را نزدیک به ۳۰۰هزار بار در هر ثانیه اجرا کند. توییتر قبلا این کار را انجام می‌داد ولی از یک جایی به بعد تصمیمش عوض شد! راه حل دوم که به Fan-out معروف است، Time Line را برای هر کاربر قبل از مشاهده توسط آن کاربر می‌سازد. در واقع Time Line را در یک حافظه Cache می‌کند. اما چگونه؟

به آمار بالا توجه کنید. به طور میانگین ۵هزار توییت در ثانیه انجام می‌شود و ۳۰۰هزار مرتبه در هر ثانیه صفحه Time Line دیده می شود. پس معقول است که عملیات ساخت Time Line به جای ساخت در هنگام مشاهده توسط هر کاربر، در هنگام ارسال توییت توسط کاربر دنبال شونده انجام شود. یعنی اگر شخصی یک توییت انجام داد، Time Line برای کاربران دیگر دنبال کننده این شخص به روز می‌شود. در واقع در هنگام توییت توسط یک کاربر، این توییت در نوعی حافظه Cache برای کاربرانی که این کاربر را دنبال کرده اند ساخته می‌شود و بعد در هنگام مشاهده Time Line توسط هر کاربر، دیگر نیازی نیست که یک Query به ازای هر بار مشاهده از Time Line انجام شود. به این کار در اصطلاح Fan-out می‌گویند. به شکل زیر نگاه کنید:

در این شکل برای مثال توییت های انجام شده در صفی قرار گرفته اند و هر کدام در هنگام ارسال توسط کاربر ارسال کننده، در حافظه Cache برای هر کاربر دنبال کننده نوشته می‌شود.

این کار معقول‌تر به نظر می‌آمد، و توییتر از روش دوم برای انجام عملیاتش استفاده کرد. ولی باز هم مشکلاتی بود. یک سری کاربران بودند که تعداد دنبال کننده‌های بسیار زیادی داشتند(یا همان سلبریتی‌های مشهور). این‌ها وقتی توییتی انجام می‌دانند(به دلیل اینکه دنبال کننده‌های زیادی داشتند)، تعداد عملیات write برای نوشتن قسمت Cache هر کاربر دنبال کننده زیاد می‌شد. مثلا فرض کنید یک شخص ۴۰میلیون دنبال کننده داشته باشد. پس با ارسال یک توییت، بایستی ۴۰میلیون عملیات نوشتن(به ازای هر کاربر دنبال کننده، یک عملیات) برای یک آدم مشهور انجام شود. برای همین توییتر تصمیم گرفت از یک عملیات ترکیبی(Hybrid) استفاده کرده. به این صورت که در هنگام مشاهده Time Line توسط یک کاربر، یک سری توییت ها توسط روش Fan-out(همان روش Cache) به کاربر می‌رسد و یک سری از توییت‌های دیگر که توسط سبلریتی‌ها تولید شده‌اند(و یک کاربر، آن سلبریتی را دنبال کرده است)، در همان لحظه مانند روش سنتی Queryزده می‌شود و به کاربر می‌رسد. در واقع Time Line هر کاربر ترکیبی از روش Fan-out(برای دنبال شونده‌های معمولی) و روش سنتی Query(برای دنبال شونده‌های سلبریتی) تولید می‌شود.