PostgreSQL-এ অর্গ চার্ট মডেল করা: adjacency lists বনাম closure
PostgreSQL-এ অর্গ চার্ট মডেল করুন—adjacency list বনাম closure table তুলনা করুন, ফিল্টারিং, রিপোর্টিং এবং পারমিশন চেকের স্পষ্ট উদাহরণসহ।

একটি অর্গ চার্টকে কী সমর্থন করতে হবে
একটি অর্গ চার্ট হলো যে কেউ কার কাছে রিপোর্ট করে এবং কিভাবে টিমগুলো বিভাগে গুচ্ছবদ্ধ হয় তার মানচিত্র। PostgreSQL-এ অর্গ চার্ট মডেল করলে আপনি শুধু প্রতিটি ব্যক্তির কাছে manager_id সংরক্ষণ করছেন না — আপনি আসল কাজকে সহায়তা করছেন: অর্গ ব্রাউজিং, রিপোর্টিং, এবং অ্যাক্সেস নিয়ম।
বেশিরভাগ ব্যবহারকারী তিনটি জিনিসই সামান্য প্রতীক্ষার মধ্যে পেতে চায়: অর্গ অন্বেষণ, লোক খোঁজা, এবং ফলাফলকে “আমার এলাকা” অনুযায়ী ফিল্টার করা। তারা এছাড়া আপডেটগুলো নিরাপদ থাকার প্রত্যাশাও করে। যখন একজন ম্যানেজার বদলে যায়, চার্টটি সারাবিশ্বে ঠিক হওয়া উচিত যাতে রিপোর্ট বা পারমিশন ভাঙে না।
ব্যবহারে, একটি ভাল মডেল কয়েকটি ঘনঘন প্রশ্নের উত্তর দিতে পারে:
- এই ব্যক্তির কমান্ড চেইন কি (শীর্ষ পর্যন্ত)?
- এই ম্যানেজারের অধীনে কে আছে (ডাইরেক্ট রিপোর্ট এবং পুরো সাবট্রি)?
- কিভাবে লোকেরা ড্যাশবোর্ডের জন্য টিম ও বিভাগের মধ্যে গুচ্ছবদ্ধ হয়?
- পুনর্গঠনগুলো কীভাবে গ্লিচ ছাড়া হয়?
- কে কী দেখতে পারে, অর্গ স্ট্রাকচারের ভিত্তিতে?
এটি একটি সাধারণ গাছের চেয়েও জটিল হয়ে যায় কারণ সংস্থাগুলো ঘনঘন পরিবর্তিত হয়। টিমগুলি বিভাগ পরিবর্তন করে, ম্যানেজাররা গ্রুপ বদলে দেয়, এবং কিছু ভিউ কেবল "লোক যারা কার কাছে রিপোর্ট করে" নয়। উদাহরণস্বরূপ: একজন ব্যক্তি একটি টিমের সদস্য, এবং টিমগুলো বিভাগে থাকে। পারমিশন আরেকটি স্তর যোগ করে: অর্গের আকৃতি আপনার সিকিউরিটি মডেলের অংশ হয়ে যায়, শুধুই একটি চার্ট নয়।
কিছু পদবী নকশাগুলোকে পরিষ্কার রাখে:
- একটি নোড হলো হায়ারার্কির এক আইটেম (একজন ব্যক্তি, একটি টিম, বা একটি বিভাগ)।
- একটি প্যারেন্ট হলো সরাসরি উপরের নোড (একটি ম্যানেজার, বা একটি বিভাগ যা একটি টিমকে অ্যাসাইন করে)।
- একটি Ancestor হলো যে কোন উপরের নোড যে কোনো দূরত্বে (আপনার ম্যানেজারের ম্যানেজার)।
- একটি Descendant হলো যে কোন নোড নিচে যে কোনো দূরত্বে (আপনার অধীনে সবাই)।
উদাহরণ: যদি Sales একটি নতুন VP-র অধীনে চলে যায়, দুইটি জিনিস তৎক্ষণাত সত্য থাকা উচিত। ড্যাশবোর্ডগুলো এখনও “সমস্ত Sales” ফিল্টার করে, এবং নতুন VP-র পারমিশনগুলো স্বয়ংক্রিয়ভাবে Sales-টিকে কভার করে।
টেবিল ডিজাইন বেছে নেওয়ার আগে সিদ্ধান্তগুলো
স্কিমা চূড়ান্ত করার আগে, আপনার অ্যাপ প্রতিদিন কোন প্রশ্নগুলোর উত্তর দিতে হবে তা স্পষ্ট করুন। "কে কার কাছে রিপোর্ট করে?" কেবল শুরুতে। অনেক অর্গ চার্টে দেখাতে হয় কে একটি বিভাগের নেতৃত্ব দেয়, কে টিমের ছুটিসম্পর্কিত অনুমোদন দেয়, এবং কে একটি রিপোর্ট দেখতে পারে।
আপনার স্ক্রিন এবং পারমিশন চেকগুলো যে প্রশ্নগুলো জিজ্ঞেস করবে সেগুলো লিখে রাখুন। যদি আপনি প্রশ্নগুলো নামতে না পারেন, তাহলে এমন একটি স্কিমা পাবেন যা দেখতে ঠিক আছে কিন্তু কুয়েরি করতে কষ্ট হবে।
যে সিদ্ধান্তগুলো সবকিছুকে গঠন করে:
- কোন কুয়েরিগুলো দ্রুত হতে হবে: সরাসরি ম্যানেজার, CEO পর্যন্ত চেইন, একটি লিডারের অধীনে পুরো সাবট্রি, নাকি “এই বিভাগের সবাই”?
- এটা কি কঠোর গাছ (এক ম্যানেজার) নাকি ম্যাট্রিক্স অর্গ (একাধিক ম্যানেজার বা লিড)?
- বিভাগগুলো কি একই হায়ারার্কির নোড কি না, নাকি আলাদা অ্যাট্রিবিউট (যেমন প্রতিটি ব্যক্তির উপর
department_id)? - কেউ কি একাধিক টিমে থাকতে পারে (শেয়ার্ড সার্ভিস, স্কোয়াড)?
- পারমিশনগুলো কিভাবে প্রবাহিত হয়: ট্রির নিচে, ট্রির উপরে, নাকি উভয় দিকে?
এসব পছন্দ নির্ধারণ করে কোন ডেটাকে “সঠিক” বলা হবে। যদি Alex একই সঙ্গে Support এবং Onboarding নেতৃত্ব দেয়, একটি একক manager_id বা "প্রতিটি টিমে এক নেতা" নিয়ম কাজ নাও করতে পারে। আপনি হয়তো একটি join টেবিল (leader টু team) বা একটি পরিষ্কার নীতি প্রয়োজন যেমন "একটি প্রাইমারি টিম, প্লাস ডটেড-লাইন টিম"।
বিভাগগুলো আরেকটি বিভক্তি। যদি বিভাগগুলো নোড হয়, আপনি প্রকাশ করতে পারবেন "Department A-তে Team B আছে এবং তাতে Person C আছে"। যদি বিভাগ আলাদা থাকে, আপনি department_id = X দিয়ে ফিল্টার করবেন, যা শুরুতে সহজ কিন্তু টিমগুলি বিভাগ জুড়ে ছড়িয়ে পড়লে ভেঙে পড়তে পারে।
অবশেষে, সহজ ভাষায় পারমিশন নির্ধারণ করুন। "একটি ম্যানেজার তার অধীনে থাকা সকলের বেতন দেখতে পারে, কিন্তু পিয়ারদের নয়" হলো একটি নিচে-দিকের নিয়ম। "যে কেউ তাদের ম্যানেজমেন্ট চেইন দেখতে পারে" হলো উপরে-দিকের নিয়ম। এটি আগে নির্ধারণ করুন কারণ এটি ঠিক কোন হায়ারার্কি মডেল স্বাভাবিক মনে হবে এবং কোনটি পরে ব্যয়বহুল কুয়েরি চাপাবে।
Adjacency list: ম্যানেজার ও টিমের জন্য একটি সরল স্কিমা
যদি আপনি সবচেয়ে কম অংশ চাচ্ছেন, adjacency list হল ক্লাসিক শুরু। প্রতিটি ব্যক্তি তাদের সরাসরি ম্যানেজারের একটি পয়েন্টার সংরক্ষণ করে, এবং সেই পয়েন্টারগুলো অনুসরণ করে গাছ তৈরি হয়।
একটি ন্যূনতম সেটআপ এরকম দেখা যায়:
create table departments (
id bigserial primary key,
name text not null unique
);
create table teams (
id bigserial primary key,
department_id bigint not null references departments(id),
name text not null,
unique (department_id, name)
);
create table employees (
id bigserial primary key,
full_name text not null,
team_id bigint references teams(id),
manager_id bigint references employees(id)
);
আপনি আলাদা টেবিলগুলো বাদ দিয়ে department_name এবং team_name employees-এর কলাম হিসেবেও রাখতে পারেন। সেটি শুরুতে দ্রুত কিন্তু পরবর্তীতে পরিচ্ছন্ন রাখা কঠিন (টাইপো, টিমের নাম পরিবর্তন, এবং অসমঞ্জস্যপূর্ণ রিপোর্টিং)। আলাদা টেবিলগুলো ফিল্টারিং এবং পারমিশন নিয়মগুলো ধারাবাহিকভাবে প্রকাশ করা সহজ করে।
শুরুতেই গার্ডরেল যোগ করুন। খারাপ হায়ারার্কি ডেটা পরে মেরামত করা কষ্টকর। ন্যূনতমভাবে, সেলফ-ম্যানেজমেন্ট প্রতিরোধ করুন (manager_id \u003c\u003e id)। এছাড়া সিদ্ধান্ত নিন একটি ম্যানেজার কি একই টিম বা বিভাগের বাইরে থাকতে পারবে কি না, এবং আপনি কি সফট ডিলেট বা ইতিহাস সংরক্ষণ করতে চান (অডিটিং রিপোর্টিং লাইনের জন্য)।
Adjacency list-এ বেশিরভাগ পরিবর্তন সহজ লেখালেখি: একটি ম্যানেজার পরিবর্তন করলে employees.manager_id আপডেট হয়, এবং টিম সরানো হলে employees.team_id আপডেট হয় (প্রায়ই ম্যানেজারের সাথে একসাথে)। গ্র্যাপ হল একটি ছোট লেখা বড় ডাউনস্ট্রিম প্রভাব ফেলতে পারে। রিপোর্টিং রোলআপ পরিবর্তিত হয়, এবং যে কোনো "ম্যানেজার সব রিপোর্ট দেখতে পারবেন" নিয়ম এখন নতুন চেইন অনুসরণ করবে।
এই সরলতা adjacency list-এর সবচেয়ে বড় শক্তি। দুর্বলতা দেখা যায় যখন আপনি প্রায়ই "এই ম্যানেজারের অধীনে সবাই" দিয়ে ফিল্টার করেন, কারণ তখন সাধারণত প্রতি বার রিকার্সিভ কুয়েরির উপর নির্ভর করতে হয়।
Adjacency list: ফিল্টারিং ও রিপোর্টিং এর সাধারণ কুয়েরি
Adjacency list-এ অনেক দরকারী অর্গ চার্ট প্রশ্ন রিকার্সিভ কুয়েরিতে পরিণত হয়। PostgreSQL-এ এভাবে মডেল করলে এই প্যাটার্নগুলো আপনি বারংবার ব্যবহার করবেন।
সরাসরি রিপোর্ট (এক স্তর)
সবচেয়ে সহজ কেস হলো একটি ম্যানেজারের তাৎক্ষণিক দল:
SELECT id, full_name, title
FROM employees
WHERE manager_id = $1
ORDER BY full_name;
এটা দ্রুত এবং পাঠযোগ্য, কিন্তু এটি কেবল এক স্তর নিচে যায়।
কমান্ড চেইন (উপরের দিকে)
কোনো একজন ব্যক্তি কার কার কাছে রিপোর্ট করে (ম্যানেজার, ম্যানেজারের ম্যানেজার, ইত্যাদি) দেখাতে রিকার্সিভ CTE ব্যবহার করুন:
WITH RECURSIVE chain AS (
SELECT id, full_name, manager_id, 0 AS depth
FROM employees
WHERE id = $1
UNION ALL
SELECT e.id, e.full_name, e.manager_id, c.depth + 1
FROM employees e
JOIN chain c ON e.id = c.manager_id
)
SELECT *
FROM chain
ORDER BY depth;
এটি approvals, escalation path, এবং ম্যানেজার breadcrumb-এ কাজ দেয়।
পুরো সাবট্রি (নিচের দিকে)
একজন লিডারের অধীনে সবাই পেতে (সব স্তর), রিকার্সন উল্টে দিন:
WITH RECURSIVE subtree AS (
SELECT id, full_name, manager_id, department_id, 0 AS depth
FROM employees
WHERE id = $1
UNION ALL
SELECT e.id, e.full_name, e.manager_id, e.department_id, s.depth + 1
FROM employees e
JOIN subtree s ON e.manager_id = s.id
)
SELECT *
FROM subtree
ORDER BY depth, full_name;
একটি সাধারণ রিপোর্ট হলো “leader Y-এর অধীনে department X-এ থাকা সবাই”:
WITH RECURSIVE subtree AS (
SELECT id, department_id
FROM employees
WHERE id = $1
UNION ALL
SELECT e.id, e.department_id
FROM employees e
JOIN subtree s ON e.manager_id = s.id
)
SELECT e.*
FROM employees e
JOIN subtree s ON s.id = e.id
WHERE e.department_id = $2;
Adjacency list কুয়েরিগুলো পারমিশনের জন্য ঝুকিপূর্ণ হতে পারে কারণ অ্যাক্সেস চেকগুলো প্রায়ই পুরো পাথের উপর নির্ভর করে (দেখুন viewer কি কোনো employee-এর ancestor?). যদি কোনো endpoint রিকার্সন ভুলে যায় বা ফিল্টার ভুল জায়গায় প্রয়োগ করে, আপনি রো লিক করতে পারেন। এছাড়া সাইকেল ও অনুপস্থিত ম্যানেজারের মতো ডেটা সমস্যা খেয়াল করুন। একটি খারাপ রেকর্ড রিকার্সন ভাঙতে পারে বা আশ্চর্যজনক ফলাফল দিতে পারে, তাই পারমিশন কুয়েরিগুলোতে সুরক্ষা ও ভাল constraints দরকার।
Closure table: কিভাবে এটি পুরো হায়ারার্কি সংরক্ষণ করে
A closure table প্রতিটি ancestor-descendant সম্পর্কই সংরক্ষণ করে, কেবল সরাসরি ম্যানেজার লিংক নয়। গাছ এক ধাপ করে না হাঁটার বদলে আপনি জিজ্ঞেস করতে পারেন: "এই লিডারের অধীনে কে আছে?" এবং সরল একটি JOIN-এ পুরো উত্তর পেতে পারেন।
আপনি সাধারণত দুইটি টেবিল রাখেন: একটি নোডগুলোর জন্য (লোক বা টিম) এবং একটি হায়ারার্কি পাথের জন্য।
-- nodes
employees (
id bigserial primary key,
name text not null,
manager_id bigint null references employees(id)
)
-- closure
employee_closure (
ancestor_id bigint not null references employees(id),
descendant_id bigint not null references employees(id),
depth int not null,
primary key (ancestor_id, descendant_id)
)
Closure table এ (Alice, Bob) এর মতো জোড়া স্টোর করে যা 뜻 করে "Alice হলো Bob-এর ancestor"। এছাড়াও একটি রো থাকে যেখানে ancestor_id = descendant_id এবং depth = 0। প্রথমদৃষ্টে ওই সেলফ-রো অদ্ভুত লাগে, কিন্তু এটি অনেক কুয়েরিকে পরিষ্কার করে তোলে।
depth জানায় দুইটি নোড কত দূরে: depth = 1 সরাসরি ম্যানেজার, depth = 2 ম্যানেজারের ম্যানেজার, ইত্যাদি। এটি গুরুত্বপূর্ণ যখন সরাসরি রিপোর্ট এবং পরোক্ষ রিপোর্ট আলাদা আচরণ করবে।
মূল সুবিধা হলো পূর্বনির্ধারিত, দ্রুত রিড:
- পুরো সাবট্রি লুকআপ দ্রুত হয় (একজন পরিচালক অধীন সবাই)।
- কমান্ড চেইন সহজ (কাউকে উপরে থাকা সব ম্যানেজার)।
depthব্যবহার করে সরাসরি বনাম পরোক্ষ সম্পর্ক পৃথক করা যায়।
খরচ হলো আপডেটের বজায় রাখার কাজ। যদি Bob-র ম্যানেজার Alice থেকে Dana-তে বদলে যায়, আপনাকে Bob এবং Bob-র নিচে থাকা সবাইয়ের closure রোগুলো পুনর্গঠন করতে হবে। সাধারণ পদ্ধতি হলো: পুরনো ancestor পাথগুলো মুছে ফেলা, তারপর Dana-র ancestor-গোলোর সাথে Bob-র সাবট্রির প্রতিটি নোড মিলিয়ে নতুন পাথ ইনসার্ট করা এবং depth পুনর্গণনা করা।
Closure table: দ্রুত ফিল্টারিং-এর জন্য সাধারণ কুয়েরি
Closure table প্রতিটি ancestor-descendant জোড়া পূর্বে সংরক্ষণ করে (প্রায়শই org_closure(ancestor_id, descendant_id, depth) হিসেবে)। এর ফলে অর্গ ফিল্টারগুলো দ্রুত হয় কারণ বেশিরভাগ প্রশ্ন একটি JOIN-এ সিম্পল হয়ে যায়।
একজন ম্যানেজারের অধীনে সবাই তালিকাভুক্ত করতে, একবার JOIN করুন এবং depth দিয়ে ফিল্টার করুন:
-- Descendants (everyone in the subtree)
SELECT e.*
FROM employees e
JOIN org_closure c
ON c.descendant_id = e.id
WHERE c.ancestor_id = :manager_id
AND c.depth > 0;
-- Direct reports only
SELECT e.*
FROM employees e
JOIN org_closure c
ON c.descendant_id = e.id
WHERE c.ancestor_id = :manager_id
AND c.depth = 1;
কোনো কর্মচারীর সকল ancestor পেতে, JOIN উল্টে দিন:
SELECT m.*
FROM employees m
JOIN org_closure c
ON c.ancestor_id = m.id
WHERE c.descendant_id = :employee_id
AND c.depth > 0
ORDER BY c.depth;
ফিল্টারিং পূর্বানুমানযোগ্য হয়। উদাহরণ: "leader X-এর অধীনে সবাই, কিন্তু কেবলমাত্র department Y-এ":
SELECT e.*
FROM employees e
JOIN org_closure c ON c.descendant_id = e.id
WHERE c.ancestor_id = :leader_id
AND e.department_id = :department_id;
কারণ হায়ারার্কি আগে থেকে গণনা করা থাকে, কাউন্ট ও হয় সরাসরি (রিকার্সন নয়)। এটি ড্যাশবোর্ড ও পারমিশন-স্কোপড টোটালগুলোর জন্য সুবিধাজনক, এবং pagination ও সার্চের সাথে ভালো খেলে কারণ আপনি ORDER BY, LIMIT/OFFSET, এবং ফিল্টার সরাসরি descendant সেটে প্রয়োগ করতে পারেন।
প্রতিটি মডেল পারমিশন ও অ্যাক্সেস চেককে কিভাবে প্রভাবিত করে
একটি সাধারণ অর্গ নিয়ম হলো: একটি ম্যানেজার তার অধীনে থাকা সকলকে দেখতে পায় (কখনো কখনো এডিটও করতে পারে)। আপনি কোন স্কিমা বেছে নেন তা নির্ধারণ করে কত বার আপনাকে "কে কার অধীনে" নির্ণয় করতে হবে।
Adjacency list-এ পারমিশন চেক প্রায়ই রিকার্সন লাগে। যদি একজন ব্যবহারকারী ২০০ কর্মচারীর তালিকা খুলে, সাধারণত আপনি একটি recursive CTE দিয়ে descendant সেট তৈরি করেন এবং টার্গেট রো-গুলোকে তার বিরুদ্ধে ফিল্টার করেন।
Closure table-এ একই নিয়ম প্রায়ই একটি সহজ অস্তিত্ব পরীক্ষা দিয়ে করা যায়: "বর্তমান viewer কি এই কর্মচারীর ancestor?" যদি হ্যাঁ, অনুমোদন করুন।
-- Closure table permission check (conceptual)
SELECT 1
FROM org_closure c
WHERE c.ancestor_id = :viewer_id
AND c.descendant_id = :employee_id
LIMIT 1;
এই সরলতা গুরুত্বপূর্ণ যখন আপনি row-level security (RLS) যোগ করেন, যেখানে প্রতিটি কুয়েরি স্বয়ংক্রিয়ভাবে একটি নিয়ম অন্তর্ভুক্ত করে যেমন "শুধুমাত্র সেই রো গুলো ফেরত দাও যেগুলো viewer দেখতে পারে"। Adjacency list-এ পলিসিটা প্রায়ই রিকার্সন এমবেড করে এবং টিউন করা কঠিন হতে পারে। Closure table-এ পলিসি প্রায়ই সোজা EXISTS (...) চেক হয়।
এজ কেসগুলোই যেখানে পারমিশন লজিক বেশি ভেঙে যায়:
- ডটেড-লাইন রিপোর্টিং: একজন ব্যক্তির কার্যত দুইটি ম্যানেজার থাকে।
- সহকারী এবং ডেলিগেট: অ্যাক্সেস হায়ারার্কির ভিত্তিতে নয়, তাই স্পষ্ট গ্রান্ট স্টোর করুন (সাধারণত মেয়াদসহ)।
- অস্থায়ী অ্যাক্সেস: সময়-সীমাবদ্ধ পারমিশনগুলো অর্গ স্ট্রাকচারে বেক করা উচিত নয়।
- ক্রস-টিম প্রজেক্ট: প্রজেক্ট সদস্যতার ভিত্তিতে অ্যাক্সেস দিন, ম্যানেজমেন্ট চেইন দিয়ে নয়।
আপনি যদি AppMaster (appmaster.io)–এ এটি তৈরি করেন, closure table প্রায়ই ভিজ্যুয়াল ডেটা মডেলে ভালো মানায় এবং অ্যাক্সেস চেকও ওয়েব ও মোবাইল উভয় পরিবেশে সহজ রাখে।
ট্রেডঅফ: গতি, জটিলতা, এবং রক্ষণাবেক্ষণ
সবচেয়ে বড় পছন্দ হলো আপনি কী অপ্টিমাইজ করবেন: সরল লিখা ও ছোট স্কিমা, নাকি "এই ম্যানেজারের অধীনে কে" এবং পারমিশন চেকের দ্রুত পড়া।
Adjacency lists টেবিল ছোট ও আপডেট সহজ রাখে। খরচ পড়ায়: একটি পুরো সাবট্রি সাধারণত রিকার্সন মানে। আপনি যদি আপনার অর্গ ছোট রাখেন, UI কিছু স্তরই লোড করে, বা হায়ারার্কি-ভিত্তিক ফিল্টার কয়েক জায়গায় ব্যবহার হয়, adjacency list ঠিক আছে।
Closure tables উল্টো ট্রেডঅফ দেয়। পড়া দ্রুত হয় কারণ আপনি সাধারণ JOIN দিয়ে সব descendant উত্তর পেয়ে যান। লেখাগুলো জটিল হয় কারণ একটি মুভ বা রিয়োগে অনেক রিলেশনশিপ রো আপডেট করতে হতে পারে।
বাস্তবে, ট্রেডঅফ সাধারণত এরকম দেখা যায়:
- Read performance: adjacency রিকার্সন লাগে; closure বেশিরভাগ ক্ষেত্রে JOIN এবং অর্গ বৃদ্ধির সাথে দ্রুত থাকে।
- Write complexity: adjacency এক
parent_idআপডেট করে; closure এক মুভে অনেক রো আপডেট করে। - Data size: adjacency লোক/টিমের সাথে বাড়ে; closure সম্পর্কের সাথে বাড়ে (কঠিন পরিস্থিতিতে, প্রায় N^2 পর্যন্ত deep tree-র জন্য)।
ইনডেক্সিং দুই মডেলে গুরুত্বপূর্ণ, কিন্তু লক্ষ্য ভিন্ন:
- Adjacency list: parent pointer (
manager_id) ইনডেক্স করুন, এবং সাধারণ ফিল্টার যেমন একটি “active” ফ্ল্যাগ। - Closure table:
(ancestor_id, descendant_id)এবং পাশাপাশি সাধারণ লুকআপের জন্যdescendant_idআলাদাভাবে ইনডেক্স করুন।
একটি সরল নিয়ম: যদি আপনি বিরলভাবে হায়ারার্কি দ্বারা ফিল্টার করেন এবং পারমিশন চেক কেবল "ম্যানেজার সরাসরি রিপোর্ট দেখা" হয়, adjacency list প্রায়ই যথেষ্ট। যদি আপনি নিয়মিতভাবে "VP X-এর অধীনে সবাই" রিপোর্ট চালান, বিভাগ ট্রি দ্বারা ফিল্টার করেন, বা অনেক স্ক্রিন জুড়ে হায়ারার্কিক্যাল পারমিশন প্রয়োগ করেন, closure tables অতিরিক্ত রক্ষণাবেক্ষণের বিনিময়ে সাধারণত উপকার দেয়।
ধাপে ধাপে: adjacency list থেকে closure table-এ সরে আসা
প্রথম দিনেই মডেল বেছে নেওয়া লাগবে না। নিরাপদ পথ হলো আপনার adjacency list (manager_id বা parent_id) রেখে পাশে একটি closure table যোগ করা, তারপর ধীরে ধীরে পড়ার পথগুলো মাইগ্রেট করা। এতে ঝুঁকি কমে এবং আপনি নতুন হায়ারার্কির কুয়েরি ও পারমিশন চেক যাচাই করতে পারবেন।
শুরু করুন একটি closure table (সাধারণত org_closure) তৈরি করে, কলামগুলোর মধ্যে ancestor_id, descendant_id, এবং depth রাখুন। এটিকে আপনার বিদ্যমান employees বা teams টেবিল থেকে আলাদা রাখুন যাতে ব্যাকফিল ও যাচাই করতে পারেন বর্তমান ফিচার ছোঁয় না।
একটি ব্যবহারিক রোলআউট:
- closure table এবং ইনডেক্সগুলো তৈরি করুন, adjacency list-কে সত্যের উৎস রেখে দিন।
- বর্তমান ম্যানেজার সম্পর্ক থেকে closure rows ব্যাকফিল করুন, প্রতিটি নোডের জন্য self-row (
depth = 0) সহ। - স্পট চেক দিয়ে যাচাই করুন: কয়েকজন ম্যানেজার নিয়ে নিশ্চিত করুন দুই মডেলে একই সাবঅর্ডিনেট সেট দেখায়।
- প্রথমে রিড-পাথগুলো স্যুইচ করুন: রিপোর্ট, ফিল্টার, এবং হায়ারার্কিক্যাল পারমিশন closure table থেকে পড়ুক।
- প্রতিটি লিখায় closure আপডেট রাখুন (re-parent, hire, move team)। স্থিতিশীল হলে রিকার্সিভ কুয়েরি অবসান করুন।
যখন আপনি যাচাই করবেন, বিশেষ করে সেই কেসগুলোর দিকে নজর দিন যা প্রায়ই অ্যাক্সেস নিয়ম ভাঙে: ম্যানেজার পরিবর্তন, শীর্ষ-স্তরের লিডার, এবং যাদের কোন ম্যানেজার নেই এমন ব্যবহারকারী।
আপনি যদি AppMaster-এ এটি তৈরি করেন, পুরনো endpointগুলো চালু রেখে নতুনগুলো যোগ করতে পারবেন যা closure table থেকে পড়ে, তারপর ফলাফলে ম্যাচ হলে স্যুইচ করুন।
অর্গ ফিল্টারিং বা পারমিশন ব্রেক করে এমন সাধারণ ভুলগুলো
অর্গ ফিচারগুলো ভাঙার দ্রুততম উপায় হলো হায়ারার্কিকে অসংগত হওয়া। ডেটা সারি বাই সারি দেখলে ঠিক মনে হতে পারে, কিন্তু ছোট ছোট ভুল কুয়েরি ভুল, ধীর পেজ, বা পারমিশন লিক সৃষ্টি করতে পারে।
একটি ক্লাসিক সমস্যা হলো সচেতনহীনভাবে সাইকেল তৈরি করা: A B-কে ম্যানেজ করে, পরে কেউ B-কে A-র ম্যানেজার সেট করে (বা ৩-৪ জনের মধ্যে লুপ)। রিকার্সিভ কুয়েরি অনির্দিষ্টকাল চলতে পারে, ডুপ্লিকেট রো ফেরত দিতে পারে, বা টাইমআউট করতে পারে। Closure table-এও সাইকেল ancestor/descendant রোগুলোর মধ্যে সমস্যার কারণ হতে পারে।
আরেকটি সাধারণ ইস্যু হলো closure drift: আপনি কারো ম্যানেজার বদলে ফেলেছেন, কিন্তু শুধুই সরাসরি সম্পর্ক আপডেট করেছেন এবং সাবট্রির closure রো পুনর্নির্মাণ ভুলে গেছেন। তখন “এই VP-র অধীনে সবাই” ধরনের ফিল্টার পুরাতন ও নতুন স্ট্রাকচারের মিশ্র ফলাফল দিতে পারে। এটি ধরা কঠিন কারণ পৃথক প্রোফাইল পেজগুলো ঠিক দেখাতে পারে।
অর্গ চার্টগুলোও অগোছালো হয়ে যায় যখন বিভাগ ও রিপোর্টিং লাইনগুলো স্পষ্ট নীতির ছাড়াই মিশে যায়। একটি বিভাগ প্রায়ই প্রশাসনিক গোষ্ঠী, আর রিপোর্টিং লাইনগুলো ম্যানেজারের ব্যাপার। যদি আপনি তাদের একই ট্রি হিসেবে বিবেচনা করেন, তখন আপনি অপ্রত্যাশিত আচরণ পাবেন যেমন "department move" আকস্মিকভাবে অ্যাক্সেস পরিবর্তন করা।
পারমিশন সবচেয়ে বেশি ভেঙে যায় যখন চেকগুলো কেবল সরাসরি ম্যানেজার দেখে। যদি আপনি অ্যাক্সেস দিন যখন viewer is manager of employee শুধুই, আপনি পূর্ণ চেইন মিস করবেন। ফলাফল হয় বা তো অতিরিক্ত ব্লক (skip-level ম্যানেজাররা তাদের অর্গ দেখতে পারে না) বা অতিরিক্ত শেয়ারিং (কেউ সাময়িক সরাসরি ম্যানেজার হওয়ার মাধ্যমে অ্যাক্সেস পায়)।
ধীর লিস্ট পেজগুলো সাধারণত প্রতিটি অনুরোধে রিকার্সিভ ফিল্টার চালানোর কারণে স্লো হয় (প্রতিটি ইনবক্স, টিকেট তালিকা, কর্মচারী সার্চ)। যদি একই ফিল্টার সব জায়গায় ব্যবহৃত হয়, তাহলে আপনি বা তো precomputed path (closure table) চান বা অনুমোদিত employee IDs-এর ক্যাশড সেট চান।
কিছু ব্যবহারিক সুরক্ষা:
- ম্যানেজার পরিবর্তন সেভ করার আগে সাইকেল ব্লক করুন এবং যাচাই করুন।
- “department” এর মানে কি তা নির্ধারণ করে রাখুন, এবং রিপোর্টিং থেকে আলাদা রাখুন।
- যদি আপনি closure table ব্যবহার করেন, ম্যানেজার পরিবর্তনে descendant রোগুলো পুনর্নির্মাণ করুন।
- পারমিশন নিয়মগুলো পুরো চেইনের উপর ভিত্তি করে লিখুন, কেবল সরাসরি ম্যানেজার নয়।
- লিস্ট পেজগুলোর জন্য রিকার্সন পুনরায় গণনা না করে অরগ-স্কোপগুলো প্রি-কম্পিউট করুন।
আপনি যদি AppMaster-এ অ্যাডমিন প্যানেল বানান, “change manager” কে একটি সংবেদনশীল ওয়ার্কফ্লো হিসেবে দেখুন: যাচাই করুন, সম্পর্কিত হায়ারার্কি ডেটা আপডেট করুন, এবং তারপরই এটি ফিল্টার ও অ্যাক্সেসকে প্রভাবিত করতে দিন।
চালু করার আগে দ্রুত যাচাই-বাছাই
আপনি আপনার অর্গ চার্টকে “সম্পন্ন” বলার আগে নিশ্চিত করুন আপনি সাধারণ ভাষায় অ্যাক্সেস ব্যাখ্যা করতে পারবেন। কেউ যদি জিজ্ঞেস করে, "কে কর্মচারী X দেখতে পারে, এবং কেন?", আপনাকে একটি নিয়ম এবং একটি কুয়েরি (বা ভিউ) দেখাতে হবে যা এটি প্রমাণ করে।
পারফরম্যান্স হলো পরবর্তী বাস্তবতা পরীক্ষা। Adjacency list-এ "এই ম্যানেজারের অধীনে সবাই দেখাও" একটি রিকার্সিভ কুয়েরি হয় যার গতি গভীরতা ও ইনডেক্সিং-এ নির্ভর করে। Closure table-এ পড়া সাধারণত দ্রুত, কিন্তু আপনাকে লিখার পথের ওপর ভরসা রাখতে হবে যাতে প্রতিটি পরিবর্তন পরে টেবিল সঠিক থাকে।
একটি সংক্ষিপ্ত প্রি-শিপ চেকলিস্ট:
- একটি কর্মচারী বেছে নিন এবং সম্পূর্ণ ভিসিবিলিটি ট্রেস করুন: কোন চেইন অ্যাক্সেস দেয়, এবং কোন রোল তা অস্বীকার করে।
- আপনার প্রত্যাশিত সাইজ দিয়ে একটি ম্যানেজার সাবট্রি কুয়েরির বেঞ্চমার্ক করুন (উদাহরণ: ৫ স্তর গভীর এবং ৫০,০০০ কর্মচারী)।
- খারাপ লেখাগুলো ব্লক করুন: সাইকেল, সেলফ-ম্যানেজমেন্ট, এবং অনাথ নোডগুলোর বিরুদ্ধে constraints ও ট্রানজেকশন চেক।
- রিয়োগ সেফটি টেস্ট করুন: মুভ, মার্জ, ম্যানেজার পরিবর্তন, এবং মাঝপথে ব্যর্থ হলে rollback।
- পারমিশন টেস্ট যোগ করুন যা বাস্তবভিত্তিক রোল (HR, manager, team lead, support) এর জন্য অনুমোদিত ও অ-অনুমোদিত উভয় কেস assert করে।
একটি ব্যবহারিক দৃশ্য যাচাই করুন: একজন সাপোর্ট এজেন্ট কেবল তাদের অ্যাসাইন করা বিভাগের কর্মচারীদের দেখতে পারে, আর একজন ম্যানেজার তাদের পুরো সাবট্রি দেখতে পারে। যদি আপনি PostgreSQL-এ অর্গ চার্ট মডেল করে উভয় নিয়মকে টেস্ট করতে পারেন, তাহলে আপনি প্রায়ই শিপ করার প্রস্তুত হবেন।
AppMaster-এ আপনি যদি এটি তৈরি করেন, তাহলে এই চেকগুলোকে অটোমেটেড টেস্ট হিসেবে রাখুন সেই endpoint-গুলোর চারপাশে যা অর্গ লিস্ট ও প্রোফাইল ফিরিয়ে দেয়, শুধু ডাটাবেস কুয়েরি নয়।
উদাহরণ দৃশ্য এবং পরবর্তীধাপ
ধরি একটি কোম্পানিতে তিনটি বিভাগ আছে: Sales, Support, এবং Engineering। প্রতিটি বিভাগের দুইটি টিম আছে, এবং প্রতিটি টিমের একটি লিড আছে। Sales Lead A তাদের টিমের জন্য ডিসকাউন্ট অনুমোদন করতে পারে, Support Lead B তাদের বিভাগের সব টিকেট দেখতে পারে, এবং VP of Engineering Engineering-এর অধীনে সবকিছু দেখতে পারে।
তারপর একটি রিয়োগ হয়: একটি Support টিম Sales-এর অধীনে চলে যায়, এবং Sales Director ও দুইটি টিম লিডের মধ্যে একটি নতুন ম্যানেজার যোগ করা হয়। পরের দিন কেউ একটি অ্যাক্সেস অনুরোধ করে: "Jamie (একজন Sales analyst)-কে Sales বিভাগের সব কাস্টমার অ্যাকাউন্ট দেখতে দিন, কিন্তু Engineering নয়।"
আপনি যদি adjacency list-এ অর্গ মডেল করেন, স্কিমা সহজ, কিন্তু অ্যাপ কাজগুলো কুয়েরি ও পারমিশন চেকের উপর সরে যায়। “সমস্ত Sales-এ সবাই” ধরনের ফিল্টার সাধারণত রিকার্সন চায়। যখন আপনি approvals যোগ করবেন (যেমন "শুধুমাত্র চেইনের ম্যানেজাররা অনুমোদন করতে পারে"), রিয়োগের পর কীরকম এজকেসগুলো ঘটতে পারে তা গুরুত্বপূর্ণ হয়ে ওঠে।
Closure table-এ রিয়োগগুলোর জন্য লেখার কাজ বেশি, কিন্তু পড়ার দিক সরল হয়। ফিল্টার ও পারমিশন প্রায়ই সহজ JOIN হয়ে যায়: "এই ইউজার কি সেই কর্মচারীর ancestor?" বা "এই টিম কি এই বিভাগের সাবট্রির অংশ?"।
এটি সরাসরি স্ক্রিনগুলোতেও দেখা যায়: লোক পিকারগুলি বিভাগ-স্কোপড, অনুরোধকারীর উপর নিকটতম ম্যানেজারের অনুমোদন রুটিং, প্রশাসনিক ভিউগুলো, এবং একটি নির্দিষ্ট তারিখে কেন অ্যাক্সেস ছিল তার অডিট রেকর্ড।
পরবর্তী ধাপ:
- প্লেইন ভাষায় পারমিশন নিয়ম লিখুন (কে কী দেখতে পাবে, এবং কেন)।
- এমন একটি মডেল বেছে নিন যা সবচেয়ে বেশি ব্যবহৃত চেকগুলো সাথে মেলে (দ্রুত পড়া বনাম সহজ লিখা)।
- একটি অভ্যন্তরীণ অ্যাডমিন টুল বানান যা আপনাকে রিয়োগ, অ্যাক্সেস অনুরোধ, এবং approvals এন্ড-টু-এন্ড পরীক্ষা করতে দেয়।
আপনি যদি দ্রুত ওর্-অ্যাওয়ার অ্যাডমিন প্যানেল ও পোর্টাল বানাতে চান, AppMaster (appmaster.io) একটি কাজের সমাধান হতে পারে: এটি PostgreSQL-ব্যাকড ডেটা মডেল করতে দেয়, ভিজ্যুয়াল Business Process-এ অনুমোদন লজিক কার্যকর করতে দেয়, এবং একই ব্যাকএন্ড থেকে ওয়েব ও নেটিভ মোবাইল অ্যাপ সরবরাহ করে।
প্রশ্নোত্তর
Adjacency list ব্যবহার করুন যখন আপনার সংস্থা ছোট, আপডেট ঘনঘন হয়, এবং অধিকাংশ ইন্টারফেস শুধু সরাসরি রিপোর্ট বা কয়েকটি স্তর দেখতে চায়। Closure table ব্যবহার করুন যখন নিয়মিতভাবে “এই লিডারের অধীনে সবাই” বা বিভাগ-ভিত্তিক ফিল্টার লাগবে, অথবা যখন অনেক পৃষ্ঠায় হায়ারার্কি-ভিত্তিক অনুমতি প্রয়োগ করতে হবে — পড়া অপারেশনগুলো তখন সরল JOIN হয়ে দ্রুত থাকে।
শুরু করুন employees(manager_id) দিয়ে এবং সরাসরি রিপোর্টগুলো পেতে ব্যবহার করুন একটি সাধারণ WHERE manager_id = ? কুয়েরি। শুধুমাত্র যেসব ফিচারের জন্য পুরো ancestry বা সম্পূর্ণ সাবট্রি দরকার (যেমন approvals, “my org” ফিল্টার, বা skip-level dashboards) তখন রিকার্সিভ কুয়েরি যোগ করুন।
সেলফ-ম্যানেজমেন্ট ব্লক করতে একটি চেক রাখুন যেমন manager_id \u003c\u003e id, এবং আপডেট করার সময় যাচাই করুন যাতে আপনি এমন কাউকে ম্যানেজার হিসেবে সেট না করেন যিনি ইতোমধ্যে সেই কর্মচারীর সাবট্রির অংশ। বাস্তবে সবচেয়ে নিরাপদ পদ্ধতি হল ম্যানেজার পরিবর্তন সেভ করার আগে ancestry পরীক্ষা করা, কারণ একবার সাইকেল তৈরি হলে রিকার্সিভ কুয়েরি ভেঙে যেতে পারে এবং অনুমতি লজিক নষ্ট হতে পারে।
একটি ভাল ডিফল্ট হল বিভাগগুলোকে প্রশাসনিক গ্রুপিং হিসেবে ধরে নেওয়া এবং রিপোর্টিং লাইনগুলোকে আলাদা ম্যানেজার ট্রি হিসেবে রাখা। এতে করে “department move” করলে কেউ রেকর্ড ভুলভাবে তার ম্যানেজার পরিবর্তন করবে না, এবং “সবাই Sales-এ” মত ফিল্টারগুলো পরিষ্কার থাকে এমনকি রিপোর্টিং লাইনগুলো বিভাগীয় সীমারেখার সাথে মেলেনা।
আপনি সাধারণত কর্মচারীর উপর একটি প্রাইমারি রিপোর্টিং ম্যানেজার রাখেন এবং ডটেড-লাইন সম্পর্কগুলো আলাদাভাবে প্রতিনিধিত্ব করেন, যেমন একটি সেকেন্ডারি ম্যানেজার সম্পর্ক বা “team lead” ম্যাপিং। এটি বেসিক হায়ারার্কি কুয়েরিগুলো ভেঙে না ফেলে একই সঙ্গে বিশেষ বিধি (প্রজেক্ট অ্যাক্সেস, অনুমোদন ডেলিগেশন) পরিচালনা করতে দেয়।
আপনি পুরোনো ancestor পাথগুলো মুছে ফেলবেন যে সাবট্রি চলে গেছে, তারপর নতুন ম্যানেজারের ancestor-গুলোকে প্রতিটি সাবট্রির নোডের সাথে মিলিয়ে নতুন পাথগুলো ইনসার্ট করবেন এবং depth পুনরায় গণনা করবেন। এটি একটি ট্রানজেকশনে করুন যাতে মাঝপথে ব্যর্থ হলে closure টেবিল আংশিকভাবে আপডেট না হয়।
Adjacency list-এ employees(manager_id) এর উপরে index রাখুন কারণ প্রায় প্রতিটি অর্গ কুয়েরি তা দিয়ে শুরু হয়, এবং সাধারণ ফিল্টার যেমন team_id বা department_id-এর জন্যও index দিন। Closure table-এ মূল index হওয়া উচিত primary key (ancestor_id, descendant_id) এবং descendant_id-এর আলাদা index যাতে “কে এই রো দেখতে পারে?” পরীক্ষা দ্রুত হয়।
Closure table-এ সাধারণত একটি EXISTS প্যাটার্ন ব্যবহার করা হয়: viewer যদি target employee-এর ancestor হয় তবে অ্যাক্সেস অনুমোদন করুন। এটি row-level security (RLS)–এ ভাল কাজ করে কারণ ডেটাবেস নিয়মটি ধারাবাহিকভাবে প্রয়োগ করতে পারে, এবং প্রতিটি API endpoint কে একই রিকার্সিভ লজিক মনে রাখতে হয় না। উদাহরণমূলক কুয়েরি: EXISTS (SELECT 1 FROM org_closure c WHERE c.ancestor_id = :viewer_id AND c.descendant_id = :employee_id)।
ইতিহাস স্পষ্টভাবে সংরক্ষণ করুন, সাধারণত একটি আলাদা টেবিলে ম্যানেজার পরিবর্তনসহ কার্যকর তারিখ স্টোর করে, বর্তমান ম্যানেজারকে ওভাররাইট না করে। এতে আপনি সহজেই জানতে পারবেন "তারিখ X-এ কে কার অধীনে ছিল" এবং রিগর্টিং/অডিট সহজ হয়।
আপনার বিদ্যমান manager_id-কে সিংহভাগ ক্ষেত্রে truth হিসেবে রেখে closure table একই সাথে তৈরি করুন এবং বিদ্যমান গাছ থেকে closure rows ব্যাকফিল করুন। প্রথমে রিড-পাথগুলো সরান (ফিল্টার, ড্যাশবোর্ড, পারমিশন চেক), তারপর লিখুন দুটো জায়গায় আপডেট করান এবং সব কিছু মিললে পুরোনো রিকার্সিভ কুয়েরিগুলো বাদ দিন।


