بانکهای اطلاعاتی کلید/مقدارمصاحبه ها
ردیس – مصاحبه با جوزایا کارلسون
این مطلب عیناً از وب سایت مطلبچه های مهندسی نرم افزار برداشته شده است که با همت جناب محمدعلی بزرگ زاده به زیبایی ترجمه شده است و مهندسی داده، با هدف جمع آوری مطالب مناسب فارسی در حوزه کلان داده به بازنشر آن پرداخته است .
در این مصاحبه که در سپتامبر ۲۰۱۴ منتشر شده است، رابرت بلومن با جوزایا کارلسون صحبت میکند. کارلسون، دکترایش را در علوم کامپیوتر از دانشگاه ارواین کالیفرنیا گرفته است. او یک معمار نرمافزار با تمرکز بر روی طراحی معماری و الگوریتمهای سیستمهای توزیعشده در تعدادی از شرکتهای فنآوری بوده است که از آن جمله گوگل و یوتیوب است. او بنیانگذار و معمار ارشد ChowNow بوده و هماکنون به ایشان مشاوره میدهد. او اکنون در zEconomy معمار ارشد است. او همچنین نویسنده کتاب Redis in Action است.
جوزایا، به SE Radio خوش آمدی.
متشکرم. خوشحالم که اینجا هستم.
امروز، میخواهیم Redis را توضیح دهیم. با این شروع کنیم که Redis چیست.
Redis یک سرور داخل حافظهایِ کلید-ساختار داده است. بیشتر با سیستم Memcached مقایسه میشود که کلیدهای رشتهای را به مقادیر رشتهای نگاشت میکند اما Redis تفاوت عمده دارد و کلیدهای رشتهای را به ساختارهای دادهای نگاشت میکند. میتوانید چندین نوع ساختار داده باشید که از آن جمله رشتههای ساده، لیستها، مجموعهها، جداول درهمسازی و چیزی است که ما آن را مجموعههای مرتب میخوانیم. اخیراً در یکی از نسخهها، نوعی برای شمارش آماری آیتمهای یکتا اضافه شده است که HyperLogLog نامیده میشود که کاملاً جدید است و مربوط به چند هفته اخیر است. هنوز افراد خیلی زیادی از آن استفاده نکردهاند.
شما گفتید که Redis یک سرور کلید-ساختار داده است. تفاوت آن با ذخیرهگاه کلید-مقدار (Key-Value Store) چیست؟
تفاوت اصلی در نوع کارهایی است که میتوانید با دادههایتان انجام دهید. در یک سرور کلید-رشته مرسوم که نگاشت (Map) خوانده میشود، میتوانید مقادیر را بگیرید یا انتساب کنید، مقادیر میتواند مثلاً با عدد ۱۰ جمع زده شود، گاهی میتوانید دادههای خود را به این رشتهها بیافزایید و آن رشتهها را طولانیتر کنید اما در Redis ضمن اینکه همه این کارها را میتوانید بکنید، با داشتن ساختار داده لیست، میتوانید از هر دو طرف لیست push و pop داشته باشید یا در لیست به دنبال آیتمی بگردید، با داشتن ساختار داده مجموعه، مفهوم ریاضی مجموعه را در اختیار دارید که مثلاً اگر دوبار رشته “Hello” را در آن وارد کنید، یک بار در آن قرار میگیرد، یا جداول درهمسازی را دارید که خودش یک ساختار کلید-مقدار دیگر است و همه انواع تعاملات مربوط به جداول درهمسازی را خواهید داشت، مجموعههای مرتب را دارید که یک جدول کلیددار مانند جداول درهمسازی است که علاوه بر آن میتوانید به آیتمهای خود بصورت مرتبشده نیز دسترسی داشته باشید. بنابراین جنبههای مختلفی در زمینه نحوه دسترسی و خواندن و نوشتن به دادهها فراهم میکند.
شما در مورد امکان تغییر ساختار دادهها در سمت سرور (Server Side) صحبت کردید. چه تفکری پشت این وجود دارد که به جای اینکه دستکاری دادهها را سمت کلاینت داشته باشیم و آنها را بصورت مقادیر گُنگ برگردانیم، تغییرات را در سمت سرور بر روی ساختارهای داده داشته باشیم؟
عمدتاً مربوط به حجم دادههایی میشود که نیاز دارید جابجا شود. به عنوان مثال اگر از Memcached استفاده کنید، میتوانید رشتهها را بفرستید و دریافت کنید، قطعاً میتوانید کمابیش ساختار دادههای دلخواه خود از قبیل JSON را در رشتهها بستهبندی کنید و خیلیها این کار را میکنند اما هرگاه بخواهید که این ساختار داده را تغییر بدهید، باید آن را بخوانید، تغییرش دهید و بعد آن را بازبنویسید، در این فرآیند، شما مجبورید داده را به نوعی قفل (Lock) کنید تا کس دیگری نتواند همزمان آن را خوانده، تغییرش داده و بعد بنویسد.
کاری که قطعاً میتوان انجام داد این است که مستقیماً خود شیء را دستکاری کرد. این کار، از لحاظ زمان CPU خیلی کاراتر است زیرا نیازی به کد و دیکد کردن نیست؛ از لحاظ پهنای باند شبکه هم کاراتر است زیرا نیازی به خواندن و نوشتن کل مقادیر نیست و فقط بخشهای کوچکی را تغییر میدهید.
کاملاً منطقی است. شما در کتابتان، مثالهای جالب زیادی در این مورد زدهاید که چگونه Redis میتواند کارهای بسیار قدرتمندی انجام دهد. آیا ممکن است مثالهایی بیاورید که به ما نشان دهد چگونه از یک یا دو ساختار داده برای توسعه یک ویژگی جالب توجه استفاده میکنید؟
قطعاً. یکی از الگوهایی که من درباره نحوه ذخیره و مرتبسازی دادهها، ترجیح میدهم موردی است که هماکنون در Redis ساخته شده است و کاملاً رایج است و آن استفاده از یک مجموعه (Set) و انبوهی از جداول درهمسازی (Hash) است. خیلیها از جداول درهمسازی به مانند یک مستند در ذخیرهگاه مستندات (Document Store) یا [یک سطر] در پایگاههای رابطهای استفاده میکنند که در آن هر سطری از جدول درهمسازی معادل با یک ستون یا صفت [از سطر مورد نظر در پایگاه رابطهای] است. افراد دادهها را به این شکل ذخیره میکنند و خوب است، چون هم میتوانید کل آیتم را دریافت و هم میتوانید فیلدهای مجزا را دریافت کنید. اما بعد به فکر مرتب کردن دادههایتان میافتید. اگر در [نام] هرکدام از جداول درهمسازی، فیلد شناسه را قرار دهید مثلاً [نام] هر کدام از جداول درهمسازی به شکل user:[id] باشد که در آن id همان شناسه کلید است و بعد یک مجموعه (Set) هم داشته باشید که همه آن شناسهها را در خود داشته باشد و اسم آن مجموعه هم فرضاً ترکیب users با نام [ستون] کلید باشد. در اینصورت، آنچه میتوانید انجام دهید این است که بر اساس هر ستون دلخواه متناظر با [فیلدهای] جداول درهمسازیتان میتوانید مرتبسازی کنید. [برای این منظور] میتوانید به Redis بگویید که فلان مجموعه را بر اساس فلان مورد مرتب کن، در واقع معیار مرتبسازی با یک رشته مشخص میشود که ستون مورد نظر را مشخص میکند.
مثلاً فرض کنید که دادههای کاربران شما شامل ایمیل هستند و میخواهید آنها را بر اساس آدرس ایمیل مرتب کنید. در اینصورت خواهید گفت:
SORT userIds By user:*->email
این به Redis میگوید که مجموعه userIds را براساس فیلدهایی مرتب کند که در جداول درهمسازی دیگری قرار گرفتهاند و اگر در انتهای آن، کلمه ALPHA را نیز بیافزایید، به Redis میگویید که آن را بصورت الفبایی مرتب کند. به این ترتیب، آیتمها را بصورت مرتب شده بر اساس ایمیل، پس میگیرید. اینها، همین حالا در Redis وجود دارد و الگوهای شناخته شدهای هستند. اگر بخواهید در حالتی که هیچ ایندکس یا چیز خاصی بر روی دادههایتان تنظیم نکردهاید، همچنان بتوانید مرتبسازیهای موردی دلخواه بر روی دادههایتان داشته باشید، چنین روشی بسیار مناسب و قدرتمند است و چیزی است که هر کسی انتظارش را نداشت.
شما با این مثال، به نکته دیگری اشاره کردید؛ آیا Redis ، امکان داشتن Wild Card بر روی زیررشتههای کلید را فراهم میکند؟
به زمینه کاری آن بستگی دارد. در حال حاضر، از معدود جاهایی که این نوع امکان را دارید در دستور SORT است. البته Redis از الگوی Publish-Subscribe هم پشتیبانی میکند، شما میتوانید یک کانال را Publish کنید و یا برای یک کانال Subscribe کنید تا پیامهای آن را دریافت کنید. وقتی میخواهید برای پیامهای از کانالهای دلخواه Subscribe کنید، میتوانید از * -که با هر کاراکتری مطابقت مییابد- استفاده کنید. به علاوه، از آنها [Wild Card ها] میتوانید در دستور KEYS هم برای پیدا کردن کلیدها استفاده کنید. همینطور دستورات جدیدی که اخیراً برای پیمایش کردن بر روی ساختارهای داده اضافه شدهاند هم Wild Card میگیرند (اشاره به دستورهای SCAN ، SSCAN ، HSCAN و ZSCAN – مترجم)، اما اینها گسترش خیلی زیادی برای Wild Card ها نبوده است.
شما به ویژگی جدید HyperLogLog اشاره کردید که من با آن آشنا نیستم. به ما بگویید چه مسألهای را حل میکند و چطور کار میکند؟
یکی از اولین کاربردهای Redis مواردی مانند شمارش بازدیدکنندگان منحصر بفردِ (غیرتکراری) مثلاً یک صفحه وب بوده است. یک راه آن – نه لزوماً بهترین راهش- این است که هر وقت، کسی صفحه را دید، او را به یک مجموعه (Set) اضافه کنیم. از آنجاییکه، مجموعه تنها شامل آیتمهای منحصر بفرد است، وقتی شماره کاربری را به یک مجموعه اضافه میکنید، تنها یک نمونه از آن اضافه خواهد شد. میتوانید یک مجموعه بسازید و مثلاً برای یک هفته از آن استفاده کنید تا بازدیدکنندگان منحصر بفرد آن هفته را بدست آورید. این خوب است اما مجبورید به تعداد همه افرادی که بازدید میکنند، حافظه تخصیصدهی کنید که برای وبسایتهای بزرگ حافظه خیلی زیادی نیاز است. راههای دیگری وجود دارد که روش کار را عوض کرده، بهبودهایی ایجاد کنید و میزان حافظه مورد استفاده را کاهش دهید. اخیراً HyperLogLog اضافه شده است تا چنین چیزی را فراهم کند البته کاربردش فقط برای شمارش بازدیدکنندگان یک وبسایت نیست.
وقتی آیتمی به HyperLogLog اضافه میکنید، آن را بصورت آماری اضافه نمیکند بلکه کد درهمسازی (Hash) آن را محاسبه کرده و بر آن اساس، در یک ساختار کوچک چند بیت را تغییر میدهد، بعداً از تعداد بیتهای تغییریافته، تخمینی از آیتمهای منحصربفرد حاصل میکند. سالواتوره، مدتی روی تحلیل آن و پیدا کردن روش بهینهسازی آن و کمینه کردن خطای آن کار کرده است ( سالواتوره سنفیلیپو ، مبدع و توسعهدهنده اصلی Redis است – مترجم) و فکر میکنم چنانچه قصد شمارش گروه بسیار حجیمی از آیتمها را داشته باشید، در این حالت، عددی که حاصل میشود نسبت به حالتیکه واقعاً آیتمها را به یک مجموعه خیلی بزرگ اضافه میکردید، تنها یک یا دو درصد فاصله خواهد داشت و حجم حافظهای هم که مصرف میشود تنها حدود ۱۲ کیلوبایت است. بنابراین با ۱۲ کیلوبایت میتوانید اعداد واقعاً خیلی خیلی بزرگی را بشمارید.
اگر درست متوجه شده باشم چیزی مشابه با Bloom Filter است.
بله، مشابه با آن است، تفاوت اصلیاش این است که Bloom Filter قرار است خیلی بزرگتر باشد و تنها بله و خیر را ذخیره کند؛ اینکه بله، فلان آیتم در آن وجود دارد یا اینکه خیر، وجود ندارد که همان کاری نیست که HyperLogLog قرار است انجام دهد. اما ایدههای مشترک زیادی برای راه انداختن هر دوی آنها بکار رفته است.
در برخی از این مثالها، شما درگیر تغییرات در چندین ساختار داده میشوید، در اینجا طبیعتاً در مورد شکل سازگاری و اتمیک بودن عملیات، سئوال مطرح میشود. Redis برای اینطور ملاحظات چه مدلی فراهم میکند؟
فرامین مجزا، خودشان اتمیک هستند. اگر شما آیتمی را در یک لیست Push کنید و پروسس دیگری هم، آیتمی را در لیست Push کند، بعلت اینکه در یک زمان رخ داده است، جایگزین آیتمی که شما Push کردهاید نخواهد شد. Redis تک نخی (Single Thread) است بنابراین هر عملیات بصورت مجزا، اتمیک است زیرا فقط یک کار میتواند در یک زمان انجام شود.
شما میتوانید از امکان تراکنش استفاده کنید. در حال حاضر، دو نوع اصلی تراکنش وجود دارد. یکی چیزی است که ما به آن تراکنش Multi-Exec میگوییم و بوسیله آن شما به Redis میگویید که: «این کارها را با همدیگر وقتی Exec فراخوانی شد، انجام بده» شما اول میگویید MULTI ، بعد تمامی دستورات مربوط به دستکاریهای خود را وارد میکنید و در نهایت میگویید EXEC و وقتی EXEC را گفتید همه دستورات به ترتیب بدون هیچگونه وقفهای از دستورات کلاینتهای متصل دیگر، اجرا میشوند.
همینطور میتوانید با استفاده از WATCH ، تراکنشهای شرطی انجام دهید به این ترتیب که میگویید که میخواهید تغییرات مربوط به فلان کلیدها را WATCH کنید و بعد اگر لازم بود دادههای آن کلیدها را میگیرید و بعد دستور MULTI را تایپ میکنید و بعد مجموعه عملیات مورد نظر خود را ارسال میکنید و بعد وقتی دستور EXEC را اجرا کنید، آنچه رخ میدهد این است که قبل از اجرای دستورات مابین MULTI و EXEC ، اگر هرکدام از کلیدهایی که در ابتدا گفتهاید که میخواهید WATCH کنید تغییر کرده باشند، تراکنش Multi-Exec، لغو میشود و پیغام خطایی دریافت میکنید که بیان میکند کلیدهای موردنظر تغییر کردهاند و بعد اگر بخواهید میتوانید تلاش مجدد داشته باشید. در تلاش مجدد اگر تغییری نباشد، دستورتان اجرا میشود و پاسخ مورد انتظار خود را دریافت خواهید کرد.
به غیر از آن با اسکریپتهای Lua هم میتوانید دستکاریهای نسبتاً پیچیده انجام دهید، هر اسکریپت Lua کمابیش یک فرمان در نظر گرفته میشود بنابراین بصورت اتمیک، اجرا میشود. هشدارهایی در این زمینه وجود دارد که در اسکریپت Lua اگر به کلیدی دسترسی پیدا کنید، ممکن است آن کلید به تازگی منقضیشده باشد اما این مسئله خیلی هم غافلگیرکننده نیست.
با توجه به اینکه Redis تک نخی است، اگر دستهای از دستورات را برای آن بفرستید و آنها را در سرور اجرا کنید، مشکلات ناسازگاری را نخواهید داشت و همه آنها بصورت اتمیک اجرا میشوند، بنابراین این مدل قفلکردن خوشبینانه (Optimistic Locking) آیا برای این شرایط طراحی شده است که ممکن است کلاینت قبل از کامیت کردن، چند بار [بر روی دادهها] عقب و جلو برود؟
بله، قطعاً در شرایطی که [بر روی دادهها] دور میزنید این اجرای شرطی و یا اجرای خوشبینانه نیاز است. من معتقدم از ابتدا به این علت به این شکل طراحی شده است که نوشتن الگوریتمهای بدون بنبست (Deadlock Free) در این حالت که دادهها را صراحتاً برای تغییرات قفل نمیکنید، خیلی سادهتر است. اگر در نوشتن برنامههای پایگاه داده مراقب نباشید، هر از چند وقت با بنبست مواجه میشوید؛ فرضاً اگر دو کلاینت داشته باشید و یک کلاینت سطر A را قفل کند و تلاش کند که سطر B را قفل کند اما در همان حال، کلاینت دوم، سطر B را قفل کرده باشد و تلاش داشته باشد که سطر A را قفل کند و نتواند، شرایط بنبست رخ میدهد زیرا میخواهد چیزی را قفل کند که کلاینت دیگر آن را قفل کرده است، این خیلی خطرناک است اما با قفل کردن خوشبینانه یا WATCH کردن آنها و توقف در صورت تغییر یافتن آنها و در غیر اینصورت ادامه دادن، دیگر هیچ سناریوی بنبستی نخواهید داشت، شما فقط به تلاشتان ادامه میدهید تا این تغییرات موفقیتآمیز شود. از لحاظ نحوه اجرا، این کمک میکند که Redis خیلی سادهتر باشد هرچند ممکن است کمی کار بیشتری از کلاینت ببرد.
شما در کتابتان مثالهایی از تولید قفلهای در سطح برنامه کاربردی (Application Level Lock) زدهاید. به نظر شما چه زمان باید از قفلهای سطح برنامه استفاده کرد و چه زمان باید از قفلهای بومی پشتیبانی شده توسط سرور استفاده کرد؟
بطور کلی من نگاه میکنم که کارهایم با دادهها چقدر پیچیده است. من عموماً، زمانی از تراکنشهای Redis استفاده میکنم که نوع دادههایی که میخواهم به آنها دسترسی داشته باشم و نوع دسترسی من ساده باشد و تنها نیاز داشته باشم که یک یا دو ساختار داده را در Redis تغییر دهم. در این صورت میتوانم انتظار داشته باشم که تعداد تلاشهای مجدد [برای اجرای تراکنش] کم باشد و در این شرایط است که کارها سریع و کارا انجام میشود و مشکلی نخواهم داشت.
اما اگر احتیاج داشته باشم که تعداد زیادی ساختار داده را دستکاری کنم یا نیاز باشد که بعد از قفل کردن دادهها در Redis، بصورت غیرمعمول دادههایی را واکشی کنم، در اینصورت همان طور که در کتابم هم اشاره کردهام، از قفلهای در سطح برنامه کاربردی استفاده میکنم؛ به این علت که اگر دستکاری دادههایتان شامل چیزهایی باشد که خارج از Redis باشد، در آن صورت، روشن است که Redis نمیتواند آنها را Watch کند و وقتی چیزهای زیادی را دستکاری میکنید احتمال موفق نشدن قفل خوشبینانه را افزایش میدهید، بنابراین مجبور به تلاش مجدد خواهید شد.
بنابراین در خیلی از شرایط که برخی از آنها را در کتابم نوشتهام، استفاده از قفل، بصورت قابل ملاحظهای سریعتر خواهد بود. گاهی من از دیدگاه کارایی هم آن را بررسی میکنم که این نتیجه را میدهد که اگر از اسکریپتهای Lua زیاد استفاده میکنید، نیازی به قفل ندارید، اگر میتوانید همه دستکاریهای دادههایتان را در اسکریپتهای ساده Lua قرار دهید، نیازی ندارید که از قفل یا قفلهای شرطی استفاده کنید، همینطور نیاز ندارید که از قفلهای خوشبینانه و یا تراکنشهای Redis استفاده کنید، همواره تنها از همان اسکریپتهای Lua استفاده میکنید، از آنجاییکه هرکدام از آن اسکریپتها در یک نوبت اجرا میشود، سریعتر هم عمل خواهد کرد.
شما چند بار به اسکریپتهای Lua اشاره کردید. میدانیم که Redis، خودش بصورت بومی در سمت سرور از Lua پشتیبانی میکند. ابتدا در مورد خود زبان Lua به ما بگویید و بعد میخواهم به این مسأله بازگردیم که بوسیله آن چه کارهای فراتر از دستورات پایه Redis را میتوانیم انجام دهیم.
من اساساً از طریق Redis بود که Lua را کشف کردم. چند سال پیش وقتی بر روی بازی Warcraft کار میکردم، تجربههایی در آن مورد داشتم و کمی پیشرفت کردم. اما Lua در اساس، یک زبان برنامهنویسی مفسری است که واقعاً دینامیک است اما در عین حال خیلی ساده است. برای این طراحی شده که کوچک باشد و اغلب برای برنامهنویسی تعبیه شده در بازیهای ویدئویی و برنامههای کاربردی استفاده میشود. در اینجا در Redis بعنوان زبان اسکریپتِ پشتیبانی شده استفاده شده است. گونهای از Lua که در Redis آورده شده، نسخه ۵.۱ از Lua است که میتواند با Lua. Jit جایگزین شود که همان کامپایلر درجای (Just in Time) زبان Lua است اما این کامپایلر بصورت پیشفرض در Redis قرار داده نشده است زیرا حتی با این وجود که در برخی موارد Lua. Jit میتواند سریعتر اجرا کند، عملیات تفسیر در ارتباط با کارایی، عملکرد سازگارتری دارد.
من به شخصه، برای سالهای زیادی طرفدار Python بودهام. هر چند Lua برخی ویژگیهایی که من به آن عادت داشتهام را ندارد، تا کنون در استفاده از آن مشکلی نداشتهام. من در واقع این را تبلیغ میکنم که دستوراتِ به تعداد زیاد تا جایی که ممکن است به دستورات مبتنی بر Lua تبدیل شود تا فهم آن برای دیگران راحتتر شود.
آیا مورد کاربرد اصلی Lua این است که میتوانید مجموعهای از عملیاتها را در سرور بدون نیاز به چندین بار نوبت یافتن، انجام دهید و به این ترتیب تعداد این نوبتها را نسبت به حالت استفاده از قفل کاهش دهید؟
مطمئن نیستم که دقیقاً به این خاطر باشد. فکر میکنم تنها به علت انعطافپذیری بوده است. بسیاری از پایگاههای داده رابطهای، نوعی زبان اسکریپتی داخلی دارند. PL/SQL یکی از رایجترین آنها است. اگر از PostgreSQL استفاده کنید، میتوانید از خیلی از زبانهای داخلی استفاده کنید، میتوانید از Python، Perl، PHP، Lua، Java و خیلی زبانهای دیگر استفاده کنید. بنابراین این مفهوم چیز خیلی جدیدی نیست. پایگاه دادههای دیگر، زبانهای تعبیهشدهای در خود دارند.
در ارتباط با Redis آنچه این امکان فراهم میکند، در وهله اول برای انعطافپذیری است. اگر بخواهید میتوانید از آن برای بروزرسانیهای معظم در یک نوبت نیز استفاده کنید. شما میتوانید سطحی از منطق برنامه خود را [در این اسکریپتها] قرار دهید. مثلاً یک گروه به نام andyet وجود دارد که اعتبارسنجی دادهها و اعتبارسنجی Schema ها، چه از نوع اعتبارسنجی انواع و چه از نوع اعتبارسنجی قیود را با استفاده از اسکریپتهای Lua در داخل Redis انجام میدهند. شاید به نوعی شگفتآور باشد اما آنها این کار را انجام دادهاند زیرا Redis بصورت پیشفرض اعتبارسنجی دادهها را پشتیبانی نمیکند و آنها نمیخواستند که در سمت کلاینت آن را بنویسند.
من میخواهم جهت بحث را به سمت مسائل حافظه و پایدارسازی (Persistence) ببرم. من فکر میکنم Redis اساساً یک ذخیرهگاه داده داخل حافظهای (In-Memory) است که برخی قابلیتهای پایدارسازی به آن وارد شده است. آیا این تشریح منصفانهای است؟
به نظر من غیرمنصفانه نیست. قطعاً اینگونه است که بصورت پیشفرض، وقتی Redis نصب میشود، همه چیز داخل حافظه است. نسخههای قدیمیتر یک ویژگی داشتند که «واسط حافظه مجازی» خوانده میشد که دادههای استفاده نشده را جایگزین میکرد که خیلی کارآمد نبود و در Redis نسخه ۲٫۴ حذف شد.
به عنوان یک معمار نرمافزار، این نقش «سرور حافظه در تمام سیستم» را در مقایسه با مفهوم پایگاه داده -یعنی چیزی که هنگام Commit قابلیت ماناسازی دارد- چطور میبینید؟
من به شخصه وقتی به افراد توصیه میکنم به آنها میگویم که [برای کار با Redis] ابتدا با دادههایی شروع کنند که نگهداری ۱۰۰% آنها، از اهمیت کمتری برخوردار باشد. مثلاً اگر یک وبسرویس راه انداخته باشند، از اولین دادههایی که به افراد توصیه میکنم استفاده کنند، کوکیهای جلسات کاربران (User Session) است. عموماً در محصولات وب، از کوکی برای ذخیرهسازی اطلاعات هویتی کاربران و خیلی چیزهای دیگر استفاده میشود و هرگاه که یک درخواست وب که شامل اطلاعات آماری است، میآید، مجبورید که یک کوکی ارسال کنید. این بروزرسانیهای کوکیها، خصوصاً در موبایلها، میتواند وقتگیر باشد. اما اگر در عوض، یک شناسه تولید شده بصورت تصادفی برای هر کوکی داشته باشید، آنگاه میتوانید آن را به Redis بفرستید تا هر نوع دادههای دلخواهی را ذخیره سازید، آنگاه نه تنها میتوانید میزان دادههایی که با کلاینت رد و بدل میشود را کمینه کنید بلکه میتوانید اطلاعات دلخواه پرارزشی را در Redis ذخیره سازید. در آن صورت نگران این نخواهید بود که Redis بمیرد و از دادههایتان پشتیبان نگرفته باشید، چرا که در این صورت تنها اطلاعات مربوط به لاگین را از دست خواهید داد، میتوانید Redis را دوباره اجرا کنید و افراد دوباره لاگین کنند.
فکر میکنم [اگر به این روش آغاز کنید] یک آشنایی خوبی با Redis برای خیلی از افرادی که میخواهند از آن استفاده کنند، فراهم میشود و بعد از آن، وقتی دیدید که چقدر سریع است و چقدر خوب کار میکند و به کار با آن عادت کردید، آنگاه متوجه این مطلب میشوید که Redis کرش نکرده است. من خودم، تنها مواجهه مشکل [کرشی] که تا به حال با Redis داشتهام به خود Redis مربوط نبوده بلکه مربوط به سروری بوده است که Redis بر روی آن اجرا بوده است. بر روی سرویسهای ابری این چیزها بیشتر پیش میآید اما در واقع اغلب به این شکل است که Redis نرمافزار کاملاً مطمئنی است و بطور منظم بروزرسانی و رفع اشکال میشود. برای من خیلی خیلی مطمئن بوده است، من الان کمی کمتر از ۴ سال است که با آن کار میکنم و در مدت این ۴ سال فقط ۲-۳ بار با شرایطی مواجه شدهام که Redis مشکلی داشته باشد.
شما در مورد استفاده از Redis در مواردی گفتید که نگهداری تمامی بخشهای دادهها الزامی نیست و از دست دادن دادهها، فاجعهبار نخواهد بود. از دیدگاه معماری، در عوض از دست دادن ویژگی مانایی، باید ویژگی دیگری را حاصل کرده باشید، آن چیست؟
در اساس، آنچه Redis در عوض از دست دادن مانایی به شما میدهد مدل دادهای است که در حال حاضر در هیچ نوع پایگاه دادهای مشابه ندارد. ساختار داده غنی که در Redis فراهم است، به شما این امکان را میدهد که برنامه کاربردی خود را مانند آن چه در زبان برنامهنویسیتان انجام میدهید، مدلسازی کنید. وقتی از یک زبان برنامهنویسی رایج برای مدلسازی دادههایتان استفاده میکنید، لیست، جداول درهمسازی و رشتهها را در اختیار دارید، مجموعهها هم هستند که کمتر رایج هستند اما در برخی زبانهای برنامهنویسی از قبیل Python وجود دارند. مجموعههای مرتب هم هستند که عموماً در زبانهای برنامهنویسی فراهم نمیشوند اما میتوانید از آنها استفاده کنید تا به نوعی ایندکس بسازید، این همان کاری است که اکثر افراد انجام میدهند.
بنابراین با این ساختارهای داده میتوانید در Redis همانند برنامهتان دادهها را ساختاردهی کنید. به این طریق میتوانید از Redis به مانند یک حافظه مشترک بزرگ استفاده نمایید. مثلاً اگر بخواهید تعداد بازدیدها [ی یک صفحه] را بشمارید، یا تاریخچه صفحاتی که اخیراً بازدید شده را نگه دارید، به جای اینکه سطری در پایگاه داده اضافه کنید، میتوانید تنها یک آیتم به انتهای یک لیست Push کنید، و اگر بخواهید تعداد عناصر لیست را مثلاً به ۵۰ محدود کنید، میتوانید آن را Trim کنید. آیا میتوانید تصور کنید که در یک پایگاه داده رابطهای چطور میشود این کار را کرد؟! عملی نیست. حتی تا به امروز. اما بخاطر ساختارهای داده در Redis میتوانید این کار را بکنید. اگر اینطور نباشد که مدل دادهتان تنها مجموعهای از سطرها و ستونها مانند پایگاههای رابطهای باشد، میتوانید به همان شکلی که در ذهن دارید برنامهتان را مدل کنید. در واقع من این کار را دارم انجام میدهم و بسیاری از چنین ایدههایی را در کتابم نوشتهام. من یک نگاشتگر اشیاء Redis برای Python ساختهام که به غیر از خود رابطهها بسیاری از مفاهیم پایگاههای رابطهای را در خود دارد؛ با این حال، یکسری روابط حداقلی و نوعی از الحاق (Join) بین آنها را میتوانید داشته باشید.
اعتراف میکنم که وقتی این سئوال اخیر را پرسیدم انتظار داشتم که بگویید تأخیر پایین (Low Latency) مهمترین چیزی است [که بدست میآوریم]، برخی سیستمهای توزیعشده بدون حالت (Stateless) تنها با تأخیر پایین، امکانپذیر هستند. آیا این فرض تأخیر پایین در جواب شما بصورت ضمنی وجود داشت؟
نه، ویژگی دیگری که وجود دارد این است که Redis خیلی سریع است و عموماً تأخیر پایینی دارد زیرا همه چیز در حافظه و جداول نگاشت، قرار میگیرد. اکثر افراد به سراغ Redis میآیند چون سریع است. ما همواره در درگاه نامههای Redis سئوالاتی را دریافت میکنیم با این مضمون که: «من شنیدهام که Redis واقعاً خیلی سریع است. چطور میتوانم از آن استفاده کنم؟ چطور میتوانم فلان چیز را با آن مدل کنم؟» من درک میکنم که افراد به این خاطر بیایند که بخواهند کارها سریعتر شود و واقعاً هم سریع است. این یکی از اولین چیزهایی است که ترغیبتان میکند اما وقتی یک مدتی از آن استفاده میکنید متوجه میشوید که سرعت شما را به اینجا آورده است اما آنچه نگهتان داشته است و باعث میشود به استفاده از آن ادامه دهید این است که میتوانید مدلهای داده غنی با آن بسازید و با فراهم کردن فرصتهای متفاوت، محدودیتهایی که برای مدلهایتان دارید را کاهش میدهد. البته قطعاً سرعت هم خیلی مهم است. اگر Redis مثلاً یک صدم سرعت کنونیاش را داشت، قطعاً کسی از آن استفاده نمیکرد چون در عمل خیلی کند بود. اما الان به مقدار کافی سریع است بلکه از خیلی از نرمافزارهای معادلش هم سریعتر است. همینطور مدلهای داده غنی ارائه میکند که کمکتان میکند برنامهتان را بسازید.
میدانم که میتوانم موضوع را بیشتر ادامه بدهم اما میخواهم موضوع را عوض کنم و به بحث پایدارسازی (Persistence) بپردازم. Redis دو نوع مدل پایدارسازی ارائه میکند که عبارتند از BGSAVE و AOF. به ما بگویید این دو چه کار میکنند و تفاوتشان چیست؟
BGSAVE در واقع یک دستور است که اجرا میشود تا عملیات تهیه یک تصویر مقطعی (Snapshot) [از دادهها] در پسزمینه آغاز شود. تصاویر مقطعی، نقاطی در زمان هستند؛ آنچه در حین گرفتن تصویر مقطعی رخ میدهد این است که Redis خودش را -با فراخوانی دستور استاندارد POSIX برای fork که یک کپی از پروسس میسازد- fork میکند و بعد پروسس کپی شده بچه، همه دادههای موجود در Redis را در یک فایل با پسوند rdb در دیسک انباشت (Dump) میکند. بعد از اینکه این کار انجام شد، نام آن فایل را عوض میکند و آن را با تصویر مقطعی قبلی جایگزین میکند و بعد به پروسس والد برمیگردد تا حالا معلوم شود که همه دادهها ذخیره شدهاند و اطلاعاتی در مورد آن ثبت میکند تا شما از نتیجه ذخیرهسازی آگاه شوید. شما میتوانید با استفاده از تنظیمات save این عملیات را خودکار کنید. نکته خوبی که در مورد این نوع روش پایدارسازی وجود دارد این است که اگر نیاز داشته باشید دادههای مربوط به لحظه خاصی را داشته باشید، میتوانید آن را حاصل کنید. تهیه تصویر مقطعی برای آغاز کردن عملیات رونوشتبرداری بر روی سیستم Slave هم استفاده میشود که احتمالاً شما به زودی در مورد آن سئوال خواهید کرد و من کمی جلوتر از بحث رفتهام.
از طرف دیگر، AOF، یا فایلهای فقط الحاق کردنی (Append Only File)، به این ترتیب عمل میکنند که در هر زمانی که عملیات نوشتن بر روی Redis وجود داشته باشد، Redis آن دستور را در دیسک در یک فایل فقط الحاق کردنی، مینویسد. کارکردش خیلی شبیه به Commit Log یا Write Ahead Log (گاهی نیز Intent Logs خوانده میشوند) در پایگاه دادههای سنتی است. تنها چیزی که در این مورد باید به آن فکر کنید این است که هر از چند وقت میخواهید دادههای این فایل را به دیسک fsync کنید، یعنی هر از چند وقت میخواهید مطمئن شوید که دادهها قطعاً به روی دیسک رفتهاند. برای آن ۳ گزینه وجود دارد. یکی اینکه در مورد fsync کردن دادهها نگرانی نداشته باشید. دیگری این است که در هر ثانیه حداقل یک بار دادهها را به دیسک fsync کنید و سومی این است که با هر دستور نوشتنی، fsync کنید. این مورد آخر، باعث کند شدن سیستم میشود در حالیکه گزینه یک ثانیه، تقریباً تغییری در رفتار Redis ایجاد نمیکند. وابسته به این است که کجا میخواهید بنویسید و اینکه نوشتنهایتان چقدر بزرگ باشد. مثلاً اگر در هر ثانیه ۱۵۰ مگابایت، مینویسید، باید دیسکی داشته باشید که بتواند ۱۵۰ مگابایت در ثانیه بنویسد.
من بسته به شرایط، گاهی از AOF، گاهی از تصاویر مقطعی و برخی مواقع هم از هر دو استفاده میکنم، بسته به اینکه چقدر بخواهم مراقب دادههایم باشم. حتی اگر از هر دو استفاده کنیم، Redis نمیتواند به مانایی (Durable) پایگاههای دادهای باشد که تضمین میکنند تا وقتی که کلیه بایتهای داده در دیسک چرخشی نوشته نشده باشد، به شما نخواهند گفت که تراکنشتان Commit شده است. البته از اینکه یک سرور با چندین گیگابایت داده را از دست بدهیم بهتر است.
این پایدارسازی چه مسألهای را برای کل نرمافزار من حل میکند؟ یعنی در مقایسه با حالتی که برنامه را بدون پایدارسازی اجرا کنم و بگویم که اگر دادهها را از دست دادم از ابتدا شروع میکنم؟
مانند هر سرور پایگاه داده دیگری است؛ مثلاً فرض کنید بر روی یک ماشین، یک سرور PostgreSQL دارید و همینطور که از آن استفاده میکنید دادهها بر روی دیسک نوشته میشود. حال فرض کنید که آن ماشین را از دست میدهید یعنی اینکه ماشین خراب میشود یا دیسک آن خراب میشود و شما دادههای روی دیسک را از دست میدهید. در این حالت به یک نسخه پشتیبان نیاز دارید. Redis مسأله نسخههای پشتیبان را حل نکرده است، بلکه این مسأله را حل کرده است که تا زمانی که نوع خرابی شما مصیبتبار نباشد بتوانید حجم خیلی خوبی از دادهها را برگردانید. من گفتم در استفاده از AOF میتوانید تنظیم کنید که «حداقل» یک بار در ثانیه fsync داشته باشید اما در عمل سرورهایی را میبینم که با دیسکهای SSD خیلی سریع، در هر ثانیه صدها بار به دیسک fsync میکنند.
بنابراین با این وجود که مانند پایگاه دادههای رابطهای سازگار و دارای خواص ACID، تضمین نشده است که همه دادهها بر روی دیسک باشند اما در عمل خیلی خوب کار میکند. تا کنون مواردی داشتهایم که افرادی که در حین تعداد زیادی عمل نوشتن، برق سیستمشان رفته است، برمیگردند و میپرسند که وقتی داشت سیستم بالا میآمد هنگام خواندن از فایل AOF پیغام خطا داد. چطور میشود درستش کرد؟ و فردی پاسخ میدهد که فلان دستور را با فلان گزینهها اجرا کن که موجب میشود دادههای خراب آخر فایل، پاک شود. و به این ترتیب بازسازی میشود و اغلب این طور است که آنها میگویند که مثلاً فکر میکنند که ۳ تا دستور نوشتن را از دست داده باشند، یعنی مثلاً حجم ۱۰۰۰۰ عمل نوشتن در ثانیه داشتهاند و با رفتن برق، ۳ تا از آنها را از دست دادهاند؛ این بد نیست، ایدهآل نیست اما بد هم نیست.
به نظر میرسد که یک مصالحه خیلی مؤثری بین مانایی (Durability) و کارایی (Performance) وجود دارد.
این قطعاً کمک میکند. اگر درایو حالت جامد (Solid State) داشته باشید، میتوانید بهتر عمل کنید. اگر بگذارید که با هر نوشتنی، fsync کند، البته که میتوانید تضمین کنید که همه دادههایتان قابل بازگشت خواهد بود اما بعد از آن، باید نگران این باشید که درایو حالت جامد بتواند به هنگام رفتن برق، دادهها را بنویسد. شما باید در مورد همه این چیزهای دیگر نگران باشید.
جزییات نوشتن بایتها بر روی دیسک خیلی مهم نیست، اگر واقعاً بخواهید نگران این چیزها باشید، متوجه خواهید شد که اغلب کامپیوترهایی که هماکنون موجود هستند، ابزارهای ذخیرهسازی کاملاً مطمئنی نیستند. حتی در مورد درایوهای حالت جامد مقالهای چند ماه پیش منتشر شد که نشان میداد تنها درایوهای حالت جامد اینتل هستند که واقعاً نسبت به رفتن برق، ایمن هستند و دیگر درایوهای حالت جامد، از دیگر شرکتها، وقتی با خرابی دادهها مواجه میشوند، هر چند که میگویند که میتوانید سیم برق را بکشید و همه دادهها همانطور که نوشته شده بودند موجود خواهند بود اما دروغ میگویند و گاهی صدمههای غیر قابل جبرانی بر روی سلولهای درایو ایجاد میشود.
دوست دارم بحث را به گفتگو درباره Redis از دیدگاه برنامهنویسی ببرم. پشتیبانی برای کلاینت Redis در زبانهای مختلف تا چه حد گسترده است؟
آخرین باری که چک کردم بیش از ۳۰ زبان پوشش داده شده بودند. همینطور ابزارهای سطح بالای تقریباً بیشماری برای آن وجود دارند. اغلب زبانهایی که کلاینت دارند، چندین عدد از آن را دارند. مثلاً برای زبان Go تعداد ۸ تا یا بیشتر کلاینت Redis وجود دارد که به نوعی غیرعاقلانه است. جاوا هم چندتایی دارد. پشتیبانی از زبانها کاملاً گسترده است و کمابیش تمامی زبانهای اصلی و خیلی از زبانهایی که به نظر من فرعی هستند را نیز شامل میشود.
همین طور خود پروتکل آنچنان بد پیادهسازی نشده است. میزان زیادی از پروتکل از نسخه ۱ تا به حال که ۲٫۸ هستیم و نزدیک به نسخه ۳٫۰ هستیم، تغییر یافته است اما اگر قبلاً کار پیادهسازی پروتکلهای ارتباطی را انجام داده باشید، پیادهسازی این پروتکل سخت نیست.
آیا عموماً افراد چیزهایی به مانند نگاشتگرهای ORM یا نوعی پل (Bridge) از ساختار دادههای زبانهای برنامهنویسی به ساختار دادههای Redis میسازند یا اینکه لزوماً به چنین چیزهایی نیاز پیدا نمیکنند؟
در واقع به کاربردشان بستگی دارد. افراد زیادی وجود دارند که از Redis بدون استفاده از هیچ نوع نگاشتگر اشیائی استفاده میکنند. آنها از Redis برای ساختن تابلوهای ویدئویی، بازیهای ویدئویی، ذخیرهسازی کوکیها و موارد مختلف دیگری استفاده میکنند که لزوماً به چنین مواردی نیاز ندارد.
اغلب کاربردهایی که من داشتهام تا همین اواخر، تنها مربوط به ساختارهای داده لاگها بوده است. اخیراً که واقعاً نیاز به مدل کردن دادهها پیدا کردم -یعنی نیاز به چیزی که مشابه با نگاشتگرهای اشیاء به روابط باشد که برای پایگاههای رابطهای موجود هستند- دنبال یک نگاشتگر اشیاء برای Redis گشتم اما از امکانات و عملکردها و روش کار API هایی که یافتم، خوشم نیامد، بنابراین API خودم را ساختم. راستش من از API خودم خیلی استفاده نکردهام و در واقع، برخی جاها برای کارهای به نسبت کوچک از آن استفاده کردهام.
شاید وقتی بخواهم که یک برنامه با مفاهیم کامل بنویسم، از آن به عنوان تنها منبع ذخیرهسازی دادهها استفاده کنم یعنی تنها از نگاشتگر Redis برای Pyphon خودم استفاده کنم. چنین کاری عموماً ظرف ۵ دقیقه آغاز میشود چرا که در آن هیچ نگرانی در مورد تنظیمات پایگاه داده، برقرار کردن کاربران، مقداردهی اولیه اشیاء و … وجود ندارد، فقط رشته اتصال (Connection String) را باید مقداردهی کنید و بعد کم و بیش همان طوری که انتظار دارید رفتار میکند.
اما نگاشتگرهای اشیاء مختلفی که برای Redis وجود دارند، ویژگیهای کاملاً متفاوتی دارند. برخی تنها، میگویند که شما یک جدول درهمسازی در Redis میگیرید و میخواهید دادهها را به آن داخل و یا از آن خارج کنید و سطح انتزاعی که برای شما فراهم میکنند همین مقدار است. برخی دیگر، با فراهم کردن انتزاعهایی برای دستکاری مجموعهها، پیشرفتهایی حاصل کردهاند. بنابراین، ویژگیهایی که بدست میآورید کاملاً به زبان برنامهنویسی و کتابخانه مورد استفادهتان بستگی دارد اما خیلی از این کتابخانهها وجود دارند. من حداقل ۴ تا از آنها را در Pyphon میشناسم و ۳ تا هم در Ruby دیدهام، میدانم که چندتایی هم در جاوا وجود دارد هرچند که وقتی با افرادی که جاوا کار میکردند صحبت کردم آنها، ناراضی بودند که همه ویژگیهایی که برخی کتابخانههای دیگر دارند را در اختیار ندارند.
من میخواهم در مورد مباحث زیرساختی و موارد مربوط به کارایی صحبت کنیم. افراد، چه حجم حافظهای را میتوانند در یک نمونه Redis تخصیص دهی کنند و چه مقدار رایج است؟
اگر از نسخه ۳۲ بیتی Redis استفاده میکنید، بسته به سیستمعاملتان ۲ یا ۳ گیگابایت میگیرید و با نسخه ۶۴ بیتی که امروزه خیلی رایج است میتوانید به همان مقدار حافظه ماشینتان، حافظه داشته باشید. من به شخصه، در زمانی از سرورهای Redis استفاده کردهام که بزرگترین نمونهای که در آمازون وجود داشت، ۷۴ گیگابایت حافظه داشت. من از آنها استفاده کردم، از سرورهایی که در آن زمان، ۴۰ یا ۵۰ گیگابایتی بودند. اینکه تا چه حد بزرگ میتواند مجاز باشد خیلی بستگی به این دارد که چه نوع عملیاتهایی انجام میدهید. خاصه اگر، عملیاتهای پرهزینهای انجام میدهید، مثلاً اشتراک بین مجموعههای بزرگی را محاسبه میکنید، در این حالت شاید استفاده از یک سرور بزرگ Redis خیلی منطقی نباشد زیرا در اینصورت کلاینتهای خیلی زیادی خواهید داشت که همگی تلاش میکنند دادههای یکسانی را دستکاری کنند و میتوانید به محدودیت CPU بخورید زیرا همانطور که میدانید Redis تک نخی (Single Thread) است. اما اگر بیشترِ کاری که انجام میدهید، این است که از آن به عنوان یک بستر ذخیرهسازی استفاده میکنید، [استفاده از یک سرور میتواند منطقی باشد.]
آن نمونههای بسیار بزرگ Redis که پیشتر در مورد آن صحبت کردیم، برای بستر تحلیلهای توییتر بودند، در این حالت گرچه حجم زیادی از عملیاتهای خواندن و نوشتن وجود داشت، هیچکدام از آن عملیاتها پرهزینه نبودند و ما میتوانستیم با قطعیت ۵۰ هزار عملیات خواندن و نوشتن در ثانیه را بصورت مداوم، حفظ کنیم. هیچکدام از آنها پرهزینه نبودند بنابراین میتوانستند با ۲۰ تا ۳۰% [توان] یک CPU اجرا شوند که برای ما عالی است. ما میتوانیم I/O های سنگین داشته باشیم و حجم عظیمی از دادهها را ذخیره کنیم و این خیلی خوب است. من مطمئنم که در حال حاضر هیچ چیز دیگری نمیتواند این کار را بکند.
آن طوری که من متوجه شدم اگر یک سرور Redis راه بیاندازم اگر بخواهم حافظه بیشتری اضافه کنم باید به تعداد عملیاتها و CPU نگاه کنم و ببینم در چه نقطهای، CPU به گلوگاه سیستم تبدیل میشود زیرا از آنجایی که Redis تک نخه است، نمیتوانم CPU بیشتری به سرور بیافزایم.
قطعاً همین طور است. عملیاتهایتان را چک میکنید که آیا میتواند انجام شود یا خیر. این بدان معناست که غیرممکن و غیرمنطقی نیست اگر چندین نمونه Redis را بر روی یک ماشین فیزیکی یا ماشین مجازی اجرا کنیم. سربار حافظهای که هر نمونه Redis دارد تنها در حد چند مگابایت است. در واقع گروههایی از افراد وجود دارند که اجرای چندین پروسس Redis بر روی یک سرور منفرد را تبلیغ میکنند که هر پروسس بر روی یک پورت مجزا شنود (Listen)کند و دادهها بر روی آنها شکسته شده باشد. بعداً اگر واقعاً نیاز داشتید که بیشتر رشد کنید، مثلاً فرض کنید که شما ۴ پروسس Redis بر روی یک ماشین اجرا کردهاید و نیاز دارید که رشد کنید، آن موقع میتوانید از برخی تکنیکهای رونوشتبرداری استفاده کنید تا دادهها را به یک سرور دیگر مهاجرت دهید و تعداد پروسسهای Redis را در صورت لزوم از ۴ به ۲ و از ۲ به ۱ کاهش دهید. برخیها حتی توصیه کردهاند که با ۶۴ سرور Redis بر روی هر ماشین آغاز کنید، من مطمئن نیستم که بخواهم چنین کاری بکنم. من به شخصه با روش آغاز کردن از یک سرور منفرد و بعداً بخش کردن آن، خیلی موفق بودهام. به این بستگی دارد که چطور دوست دارید کار کنید، آیا دوست دارید از قبل کار را انجام دهید یا بعداً انجامش دهید.
اگر برنامهای با تعداد زیادی کلید داشته باشم، پیچیدگی الگوریتمی جستجوی کلید در Redis چقدر است و چطور برای تعداد عظیم کلیدها مقیاس میپذیرد؟
از O(1) است. جدول اصلی Redis برای جستجوی کلیدها، یک جدول درهمسازی (Hash Table) است بنابراین کد درهمسازی را محاسبه میکنید، در جدول درهمسازی نگاه میکنید و از آن جایی که Redis از باکتها و زنجیره لیستهای پیوندی استفاده میکند، بسته به اینکه چقدر جدول درهمسازیتان پر شده باشد، مجبور خواهید بود که چندین آیتم را بگردید؛ Redis بصورت خودکار اندازه جدول درهمسازیاش را تغییر میدهد بنابراین، این امکان وجود دارد که مجبور شوید دو مدخل، یکی در جدول درهمسازی کنونی و دیگری در جدول درهمسازی قبلی را بگردید اما با این وجود خیلی سریع است. در واقع، مشکل اصلی کارایی در Redis که باعث میشود در یک ماشین با ابعاد منطقی نتوانیم از ۱۰۰ هزار و ۲۰۰ هزار عملیات در ثانیه به ابعاد یک میلیون و دهها میلیون عملیات برسیم، مربوط به I/O شبکه است که همان چیزی است که مانع رسیدن دیگر سیستمها هم [به چنین نرخهایی] میشود. بنابراین در حالت کلی، Redis عموماً بیش از آنکه محدود به سرعت جستجوی جداول درهمسازی بشود، چندین ده برابر بیشتر از آن، محدود به نرخ I/O شبکه میشود.
ما پیش از این در مورد ایده ارشد/کارگزار (Master/Slave) و یک لحظه پیش هم در مورد اجرای چندین نمونه پروسس Redis در یک محفظه صحبت کردیم. برای مقیاس دادن به Redis چندین گزینه وجود دارد. ممکن است به ما بگویید آنها کدامند؟
قطعاً. سادهترین راه مقیاس دادن به Redis این است که Redis را بر روی ماشین بزرگتری که حافظه بیشتری دارد قرار دهیم. این کار رایجی است که اغلب افراد تا زمانی که نتوانند هزینه چنین ماشین بزرگی را متحمل شوند یا نتوانند آن را تهیه کنند، آن را انجام میدهند.
روش دوم این است که اگر نوشتنهای سنگینی ندارید و نوشتنهای معمولی دارید اما در عوض، خواندنهای خیلی زیادی دارید، حقه رایج این است که کارگزارهای خواندن (Read Slaves) اضافه کنید. Redis مفهومی با عنوان رونوشتبرداری ارشد/کارگزار دارد. ظاهراً به هر تعدادی که بخواهید میتوانید کارگزار اضافه کنید اما بعد از اینکه چند تا اضافه کردید، با محدودیت پهنای باند خروجی گره ارشد مواجه میشوید. مثلاً اگر یک ارشد و ۵۰ تا کارگزار داشته باشید، هر نوشتنی بر روی ارشد، باید ۵۰ بار رونوشتبرداری شود که واقعاً در عمل، شدنی نیست اما از آنجایی که کارگزارها خودشان میتوانند کارگزارهای دیگری داشته باشند، میتوانید یک ساختار درختی برای این کار بنا کنید. در واقع هفته گذشته یکی از کاربران از طریق درگاه نامههای Redis، درخواست همچنین سناریویی داشت. بنابراین برای خواندنها میتوانید از کارگزارها استفاده کنید.
اما اگر نیاز دارید که حجم زیادی از نوشتنها را رسیدگی کنید، تنها کاری که میتوانید بکنید این است که دادههایتان را تفکیک کنید (Shard) به این معنا که چندین سرور Redis اجرا کنید و از تفکیک کلیدها (Key Sharding) یا یک تفکیک دادههای مرکزی برای بخش کردن کلیدهایتان از یک سرور Redis منفرد به چندین سرور Redis استفاده کنید. در واقع ابزاری به نام twemproxy وجود دارد که توییتر توسعه داده است تا به عنوان پروکسیِ فراخوانیهای پایگاه دادههای تفکیک شده Redis و همینطور فراخوانیهای Memcached بکار رود. بنابراین اگر میخواهید که کلاینتهایتان نگران دادههای تفکیک شده نباشند، میتوانید مجموعهای از پروکسیها داشته باشید که این تفکیکشدنها و مدیریت اتصالات را رسیدگی کنند و تنها با آن پروکسیها در ارتباط باشید.
وقتی من کار تفکیک کردن دادهها را آغاز میکنم، آیا این باعث برهم خوردن تعادل میشود به این معنا که برخی از عملیاتها دیگر در سرور قابل انجام نخواهد بود، مثلاً برخی اشتراک گرفتنهای مجموعهای و عملیاتهای پیچیده چندمقداری بین بخشهای تفکیکشده (Shards) مختلف…؟
بله، متأسفانه وقتی دادهها را تفکیک میکنید، دیگر نمیتوانید دستورهای چندکلیدهای که شامل کلیدهایی از بخشهای تفکیکشده مختلف هستند را انجام دهید چرا که آنها بر روی سرورهای Redis مختلفی قرار گرفتهاند. در واقع Redis مفهومی به عنوان مهاجرت دادهها ندارد، وقتی شما به این ترتیب، دادهها را تفکیک میکنید Redis حتی نمیداند که بخشی از دادهها و نه همه آنها را دارد.
روش دیگری برای مقیاس کردن با عنوان کلاستر Redis مطرح است که الان [سپتامبر ۲۰۱۴] در اولویت کاری سالواتوره قرار گرفته است که خودکارسازیهای بیشتری از قبیل تفکیکسازیهای خودکار، رونوشتبرداریهای خودکار و ویژگیهای دیگری ارائه میکند اما همچنان در ارتباط با عملیاتهای چندکلیدی این محدودیت را دارد که باید همه کلیدها بر روی یک سرور باشند گرچه به شما اجازه میدهد که تعیین کنید که چطور میخواهید دادههایتان را تفکیک کنید بنابراین میتوانید دادههایی که باید بر روی یک ماشین قرار گیرند را اجبار کنید که روی یک ماشین قرار بگیرند به عنوان مثال اگر کوکیهای مربوط به کاربران را ذخیره میکنید میتوانید به Redis بگویید که همه این نوع دادهها باید بر روی یک ماشین قرار بگیرند (در نسخه ۳٫۰ از Redis که در سپتامبر ۲۰۱۵ عرضه شد، امکان کلاستر اضافه شد -مترجم).
در ارتباط با ارشد/کارگزار پیشتر اشاره کردید که از همان پروسس منشعبشدهای بهره میگیرد که BGSAVE استفاده میکند، این چه تأثیری بر روی میزانی از حافظه که میتواند به Redis تخصیص یابد خواهد داشت؟ منظورم این است که آیا باید آگاه باشم کهfork چه سهمی از حافظه را نیاز دارد؟
قطعاً در این مورد نگرانی داریم. یک توسعهدهنده یا معمار بیش از حد محتاط خواهد گفت که Redis باید تنها نیمی از حافظهی در دسترس ماشین را استفاده کند و تنها در این صورت است که هنگام گرفتن تصویر مقطعی یا رونوشتبرداری یا در رویه رایج ذخیرهسازی دادهها، میتوانیم از عهده حجم عظیمی از نوشتن برآییم زیرا در این صورت میتوانیم بالقوه هنگام fork کردن، حجم حافظهای که Redis استفاده میکند را دو برابر کنیم خصوصاً اگر حجم دادههایی که در Redis است به سرعت در حال رشد باشد.
اما آنچه Redis انجام میدهد در واقع این است که حتی از طریق فایلهای لاگ به اطلاعتان میرساند که چه مقدار حافظه اضافی توسط پروسسهای فرزند استفاده شدهاند. بنابراین اگر به لاگهایتان توجه کنید میتوانید کشف کنید که چه مقدار حافظه برای آن عملیاتهایی که میخواهید انجام دهید، مورد نیاز است. بنابراین در واقع میتوانید مقادیر مناسب میزان حافظهای که در Redis ذخیره میکنید و میزان حافظهای که برای گرفتن تصویر مقطعی برای کارگزار یا برای ذخیره دادههایتان دارید در مقایسه با مقدار حافظه در دسترس ماشین را بیابید. این [روش] ایدهآل نیست اما میتواند ایدهی خوبی به شما بدهد.
من در خلال برخی بحثهایی که در درگاه نامههای Redis میشد، کمی در مورد کلاستر و نگهبان (Sentinel) در Redis مطالعه کردم که پروژههای مستقلی هستند که برخی اهداف مشترکی هم دارند. به نظر من، Redis در کارهایی که میتواند انجام دهد، یک راه حل درخشان است و خیلی از آنها مبتنی بر این نکته است که Redis تک نخی است و در یک آدرس منفرد قرار میگیرد اما وقتی تلاش میکنید که آن را مقیاس دهید تا یک سیستم توزیعشده شود، در این حال، بیشتر در راستای ذخیرهگاههای از نوع Dynamo حرکت میکنید که واقعاً یک موجود دیگری است و نیاز به یک خط فکری مجزایی دارد تا بتوانید کارهایی که آنها خوب انجام میدهند، را انجام دهید. شما چطور به آن پاسخ میدهید؟ آیا در این مورد منصف بودهام؟
فکر میکنم شاید یک کمی بیانصافی کردهاید. عملیاتهای زیادی هست که Dynamo یا بطور کلی سرورهای داده مشابه BigTable ارائه میکنند. یکی از مهمترین آنها این است که میتوانید حجم عظیمی از دادهها را در آن قرار دهید اما در اساس این سیستمها این طور کار میکنند که دادههایتان را جایی قرار میدهند که بصورت اختیاری میتواند ایندکس هم داشته باشد، و وقتی شما پرسوجویی انجام میدهید آن پرسوجو بر روی دادههای موردنظرتان از هر ماشینی که باشد اجرا میشود. به نوعی مانند MapReduce میماند، در آنجا در واقع چندین پایگاه داده وجود دارد که هرکدام دادهها را در جایی ذخیره کردهاند و اگر پرسوجویی بکنید یک MapReduce انجام میدهید. در مقابل، Redis لزوماً برای این نیست که چندین ترابایت داده را ذخیره کند، لزوماً برای این نیست که راهحل آرشیو کردن دادههای ۲۰ ساله شما باشد، برای این نیست که تحلیلهای آماری بر روی ۲۰ میلیون ویدئوی مشاهده شده در ۲ هفته پیش را انجام دهد، برای این منظورها نیست. بلکه برای ذخیرهسازی آن دسته از دادههایی است که اولاً میخواهید دسترسی سریع به آنها داشته باشید و دوماً دسترسیهای به شکلهای ساختیافتهای داشته باشید که هم سریع باشد، چون دادهها را بر اساس کلید یا ایندکسهایی، در حافظه Redis قرار دادهاید. برای این کار است، شما عموماً در Redis نمیگویید که «تعداد آیتمهایی را بشمار که فلان ویژگی را دارند و آنها را بر اساس فلان چیز گروه کن!» اینها البته اعمال رایجی در Dynamo یا BigTable هستند.
بنابراین نوع دادههایی که ذخیره میکنید و مشخصههای دسترسی به دادهها کاملاً متفاوت است. به سئوال اصلی برگردیم که آیا [Redis] پیچیدهتر نشده است؟ قطعاً این طور است. دیگر این طور نیست که بتوانید هر چیزی را بررسی کنید یا هر چیزی را مرتب کنید یا به هر نوع دادهای به هر روشی که خواستید دسترسی یابید. اما خیلی از این محدودیتهای همگامسازی در پایگاه دادههای رابطهای هم که در آنها مجبورید که تفکیک دادهها (Shard) داشته باشید نیز وجود دارد.
قطعاً. در حقیقت من به یک راه حل کلاستر کردن MySQL فکر میکنم که در آن دیگر نمیتوانید الحاق (Join) داشته باشید.
همین طور است، پکیج مشابهی برای PostgreSQL هم وجود دارد، واضح است که وقتی یک پرسوجویی را بر روی هر کدام از دادههای تفکیکشده انجام میدهید، مجبورید که نتایج را خودتان ادغام کنید.
شما در مورد یک پروژه متنباز صحبت میکنید. آنجا چه تعداد Commiter دارید؟ اجتماعتان تا چه مقدار فعال است؟ چه منابعی آنجا وجود دارد؟
Redis یک نویسنده اصلی دارد که سالواتوره سنفیلیپو است که مبدع و نویسنده اولیه آن است. Redis ایده او بوده است و تقریباً همه آن را توسعه داده است. اما قطعاً الان مشارکتکنندههای بیشتری وجود دارند. هر چه زمان میگذرد افراد بیشتری وصله کد (Patch) میدهند. من به شخصه، فقط یک وصله کد در Redis دارم -هر چند که خیلی وقت است از آنها استفاده نکردهام- که مربوط به ویژگی است که به شما اجازه میدهد ZUNIONSTORE و ZINTERSTORE را بر روی مجموعهها هم انجام دهید.
اگر در مخزن Redis چک کنید میبینید که سالواتوره، نسبت به نفر دوم که پیترن نوردیس است، ۱۰ برابر خط کد بیشتری کامیت کرده است. نوردیس به عنوان سالواتوره شماره ۲، حداقل در گذشته بصورت پارهوقت در VMWare و آزمایشگاههای توییتر مشغول بوده است. اما همانطور که میبینید، به تدریج غیر فعال شده و به نظر میرسد که افراد دیگری فعالتر شدهاند. اما بیشتر سالواتوره هست و این نرمافزار، بیشتر تک نفره بوده است و خیلی جالب است که این کار چقدر ساده انجام شده است.
یک درگاه نامه (Mailing List) هم هست که شما در آن خیلی فعال هستید.
بله، درگاه نامه Redis-db است. ابتدای کار، کمی بعد از آنکه شروع به استفاده از Redis کردم، به آن پیوستم تا سئوالاتی بپرسم و یا پیشنهاد ویژگی (Feature Request) داشته باشم. بعد افراد سئوالاتی کردند و من جوابشان را دادم و تا به حال به پاسخ دادن ادامه دادهام. افراد به پرسیدن سئوالات جالب ادامه میدهند و من به یادگیری بیشتر درباره Redis و پاسخ دادن ادامه میدهم چون برایم جالب است. من این کار را در درگاههای نامه دیگری در گذشته انجام دادهام اما حداقل الان فقط در درگاه نامه Redis هستم.
در واقع به همین طریق بوده است که در نهایت یک کتاب نوشتم. وقتی ناشر (Manning) دید که من چقدر در درگاه نامه زرنگ هستم، با خود گفت، این فرد احتمالاً کاری که میکند را بلد است و با من تماس گرفتند و مذاکراتی داشتیم و در نهایت من کتاب را کمابیش بر اساس مشکلاتی نوشتم که افراد در درگاه نامه Redis مطرح میکردند. مشکلات متداول و راهحلهای متداول و روشهای متداول برخورد با مشکلات کاملاً رایج [را شامل میشد].
بغیر از کتابتان، آیا جای دیگری هست که بخواهید به شنوندگانی که میخواهند بیشتر کار شما را پیگیری کند، معرفی کنید. چون شما در درگاه نامه خیلی فعال هستید.
بغیر از آنها، من بصورت دورهای، پستهایی در وبلاگ میگذارم. گاهی درباره Redis صحبت میکنم، گاهی درباره Python صحبت میکنم، گاهی هم درباره چیزهای دیگر صحبت میکنم. اخیراً پستی در مورد تجربیاتم درباره Soylent نوشتم که یک محصول غذایی است، در واقع یک پودر مغذی است.
جوزیا کارلسون، خیلی متشکرم که با SE Radio گفتگو کردید.
باعث خوشبختی من است.