PostgreSQL-এর সারি-স্তরের নিরাপত্তা (RLS) SaaS-এ টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে সাহায্য করে। কখন ব্যবহার করবেন, কিভাবে পলিসি লিখবেন, এবং কি এড়িয়ে চলবেন তা শিখুন।

tenant_id যাচাই করে, অন্যটি সদস্যত্ব চেক করে, একটা ব্যাকগ্রাউন্ড জব ভুলে যায়, এবং একটি “অ্যাডমিন এক্সপোর্ট” পথ মাসের পর মাস “অস্থায়ী” থাকতেই থাকে। এমনকি সাবধানী দলগুলোও একটা জায়গা মিস করে ফেলে।\n\nPostgreSQL সারি-স্তরের নিরাপত্তা (RLS) একটি নির্দিষ্ট সমস্যা সমাধান করে: এটি ডেটাবেসকে প্রয়োগ করায় কোন সারিগুলো একটি অনুরোধের জন্য দৃশ্যমান হবে। মানসিক মডেলটি সহজ: প্রতিটি SELECT, UPDATE, এবং DELETE স্বয়ংক্রিয়ভাবে নীতির দ্বারা ফিল্টার করা হয়, ঠিক যেভাবে প্রতিটি অনুরোধ অথেন্টিকেশন মিডলওয়্যারের দ্বারাও ফিল্টার করা হয়।\n\n“সারি” অংশটা গুরুত্বপূর্ণ। RLS সবকিছু রক্ষা করে না:\n\n- এটি নিজে কলামগুলি লুকায় না (ভিউ বা কলাম প্রিভিলেজ ব্যবহার করুন)।\n- এটি অনিরাপদ ফাংশনকে নিরাপদ করে না (উন্নত অধিকার নিয়ে চলা একটা ফাংশন এখনও ডেটা ফাঁস করতে পারে)।\n\n- এটি ব্যবসায়িক নিয়ম যাচাই করে না (উদাহরণ: “শুধু মালিকরাই বিলিং সেটিংস পরিবর্তন করতে পারবে”)।\n\nএকটি বাস্তব উদাহরণ: আপনি একটি ড্যাশবোর্ডের জন্য প্রোজেক্ট তালিকা এনডপয়েন্ট যোগ করেন যা ইনভয়েসের সাথে জয়েন করে। কেবল অ্যাপ-ভিত্তিক অথরাইজেশনে, projects-এ টেন্যান্ট ফিল্টার করা সহজ, কিন্তু invoices ফিল্টার করা ভুলে যাওয়া বা এমন কী-তে জয়েন করা যা টেন্যান্ট লঙ্ঘন করে এমনসব ভুল হয়। RLS থাকলে উভয় টেবিল টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে পারে, তাই কুয়েরিটি নিরাপদভাবে ব্যর্থ করে ডেটা ফাঁস না করে।\n\nট্রেড-অফ বাস্তব। আপনি পুনরাবৃত্তি করা অনুমোদন কোড কম লেখেন এবং লিক হওয়ার স্থানগুলো কমিয়ে দেন। কিন্তু আপনাকে নতুন কাজও নিতে হয়: নীতিগুলো সাবধানে ডিজাইন করা, অল্পতেই পরীক্ষা করা, এবং মেনে নেওয়া যে একটি নীতি এমন একটি কুয়েরি ব্লক করতে পারে যা আপনি কাজ করবে বলে ভেবেছিলেন।\n\n## কখন RLS অনুমোদন সহজ করে (আর কখন এটা যন্ত্রণার কারণ)\n\nRLS কষ্টসাধ্য মনে হতে পারে যতক্ষণ না আপনার অ্যাপ কয়েকটি এন্ডপয়েন্ট ছাড়িয়ে wächst। যদি আপনার কঠোর টেন্যান্ট সীমানা থাকে এবং অনেক কুয়েরি-পাথ থাকে (তালিকা স্ক্রিন, সার্চ, এক্সপোর্ট, অ্যাডমিন টুল), ডেটাবেসে নিয়ম রাখলে আপনাকে প্রতিবার একই ফিল্টার যোগ করতে মনে রাখার দরকার পড়ে না।\n\nRLS মিলবে যখন নিয়মটা বিরক্তিকরভাবে সাধারণ এবং সার্বজনীন: “একজন ব্যবহারকারী শুধু তাদের টেন্যান্টের সারিগুলোই দেখতে পারবে” বা “একজন ব্যবহারকারী শুধু তারা যার সদস্য তার প্রোজেক্টগুলোই দেখতে পারবে।” এই সেটআপগুলোতে, নীতিগুলো ভুল হওয়ার সম্ভাবনা কমায় কারণ প্রতিটি SELECT, UPDATE, এবং DELETE একই গেট দিয়ে যায়, এমনকি পরে কোনো কুয়েরি যোগ হলে ও সে ক্ষেত্রে।\n\nএটি রিড-হেভি অ্যাপগুলিতেও সাহায্য করে যেখানে ফিল্টারিং লজিক অভিন্ন থাকে। যদি আপনার API-তে ইনভয়েস লোড করার 15টি ভিন্ন উপায় থাকে (স্ট্যাটাস, তারিখ, কাস্টমার, সার্চ দ্বারা), RLS আপনাকে প্রতিটি কুয়েরিতে টেন্যান্ট ফিল্টার পুনরায় ইমপ্লিমেন্ট করা বন্ধ করে ফিচারের দিকে মনোযোগ দিতে দেয়।\n\nRLS কষ্ট দেয় যখন নিয়মগুলো সারি-ভিত্তিক নয়। প্রতি-ফিল্ড নিয়ম যেমন “তুমি বেতন দেখতে পারবে কিন্তু বোনাস নয়” বা “এই কলাম HR না হলে মাস্ক কর” প্রায়ই জটিল SQL এবং রক্ষণাবেক্ষণে কষ্টদায়ক ব্যতিক্রমে পরিণত হয়।\n\nএটি ভারী রিপোর্টিংয়ের জন্যও খারাপ ফিট হতে পারে যেখানে আসলেই বিস্তৃত অ্যাক্সেস দরকার। দলগুলো প্রায়ই “শুধু এই জবটির জন্য” বাইপাস রোল তৈরি করে, এবং সেখানে ভুলগুলো জমে যায়।\n\nকর্মসূচি নেওয়ার আগে সিদ্ধান্ত নিন: ডেটাবেসকে চূড়ান্ত গেটকিপার বানাবেন কি না। যদি হ্যাঁ, তবে ডিসিপ্লিনের পরিকল্পনা করুন: ডাটাবেস আচরণ পরীক্ষা করুন (শুধুমাত্র API রেসপন্স নয়), মাইগ্রেশনকে সিকিউরিটি চেঞ্জ হিসেবেই বিবেচনা করুন, দ্রুত বাইপাস এড়িয়ে চলুন, ব্যাকগ্রাউন্ড জব কীভাবে অথেনটিকেট করে ঠিক করুন, এবং নীতিগুলো ছোট ও পুনরাবৃত্তিযোগ্য রাখুন।\n\nযদি আপনি এমন টুলিং ব্যবহার করেন যা ব্যাকএন্ড জেনারেট করে, এটি ডেলিভারি দ্রুত করতে পারে, কিন্তু এটি রোল, টেস্ট, এবং একটি সরল টেন্যান্ট মডেলের প্রয়োজন দূর করে না। (উদাহরণস্বরূপ, Koder.ai Go এবং PostgreSQL ব্যবহার করে জেনারেটেড ব্যাকএন্ড দেয়, এবং তবুও আপনি RLS ইচ্ছাকৃতভাবে ডিজাইন করতে চান, পরে “স্প্রিঙ্কল” করা নয়)।\n\n## RLS নীতিগুলো পরিচালনাযোগ্য করে এমন ডেটা মডেল মৌলিকগুলো\n\nRLS সহজ হয় যখন আপনার স্কিমা ইতিমধ্যেই পরিষ্কারভাবে বলে দেয় কে কি মালিক। যদি আপনি ঝাপসা মডেল দিয়ে শুরু করে এবং “নীতিতে ঠিক করে নেব” ভাবেন, আপনি সাধারণত ধীর কুয়েরি ও বিভ্রান্তিকর বাগ পাবেন।\n\n### যেখানে প্রযোজ্য সেখানে টেন্যান্ট কী রাখুন\n\nএকটি টেন্যান্ট কী (যেমন org_id) পিক করুন এবং ধারাবাহিকভাবে ব্যবহার করুন। বেশিরভাগ টেন্যান্ট-অধিকারভুক্ত টেবিলে এটি থাকা উচিত, এমনকি তারা অন্য টেবিলকেও রেফারেন্স করলেও। এটি পলিসিগুলোর ভিতরে জয়েন এড়ায় এবং USING চেকগুলো সহজ রাখে।\n\nব্যবহারিক নিয়ম: যদি একটি সারি গ্রাহক ক্যানসেল করলে অদৃশ্য হয়ে যাওয়া উচিত, তাহলে সম্ভবত তাতে org_id থাকা দরকার।\n\n### সদস্যত্বগুলি স্পষ্টভাবে মডেল করুন\n\nRLS নীতিগুলো সাধারণত এক প্রশ্নের উত্তর দেয়: “এই ব্যবহারকারী কি এই org-এর সদস্য, এবং তারা কী করতে পারে?” এটি অ্যাড-হক কলাম থেকে প্রায়ই অনুমান করা কঠিন।\n\nকোর টেবিলগুলো ছোট এবং নিরব রাখুন:\n\n- users (প্রতি ব্যক্তির একটি সারি)\n- orgs (প্রতি টেন্যান্ট একটি সারি)\n- org_memberships (user_id, org_id, role, status)\n- ঐচ্ছিক: per-project access-এর জন্য project_memberships\n\nএর ফলে আপনার নীতিগুলো একটি ইনডেক্স করা লুকআপ দিয়ে সদস্যত্ব চেক করতে পারে।\n\n### শেয়ার হওয়া রেফারেন্স ডেটা আলাদা রাখুন\n\nসবকিছুতেই org_id লাগবে না। দেশ, প্রোডাক্ট ক্যাটাগরি, বা প্ল্যান টাইপের মতো রেফারেন্স টেবিলগুলো সাধারণত সব টেন্যান্টের জন্য শেয়ার করা হয়। এগুলোকে বেশিরভাগ রোলে রিড-ওয়ানলি করে রাখুন এবং এক org-এর সাথে বেঁধে রাখবেন না।\n\nটেন্যান্ট-অধিকারভুক্ত ডেটা (প্রোজেক্ট, ইনভয়েস, টিকেট) শেয়ার্ড টেবিল থেকে টেন্যান্ট-নির্দিষ্ট বিবরণ টেনে নিয়ে না করা ভাল। শেয়ার্ড টেবিলগুলোকে মিনিমাল ও স্থিতিশীল রাখুন।\n\n### FK, ক্যাসকেড, এবং ইনডেক্সিং\n\nRLS-এও ফোরেন কি কাজ করে, তবে ডিলেটগুলো আপনাকে চমকে দিতে পারে যদি ডিলেট করতে থাকা রোল ডিপেনডেন্ট সারিগুলো “দেখে” না। ক্যাসকেড পরিকল্পনা করে নিন এবং বাস্তব ডিলিট ফ্লো টেস্ট করুন।\n\nযেসব কলামে আপনার নীতিগুলো ফিল্টার করে সেগুলো ইনডেক্স করুন, বিশেষ করে org_id এবং সদস্যত্ব কী। এমন নীতি যা দেখতে “WHERE org_id = ...” তার উচিত মিলিয়ন সারি হলে ফুল-টেবিল স্ক্যান না হওয়া।\n\n## RLS নীতিগুলো কীভাবে কাজ করে, ভয় ছাড়া\n\nRLS হল প্রতি-টেবিল একটি সুইচ। একবার এনেব্ল করলে PostgreSQL আপনার অ্যাপ কোডকে টেন্যান্ট ফিল্টার মনে রাখার উপর বিশ্বাস করা বন্ধ করে দেয়। প্রতিটি SELECT, UPDATE, এবং DELETE নীতিগুলোর দ্বারা ফিল্টার হয়, এবং প্রতিটি INSERT ও UPDATE নীতির মাধ্যমে ভ্যালিডেট হয়।\n\nসবচেয়ে বড় মানসিক পরিবর্তন: RLS চালু হলে যেসব কুয়েরি আগে ডেটা ফিরিয়ে দিত এখন ত্রুটি ছাড়াই শূন্য সারি ফিরিয়ে দিতে পারে। এটা PostgreSQL-এর অ্যাক্সেস কন্ট্রোল।\n\n### নীতিগুলো বাস্তবে কী করে\n\nপলিসি হলো টেবিলের সাথে জোড়া ছোট নিয়ম। এগুলো দুইটি চেক ব্যবহার করে:\n\n- USING হলো রিড ফিল্টার। যদি একটি সারি USING-এ মেলে না, তা SELECT-এর জন্য অদৃশ্য এবং UPDATE বা DELETE-এ টার্গেট করা যাবে না।\n- WITH CHECK হলো রাইট গেট। এটি নির্ধারণ করে কোন নতুন বা পরিবর্তিত সারি INSERT বা UPDATE-এ অনুমোদিত।\n\nএকটি সাধারণ SaaS প্যাটার্ন: USING নিশ্চিত করে আপনি শুধু আপনার টেন্যান্টের সারিগুলো দেখেন, এবং WITH CHECK নিশ্চিত করে আপনি অনুমান করে কোনো টেন্যান্ট আইডি লিখে অন্য কারো টেন্যান্টে সারি ঢুকাতে পারবেন না।\n\n### PERMISSIVE বনাম RESTRICTIVE, এক বাক্যে\n\nপরে আরও নীতিগুলো যোগ করলে এটার গুরুত্ব বাড়ে:\n\n- PERMISSIVE (ডিফল্ট): যদি কোনো নীতি কোনও সারিকে অনুমোদন করে, সারিটি অনুমোদিত।\n- RESTRICTIVE: শুধুমাত্র যদি সব restrictive নীতি সেটি অনুমোদন করে তখনই সারিটি অনুমোদিত হবে (permissive আচরণের উপরে)।\n\nযদি আপনি টেন্যান্ট মিল+রোল চেক+প্রোজেক্ট সদস্যত্ব মত লেয়ার করতে চান, restrictive নীতিগুলো উদ্দেশ্যকে স্পষ্ট করতে পারে, কিন্তু ভুল করে একটি শর্ত বাদ দিলে এটি নিজেই আপনাকে আউট করে দিতে পারে।\n\n### Postgres কিভাবে জানে কে ব্যবহারকারী\n\nRLS-কে একটি নির্ভরযোগ্য “কে কল করছে” মান দরকার। সাধারণ অপশনগুলো:\n\n- প্রতি অনুরোধে সেট করা সেশন ভ্যারিয়েবল (উদাহরণ: app.user_id এবং app.tenant_id)।\n- JWT ক্লেইমকে API লেয়ার দ্বারা সেশন সেটিংসে ম্যাপ করা।\n- রোল সোয়িচিং (SET ROLE ...) per request, যা কাজ করতে পারে কিন্তু অপারেশনাল ওভারহেড বাড়ায়।\n\nএকটি পদ্ধতি বেছে নিয়ে সারাদিন সব জায়গায় তা প্রয়োগ করুন। পরিষেবা জুড়ে ভিন্ন পরিচয় সূত্র মিশালে দ্রুত ভুল হয়।\n\n### ডিবাগে সাহায্যের জন্য নীতি নামকরণ\n\nএকটি সম্ভাব্য কনভেনশন ব্যবহার করুন যাতে স্কিমা ডাম্প এবং লগ পাঠযোগ্য থাকে। উদাহরণ: {table}__{action}__{rule}, যেমন projects__select__tenant_match।\n\n## ধাপে ধাপে: একটি টেবিলে RLS যোগ করুন এবং কাজ করছে প্রমাণ করুন\n\nআপনি যদি RLS-এ নতুন হন, একটি টেবিল দিয়ে শুরু করুন এবং ছোট একটি প্রুফ তৈরি করুন। লক্ষ্য হল পুরো কভারেজ নয়—লক্ষ্য হল ডাটাবেস এমনভাবে কেটে দিয়েছে যে অ্যাপ বাগ হলেও ক্রস-টেন্যান্ট অ্যাক্সেস বন্ধ থাকে।\n\n### অনুশীলনের জন্য একটি ছোট টেবিল\n\nধরুন একটি সরল projects টেবিল। প্রথমে tenant_id এমনভাবে যোগ করুন যাতে লেখা ভেঙে না যায়।\n\nsql\nALTER TABLE projects ADD COLUMN tenant_id uuid;\n\n-- Backfill existing rows (example: everyone belongs to a default tenant)\nUPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid\nWHERE tenant_id IS NULL;\n\nALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;\n\n\nএরপর মালিকানা ও অ্যাক্সেস আলাদা করুন। একটি প্রচলিত প্যাটার্ন: একটি রোল টেবিলগুলোর মালিক হয় (app_owner), আর অন্য রোল API-র জন্য ব্যবহৃত হয় (app_user)। API রোল টেবিল মালিক না হওয়া উচিত, না হলে এটি পলিসি বাইপাস করতে পারে।\n\nsql\nALTER TABLE projects OWNER TO app_owner;\nREVOKE ALL ON projects FROM PUBLIC;\nGRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;\n\n\nএখন সিদ্ধান্ত নিন অনুরোধ কিভাবে Postgres-কে বলে কোন টেন্যান্ট সে সার্ভ করছে। এক সহজ পদ্ধতি হলো অনুরোধ-স্কোপড সেটিং। আপনার অ্যাপ ট্রানজেকশন খুলার পর সেটি সেট করে।\n\nsql\n-- inside the same transaction as the request\nSELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);\n\n\nRLS চালু করে শুরু করুন রিড অ্যাক্সেস দিয়ে।\n\nsql\nALTER TABLE projects ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY projects_tenant_select\nON projects\nFOR SELECT\nTO app_user\nUSING (tenant_id = current_setting('app.current_tenant')::uuid);\n\n\nদুইটি ভিন্ন টেন্যান্ট চেষ্টা করে এবং সারি গণনা পরিবর্তিত হয় কি না দেখে প্রমাণ করুন এটা কাজ করছে।\n\n### লেখার নিয়ম যোগ করা (WITH CHECK)\n\nরিড পলিসি লেখাকে রক্ষা করে না। WITH CHECK যোগ করুন যাতে INSERT এবং UPDATE-এ কেউ ভুল টেন্যান্টে সারি ঢুকিয়ে না দেয়।\n\nsql\nCREATE POLICY projects_tenant_write\nON projects\nFOR INSERT, UPDATE\nTO app_user\nWITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);\n\n\nব্যবহার করে দ্রুত যাচাই (সহ ত্রুটি) করার সহজ উপায় হল একটি ছোট SQL স্ক্রিপ্ট রাখা যা আপনি প্রতিটি মাইগ্রেশনের পরে পুনরায় চালাতে পারেন:\n\n- BEGIN; SET LOCAL ROLE app_user;\n- SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;\n- INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (ফেল হওয়া উচিত)\n- UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (ফেল হওয়া উচিত)\n- ROLLBACK;\n\nআপনি যদি সেই স্ক্রিপ্ট চালাতে পারেন এবং প্রতিবার একই ফল পান, তাহলে RLS অন্যান্য টেবিলে বিস্তার করার আগে আপনার কাছে নির্ভরযোগ্য বেসলাইন আছে।\n\n## SaaS অ্যাপে পুনরায় ব্যবহারযোগ্য পলিসি প্যাটার্নগুলো\n\nঅধিকাংশ দল একই অনুমোদন চেক বারবার করতে করতে ক্লান্ত হয়ে RLS গ্রহণ করে। ভালো খবর হলো যে প্রয়োজনীয় পলিসি আকারগুলো সাধারণত ধারাবাহিক।\n\n### মালিক সারি বনাম সদস্য সারি\n\nকিছু টেবিল স্বাভাবিকভাবেই একজন ব্যবহারকারীর মালিকানাধীন (নোট, API টোকেন)। অন্যগুলোর মালিকানা টেন্যান্টভিত্তিক যেখানে অ্যাক্সেস সদস্যত্বের উপর নির্ভর করে। এগুলোকে আলাদা প্যাটার্ন হিসেবে দেখুন।\n\nমালিক-শুধু ডেটার জন্য, পলিসিগুলো প্রায়ই চেক করে created_by = app_user_id()। টেন্যান্ট ডেটার জন্য, পলিসিগুলো প্রায়ই চেক করে ব্যবহারকারী কি org-এর একটি সদস্য আছে কি না।\n\nনীতিগুলো পড়তে সহজ রাখতে পরিচয়কে ছোট SQL হেল্পারে কেন্দ্র করে পুনঃব্যবহার করুন:\n\nsql\n-- Example helpers\ncreate function app_user_id() returns uuid\nlanguage sql stable as $$\n select current_setting('app.user_id', true)::uuid\n$$;\n\ncreate function app_is_admin() returns boolean\nlanguage sql stable as $$\n select current_setting('app.is_admin', true) = 'true'\n$$;\n\n\n### রিড নিয়মগুলোকে লেখার নিয়ম থেকে আলাদা রাখুন\n\nরিডগুলো প্রায়ই লেখার থেকে বেশি বিস্তৃত হয়। উদাহরণ: যে কেউ org-এর সদস্য হলে প্রোজেক্ট SELECT করতে পারে, কিন্তু শুধু সম্পাদকরা UPDATE পারে, এবং শুধুমাত্র মালিকেরা DELETE করতে পারে।\n\nস্পষ্ট রাখুন: একটি SELECT-এর জন্য এক পলিসি, INSERT/UPDATE-এর জন্য WITH CHECK সহ একটি পলিসি (রোল অনুযায়ী), এবং DELETE-এর জন্য প্রায়ই আনয় মাতৃক কড়া একটি পলিসি।\n\n### RLS বন্ধ না করে অ্যাডমিন ওভাররাইড\n\n“অ্যাডমিনের জন্য RLS বন্ধ করে দিন” এড়িয়ে চলুন। বদলে নীতিগুলোর মধ্যে একটি এস্কেপ হ্যাচ যোগ করুন, যেমন app_is_admin() — এতে করে আপনি অবিচ্ছিন্ন সেবা রোলকে দুর্ঘটনাক্রমে পূর্ণ অ্যাক্সেস দেবেন না।\n\n### সফট ডিলিট ও স্ট্যাটাস ফ্ল্যাগ\n\nআপনি যদি deleted_at বা status ব্যবহার করেন, তা SELECT পলিসিতে বানানো রাখুন (deleted_at is null)। নাহলে কেউ ফ্ল্যাগগুলো উল্টে উধাও হওয়া সারি “পুনরুজ্জীবিত” করতে পারবে যেগুলো অ্যাপ চেয়েছিল স্থায়ীভাবে শেষ।\n\n### UPSERT: WITH CHECK-কে সদয় রাখুন\n\nINSERT ... ON CONFLICT DO UPDATE-এর ক্ষেত্রে WITH CHECK লিখার পরে থাকা সারির জন্য সন্তুষ্ট হতে হবে। যদি আপনার পলিসি চায় created_by = app_user_id() নিশ্চিত করা, তাহলে নিশ্চিত করুন আপসার্টে ইনসার্টের সময় created_by সেট হচ্ছে এবং আপডেটে তা ওভাররাইট করা হচ্ছে না।\n\nআপনি যদি ব্যাকএন্ড কোড জেনারেট করেন, এই প্যাটার্নগুলো ইন্টারনাল টেমপ্লেটে পরিণত করা ভাল যাতে নতুন টেবিলগুলো ডিফল্ট হিসেবে নিরাপদ শুরু পায়।\n\n## সাধারণ RLS ফুট-গানস যা ডিবাগিং কষ্টদায়ক করে\n\nRLS দারুণ কাজ করে যতক্ষণ না ছোট একটি বিশদ PostgreSQL-কে “এপস-ভিত্তিকভাবে” ডেটা সাকার বা দেখায় বলে মনে হয়। নীচের ভুলগুলোই সবচেয়ে সময় নষ্ট করে।\n\n### নীরব টেন্যান্ট লিক ঘটানোর ফুট-গানস\n\nপ্রথম ফাঁদ হল INSERT ও UPDATE-এ WITH CHECK ভুলে যাওয়া। USING কেবল আপনি কি দেখেন তা নিয়ন্ত্রণ করে, আপনি কি তৈরি করতে পারেন তা নয়। WITH CHECK ছাড়া একটি অ্যাপ বাগ ভুল টেন্যান্টে সারি লিখতে পারে, এবং আপনি তা লক্ষ্য করবেন না কারণ একই ব্যবহারকারী সেটি পড়তে পারবে না।\n\nআরেকটি সাধারণ লিক হল “লিকি জয়েন”। আপনি সঠিকভাবে projects-এ ফিল্টার করেন, তারপর invoices, notes, বা files-এ জয়েন করেন যারা একইভাবে সুরক্ষিত নয়। সমাধানটি কঠোর কিন্তু সরল: টেন্যান্ট ডেটা প্রকাশ করতে পারে এমন প্রতিটি টেবিলের নিজস্ব পলিসি থাকা দরকার, এবং ভিউগুলো কেবল এক টেবিল নিরাপদ হওয়ার ওপর নির্ভর করা উচিত নয়।\n\nসাধারন ব্যর্থতার প্যাটার্নগুলো দ্রুত দেখা যায়:\n\n- একটি রিড পলিসি আছে, কিন্তু লেখার পলিসি WITH CHECK বাদ আছে।\n- একটি পলিসি এমন একটি জয়েন ব্যবহার করে যা অন্য টেবিলে নিরাপদ নয়।\n- অ্যাক্সেস একটি ভিউতে রক্ষা করা হয়, কিন্তু অধীনস্থ টেবিল এখনও খোলা।\n- আপনি ধরে নেন “অ্যাপ সবসময় tenant_id সেট করে”, এবং একটি ব্যাকগ্রাউন্ড জব ভুলে যায়।\n- আপনি সুপারইউজার রোলে টেস্ট করেন, তাই বাস্তব আচরণ কখনও দেখা যায় না।\n\n### আচরণ বিভ্রান্তিকর করে এমন ফুট-গানস\n\nএকই টেবিলকে রেফারেন্স করা নীতিগুলো (সরাসরি বা ভিউয়ের মাধ্যমে) রিকারশন করলে বিস্ময়কর ফল দেয়। একটি পলিসি সদস্যত্ব চেক করতে এমন একটি ভিউ কুয়েরি করতে পারে যা আবার সুরক্ষিত টেবিল পড়ে, ফলে এরর, ধীর কুয়েরি, বা এমন একটি পলিসি তৈরি হতে পারে যা কখনই মেলে না।\n\nরোল সেটআপও বিভ্রান্তির উৎস। টেবিল মালিক এবং উর্ধ্বতন রোল RLS বাইপাস করতে পারে, তাই আপনার টেস্টগুলো সঠিক নীচু-অধিকার রোলে চালানো উচিত।\n\nSECURITY DEFINER ফাংশন সম্পর্কে সতর্ক থাকুন। এগুলো ফাংশনের মালিকের অধিকার নিয়ে চলে, তাই একটি হেল্পার যেমন current_tenant_id() ঠিক থাকতে পারে, কিন্তু এমন একটি “সুবিধাজনক” ফাংশন যা ডেটা পড়ে সেটা অনিচ্ছাকৃতভাবে টেন্যান্ট ছাড়িয়ে পড়তে পারে যদি আপনি তা RLS সম্মান করে তৈরি না করেন।\n\nএছাড়া security definer ফাংশনগুলোর ভিতরে নিরাপদ search_path সেট করুন। না করলে ফাংশন সেই সেশনের অবস্থার ওপর নির্ভর করে একই নামে ভিন্ন অবজেক্ট পেয়ে নিতে পারে, এবং আপনার পলিসি লজিক নীরবে ভুল জিনিসের দিকে ইঙ্গিত করতে পারে।\n\n## RLS ডিবাগিং: কী ঘটছে তা দেখার ব্যবহারিক উপায়গুলো\n\nRLS বাগগুলো সাধারণত অনুপস্থিত প্রসঙ্গজনিত — “খারাপ SQL” নয়। একটি নীতি কাগজে সঠিক হলেও ব্যর্থ হতে পারে কারণ সেশন রোল আপনি ভেবেছেন তার চেয়ে আলাদা, অথবা অনুরোধ কখনই আপনার নীতির জন্য প্রয়োজনীয় টেন্যান্ট/ব্যবহারকারী ভ্যালু সেট করেনি।\n\nএকটি নির্ভরযোগ্য উপায় প্রোডuction রিপোর্ট পুনরুত্পাদন করা হল একই সেশন সেটআপ লোকালেও মিরর করা এবং একই কুয়েরিটি চালানো। সাধারণত এর মানে:\n\n- SET ROLE app_user; (বা প্রকৃত API রোল)\n- SELECT set_config('app.tenant_id', 't_123', true); এবং SELECT set_config('app.user_id', 'u_456', true);\n- আপনার অ্যাপ চালানো একই SQL কুয়েরি চালান (প্যারামিটারসহ)\n- Postgres কি দেখছে তা নিশ্চিত করুন: SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);\n\nযখন আপনি নিশ্চিত না কোন পলিসি প্রয়োগ হচ্ছে, অনুমান না করে ক্যাটালগ চেক করুন। pg_policies প্রতিটি নীতি, কমান্ড, এবং USING ও WITH CHECK এক্সপ্রেশন দেখায়। এটিকে pg_class-এর সাথে জোড়া দিন যাতে নিশ্চিত হন টেবিলে RLS চালু আছে এবং বাইপাস করা হয়নি।\n\nপারফরম্যান্স সমস্যা মনে হতে পারে অথরাইজেশন সমস্যা। একটি নীতি যা সদস্যত্ব টেবিলে জয়েন করে বা একটি ফাংশন কল করে সেটা সঠিক হলেও টেবিল বড় হলে ধীর হতে পারে। পুনরুত্পাদিত কুয়েরিতে EXPLAIN (ANALYZE, BUFFERS) চালান এবং সিকোয়েন্সিয়াল স্ক্যান, অপ্রত্যাশিত নেস্টেড লুপ, বা দেরিতে প্রয়োগ হওয়া ফিল্টার খুঁজুন। (tenant_id, user_id) এবং সদস্যত্ব টেবিলগুলোর উপর অনুপস্থিত ইনডেক্স সাধারণ কারণ।\n\nএছাড়া অ্যাপ লেয়ারে প্রতি অনুরোধে তিনটি মান লগ করুন: টেন্যান্ট আইডি, ব্যবহারকারী আইডি, এবং ডাটাবেস রোল। যখন এগুলো আপনি মনে করছেন সেটার সাথে মেলে না, RLS “ভুল” আচরণ করবে কারণ ইনপুটগুলোই ভুল।\n\nটেস্টের জন্য কয়েকটি সিড টেন্যান্ট রাখুন এবং ব্যর্থতাগুলো স্পষ্ট করে তুলুন। একটি ছোট টেস্ট সুইট সাধারণত রাখবে: “Tenant A tenant B পড়তে পারবে না”, “মেম্বার হওয়া ছাড়া ব্যবহারকারী প্রোজেক্ট দেখতে পারবে না”, “মালিক আপডেট করতে পারে, ভিউয়ার পারে না”, “ইনসার্ট ব্লক হয় যদি tenant_id কনটেক্সটের সঙ্গে মেল না করে”, এবং “অ্যাডমিন ওভাররাইড যেখানে উদ্দেশ্য ছিল সেখানে শুধুমাত্র প্রযোজ্য”।\n\n## প্রোডাকশনের জন্য RLS-এর পূর্ব-রিলিজ চেকলিস্ট\n\nRLS-কে ফিচার টগল হিসেবে নয়, সিটবেল্ট হিসেবে বিবেচনা করুন। ছোট ভুলগুলো “সবার ডেটা সবাই দেখতে পাবে” বা “সবকিছু শূন্য সারি ফিরায়” তে পরিণত হতে পারে।\n\n### ডেটা মডেল ও নীতি আকার\n\nনিশ্চিত করুন টেবিল ডিজাইন ও নীতি নিয়মগুলো আপনার টেন্যান্ট মডেলের সাথে মেলে:\n\n- প্রতিটি টেন্যান্ট-অধিকারভুক্ত টেবিলে একটি পরিষ্কার টেন্যান্ট কী থাকা উচিত (সাধারণত tenant_id)। যদি না থাকে, কেন নেই তা নথিভুক্ত করুন (উদাহরণ: গ্লোবাল রেফারেন্স টেবিল)।\n- প্রতিটি টেন্যান্ট-অধিকারভুক্ত টেবিলে RLS চালু করুন, শুধুমাত্র “মুখ্য” টেবিল না। যদি কিছু পথ কখনই বাইপাস করা যাবে না, তখন সেই টেবিলে FORCE ROW LEVEL SECURITY বিবেচনা করুন।\n- রিড ও রাইট নিয়ম আলাদা রাখুন। রিডে USING ব্যবহার করুন। লেখার ক্ষেত্রে WITH CHECK থাকা দরকার যাতে ইনসার্ট ও আপডেট কোনো সারিকে অন্য টেন্যান্টে সরাতে না পারে।\n- পলিসি প্রেডিকেটগুলো ইনডেক্স-ফ্রেন্ডলি রাখুন। যদি নীতিগুলো tenant_id দিয়ে ফিল্টার করে বা সদস্যত্ব টেবিল জয়েন করে, তখন মিলানো ইনডেক্স যোগ করুন।\n\nএকটি সরল স্যানিটি সিনারিও: একটি টেন্যান্ট A ব্যবহারকারী তাদের নিজস্ব ইনভয়েস পড়তে পারে, কেবলমাত্র টেন্যান্ট A'র জন্য ইনভয়েস ইনসার্ট করতে পারে, এবং টেন্যান্ট আইডি পরিবর্তন করে ইনভয়েস আপডেট করতে পারবে না।\n\n### রোল, পারফরম্যান্স, এবং টেস্ট\n\nRLS কেবল আপনার অ্যাপ যে রোল ব্যবহার করে তার মত শক্তিশালী।\n\n- নিশ্চিত করুন অ্যাপ কখনও সুপারইউজার, টেবিল মালিক, বা এমন কোনো রোলে কানেক্ট করে না যার bypassrls আছে।\n- প্রোডাকশন-সদৃশ ডেটা ভলিউম নিয়ে কিছু বাস্তব কুয়েরি চালান এবং কুয়েরি প্ল্যান পরীক্ষা করুন।\n- স্বয়ংক্রিয় নেগেটিভ টেস্ট যোগ করুন যা ক্রস-টেন্যান্ট অ্যাক্সেস ব্যর্থ হয় সেটা প্রমাণ করে।\n\n## উদাহরণ: সদস্যত্ব-ভিত্তিক অ্যাক্সেস সহ মাল্টি-টেন্যান্ট প্রোজেক্টস অ্যাপ\n\nএকটি B2B অ্যাপ কল্পনা করুন যেখানে কোম্পানি (orgs) প্রোজেক্ট রাখে, এবং প্রোজেক্টে টাস্ক থাকে। ব্যবহারকারীরা একাধিক org-এ থাকতে পারে, এবং একজন ব্যবহারকারী কিছু প্রোজেক্টের সদস্য নাও হতে পারে। RLS-এর জন্য এটি ভাল ফিট কারণ ডাটাবেস API এন্ডপয়েন্ট ভুলে গেলেও টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে পারে।\n\nসরল মডেল: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). tasks-এ থাকা org_id ইচ্ছাকৃত—এটি নীতিগুলো সহজ রাখে এবং জয়েন করার সময় বিস্ময় কমায়।\n\nক্লাসিক লিকটি ঘটে যখন টাস্কগুলিতে কেবল project_id থাকে এবং আপনার পলিসি অ্যাক্সেস পরীক্ষা করতে projects-এ জয়েন করে। একটি ভুল (প্রজেক্টে permissive পলিসি, জয়েন যা শর্ত হারায়, বা ভিউ যা কনটেক্সট বদলে দেয়) অন্য একটি org-এর টাস্ক এক্সপোজ করতে পারে।\n\nএকটি নিরাপদ মাইগ্রেশন পথ যা প্রোডাকশন ট্র্যাফিক ভাঙবে না:\n\n- প্রথমে স্কিমা পরিবর্তন পাঠান (tasks-এ org_id যোগ করুন, সদস্যত্ব টেবিল যোগ করুন)।\n- projects.org_id থেকে tasks.org_id ব্যাকফিল করুন, তারপর NOT NULL যোগ করুন।\n- পলিসি যোগ করুন কিন্তু স্টেজিং-এ পরীক্ষা করার সময় RLS ডিসেবলই রাখুন।\n- RLS এনাবেল করুন, তারপর ফোর্স করুন, এবং তারপর পুরনো অ্যাপ-সাইড ফিল্টারগুলো সরান।\n\nসাপোর্ট অ্যাক্সেস সাধারণত একটি সংকীর্ণ ব্রেক-গ্লাস রোলে রাখা ভালো, RLS বন্ধ করার বদলে। এটি সাধারণ সাপোর্ট অ্যাকাউন্ট থেকে আলাদা রাখুন এবং যখন ব্যবহার করা হয় তা স্পষ্ট করুন।\n\nনীতিগুলো নথিভুক্ত করুন যাতে ধীরে ধীরে পলিসি ভ্রান্ত না হয়: কোন সেশন ভ্যারিয়েবলগুলো সেট করতে হবে (user_id, org_id), কোন টেবিলে org_id থাকতে হবে, “member” মানে কী, এবং কিছু SQL উদাহরণ যা ভুল org-এ চালালে 0 সারি ফিরিয়ে আনা উচিত।\n\n## পরবর্তী ধাপ: RLS নিরাপদে রোলআউট করুন এবং এটি রক্ষণাবেক্ষণযোগ্য রাখুন\n\nRLS সহজ হয়ে যায় যদি আপনি এটাকে পণ্য পরিবর্তন হিসেবে আচরণ করেন। ছোট খণ্ডে রোলআউট করুন, টেস্ট দিয়ে আচরণ প্রমাণ করুন, এবং প্রতিটি নীতির কারণ স্পষ্ট লিখে রাখুন।\n\nএকটি কার্যকর রোলআউট প্ল্যান সাধারণত:\n\n- একটি স্পষ্ট টেন্যান্ট মালিকানায় থাকা টেবিল দিয়ে শুরু করুন (উদাহরণ: projects) এবং সেটি লকডাউন করুন।\n- কয়েকটি রোল (মালিক, সদস্য, আউটসাইডার) এর জন্য অনুমোদিত ও অবরুদ্ধ রিড/রাইট কেস কভার করে টেস্ট যোগ করুন।\n- ব্যাচে বিস্তার করুন (একটি ফিচার এরিয়া একবারে) যাতে আপনি এক সেশনে ডিবাগ করতে পারেন।\n- রোলআউটের সময় পারমিশন ত্রুটিগুলো মনিটর করুন এবং কম ঝুঁকির উইন্ডোতে ডিপ্লয় করুন।\n\nপ্রথম টেবিল স্থির হলে, নীতি পরিবর্তনগুলো ইচ্ছাকৃত করুন। মাইগ্রেশনগুলোতে নীতি রিভিউ ধাপ যোগ করুন, এবং প্রতিটি নীতির উদ্দেশ্য (কে কি এবং কেন অ্যাক্সেস করবে) লিখে রাখুন সাথে মিল রেখে একটি টেস্ট আপডেট দিন। এটি “আরেকটা OR যোগ করুন” পলিসি থেকে ধীরে ধীরে ছিদ্র হওয়া রোধ করবে।\n\nযদি আপনি দ্রুত এগোচ্ছে থাকেন, Koder.ai (koder.ai) মত টুলগুলো Go + PostgreSQL স্টার্টিং পয়েন্ট জেনারেট করতে সাহায্য করতে পারে, তারপর আপনি একই ডিসিপ্লিন ব্যবহার করে RLS পলিসি ও টেস্ট লেয়ার করতে পারবেন।\n\nশেষে, রোলআউটের সময় নিরাপত্তা রেল রাখুন। পলিসি মাইগ্রেশনের আগে স্ন্যাপশট নিন, রোলব্যাক প্র্যাকটিস করুন যতক্ষণ না এটা বিরক্তিকর না লাগে, এবং সাপোর্টের জন্য একটি ছোট ব্রেক-গ্লাস পথ রাখুন যা পুরো সিস্টেমে RLS বন্ধ করে না।RLS PostgreSQL-কে নিয়ম করে দেয় যে কোন সারিগুলো একটি অনুরোধের জন্য দেখা বা লেখার যোগ্য — সুতরাং টেন্যান্ট বিচ্ছিন্নতা সম্পূর্ণরূপে প্রত্যেক এন্ডপয়েন্টকে সঠিক WHERE tenant_id = ... মনে রাখার ওপর নির্ভর করে না। প্রধান সুবিধা হলো—অ্যাপ বড় হলে একবার মিস করা ফিল্টার থেকে হওয়া বাগগুলো কমানো।
যখন অ্যাক্সেস নিয়মগুলো সারি-ভিত্তিক এবং নিরবচ্ছিন্ন—যেমন টেন্যান্ট বিচ্ছিন্নতা বা সদস্যত্ব-ভিত্তিক অ্যাক্সেস—এবং আপনার কাছে নানা রকম কুয়েরি পথ থাকে (সার্চ, এক্সপোর্ট, অ্যাডমিন স্ক্রীন, ব্যাকগ্রাউন্ড জব), তখন RLS-এর অতিরিক্ত জটিলতা মূল্যবান হয়ে ওঠে। সাধারণত এটি উপযুক্ত নয় যদি নিয়মগুলো বেশিরভাগ ক্ষেত্রেই ফিল্ড-ভিত্তিক, ব্যতিক্রমসম্বলিত, বা ব্যাপক রিপোর্টিং যেখানে ক্রস-টেন্যান্ট রিড দরকার।
RLS সারি দৃশ্যমানতা এবং মৌলিক লিখনের নিয়ন্ত্রণ দেয়, কিন্তু অন্য কিছু থেকে আপনাকে রক্ষা করে না। কলাম-গোপনীয়তা সাধারণত ভিউ এবং কলাম প্রিভিলেজ দিয়ে মোকাবিলা করতে হয়, এবং জটিল ব্যবসায়িক নিয়ম (যেমন বিলিং মালিকানা বা অনুমোদন প্রবাহ) এখনও অ্যাপ্লিকেশন লজিক বা সাবধানে ডিজাইন করা ডাটাবেস কনস্ট্রেইন্টে থাকা উচিত।
নিম্ন-প্রিভিলেজ রোল বানান যা API ব্যবহার করবে (টেবিল মালিক নয়), RLS চালু করুন, তারপর একটি SELECT পলিসি এবং INSERT/UPDATE-এর জন্য WITH CHECK পলিসি যোগ করুন। অনুরোধ-স্কোপড সেশন ভ্যারিয়েবল (যেমন app.current_tenant) সেট করে যাচাই করুন যে সেটা বদলালে আপনি যে সারিগুলো দেখতে/লেখতে পারেন তা বদলে যাচ্ছে।
একটি সাধারণ পদ্ধতি হল প্রতি অনুরোধে সেশন ভ্যারিয়েবল সেট করা, যেমন app.tenant_id এবং app.user_id। কী জারুরি—সব কোডপাথ (ওয়েব অনুরোধ, জব, স্ক্রিপ্ট) একই ভ্যালুগুলো সেট করবে। যদি না করে, আপনি বিভ্রান্তিকর "শূন্য সারি" আচরণ পাবেন।
USING নিয়ন্ত্রণ করে কোন বিদ্যমান সারি দেখা যায় এবং কোন সারিগুলো SELECT, UPDATE, DELETE-এর টার্গেট হতে পারে। WITH CHECK নিয়ন্ত্রণ করে কোন নতুন বা পরিবর্তিত সারি INSERT/UPDATE-এ অনুমোদিত হবে—এটি নিশ্চিত করে যে কেউ ভুল টেন্যান্ট আইডি দিয়ে অন্য টেন্যান্টে লেখা করতে পারবে না।
কারণ যদি আপনি কেবল USING যোগ করেন, তাহলে একটি বাগযুক্ত এন্ডপয়েন্ট এখনও ভুল টেন্যান্টে সারি INSERT বা UPDATE করতে পারে, এবং সেটা আপনি খুঁজে পাবেন না কারণ একই ব্যবহারকারী সেই খারাপ সারিটি পড়তে পারবে না। তাই পড়ার নিয়মের সঙ্গে মিল রেখে লেখার জন্য WITH_CHECK সারি যোগ করা জরুরি যাতে খারাপ ডেটা প্রথমে তৈরি না হয়।
নীতিগুলো সহজ ও দ্রুত রাখার জন্য টেন্যান্ট কী (যেমন org_id) সরাসরি টেন্যান্ট-অধিকারভুক্ত টেবিলগুলোতে রাখুন, এমনকি যদি তারা অন্য টেবিলকে রেফারেন্স করে। স্পষ্ট org_memberships ও (প্রয়োজন হলে) project_memberships রাখুন যাতে পলিসিগুলো একটি ইনডেক্স করা লুকআপে শেষ হয়, জটিল ইনফারেন্সে নয়।
প্রথমে আপনার অ্যাপ যে সেশন কনটেক্সট ব্যবহার করে তা স্থানীয়ভাবে মিলিয়ে পুনরুত্পাদন করুন: একই রোল এবং সেশন সেটিংস সেট করে সঠিক SQL কুয়েরি চালান। তারপর নিশ্চিত করুন RLS চালু আছে ও pg_policies দেখে কোন USING ও WITH CHECK এক্সপ্রেশানগুলো প্রযোজ্য—কারণ RLS-এর ভুল প্রায়ই সনাক্ত হয়ে না দেখা যায়নি সেশন ইনপুট ভুল থাকায়।
হ্যাঁ—জেনারেট করা কোডকে একটি শুরু হিসেবে নিন, নিরাপত্তার সিস্টেম হিসেবে নয়। যদি আপনি Koder.ai দিয়ে Go + PostgreSQL ব্যাকএন্ড জেনারেট করেন, আপনাকে এখনও আপনার টেন্যান্ট মডেল নির্ধারণ করতে হবে, সেশন আইডেন্টিটি কনসিস্টেন্ট করতে হবে, এবং নতুন টেবিলগুলো সঠিক সুরক্ষার ছাড়া পাঠানো না হয় সেটা নিশ্চিত করতে পলিসি ও টেস্ট যোগ করা দরকার।