কম্পিউটার টিউটোরিয়াল

Redis Streams সম্পর্কে সাধারণ ভুল ধারণা—আপনার যা জানা দরকার

Streams কে ভুলভাবে বর্ণনা করার দায় আমারও আছে—আমি এটিকে "একটি সিরিজ অফ হ্যাশমাপ-লাইক এলিমেন্টস, টাইম অনুযায়ী অর্ডার্ড, একটি সিঙ্গেল কী আন্ডারে" বলে ডিফাইন করেছিলাম। এটি ভুল। শেষের টুকরো টাইম ও কী সম্পর্কে ঠিক আছে, কিন্তু প্রথম অংশটি সম্পূর্ণ ভুল।

দেখা যাক কেন Streams গুলমোক্ষ হয় এবং সে আসলে কীভাবে আচরণ করে। আমরা এই গুলমোক্ষের ভালো ও মন্দ দিকগুলো মূল্যায়ন করব এবং কীভাবে এটি আপনার সফটওয়্যারকে প্রভাবিত করতে পারে। শেষে, আমরা কিছু নন-অবিয়াস প্যাটার্ন দেখব যা Redis Streams-এর অল্প পরিচিত বৈশিষ্ট্যগুলোর সুবিধা নেয়।

পটভূমি

প্রথমে XADD কমান্ডটি দেখি, কারণ গুলমোক্ষটি এখান থেকেই শুরু হয়। অফিসিয়াল redis.io ডকুমেন্টেশনে কমান্ড সিগনেচারটি দেখতে এভাবেই:

XADD key ID field value [field value ...]

key স্বব্যাখ্যামূলক। id হলো নতুন এন্ট্রির টাইমস্ট্যাম্প/সিকোয়েন্স কম্বো, কিন্তু বাস্তবে এটি প্রায়শই * হয় অটো জেনারেশনের জন্য। এই বিভ্রান্তির মূল原因 field এবং value। HSET কমান্ডের সিগনেচার দেখলে খুব মিলते জুলতে প্যাটার্ন পাবেন:

HSET key field value [field value ...]

দুই কমান্ডের সিগনেচার শুধু একটি আর্গুমেন্টের পার্থক্য রাখে এবং XADD-এ সেটি প্রায়শই একটি একক *। দেখতে খুব একই, একই টার্ম ব্যবহার, তো একই নাকি?

ঠিক আছে। সমস্যাটি চালু রাখতে, Redis কে একদম বাদ দিয়ে প্রোগ্রামিং ল্যাঙ্গুয়েজগুলো ফিল্ড-ভ্যালু পেয়ার কীভাবে হ্যান্ডেল করে সেটা দেখি। অধিকাংশ ক্ষেত্রে, ভাষা যেকোনো হোক, ফিল্ড-ভ্যালু আর্টিকুলেট করার সবচেয়ে প্রতিনিধিত্বমূলক উপায় হলো ফিল্ডের একটি সেট (রিপিট নেই) যা ভ্যালুর সাথে কোরিলেটেড—কিছু ভাষা ফিল্ডের ক্রম রক্ষা করে আর কিছু না করে। একটু গভীরে যাই একটি ছোট ক্রস-ল্যাঙ্গুয়েজ তুলনার মাধ্যমে:

এটি খুব সুন্দরভাবে Redis হ্যাশের সাথে ম্যাপ হয়—এই সব হ্যাশের বৈশিষ্ট্য আর্টিকুলেট করতে পারে, যা আনঅর্ডার্ড এবং রিপিট নেই। PHP অ্যারে, Python dict, এবং JavaScript maps ফিল্ড অর্ডার ডিফাইন করতে পারে, কিন্তু আপনি যদি Redis-এ হ্যাশের সাথে কাজ করেন, তবে তা মাইনে করে না, আপনাকে শুধু জানতে হবে যে অ্যাপ্লিকেশন লেভেলে এই অর্ডার বা আশা করা যায় না।

অধিকাংশ মানুষের স্বাভাবিক নিষ্কর্ষ হয়েছে যেহেতু HSET এবং XADD কমান্ড সিগনেচারগুলোর একটি কোরিলেশন আছে, তাহলে রিটার্ন-এও একই কোরিলেশন থাকবে। অবশ্য, প্রোটোকল লেভেলে RESP2-এ, দুটোই ইন্টারলিভড RESP অ্যারে হিসেবে রিটার্ন হয়। এটি RESP3-এর প্রাথমিক ভার্সনগুলোতে অব্যাহত থাকে যেখানে HGETALL এবং XREAD-এর রেসপন্স দুটোই ম্যাপ ছিল (বादে আরো)।

একটি বাগ আমার মতামত পরিবর্তন করল

সাধারণত আমি জাভাস্ক্রিপ্ট এবং মাঝেমধ্যে Python-এ কোড করি। Redis সম্পর্কে কমিউনিকেশন করার কারণে, আমাকে যতটা সম্ভব মানুষের কাছে পৌঁছাতে হয় এবং এই দুটি ভাষা খুব ভালোভাবে বোঝা যায়, তাই যেকোনো ভাষায় ডেমো বা স্যাম্পল কোড ডেভেলপার ওয়ার্ল্ডের বড় পorsion কে বুঝতে সাহায্য করে। সম্প্রতি আমার একটি PHP কনফারেন্সে কথা বলার সুযোগ পেলাম এবং কিছু বিদ্যমান Python কোড PHP-তে কনভার্ট করতে হলো। আমি প্রায় ২০ বছর ধরে PHP ব্যবহার করেছি অন-অফ, কিন্তু এটি কখনো আমার প্যাশন ছিল না। নির্দিষ্ট ডেমো mod_php স্টাইল এক্সিকিউশনে ভালো ফিট করত না, তাই আমি swoole এবং এর কো-রুটিন এক্সিকিউশন ব্যবহার করলাম (সাইড নোট হিসেবে, জাভাস্ক্রিপ্ট ওয়ার্ল্ডে কমফরটেবল থাকার কারণে, swoole PHP কে আমার জন্য খুব খুব ফ্যামিলিয়ার করে দিলো)। লাইব্রেরি একটু পুরানো ছিল এবং রসদার Redis কমান্ড পাঠাতে হতো কোনো রিয়েল ক্লায়েন্ট লাইব্রেরি সহায়তা ছাড়া রিটার্ন ডিকোড করার জন্য হাই লেভেলে। সাধারণত, রসদার Redis কমান্ড পাঠানো এবং রেজাল্ট ডিকোড করা একটু বেশি কন্ট্রোল দেয় এবং কঠিন নয়।

তো, XADD-এ কমান্ড পাঠাতে সময়, আমি ফিল্ড-ভ্যালু পোर्शन বিল্ড করছিলাম এবং একটি অফ-বাই-ওয়ান এরর ছিল (PHP-তে বছর পর বছর ফিরে যাওয়ার দায়)। এর ফলে আমি অনিচ্ছাক্রমে নিচের মতো কিছু পাঠিয়ে দিলাম:

XADD myKey * field0 value1 field0 value2 field2 value3

কোরিলেটেড ফিল্ড ও ভ্যালু পাঠানোর বদলে (field0 to value0 এবং так дале)।

বादে কোডে, আমি XREAD-এর রেজাল্ট একটি বিদ্যমান PHP অ্যারে (যা অ্যাসোসিয়েটিভ) তে একটি লুপে রাখছিলাম, প্রতিটি ফিল্ডকে কী হিসেবে এবং প্রতিটি ভ্যালুকে ভ্যালু হিসেবে অ্যাসাইন করছিলাম এবং ইতিমধ্যে সেট করা কিছুর স্কিপ করছিলাম। তাই আমি একটি অ্যারে দিয়ে শুরু করলাম এরকম:

array(1) { ["foo"]=> string(3) "bar" }

এবং শেষ হলো এরকম অ্যারে:

array(3) { ["foo"]=> string(3) "bar" ["field0"]=> string(6) "value1" ["field2"]=> string(6) "value3" }

আমি বুঝতে পারলাম না কীভাবে এটা সম্ভব। আমি দ্রুত বাগটি খুঁজে পেলাম কেন value1 অ্যাসাইন হয়েছে field0-এ (উপরোক্ত অফ-বাই-ওয়ান এরর XADD-এ), কিন্তু কেন field0 সেট হয়নি value2-এ? HSET-এ, ফিল্ড যোগ করার এই আচরণ মূলত আপসার্ট—ফিল্ড থাকলে আপডেট করো, না থাকলে ফিল্ড যোগ করো এবং ভ্যালু সেট করো।

MONITOR লগ চেক করে এবং ভ্যালু রিপ্লে করে, আমি XREAD রান করলাম নিচের মতো:

> XREAD STREAMS myKey 0 1) 1) "myKey" 2) 1) 1) "1592409586907-0" 2) 1) "field0" 2) "value1" 3) "field0" 4) "value2" 5) "field2" 6) "value3"

রিপিট উপস্থিত এবং রেকর্ড হয়েছে, আপসার্টেড নয়; অতিরিক্তভাবে ক্রম সংরক্ষিত রয়েছে। এটি হ্যাশের মতো কিছু নয়!

JSON ব্যবহার করে এটিকে অনুমান করলে, আমি মনে করেছিলাম স্ট্রিম এন্ট্রি দেখতে এরকম:

{ "field1" : "value", "field2" : "value", "field3" : "value" }

কিন্তু আসলে সেগুলো দেখতে এরকম:

[ ["field1", "value"], ["field2", "value"], ["field3", "value"] ]

এটি কী বোঝায়?

ভালো খবর

আপনার কোড যদি Streams-এর সাথে কাজ করে এবং আপনি অনুমান করেন এন্ট্রিগুলো হ্যাশ ম্যাপের মতো, তবে আপনি সম্ভবত ঠিক আছেন। আপনাকে শুধু ডুপ্লিকেট ফিল্ড যোগ করার সম্ভাব্য বাগের ওপর নজর রাখতে হবে, কারণ সেগুলো একটি নির্দিষ্ট অ্যাপ্লিকেশনে আপনার আশা অনুযায়ী আচরণ নাও করতে পারে। এটি সব পরিস্থিতি বা ক্লায়েন্ট লাইব্রেরিতে প্রযোজ্য নাও হতে পারে, কিন্তু একটি ভালো অভ্যাস হবে এমন একটি ডেটা স্ট্রাকচার सप্লাই করা যা রিপিট অনুমতি না দেয় (উপরোক্ত দেখুন) এবং আর্গুমেন্টে সিরিয়ালাইজ করা XADD-এ সাপ্লাই করার সময়। ইউনিক ফিল্ড ইন এবং ইউনিক ফিল্ড আউট।

মন্দ খবর

সব ক্লায়েন্ট লাইব্রেরি এবং টুল সেটা সঠিক করে না। আপেক্ষিকভাবে লো-লেভেল ক্লায়েন্ট লাইব্রেরি (non-exhaustive: node_redis, hiredis) Redis-এর আউটপুটকে ল্যাঙ্গুয়েজ কনস্ট্রাক্টে রূপান্তর করার ক্ষেত্রে কিছুই করে না। অন্যান্য হাই-লেভেল লাইব্রেরি করে Redis-এর আসল রিটার্নকে ল্যাঙ্গুয়েজ কনস্ট্রাক্টে অ্যাবস্ট্রাক্ট করে—আপনার পছন্দের লাইব্রেরি এটি করছে কিনা চেক করুন এবং করলে একটি ইস্যু ফাইল করুন। কিছু হাই-লেভেল লাইব্রেরি শুরুর থেকেই সঠিক করলো (stackexchange.redis), তাই সেগুলোর প্রশংসা করা উচিত।

অন্য একটু মন্দ অংশ: আপনি যদি RESP3-এর ভেরি আর্লি অ্যাডপ্টার হন, তবে আপনি XREAD / XREADGROUP-কে RESP3 ম্যাপ টাইপ রিটার্ন করতে পেতে পারেন। অর্লি এপ্রিল পর্যন্ত, ডেভেলপমেন্ট-অন্ডার Redis 6 কনফিউজিংলি স্ট্রিম পড়ার সময় রিপিটসহ ম্যাপ রিটার্ন করত। ঈশ্বরের কৃপায়, এটি সমাধান হয়েছে এবং Redis 6-এর GA ভার্সন—প্রোডাকশনে RESP3 ব্যবহার করার প্রথমবার—সঠিক রিটার্ন XREAD / XREADGROUP-এ শিপ করেছে।

মজার অংশ

যেহেতু আমি ব্যাখ্যা করলাম কীভাবে আপনি সম্ভবত Streams সম্পর্কে ভুল হয়েছেন, চিন্তা করি কীভাবে আপনি এই এতক্ষণ গুলমোক্ষপ্রাপ্ত স্ট্রাকচার থেকে সুবিধা নিতে পারেন।

স্ট্রিম এন্ট্রিতে অর্ডারে সেমান্টিক মিনিং প্রয়োগ করুন

তো, আপনার আসলে এই প্যাটার্নে তিনটি ভেক্টর আছে। ভেক্টর গ্রাফিক্সের পাথ সংরক্ষণ করার কল্পনা করুন। প্রতিটি স্ট্রিম এন্ট্রি হবে একটি ইউনিক পলিগন বা পাথ এবং ফিল্ড ও ভ্যালু হবে কোঅর্ডিনেট। উদাহরণস্বরূপ, এই SVG ফ্র্যাগমেন্টটি নিন:

<polyline points="50,150 50,200 200,200 200,100">

এটি আর্টিকুলেট করা যায়:

> XADD mySVG * 50 150 50 200 200 200 200 100

প্রতিটি অতিরিক্ত শেপ একই কীতে অন্য একটি এন্ট্রি হবে। আপনি যদি Redis Hash-এ এরকম কিছু করার চেষ্টা করেন, তবে আপনার শুধু দুটি কোঅর্ডিনেট থাকবে এবং কোনো অর্ডার গ্যারান্টি নেই। মেনে নেওয়া যাক, আপনি বিটফিল্ডের মতো জিনিস দিয়ে এটি করতে পারেন, কিন্তু লেথ ও কোঅর্ডিনেট সাইজের ফ্লেক্সিবিলিটি খুব বেশি হারাবেন। Streams-এ, আপনি সম্ভবত টাইমস্ট্যাম্পের সাথে কিছু নাটকিয়া করতে পারেন যাতে একটি শেপ সিরিজ টাইমের সাথে আপিয়ার করে।

সিকোয়েন্সড আইটেমের একটি টাইম-অর্ডার্ড সেট তৈরি করুন

এটিতে একটি ছোট হ্যাক লাগে, কিন্তু অনেক ফাংশনালিটি দিতে পারে। কল্পনা করুন আপনি একটি অ্যারে-লাইক ডেটার সিকোয়েন্স রাখছেন। কার্যকরভাবে, একটি অ্যারে অফ অ্যারে—JSON-এ এটিকে চিন্তা করুন এরকম:

[ ["A New Hope", "The Empire Strikes Back", "Return of the Jedi"], ["The Phantom Menace", "Attack of the Clones", "Revenge of the Sith"], ["The Force Awakens", "The Last Jedi", "The Rise of Skywalker"] ]

আপনি এটি একটি সিরিজ স্ট্রিম এন্ট্রি হিসেবে আর্টিকুলেট করতে পারেন একটি ছোট нюআনসসহ: আপনাকে নিশ্চিত করতে হবে আপনার ইনার লিস্টের (সিউডো) এলিমেন্টের সংখ্যা অড্ড না হয়। যদি অড্ড হয়, উপরের মতো, তবে আপনাকে সেটি কীভাবে রেকর্ড করতে হবে—এখানে আমি একটি খালি স্ট্রিং দিয়ে করছি:

> XADD starwars * "A New Hope" "The Empire Strikes Back" "Return of the Jedi" "" "1592427370458-0" > XADD starwars * "The Phantom Menace" "Attack of the Clones" "Revenge of the Sith" "" "1592427393492-0" > XADD starwars * "The Force Awakens" "The Last Jedi" "The Rise of Skywalker" "" "1592427414475-0" > XREAD streams starwars 0 1# "starwars" => 1) 1) "1592427370458-0" 2) 1) "A New Hope" 2) "The Empire Strikes Back" 3) "Return of the Jedi" 4) "" 2) 1) "1592427393492-0" 2) 1) "The Phantom Menace" 2) "Attack of the Clones" 3) "Revenge of the Sith" 4) "" 3) 1) "1592427414475-0" 2) 1) "The Force Awakens" 2) "The Last Jedi" 3) "The Rise of Skywalker" 4) ""

এই প্যাটার্নে আপনি অনেক কিছু পাবেন (মাইনর) খরচে যেকোনো ০-লেথ স্ট্রিং ফিল্টার আউট করতে হবে।

স্ট্রিম পেজিনেশন ক্যাশ হিসেবে

একটি ট্রিকি জিনিস যা খুব দেখা যায় সেটি হলো একটি সাইটে আইটেমের লিস্টিং (ই-কমার্স, মেসেজ বোর্ড, ইত্যাদি)। এটি সাধারণত ক্যাশ করা হয় কিন্তু मैंने লোকেদের সবচেয়ে ভালো মেথড খুঁজে পাওয়ার জন্য চুল খাচ্ছে دیده আছে—আপনি পুরো রেজাল্ট সেট ক্যাশ করবেন সর্টেড সেটের মতো কিছুতে এবং ZRANGE দিয়ে আউটওয়ার্ড পেজিনেট করবেন, নাকি ফুল পেজ স্ট্রিং কীতে স্টোর করবেন? দুভাবেরই মেরিট এবং ডাউনসাইড আছে।

যে কোনোভাবে, Streams আসলে এই কাজে আসে। উদাহরণস্বরূপ, ই-কমার্স লিস্টিং। আপনার একটি সিরিজ আইটেম আছে, প্রতিটির একটি ID। সেগুলো একটি ফিনিট সিরিজে লিস্টেড থাকে যা সাধারণত একটি রিভার্সাল دار (A-Z, Z-A, কম থেকে বেশি, বেশি থেকে কম, হাইএস্ট-টু-লোয়েস্ট রেটিং, লোয়েস্ট-টু-হাইএস্ট রেটিং, ইত্যাদি)।

এই ধরণের ডেটা একটি স্ট্রিমে মডেল করতে, আপনি একটি নির্দিষ্ট "চান্ক" সাইজ নির্ধারণ করবেন এবং সেটি একটি এন্ট্রি করবেন। কেন চান্ক এবং ফুল রেজাল্ট পেজ না এন্ট্রি হিসেবে? কারণ এটি আপনাকে আপনার পেজিনেশনে ভিন্ন আকারের পেজ রাখতে দেয় (যেমন ১০ আইটেম পার পেজ ২টি চান্ক অফ ৫ দিয়ে বানানো যাবে, در ২৫ পার পেজ ৫টি চান্ক অফ ৫ দিয়ে)। প্রতিটি এন্ট্রি ফিল্ড থাকবে যা প্রোডাক্ট ID-এ ম্যাপ করে এবং ভ্যালু হবে প্রোডাক্ট ডেটা। এই সরলীকৃত উদাহরণটি দেখুন কৃত্রিমভাবে কম চান্ক সাইজসহ:

Redis Streams সম্পর্কে সাধারণ ভুল ধারণা—আপনার যা জানা দরকার

যখন আপনি ক্যাশড ভ্যালু রিট্রিভ করতে চান, XRANGE COUNT আর্গুমেন্টের সাথে রান করবেন যেটি আপনার ইন্টারফেসের জন্য একটি রেজাল্ট পেজ বানানোর চান্ক সংখ্যা। তাই, যদি আপনি ৪ আইটেমের প্রথম পেজ পেতে চান, রান করুন:

> XRANGE listcache - + COUNT 2 1) 1) "0-1" 2) 1) "123" 2) "{ \"Red Belt\", ... }" 3) "456" 4) "{ \"Yellow Belt\", ... }" 2) 1) "0-2" 2) 1) "789" 2) "{ \"Blue Belt\", ... }" 3) "012" 4) "{ \"Purple Belt\", ... }"

২য় পেজের ৪ আইটেম পেতে, আপনাকে একটি লোয়ার বাউন্ড স্ট্রিম ID ১ দিয়ে ইনক্রিমেন্ট দিতে হবে, আমাদের কেসে, লোয়ার বাউন্ড হবে 0-2।

> XRANGE listcache 0-3 + COUNT 2 1) 1) "0-3" 2) 1) "345" 2) "{ \"Black Belt\", ... }" 3) "678" 4) "{ \"Red Boa\", ... }" 2) 1) "0-4" 2) 1) "901" 2) "{ \"Yellow Boa\", ... }" 3) "234" 4) "{ \"Green Belt\", ... }"

এটি Sorted Sets বা Lists এর চেয়ে কম্পিউটেশনাল কমপ্লেক্সিটি সুবিধা দেয় কারণ এই ইউজ কেসে XRANGE কার্যকরভাবে O(1), কিন্তু কিছু জিনিস মনে রাখতে হবে:

  • XREVRANGE রিভার্সালের জন্য ব্যবহার করা যায়, তবে এটি শুধু "চান্ক"-গুলোর অর্ডার রিভার্স করবে। প্রতিটি চান্কের ভেতর আপনাকে অ্যাপ্লিকেশন লজিকে অর্ডার রিভার্স করতে হবে যা তুলনামূলকভাবে ত্রিবিয়াল।
  • লিস্টের ভিন্ন অংশে সিক করা "ফ্রি" যদি আপনি ম্যানুয়ালি স্ট্রিম ID লিনিয়ারলি সেট করেন, তাহলে চান্ক ১ হলো স্ট্রিম ID 0-1, চান্ক ২ হলো 0-2 এবং így дале। মনে রাখবেন আপনাকে ১ দিয়ে শুরু করতে হবে ০ দিয়ে না, কারণ আপনি 0-0-এ স্ট্রিম এন্ট্রি যোগ করতে পারবেন না।

যেকোনো কীতে আপনি এক্সপায়ারি ব্যবহার করতে পারেন স্ট্রিম কতক্ষণ থাকবে সেটি ম্যানেজ করতে। কীভাবে এটি করা যায় তার একটি উদাহরণ stream-row-cache-এ আছে।

আশা করি এই পোস্ট আপনাকে কিছু অতিরিক্ত কনটেক্সট দিবে কীভাবে Streams আসলে কাজ করে এবং আপনি কীভাবে আপনার অ্যাপ্লিকেশনে Streams-এর এই অধিকাংশ অজানা বৈশিষ্ট্যগুলোর সুবিধা নিতে পারেন।

  1. আইফোনের ১০টি জরুরি টিপস ও ট্রিক যা আপনি নিশ্চিত জানেন না

  2. আইফোন ক্যাশ ক্লিয়ার করার সঠিক পদ্ধতি এবং স্টোরেজ ফিরে পাওয়া

  3. PUBG সম্পর্কে ১১টি অজানা তথ্য যা প্রতিটি খেলোয়াড় জানা উচিত

  4. ফেসবুকের লুকানো ট্রিকস: প্রতিটি ইউজারের জানা উচিত