২৫ ডিসে, ২০২৫·8 মিনিট পড়তে

ট্রানজেকশনাল CRUD অ্যাপের জন্য PostgreSQL বনাম MariaDB

PostgreSQL বনাম MariaDB: ইনডেক্সিং, মাইগ্রেশন, JSON, এবং কুয়েরি ফিচারগুলোর একটি ব্যবহারিক দৃষ্টিতে — যা জরুরী হয়ে ওঠে যখন একটি CRUD অ্যাপ প্রোটোটাইপ থেকে বড় হয়।

ট্রানজেকশনাল CRUD অ্যাপের জন্য PostgreSQL বনাম MariaDB

যখন একটি CRUD অ্যাপ প্রোটোটাইপ ছাড়িয়ে যায়

একটি প্রোটোটাইপ CRUD অ্যাপ সাধারণত দ্রুত লাগে কারণ ডেটা ছোট, টিম ক্ষুদ্র, এবং ট্রাফিক অনুমেয়। সহজ কুয়েরি, কয়েকটি ইনডেক্স, এবং ম্যানুয়াল স্কিমা টুইকেই অনেক সময় চলে যায়। তারপর অ্যাপটি বাস্তবে ব্যবহারকারী, বাস্তব কর্মপ্রবাহ, এবং নির্দিষ্ট সময়সীমা পায়।

বৃদ্ধি ওয়ার্কলোড পরিবর্তন করে। তালিকা ও ড্যাশবোর্ড সারাদিন খোলা থাকে। একই রেকর্ড অনেকেই এডিট করে। ব্যাকগ্রাউন্ড জব ব্যাচে লেখা শুরু করে। তখনই “কাল কাজ করছিল” থেকে ধীর পেজ, এলোমেলো টাইমআউট, এবং পিক সময়ে লক ওয়েট দেখা যায়।

আপনি সম্ভবত সেই লাইন পার করে ফেলেছেন যদি আপনি দেখতে পান: পেজ 20 পরে তালিকা পেজ ধীর হয়ে যায়, রিলিজে ডেটা ব্যাকফিল আছে (শুধু নতুন কলাম নয়), মেটাডেটা ও ইন্টিগ্রেশন পে'লোডের জন্য আরও “ফ্লেক্স ফিল্ড” এসেছে, বা সাপোর্ট টিকেটে বলা হয় “সংরক্ষণ করতে অনেক সময় লাগে” ব্যস্ত সময়ে।

এই সময়েই PostgreSQL এবং MariaDB তুলনা ব্র্যান্ড পছন্দ হওয়া বন্ধ হয়ে বাস্তবগত প্রশ্ন হয়ে ওঠে। ট্রানজেকশনাল CRUD ওয়ার্কলোডের জন্য সিদ্ধান্তটা সাধারণত নির্ধারণ করে এমন বিস্তারিতগুলো: ইনডেক্সিং অপশন যখন কুয়েরি জটিল হয়, টেবিল বড় হলে মাইগ্রেশন নিরাপত্তা, JSON স্টোরেজ ও কুয়েরি, এবং কুয়েরি ফিচার যা অ্যাপ্লিকেশন-পার্শ্বীয় কাজ কমায়।

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

আপনার অ্যাপের চাহিদা দিয়ে শুরু করুন, ডাটাবেস ব্র্যান্ড দিয়ে নয়

ভালো শুরু বিন্দু হল “PostgreSQL বনাম MariaDB” নয়। বরং আপনার অ্যাপের দৈনন্দিন আচরণ: রেকর্ড তৈরি, কিছু ফিল্ড আপডেট, ফিল্টার করা ফলাফল তালিকাভুক্ত করা, এবং অনেক মানুষ একসাথে ক্লিক করলে সঠিক থাকা।

আপনার সবচেয়ে ব্যস্ত স্ক্রীনগুলো কী করে তা লিখে রাখুন। প্রতিটি লেখার জন্য কতটি রিড হয়? কখন স্পাইক আসে (সকালের লগইন, মাসশেষ রিপোর্ট, বড় ইম্পোর্ট)? আপনি নির্দিষ্ট ফিল্টার ও সোর্টগুলো ক্যাপচার করুন, কারণ পরে সেগুলোই ইনডেক্স ডিজাইন ও কুয়েরি প্যাটার্ন চালায়।

তারপর আপনার নন-নেগোশিয়েবল গুলো নির্ধারণ করুন। অনেক টিমের জন্য এর মানে হতে পারে অর্থ বা ইনভেন্টরির জন্য কড়া কনসিস্টেন্সি, “কারা কী বদলিয়েছে”–এর জন্য অডিট ট্রেইল, এবং এমন রিপোর্টিং কুয়েরি যা স্কিমা বদলানোর সময় ভেঙে পড়বে না।

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

শেষে, কয়েকটি স্পষ্ট লক্ষ্য দিয়ে “পর্যাপ্ত দ্রুত” ধেয়ার্য করুন। উদাহরণস্বরূপ: সাধারণ লোডে p95 API ল্যাটেন্সি 200–400 মি.সেক। পিক কনকারেন্সিতে p95 (সম্ভবত সাধারণের 2x), আপডেট চলাকালে সর্বাধিক গ্রহণযোগ্য লক ওয়েট (100 মি.সেকের নিচে), এবং ব্যাকআপ ও রিস্টোর সময় সীমা।

CRUD গতি নির্ধারণকারী ইনডেক্সিং মৌলিক বিষয়

অধিকাংশ CRUD অ্যাপ দ্রুত লাগে যতক্ষণ না টেবিল মিলিয়ন রো পৌঁছায় এবং প্রত্যেক স্ক্রিন হয়ে যায় “ফিল্টার করা তালিকা সহ সোর্টিং।” তখন ইনডেক্সিংই 50 মি.সেক কুয়েরি এবং 5 সেকেন্ড টাইমআউটের মধ্যে পার্থক্য।

B-tree ইনডেক্স দুই PostgreSQL ও MariaDB-তেই ডিফল্ট ওয়ার্কহর্স। যখন আপনি একটি কলামে ফিল্টার করেন, কীতে যোগ দেন, এবং আপনার ORDER BY ইনডেক্স ক্রমের সাথে মেলে তখন এগুলো সহায়ক। সত্যিকারের পারফরম্যান্স পার্থক্য সাধারণত সিলেক্টিভিটি (কতটি রো মেলে) এবং ইনডেক্স ফিল্টার ও সোর্ট একসাথে সন্তোষজনকভাবে করতে পারে কিনা তার উপর নির্ভর করে।

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

“স্মার্ট” ইনডেক্স দিয়ে পার্থক্য দেখা যায়। PostgreSQL আংশিক (partial) এবং এক্সপ্রেশন ইনডেক্স সমর্থন করে, যা নির্দিষ্ট স্ক্রিনের জন্য ভাল (যেমন কেবল “open” টিকিটগুলোর ইনডেক্স)। এগুলো শক্তিশালী, কিন্তু দলগুলোকে বিস্মিত করতে পারে যদি কুয়েরি পুনিপূরণ (predicate) ঠিক মেলে না।

ইনডেক্স বিনামূল্যের নয়। প্রতিটি ইনসার্ট ও আপডেট প্রতিটি ইনডেক্সও আপডেট করতে হবে, তাই সহজেই আপনি একটি স্ক্রিন দ্রুত করে ক্লিক করে প্রতিটি লেখাই ধীর করে দিতে পারেন।

একটি সাদামাটা শৃঙ্খলা বজায় রাখার উপায়:

  • একটি বাস্তব কুয়েরি পথ (নাম দিয়ে বলা যাবে এমন একটি স্ক্রিন বা API কল) ছাড়া ইনডেক্স যোগ করবেন না।
  • অনেক ওভারল্যাপিং ইনডেক্সের চেয়ে একটি ভাল কম্পোজিট ইনডেক্সই পছন্দ করুন।
  • ফিচার পরিবর্তনের পরে ইনডেক্স পুনরায় পরীক্ষা করুন এবং অপ্রয়োজনীয়গুলো সরান।
  • রক্ষণাবেক্ষণের পরিকল্পনা রাখুন: PostgreSQL-এ নিয়মিত VACUUM/ANALYZE দরকার বloat এড়াতে; MariaDB-তেও ভালো স্ট্যাটিস্টিকস ও মাঝে মাঝে ক্লিনআপ দরকার।
  • ধারণার উপর নির্ভর না করে পরিমাপ করুন—পরিবার না করে আগে ও পরে মাপুন।

বাস্তব স্ক্রিনের জন্য ইনডেক্সিং: তালিকা, সার্চ, এবং পেজিনেশন

অধিকাংশ CRUD অ্যাপ কয়েকটি স্ক্রিনেই সময় কাটায়: ফিল্টার সহ একটি তালিকা, একটি সার্চ বক্স, এবং একটি ডিটেইল পেজ। আপনার ডাটাবেস পছন্দটি কম গুরুত্বপূর্ণ যতক্ষণ না আপনার ইনডেক্সগুলো সেই স্ক্রিনগুলোর সাথে মেলে, কিন্তু দুই ইঞ্জিনই টেবিল বড় হলে আলাদা টুল দেয়।

তালিকা পেজের জন্য চিন্তা করুন এই ক্রমে: প্রথমে ফিল্টার, তারপর সোর্ট, তারপর পেজিনেশন। একটি সাধারণ প্যাটার্ন: “অ্যাকাউন্ট X-এর সব টিকিট, status in (open, pending), newest first.” একটি কম্পোজিট ইনডেক্স যা ফিল্টার কলামগুলো দিয়ে শুরু করে এবং সোর্ট কলাম দিয়ে শেষ হলে সাধারণত জিতে যায়।

পেজিনেশন বিশেষ যত্ন দাবি করে। Offset পেজিনেশন (পেজ 20 OFFSET 380 সহ) স্ক্রল বাড়ার সাথে ধীর হয় কারণ ডাটাবেসকে আগের রো গুলোও অতিক্রম করতে হয়। Keyset পেজিনেশন স্থিতিশীল থাকে: আপনি শেষ দেখা মান (যেমন created_at এবং id) পাঠান এবং “তার থেকে পুরোনো পরবর্তী 20” চান। এটি নতুন রো এসে গেলে ডুপ্লিকেট ও গ্যাপ কমায়।

PostgreSQL-এ তালিকা স্ক্রিনের জন্য একটি দরকারী অপশন আছে: INCLUDE ব্যবহার করে “কভারিং” ইনডেক্স, যা ভিজিবিলিটি ম্যাপ অনুমোদন করলে ইনডেক্স-ওনলি স্ক্যান সক্ষম করে। MariaDB-এও কভারিং রিড করা যায়, কিন্তু সাধারণত আপনি প্রয়োজনীয় কলামগুলো সরাসরি ইনডেক্স ডেফিনিশনে রাখেন। এটি ইনডেক্সকে প্রশস্ত এবং রক্ষণাবেক্ষণে বেশি খরচসাপেক্ষ করে তুলতে পারে।

আপনি সম্ভবত উন্নত ইনডেক্স চাইবেন যদি একটি তালিকা এন্ডপয়েন্ট টেবিল বাড়ার সাথে ধীর হয়ে যায় যদিও সেটা কেবল 20–50 রো ফিরিয়ে দেয়, ORDER BY বাদ দিলে সোর্টিং দ্রুত হয়, অথবা সাধারণ ফিল্টারগুলিতে I/O নিয়ে লাফ দেখা যায়। দীর্ঘ কুয়েরি ব্যস্ত সময়ে লক ওয়েটও বাড়ায়।

উদাহরণ: একটি অর্ডার স্ক্রিন যা customer_id এবং status দিয়ে ফিল্টার করে এবং created_at দ্বারা সর্স করে সাধারণত (customer_id, status, created_at) দিয়ে শুরু হওয়া ইনডেক্সে লাভ পায়। পরে যদি আপনি “অর্ডার নাম্বার দিয়ে সার্চ” যুক্ত করেন, সেটি সাধারণত আলাদা ইনডেক্স হবে, তালিকা ইনডেক্সে ঠুকা নয়।

মাইগ্রেশন: ডেটা বড় হলে রিলিজ নিরাপদ রাখা

Make changes easier to track
প্রক্রিয়া এবং ডেটা পরিবর্তনগুলো এক জায়গায় ডিজাইন করে অডিট-ফ্রেন্ডলি ওয়ার্কফ্লো যোগ করুন।
প্রোজেক্ট শুরু করুন

মাইগ্রেশন দ্রুতই “টেবিল বদলাও” থাকেনা। একবার বাস্তব ব্যবহারকারী ও ইতিহাস থাকলে আপনাকে ডেটা ব্যাকফিল, কনস্ট্রেইন্ট শক্ত করা, এবং পুরোনো ডেটা রূপ না ভেঙে পরিষ্কার করা টিপুন করতে হবে।

একটি নিরাপদ ডিফল্ট হল expand, backfill, contract। এমনভাবে যোগ করুন যাতে অব্যাহত কোড বিঘ্নিত না হয়, ছোট ধাপে ডেটা কপি বা হিসাব করুন, তারপর পরে পুরোনো পথটি সরান।

প্রায়ই এর অর্থ: নতুন nullable কলাম বা টেবিল যোগ করুন, ব্যাকফিল ব্যাচে করুন যখন লেখাগুলি সঙ্গতিশীল থাকে, পরে NOT NULL, ফরেন কি, ইউনিক রুল দিয়ে যাচাই করুন, এবং তারপর পুরোনো কলাম, ইনডেক্স ও কোড পথ মুছুন।

সব স্কিমা পরিবর্তন সমান নয়। কলাম যোগ করা সাধারণত কম রিস্ক। বড় টেবিলে ইনডেক্স যোগ করাও ব্যয়বহুল হতে পারে, তাই তা কম ট্রাফিক সময়ে পরিকল্পনা করুন ও পরিমাপ করুন। কলামের টাইপ বদলানো সবচেয়ে ঝুঁকিপূর্ণ হতে পারে কারণ এটি ডেটা রিরাইট বা লেখাকে ব্লক করতে পারে। একটি সাধারণ সেফ প্যাটার্ন: নতুন টাইপ সহ একটি নতুন কলাম তৈরি করুন, ব্যাকফিল করুন, তারপর রিড ও রাইট স্যুইচ করুন।

রোলব্যাকও বড় পরিসরে ভিন্ন অর্থ বহন করে। স্কিমা রোলব্যাক কেতেকি সহজ; ডেটা রোলব্যাক প্রায়ই নয়। যদি মাইগ্রেশন ডেস্ট্রাকটিভ ডিলিট বা লসি ট্রান্সফর্ম করে, আপনি কি undo করতে পারবেন তা স্পষ্ট করুন।

JSON সমর্থন: ভবিষ্যতের ব্যথা ছাড়া নমনীয় ফিল্ড

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

PostgreSQL ও MariaDB উভয়েতেই JSON সাধারণত ভাল কাজ করে যখন সেটা বিরলভাবে ফিল্টার করা হয় এবং প্রধানত প্রদর্শিত হয়, ডিবাগিংয়ের জন্য রাখা হয়, প্রতি ব্যবহারকারী বা টেন্যান্টের জন্য একটি “সেটিংস” ব্লব হিসেবে থাকে, বা ছোট ঐচ্ছিক অ্যাট্রিবিউটের জন্য ব্যবহৃত হয় যা রিপোর্টিং চালায় না।

JSON ইনডেক্সিংয়ে দলগুলো বিস্মিত হয়। একটি JSON কী একবার কুয়েরি করা সহজ। বড় টেবিলে তার ওপর ফিল্টার ও সোর্ট করলে পারফরম্যান্স অধরে পড়ে। PostgreSQL JSON পাথ ইনডেক্সিংয়ে শক্তিশালী অপশন আছে, কিন্তু আপনাকে শৃঙ্খলাবদ্ধ থাকতে হবে: কয়েকটি কীই ইনডেক্স করুন যেগুলো আপনি সত্যিই ফিল্টার করবেন এবং বাকিকে অনইনডেক্সড পে'লোড হিসাবে রাখুন। MariaDB-ও JSON কুয়েরি করতে পারে, কিন্তু জটিল “JSON-এর ভিতর সার্চ” প্যাটার্ন প্রায়ই ভঙ্গুর ও দ্রুত রাখার কঠিন হয়ে ওঠে।

JSON কনস্ট্রেইন্ট দুর্বল করে। একটি অস্ট্রাকচার্ড ব্লবে থাকা “এটি অবশ্যই এই মানগুলোর একটি হতে হবে” বা “সর্বদা উপস্থিত” enforce করা কঠিন, এবং রিপোর্টিং টুল সাধারণত টাইপ করা কলাম পছন্দ করে।

একটি নিয়ম যা স্কেলে কাজ করে: অজানা জিনিসের জন্য JSON দিয়ে শুরু করুন, কিন্তু যখন আপনি (1) তার ওপর ফিল্টার বা সোর্ট করেন, (2) কনস্ট্রেইন্ট চাইবেন, বা (3) সেটি প্রতি সপ্তাহে ড্যাশবোর্ডে দেখছেন—তখন কলাম বা child টেবিলে নরমালাইজ করুন। একটি অর্ডারের পুরো শিপিং API রেসপন্স JSON হিসেবে রাখা সাধারণত ঠিক আছে। কিন্তু delivery_statuscarrier মত ফিল্ডগুলোকে বাস্তব কলাম হিসেবেই রাখা উচিত যখন সাপোর্ট ও রিপোর্টিং তাদের উপর নির্ভর করে।

পরিণত অ্যাপগুলোতে দেখা দেয় এমন কুয়েরি ফিচার

Keep flexible fields under control
JSON কেবল পে'লোডের জন্য রাখুন, গুরুত্বপূর্ণ ফিল্ডগুলো কলাম হিসেবে উন্নীত করুন এবং তা দ্রুত আপনার অ্যাপ মডেলে প্রতিফলিত করুন।
এখন তৈরি করুন

শুরুতে, অধিকাংশ CRUD অ্যাপ সাধারণ SELECT, INSERT, UPDATE, এবং DELETE চালায়। পরে আপনি activity feed, audit view, admin report, এবং সার্চ যোগ করেন যা ফিলিং সাথে ইমপ্রেসিভ লাগতে হবে। তখন এই পছন্দটা ফিচার ট্রেড-অফের মতো লাগতে শুরু করে।

CTE ও সাবকোয়েরি জটিল কুয়েরি পড়তে সহজ রাখে। যখন আপনি ধাপে ধাপে একটি ফল তৈরি করেন (অর্ডার ফিল্টার, পেমেন্ট যোগ, টোটাল হিসাব) তখন এগুলো দরকারী। কিন্তু পাঠযোগ্যতা কখনো কখনো খরচ লুকায়। যখন একটি কুয়েরি ধীর হয়, আপনাকে হয়তো CTE কে সাবকোয়েরি বা join হিসেবে পুনর্লিখতে হবে এবং Execution Plan পুনরায় দেখা দরকার হবে।

Window functions প্রথমবারই দরকার হয় যখন কেউ চায় “গ্রাহকদের ব্যয় অনুসারে rank দেখান”, “রানিং টোটাল” বা “প্রতিটি টিকিটের সর্বশেষ স্ট্যাটাস”। এইগুলো প্রায়ই অ্যাপ লুপ দূর করে এবং কুয়েরির সংখ্যা কমায়।

Idempotent writes বড় সমস্যার সময় আরেকটি চাহিদা হয়ে ওঠে। রিট্রাই হলে (মোবাইল নেটওয়ার্ক, ব্যাকগ্রাউন্ড জব) upserts আপনাকে ডাবল-ক্রিয়েশন ছাড়া নিরাপদে লেখার সুযোগ দেয়:

  • PostgreSQL: INSERT ... ON CONFLICT
  • MariaDB: INSERT ... ON DUPLICATE KEY UPDATE

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

উদাহরণ: একটি অর্ডার পোর্টাল শুরুতে “তালিকা দেখাও” দিয়ে কাজ করে। একটি বছর পরে সেটা চায় “প্রতিটি গ্রাহকের সর্বশেষ অর্ডার দেখাও, মাসিক ব্যয়ের দ্বারা র‍্যাংক করো, নামের টাইপো সহ সার্চ করো।” এসব তখনই ডাটাবেস ক্ষমতার ব্যাপার হয়, না শুধু UI কাজ।

লোডে ট্রানজ্যাকশন, লকস, ও কনকারেন্সি

যখন ট্রাফিক কম থাকে, অধিকাংশ ডাটাবেস ঠিকই চলে। লোডে পার্থক্যটি প্রায়ই এই নিয়ে যে আপনি একই ডেটায় কিভাবে সমবায় পরিবর্তন সামলান, কাঁচা গতি নয়। PostgreSQL এবং MariaDB উভয়ই ট্রানজেকশনাল CRUD ওয়ার্কলোড চালাতে পারে, কিন্তু কনটেনশন ডিজাইন করাটা জরুরী।

সাধারণ ভাষায় আইসোলেশন

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

আসলে কি লক পেইন সৃষ্টি করে

CRUD অ্যাপের লকিং সমস্যাগুলো সাধারণত কয়েকটি বারবার দেখা বিষয় থেকে আসে: হট রো যা সবাই আপডেট করে, এমন কাউন্টার যা প্রতিটি অ্যাকশনে বদলে যায়, জব কিউ যেখানে অনেক ওয়ার্কার একই "পরবর্তী কাজ" নিতে চায়, এবং দীর্ঘ ট্রানজ্যাকশন যা অন্য কাজ ধরে রাখে যতক্ষণ মানুষ বা নেটওয়ার্কে অপেক্ষা চলে।

কনটেনশন কমাতে ট্রানজ্যাকশন ছোট রাখুন, কেবল প্রয়োজনীয় কলাম আপডেট করুন, এবং ট্রানজ্যাকশনের ভিতরে নেটওয়ার্ক কল এড়িয়ে চলুন।

একটি সহায়ক অভ্যাস হলো কনফ্লিক্টে রিট্রাই করা। যদি দুই সাপোর্ট এজেন্ট একই সময়ে একই টিকিট সেভ করে, তাহলে নীরবে ফেল করা না করে কনফ্লিক্ট ডিটেক্ট করে লেটেস্ট রো রিলোড করে ব্যবহারকারীকে আবার আপডেট করতে বলুন।

সমস্যা আগে ধরতে, ডেডলক, দীর্ঘ-চলমান ট্রানজ্যাকশন, এবং এমন কুয়েরি দেখুন যা রান করার বদলে অপেক্ষায় বেশি সময় কাটায়। রিলিজের পরে ধীর কুয়েরি লগ রুটিনে রাখুন, বিশেষ করে নতুন স্ক্রিন বা ব্যাকগ্রাউন্ড জব যোগ করলে।

লঞ্চের পর গুরুত্বপূর্ণ অপারেশনাল বিষয়

Test your busiest screens first
প্রারম্ভিকেই তালিকা স্ক্রিন গুলো বাস্তব ফিল্টার এবং সোর্টিং সহ তৈরি করে ইনডেক্সিং ও পেজিনেশন সমস্যাগুলো দ্রুত ধরুন।
শুরু করুন

লঞ্চের পরে আপনি শুধুমাত্র কুয়েরি গতি অপ্টিমাইজ করছেন না—আপনি পুনরুদ্ধার, নিরাপদ পরিবর্তন, এবং পূর্বানুমিত পারফরম্যান্স অপ্টিমাইজ করছেন।

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

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

আপগ্রেডও আর “ক্লিক করে আশা করা” থেকরে। রক্ষণাবেক্ষণের উইন্ডো পরিকল্পনা করুন, কম্প্যাটিবিলিটি নোট পড়ুন, এবং প্রোডাকশনের ডেটার কপি নিয়ে আপগ্রেড পথ টেস্ট করুন। এমনকি ন্যূনতম ভার্সন বাম্পও কুয়েরি প্ল্যান বা ইনডেক্স ও JSON ফাংশন আচরণ বদলে দিতে পারে।

সরল অবজার্ভেবিলিটি আগেই লাভ দেয়। শুরু করুন ধীর কুয়েরি লগ, মোট সময় অনুযায়ী শীর্ষ কুয়েরি, কানেকশন স্যাচুরেশন, রিপ্লিকেশন ল্যাগ (যদি ব্যবহার করেন), ক্যাশ হিট অনুপাত ও I/O চাপ, এবং লক ওয়েট ও ডেডলক ইভেন্ট মনিটরিং দিয়ে।

কিভাবে বেছে নেবেন: একটি প্র্যাকটিক্যাল মূল্যায়ন প্রক্রিয়া

Design data without rewrite pain
PostgreSQL স্কিমা ভিজ্যুয়ালি মডেল করুন এবং যখন চাহিদা বদলে যায় পরিষ্কার Go কোড জেনারেট করুন।
AppMaster চেষ্টা করুন

আপনি যদি আটকে থাকেন, ফিচার তালিকা পড়া বন্ধ করে নিজের ওয়ার্কলোড নিয়ে ছোট একটি ট্রায়াল চালান। লক্ষ্য পারফেক্ট বেঞ্চমার্ক নয়—লক্ষ্য হল টেবিল মিলিয়ন রো পৌঁছালে এবং আপনার রিলিজ সাইকেল দ্রুত হলে অপ্রত্যাশিত পরিস্থিতি এড়ানো।

1) প্রোডাকশনের মত একটি মিনি টেস্ট বানান

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

একটি দ্রুত উদাহরণ হল “Customers” তালিকা যা status দিয়ে ফিল্টার করে, নামে সার্চ করে, last activity দ্বারা সোর্ট করে, এবং পেজিনেট করে। একটাই স্ক্রিন প্রায়ই বলে দেয় আপনার ইনডেক্সিং ও প্ল্যানার আচরণ ভাল থাকবে কি না।

2) বাস্তব রিলিজের মত মাইগ্রেশন অনুশীলন করুন

স্টেজিং ডেটাসেট কপি তৈরি করে সেই পরিবর্তনগুলো অনুশীলন করুন যা আসতে পারে: কলাম যোগ, টাইপ পরিবর্তন, ডেটা ব্যাকফিল, ইনডেক্স যোগ। কত সময় লাগে মাপুন, এটি লেখাকে ব্লক করে কি না, এবং ডেটা পরিবর্তনের পরে রোলব্যাকের প্রকৃত মানে কী।

3) সহজ একটি স্কোরকার্ড ব্যবহার করুন

টেস্ট করার পরে প্রতিটি অপশনকে স্কোর করুন আপনার বাস্তব কুয়েরিগুলোর পারফরম্যান্স, সঠিকতা ও নিরাপত্তা (কনস্ট্রেইন্ট, ট্রানজ্যাকশন, এজ কেস), মাইগ্রেশন ঝুঁকি (লকিং, ডাউনটাইম, রিকভারি অপশন), অপস পরিশ্রম (ব্যাকআপ/রিস্টোর, রিপ্লিকেশন, মনিটরিং), এবং টিমের আরাম।

পরবর্তী 12 মাসের ঝুঁকি কমায় এমন ডাটাবেসটি বেছে নিন—একটি মাইক্রো-টেস্ট জিতে যাওয়া অপশন নয়।

সাধারণ ভুলত্রুটি এবং ফাঁদ

সবচেয়ে ব্যয়বহুল ডাটাবেস সমস্যাগুলো সাধারণত “দ্রুত জয়” হিসেবে শুরু হয়। উভয় ডাটাবেস ট্রানজেকশনাল CRUD অ্যাপ চালাতে পারে, কিন্তু ভুল অভ্যাস যে কোনটিকেই কষ্ট দেবে যখন ট্রাফিক ও ডেটা বাড়বে।

একটি সাধারণ ফাঁদ হল JSON-কে সবকিছুই দ্রুত সমাধান হিসেবে দেখা। একটি নমনীয় “extras” ফিল্ড প্রকৃতপক্ষে ঐচ্ছিক ডেটার জন্য ঠিক আছে, কিন্তু core ফিল্ডগুলো—status, timestamps, foreign keys—বাস্তব কলাম হিসেবে থাকা উচিত। না হলে আপনি ধীর ফিল্টার, অদ্ভুত ভ্যালিডেশন, এবং রিপোর্টিং যখন গুরুত্বপূর্ণ তখন কষ্টকর রিফ্যাক্টরের সম্মুখীন হবেন।

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

মাইগ্রেশন টেবিল লক করলে কাঁটা ঝোঁকায় কাটা দিতে পারে। বড়-ব্যাংগ পরিবর্তন যেমন বড় কলাম রিরাইট, default সহ NOT NULL যোগ করা, বা বড় ইনডেক্স তৈরি করা কয়েক মিনিটের জন্য লেখাকে ব্লক করতে পারে। ঝুঁকিপূর্ণ পরিবর্তনগুলো ধাপে ভেঙে নিন এবং অ্যাপ শ্যুমিং সময়ে শিডিউল করুন।

আরও একটি সতর্কতা: ORM ডিফল্টে চিরকাল নির্ভর করবেন না। একবার তালিকা ভিউ 1,000 রো থেকে 10 মিলিয়ন এ গেলে, আপনাকে কুয়েরি প্ল্যান পড়তে হবে, অনুপস্থিত ইনডেক্স খুঁজতে হবে, এবং ধীর জয়েন ঠিক করতে হবে।

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

প্রতিশ্রুতির আগে দ্রুত চেকলিস্ট

Build the real app next
একটি ভিজ্যুয়াল মডেল থেকে আপনার CRUD প্রোটোটাইপকে প্রোডাকশন-রেডি ব্যাকএন্ড, ওয়েব এবং মোবাইল অ্যাপে পরিণত করুন।
শুরু করুন

সাইড বেছে নেওয়ার আগে আপনার সবচেয়ে ব্যস্ত স্ক্রিন ও রিলিজ প্রক্রিয়ার উপর ভিত্তি করে একটি দ্রুত বাস্তবতা পরীক্ষা করুন।

  • শীর্ষ স্ক্রিনগুলো কি পিক-লোডে দ্রুত থাকবে? বাস্তব ফিল্টার, সোর্টিং, ও পেজিনেশন দেখিয়ে সবচেয়ে ধীর তালিকা পেজটি টেস্ট করুন এবং নিশ্চিত করুন আপনার ইনডেক্সগুলো সেগুলোই মেলে।
  • আপনি কি নিরাপদভাবে স্কিমা পরিবর্তন পাঠাতে পারবেন? পরবর্তী বড় পরিবর্তনের জন্য expand-backfill-contract প্ল্যান লিখে রাখুন।
  • JSON বনাম কলামের জন্য আপনার কি স্পষ্ট নিয়ম আছে? কোন JSON কী সার্চেবল বা sortable হতে হবে তা নির্ধারণ করুন এবং কোনগুলো নমনীয় থাকবে তা ঠিক করুন।
  • আপনি কি নির্দিষ্ট কুয়েরি ফিচারের উপর নির্ভর করছেন? upsert আচরণ, window functions, CTE আচরণ, এবং functional বা partial ইনডেক্স দরকার কিনা পরীক্ষা করুন।
  • লঞ্চের পরে কি আপনি এটি অপারেট করতে পারবেন? ব্যাকআপ থেকে রিস্টোর করতেও সক্ষম হন, ধীর কুয়েরি পরিমাপ করুন, এবং ল্যাটেন্সি ও লক ওয়েট বেসলাইন করুন।

উদাহরণ: সহজ অর্ডার ট্র্যাকিং থেকে ব্যস্ত কাস্টমার পোর্টাল

একটি কাস্টমার পোর্টাল কল্পনা করুন যা সারলভাবে শুরু হয়: কাস্টমার লগইন করে, অর্ডার দেখে, ইনভয়েস ডাউনলোড করে, এবং সাপোর্ট টিকিট খুলে। সপ্তার প্রথমে প্রায় যেকোন ট্রানজেকশনাল ডাটাবেস ঠিকঠাক চলে। পেজগুলো দ্রুত লোড হয়, স্কিমা ছোট।

কয়েক মাস পরে বৃদ্ধির মুহূর্তগুলো দেখা দেয়। কাস্টমাররা চায় ফিল্টার যেমন “গত 30 দিনে শিপ করা অর্ডার, কার্ডে ভুক্তি, আংশিক রিফান্ড সহ।” সাপোর্ট দ্রুত সাপ্তাহিক রিভিউর জন্য CSV এক্সপোর্ট চায়। ফাইনান্স চায় অডিট ট্রেইল: কে কখন ইনভয়েস স্ট্যাটাস বদলিয়েছে এবং কি থেকে কি হয়েছে। কুয়েরি প্যাটার্নগুলো মূল স্ক্রিনগুলোর থেকে বহুবিধ ও বিস্তৃত হয়ে যায়।

এখানেই সিদ্ধান্তটি নির্দিষ্ট ফিচার ও সেগুলোর বাস্তব লোডে কিভাবে আচরণ করে তার ব্যাপারে যায়।

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

রিপোর্টিং আরেকটি চাপদাতা। অর্ডার, ইনভয়েস, পেমেন্ট, টিকিট যোগ করে অনেক ফিল্টার করলে আপনি কম্পোজিট ইনডেক্স, কুয়েরি প্ল্যানিং, এবং কিভাবে ইনডেক্সগুলো বদলানো সহজে করা যায় তা নিয়ে যত্নবান হবেন। মাইগ্রেশনগুলোও আর “শুক্রবার স্ক্রিপ্ট চালাও” নয়; ছোট স্কিমা পরিবর্তন মিলিয়ন রো স্পর্শ করতে পারে তাই প্রতিটি রিলিজে এটি বিবেচিত হয়ে ওঠে।

প্র্যাকটিক্যাল এগিয়ে যাওয়ার উপায়: ছয় মাসে আপনি যে পাঁচটি বাস্তব স্ক্রিন ও এক্সপোর্ট আশা করেন তা লিখে রাখুন, অডিট ইতিহাস টেবিল শুরুতেই যোগ করুন, আপনার ধীরতম কুয়েরিগুলো নিয়ে বাস্তব ডেটা সাইজ দিয়ে বেঞ্চমার্ক করুন (না যে একটি Hello-World CRUD), এবং JSON ব্যবহার, ইনডেক্সিং, ও মাইগ্রেশনের টিম রুল ডকুমেন্ট করুন।

যদি আপনি দ্রুত এগোতে চান বারে-হাতে সব স্তর তৈরির বদলে, AppMaster (appmaster.io) আপনার ভিজ্যুয়াল মডেল থেকে প্রোডাকশন-রেডি ব্যাকএন্ড, ওয়েব অ্যাপ এবং নেটিভ মোবাইল অ্যাপ জেনারেট করতে পারে। এটি আপনাকে স্ক্রিন, ফিল্টার, এবং বিজনেস প্রসেসগুলোকে বাস্তব কুয়েরি ওয়ার্কলোড হিসেবে দ্রুত বিবেচনা করতে প্রণোদিত করে, যাতে ইনডেক্সিং ও মাইগ্রেশন ঝুঁকি প্রোডাকশনে পৌঁছানোর আগেই ধরতে পারেন।

প্রশ্নোত্তর

বর্ধমান CRUD অ্যাপের জন্য কোনটি বেছে নিব: PostgreSQL নাকি MariaDB?

প্রথমে আপনার বাস্তব ওয়ার্কলোড লিখে নিন: সবচেয়ে ব্যস্ত তালিকা স্ক্রিন, ব্যবহৃত ফিল্টার ও সর্স, এবং শীর্ষ লেখার পথগুলো। উভয়ই CRUD ভালো চালাতে পারে, কিন্তু নিরাপদ পছন্দটি সেই ডাটাবেস যা পরবর্তী বছরে আপনি কিভাবে ইনডেক্স, মাইগ্রেশন এবং কুয়েরি করবেন তার সাথে মিলবে—না যে কোন নামটি বেশি পরিচিত।

আমার প্রোটোটাইপ ডাটাবেস সেটআপ ব্যর্থ হচ্ছে—এর পরিষ্কার লক্ষণগুলো কী?

যদি তালিকা পেজগুলো পেজ বাড়ার সাথে সাথে ধীর হয়ে যায়, তাহলে আপনার OFFSET স্ক্যানের খরচ বেড়েছে। যদি ব্যস্ত সময়ে সেভিং আটকে থাকে, তাহলে লক কনটেনশন বা দীর্ঘ ট্রানজ্যাকশন হতে পারে। আর যদি রিলিজগুলিতে এখন ব্যাকফিল বা বড় ইনডেক্স থাকে, তাহলে মাইগ্রেশনগুলো আর শুধু স্কিমা পরিবর্তন নয়—এগুলো নির্ভরযোগ্যতার প্রশ্ন।

রিয়েল তালিকা স্ক্রিন ও ড্যাশবোর্ডের জন্য ইনডেক্স কিভাবে ডিজাইন করব?

প্রতিটি গুরুত্বপূর্ণ স্ক্রিন কেজে একটি কম্পোজিট ইনডেক্স রাখুন; সবচেয়ে কনসিস্টেন্ট ফিল্টারগুলো প্রথমে এবং সোর্ট কলাম শেষে রাখুন। উদাহরণস্বরূপ, মাল্টি-টেন্যান্সি তালিকার জন্য (tenant_id, status, created_at) একটি সাধারণ ভালো প্যাটার্ন।

কেন OFFSET পেজিনেশন ধীর হয়, আর কী ব্যবহার করা উচিত?

Offset পেজিনেশন তখন ধীরে হয়ে যায় যখন উচ্চ পেজে পৌঁছালে ডাটাবেস আগের রো গুলোও অতিক্রম করতে থাকে। বদলে keyset পেজিনেশন ব্যবহার করুন—শেষ দেখা created_atid পাঠিয়ে “next 20 older than that” চাইলে পারফরম্যান্স আরও স্থিতিশীল থাকে এবং নতুন রো আসার সময় ডুপ্লিকেট বা ফাঁক কমে।

একটি CRUD অ্যাপের জন্য কতগুলো ইনডেক্স অনেক বেশি?

প্রতিটি সেই ইনডেক্স যোগ করুন যেটার জন্য আপনি সুনির্দিষ্ট একটি স্ক্রিন বা API কল নাম বলতে পারেন। অত্যধিক ও ওভারল্যাপিং ইনডেক্সগুলো প্রতিটি ইনসার্ট ও আপডেটে ধীরতা বাড়ায় এবং মাইগ্রেশনকে ভারী করে।

বড় টেবিলগুলিতে স্কিমা মাইগ্রেশন সবচেয়ে নিরাপদভাবে কিভাবে করব?

প্রধান নীতি: expand, backfill, contract। নতুন কাঠামো যুক্ত করুন এমনভাবে যাতে পুরোনো কোড ব্যাহত না হয়, ছোট ছোট ব্যাচে ডেটা ব্যাকফিল করুন, পরে constraints দিয়ে যাচাই করুন (যেমন NOT NULL), এবং সব ঠিক থাকলে পুরোনো কলাম/পাথ মুছুন।

কবে ডেটা JSON-এ রাখব আর কবে বাস্তব কলাম হিসেবে?

JSON রাখুন সেই ডেটার জন্য যা বেশি মাত্রায় প্রদর্শিত হয় বা ডিবাগিং/পে'লোড হিসেবে ভালো থাকে। যদি আপনি নিয়মিতভাবে সেই ফিল্ড দিয়ে ফিল্টার বা সোর্ট করবেন, তখন তাকে রিল কলাম হিসেবে আনুন। এটি ধীর JSON-কেন্দ্রিক কুয়েরি ও কঠিন ভ্যালিডেশন থেকে বাঁচায়।

রিট্রাই কিভাবে নিরাপদে হ্যান্ডেল করব যাতে ডুপ্লিকেট তৈরি না হয়?

রাইট-রিটারির সময় ডুপ্লিকেট তৈরি না করতে upserts অপরিহার্য। PostgreSQL এ INSERT ... ON CONFLICT ব্যবহার করেন, আর MariaDB তে INSERT ... ON DUPLICATE KEY UPDATE—উভয় ক্ষেত্রেই ইউনিক কী ঠিক করে নিন যাতে রিট্রাইতে নতুন রেকর্ড না তৈরি হয়।

CRUD অ্যাপে লক ও ডেডলক আসলে কী কারণে হয়?

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

আমি কি একটি রিড রিপ্লিকা যোগ করব, এবং সেটি এপে কিভাবে পরিবর্তন আনে?

হ্যাঁ, যদি পড়ে-ভিত্তিক পেজ বা রিপোর্টে আপনাকে পড়া-ভিত্তিক লোড সামলাতে হয়। খেয়াল রাখবেন যে রিপ্লিকার কিছু লেটেন্সি থাকতে পারে; তাই যেসব রিডসকে “ঠিক এখন” হওয়া জরুরি (যেমন অর্ডার প্লেস করার পরপরই দেখা) সেগুলো প্রাইমারির কাছ থেকে পড়ান।

শুরু করা সহজ
কিছু আশ্চর্যজনকতৈরি করুন

বিনামূল্যের পরিকল্পনা সহ অ্যাপমাস্টারের সাথে পরীক্ষা করুন।
আপনি যখন প্রস্তুত হবেন তখন আপনি সঠিক সদস্যতা বেছে নিতে পারেন৷

এবার শুরু করা যাক