ذخیره ی داده های حجیم در Redis

ذخیره ی داده های حجیم در Redis

Redis به عنوان یک دیتابیس بسیار سریع شناخته می‌شود زیرا می‌تواند داده‌ها را با سرعت بالا فراخوانی و ذخیره کند. در واقع بین خواندن و نوشتن در Redis تفاوت چندانی از نظر بهره‌وری وجود ندارد.
به طور کلی عملیات نوشتن در Redis می‌تواند به دو شکل انجام بگیرد:
1. ایجاد تغییر روی داده مانند set ، union ، incr و...
2. افزودن داده‌های حجیم به Redis مانند وارد کردن میلیون ها رکورد.
بسیار مهم است که بدانیم چگونه می‌توان در زمان کم، حجم زیادی از داده‌ها را به Redis اضافه کرد. در این قسمت، روش های افزودن انبوهی از داده را بررسی می‌کنیم.

Pipeline

قابلیت pipeline در Redis این امکان را به ما می‌دهد که چندین دستور را بدون این که منتظر پاسخ Redis بمانیم اجرا کنیم و پس از اجرای کامل دستورات پاسخ آن‌ها را دریافت کنیم. مزیت استفاده از pilpeline این است که بهره‌وری ارتباط با سرور Redis را تا حد زیادی (در برخی شرایط تا 5 برابر) افزایش می‌دهد. نحوه نگارش کد برای استفاده از pipeline در Redis به زبان برنامه‌نویسی و کتابخانه ای که با آن کار می‌کنید بستگی دارد. تکه کد زیر استفاده از pipeline در زبان Ruby توسط کتابخانه redis-rb را نشان می‌دهد. در این مثال Redis به صورت Local روی پورت 6379 نصب شده.

require "redis"
redis = Redis.new(:host => "127.0.0.1", :port => 6379)
redis.pipelined do
    redis.set "user", "user1"
    redis.set "userid", 1
    redis.incr "totallogin"
end

نوشتن و بروزرسانی چندین کلید در Redis از کارهای معمول است. هنگامی که می‌خواهیم چندین کلید را بروزرسانی کنیم دستورات به ترتیب به Redis ارسال می‌شوند. برای این که تأثیر اجرای دستورات Redis روی بهره‌وری را مشاهده کنیم بهتر است ابتدا با نحوه اجرای دستورات در آن آشنا شویم. تصور کنید نیاز است شما دو کلید جدید در Redis بنویسید و مقدار یک کلید شمارنده را افزایش دهید. در این مورد به طور معمول سه دستور متفاوت اجرا می‌کنیم.

SET user User1
OK
SET userid 1
OK
INCR totalLogin
(integer) 14

اگر زمان رفت و برگشت شبکه از سیستم ما تا سرور را 100 میلی ثانیه و زمان اجرای دستور توسط Redis را نادیده بگیریم زمان اجرای همه این 3 دستور برابر است با:

 100ms + 100ms +100ms = 300ms = زمان کل

بنابراین زمان اجرای دستورات متناسب با تعداد آن‌ها زیاد می‌شود. علت این موضوع این است که سرور Redis با پروتکل TCP کار می‌کند (پس از ارسال هر درخواست تا دریافت پاسخ منتظر می ماند). سرور و کلاینت (برنامه ما) توسط یک Connection سوکت با یک دیگر ارتباط دارند. به همین دلیل تاخیر شبکه حتا در صورتی که برنامه ما و سرور Redis یک جا باشند در زمان اجرای دستورات تأثیرگذار است. تصور کنید سرور Redis خود را به گونه‌ای پیکربندی کرده‌ایم که 50000 درخواست در ثانیه را پاسخ دهد. اما اگر تاخیر شبکه ما 100ms باشد، فارغ از این که سرعت سرور Redis مان چقدر است، تنها می‌توانیم 10 درخواست در ثانیه را پاسخ بدهیم. از آن جایی که کاهش زمان رفت و آمد بین سرور و کلاینت امکان‌پذیر نیست، بهترین راه حل کاهش رفت و آمد هاست. به طور خلاصه هرچه رفت و آمد کمتری داشته باشیم، Redis درخواست های بیشتری را می‌تواند در هر ثانیه پردازش کند. Redis برای این مشکل راه حلی به نام pipeline را ارائه می‌کند.

مثالی که بالاتر آورده شد را دوباره بررسی می‌کنیم تا ببینیم  pipeline چگونه به ما کمک می‌کند. از آن جایی که تمام دستورات یکجا به سرور ارسال می‌شوند تنها یک رفت و آمد بین سرور و کلاینت وجود خواهد داشت. بنابراین زمان صرف شده کمی بیشتر از 100ms خواهد بود و درنهایت ما با استفاده از pipeline رشد 200 درصدی در بهره‌وری اجرای دستورات ساده را خواهیم داشت.

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

می‌توانیم چند صد دستور را در یک pipeline قرار دهیم، پاسخ آن را دریافت کرده و سپس pipeline بعدی را اجرا کنیم تا مصرف مموری را مدیریت کنیم. با انجام این کار بهره‌وری نسبت به قبل تقریبا ثابت می ماند.

به طور خلاصه می‌توان گفت pipeline با کم کردن رفت و آمد ها بین سرور و کلاینت، یک راه مؤثر جهت نوشتن داده‌ها با سرعت بیشتر در Redis در اختیار ما قرار می‌دهد.

با این حال ممکن است شرایطی به وجود بیاید که نیاز داشته باشیم میلیون ها رکورد داده را در زمان کوتاهی به Redis اضافه کنیم.

درج داده ی حجیم


در این بخش نگاهی به نحوه ذخیره میلیون ها رکورد داده با بیشترین سرعت ممکن می‌اندازیم. به طور معمول ذخیره حجم زیادی از داده‌ها در Redis به دلایل زیر نیاز می‌شود:

هنگام استفاده از Redis جهت ذخیره داده، درج اطلاعات با حجم زیاد از طریق دیتابیس‌های رابطه ای مانند MySQL رخ می‌دهد.
هنگام استفاده از Redis به عنوان لایه cache، درج اطلاعات حجیم برای پر کردن cache با داده‌هایی که بیشترین دسترسی را دارند اتفاق می‌افتد.
بارگذاری داده‌هایی که از قبل موجود بوده است.
در این شرایط داده‌ای که قصد اضافه کردن آن را داریم معمولا حجم زیادی دارد و شامل میلیون ها رکورد است. برای دست یابی به این هدف اجرای دستورات پشت سر هم ایده خوبی نیست. زیرا سرعت کمی دارد و رفت و برگشت بین سرور و کلاینت هزینه بر است. می‌توانیم از pipeline استفاده کنیم اما pipeline به ما این اجازه را نمی‌دهد که هنگام ذخیره کردن، همان داده‌ها را بخوانیم. زیرا Redis داده‌ها را تا قبل از اجرای آخرین دستور ثبت نمی‌کند.

معروف‌ترین راه ذخیره کردن حجم زیادی از داده در Redis ایجاد یک فایل متنی با دستوراتی در قالب پروتکل Redis و استفاده از آن، جهت بارگذاری داده است. Cli ارائه شده برای Redis این امکان را برای ما فراهم می‌کند که یک فایل خام با دستوراتی که در پروتکل Redis مشخص شده است را بارگذاری کنیم. این پروتکل ساده و binary-safe است (تضمین می‌شود که پس از اجرای عملیات فایل دچار خرابی نشود). این قالب مانند زیر است:

*<args><cr><lf>
$<len><cr><lf>
<arg0><cr><lf>
<arg1><cr><lf>
...
<argN><cr><lf>

در بالا cr همان r\ (کاراکتر 13 ASCII) که باعث می‌شود نشانگر موجود روی خط به ابتدای خط برود و lf همان n\ (کاراکتر 10 ASCII) است که باعث می‌شود نشانگر موجود روی خط به خط بعدی برود. استفاده این دو در کنار هم (r\n\) نشانگر را به ابتدای خط بعد می برد.

برای مثال دستور زیر را می‌خواهیم اجرا کنیم:

SET samplekey testvalue

این دستور در یک فایل خام، مانند زیر خواهد بود:

*3<cr><lf>
$3<cr><lf>
SET<cr><lf>
$8<cr><lf>
samplekey<cr><lf>
$9<cr><lf>
testvalue<cr><lf>

3* : تعداد ورودی‌ها را مشخص می‌کند. در مثال ما 3 ورودی وجود دارد (SET و  samplekey و testvalue).
$3 : نشان دهنده تعداد بایت های دستور است (در مثال ما دستور SET سه بایت دارد).
SET : دستوری است که می‌خواهیم اجرا کنیم.
$8 : تعداد بایت های کلید مورد نظر ماست (در مثال ما samplekey هشت بایت دارد).
samplekey : نامی است که برای کلید خود انتخاب کردیم (اولین ورودی دستور SET است).
$9 : تعداد بایت های testvalue است که در مثال ما 9 بایت دارد.
testvalue : مقداری است که می‌خواهیم داخل کلید گفته شده ذخیره شود (ورودی دوم دستور SET است).

این مثال به رشته زیر تفسیر می‌شود:

*3\r\n$3\r\nSET\r\n$8\r\nsamplekey\r\n$9\r\ntestvalue\r\n

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

cat redis-data.txt | redis-cli --pipe

بعد از اجرای دستور، پاسخی مانند زیر دریافت می‌کنیم:

All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000

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

جمع‌بندی


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

نظرات
اگر login نکردی برامون ایمیلت رو بنویس: