কম্পিউটার

আপনি সম্ভবত রেডিস স্ট্রিমগুলি ভুল সম্পর্কে চিন্তা করছেন

আমি ব্যক্তিগতভাবে স্ট্রীমগুলিকে ভুলভাবে বর্ণনা করার জন্য দোষী—আমি এটিকে "একটি কীর অধীনে সময়ের দ্বারা আদেশকৃত হ্যাশম্যাপের মতো উপাদানগুলির একটি সিরিজ" হিসাবে সংজ্ঞায়িত করেছি৷ এটি ভুল . সময় এবং কী সংক্রান্ত শেষ বিট ঠিক আছে, কিন্তু প্রথম বিট সব ভুল।

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

পটভূমি

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

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

কী স্ব-ব্যাখ্যামূলক। id নতুন এন্ট্রির জন্য টাইমস্ট্যাম্প/সিকোয়েন্স কম্বো, কিন্তু বাস্তবে, এটি প্রায় সবসময়ই * অটো জেনারেশন নির্দেশ করে। এই বিভ্রান্তির মূল শুরু হয় ক্ষেত্র এবং মান দিয়ে। আপনি যদি HSET-এর জন্য কমান্ড স্বাক্ষরটি দেখেন, আপনি একটি সুন্দর অনুরূপ প্যাটার্ন দেখতে পাবেন:

HSET key field value [field value ...]

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

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

এই মানচিত্রটি রেডিস হ্যাশের সাথে সুন্দরভাবে তৈরি করে - এই সবগুলিই একটি হ্যাশের বৈশিষ্ট্যগুলিকে স্পষ্ট করতে পারে, যেটি ক্রমবিহীন এবং কোন পুনরাবৃত্তি নেই। পিএইচপি অ্যারে, পাইথন ডিক্টস এবং জাভাস্ক্রিপ্ট মানচিত্র ফিল্ড অর্ডার নির্ধারণ করতে পারে, কিন্তু আপনি যদি রেডিসে হ্যাশের সাথে কাজ করেন, তাহলে এটা কোন ব্যাপার না, আপনাকে শুধু জানতে হবে যে আপনি অ্যাপ্লিকেশন স্তরে এই অর্ডারের উপর নির্ভর করতে পারবেন না .

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

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

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

সুতরাং, XADD-এ কমান্ড পাঠানোর সময়, আমি ক্ষেত্র-মূল্যের অংশ তৈরি করছিলাম এবং একটি অফ-বাই-ওয়ান ত্রুটি ছিল (অনুপস্থিতির বছর পরে পিএইচপি-তে ফিরে যাওয়ার জন্য এটিকে চক করুন)। এর ফলে আমি অনিচ্ছাকৃতভাবে এর লাইন বরাবর কিছু পাঠাচ্ছি:

XADD myKey * field0 value1 field0 value2 field2 value3

পারস্পরিক সম্পর্কযুক্ত ক্ষেত্র এবং মান পাঠানোর পরিবর্তে (field0 value0 থেকে ইত্যাদি)।

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

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-এ, ক্ষেত্র যোগ করার জন্য এই আচরণটি মূলত আপসার্ট- একটি ক্ষেত্র বিদ্যমান থাকলে আপডেট করুন, অন্যথায় ক্ষেত্র যোগ করুন এবং মান সেট করুন।

মনিটর লগগুলি পরীক্ষা করে এবং মানগুলি পুনরায় প্লে করে, আমি নিম্নরূপ 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"]
]

এর মানে কি?

সুসংবাদ

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

খারাপ খবর

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

অন্য অংশটি কিছুটা খারাপ:আপনি যদি RESP3-এর খুব প্রাথমিকভাবে গ্রহণকারী হয়ে থাকেন, তাহলে আপনি XREAD/XREADGROUP-এর RESP3 মানচিত্রের ধরণ ফেরত দেওয়ার অভিজ্ঞতা থাকতে পারেন। এপ্রিলের শুরু পর্যন্ত, Redis 6-এর আন্ডার-ডেভেলপমেন্ট সংস্করণটি স্ট্রীম পড়ার সময় বিভ্রান্তিকরভাবে পুনরাবৃত্তি সহ মানচিত্র ফেরত দিয়েছিল। সৌভাগ্যবশত, এটি সমাধান করা হয়েছে এবং Redis 6-এর GA সংস্করণ—প্রথমবার আপনার উৎপাদনে RESP3 ব্যবহার করা উচিত ছিল—XREAD/XREADGROUP-এর জন্য যথাযথ রিটার্ন সহ পাঠানো হয়েছে৷

মজার অংশ

যেহেতু আমি স্ট্রীম সম্পর্কে সম্ভবত আপনি কীভাবে ভুল তা দেখেছি, আসুন আপনি কীভাবে এই আগে থেকে ভুল বোঝাবুঝি কাঠামোটি ব্যবহার করতে পারেন সে সম্পর্কে একটু চিন্তা করি।

স্ট্রিম এন্ট্রিতে অর্ডার করার জন্য শব্দার্থিক অর্থ প্রয়োগ করুন

সুতরাং, আপনার কাছে এই প্যাটার্নে খেলতে আসলে তিনটি ভেক্টর আছে। ভেক্টর গ্রাফিক্সের জন্য একটি পথ সঞ্চয় করার কল্পনা করুন। প্রতিটি স্ট্রীম এন্ট্রি একটি অনন্য বহুভুজ বা পথ হবে এবং ক্ষেত্র এবং মানগুলি স্থানাঙ্ক হবে। উদাহরণস্বরূপ, SVG-এর এই অংশটি নিন:

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

এটিকে এভাবে বলা যেতে পারে:

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

প্রতিটি অতিরিক্ত আকৃতি একই কীতে আরেকটি এন্ট্রি হবে। আপনি যদি রেডিস হ্যাশের সাথে এরকম কিছু করার চেষ্টা করেন তবে আপনার কাছে কেবল দুটি স্থানাঙ্ক থাকবে এবং কোনও অর্ডার গ্যারান্টি থাকবে না। অবশ্যই, আপনি বিটফিল্ডের মতো জিনিসগুলির সাথে এটি করতে পারেন, তবে দৈর্ঘ্য এবং সমন্বয়ের আকারের ক্ষেত্রে আপনি এক টন নমনীয়তা হারাবেন। স্ট্রিমগুলির সাথে, আপনি সম্ভবত সময়ের সাথে প্রদর্শিত আকারগুলির একটি সিরিজ উপস্থাপন করতে টাইমস্ট্যাম্পের সাথে কিছু ঝরঝরে করতে পারেন৷

অনুক্রমকৃত আইটেমগুলির একটি টাইম-অর্ডার সেট তৈরি করুন

এটির জন্য একটি ছোট হ্যাক প্রয়োজন, তবে এটি অনেক কার্যকারিতা প্রদান করতে পারে। কল্পনা করুন যে আপনি অ্যারের মতো ডেটার একটি ক্রম রাখছেন। কার্যকরীভাবে, অ্যারের একটি অ্যারে- 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) ""

যেকোন 0-দৈর্ঘ্যের স্ট্রিং ফিল্টার করার জন্য (অল্প) খরচে আপনি এই প্যাটার্নে অনেক লাভ করেন৷

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

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

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

একটি স্ট্রীমে এই ধরনের ডেটা মডেল করতে, আপনি একটি নির্দিষ্ট "খণ্ড" আকার নির্ধারণ করবেন এবং এটি একটি এন্ট্রি করবেন। কেন একটি এন্ট্রিতে খণ্ড এবং পূর্ণ ফলাফল পৃষ্ঠা নয়? এটি আপনাকে আপনার পেজিনেশনে বিভিন্ন আকারের পৃষ্ঠা রাখার অনুমতি দেয় (যেমন প্রতি পৃষ্ঠায় 10টি আইটেম প্রতিটি 5টির 2টি খণ্ড দিয়ে তৈরি করা যেতে পারে, যেখানে প্রতি পৃষ্ঠায় 25টি প্রতিটি 5টির 5টি খণ্ড হবে)। প্রতিটি এন্ট্রিতে এমন ক্ষেত্র থাকবে যা পণ্য আইডিতে ম্যাপ করবে এবং মানগুলি হবে পণ্য ডেটা। একটি কৃত্রিমভাবে কম খণ্ড আকার সহ এই সরলীকৃত উদাহরণটি দেখুন:

আপনি সম্ভবত রেডিস স্ট্রিমগুলি ভুল সম্পর্কে চিন্তা করছেন

যখন আপনি ক্যাশে করা মানগুলি পুনরুদ্ধার করতে চান, তখন আপনি 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\", ... }"

4টি আইটেমের দ্বিতীয় পৃষ্ঠা পেতে, আপনাকে 1 দ্বারা বৃদ্ধি করে একটি নিম্ন বাউন্ড স্ট্রিম আইডি প্রদান করতে হবে, আমাদের ক্ষেত্রে, নিম্ন সীমাটি হবে 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\", ... }"

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

  • XREVRANGE রিভার্সাল ব্যবহার করা যেতে পারে, তবে এটি শুধুমাত্র "খণ্ডগুলির" ক্রমকে বিপরীত করবে। প্রতিটি খণ্ডের ভিতরে আপনাকে অ্যাপ্লিকেশন যুক্তিতে ক্রমটি বিপরীত করতে হবে যা তুলনামূলকভাবে তুচ্ছ হওয়া উচিত।
  • তালিকাটির বিভিন্ন অংশে খোঁজ করা "বিনামূল্যে" যদি আপনি ম্যানুয়ালি স্ট্রিম আইডি রৈখিকভাবে সেট করেন, তাই খণ্ড 1 হল স্ট্রিম আইডি <মার্ক>0-1 , খণ্ড 2 হল স্ট্রিম আইডি 0-2 এবং তাই মনে রাখবেন যে আপনাকে 0 এর পরিবর্তে 1 দিয়ে শুরু করতে হবে কারণ আপনি 0-0 এ একটি স্ট্রিম এন্ট্রি যোগ করতে পারবেন না

যেকোনো কীর মতো, আপনি মেয়াদ শেষ হওয়ার তারিখ ব্যবহার করে স্ট্রিমটি কতক্ষণ থাকবে তা পরিচালনা করতে পারেন। এটি কীভাবে করা যায় তার একটি উদাহরণ স্ট্রিম-সারি-ক্যাশে।

আমি আশা করি এই পোস্টটি আপনাকে স্ট্রিমগুলি কীভাবে কাজ করে এবং কীভাবে আপনি আপনার অ্যাপ্লিকেশনগুলিতে স্ট্রিমগুলির এই ব্যাপকভাবে অজানা বৈশিষ্ট্যগুলি ব্যবহার করতে পারেন সে সম্পর্কে কিছু অতিরিক্ত প্রসঙ্গ দেবে৷


  1. 10 আইফোন হ্যাকস সম্পর্কে আপনি সম্ভবত জানেন না!

  2. আপনি সম্ভবত ভুল উপায়ে আইফোন ক্যাশে সাফ করছেন – এটি কীভাবে সঠিক করবেন তা এখানে রয়েছে

  3. PUBG সম্পর্কে 11টি তথ্য যা আপনি সম্ভবত জানেন না

  4. ফেসবুক ট্রিকস সম্পর্কে আপনি সম্ভবত জানেন না