📍 ধানমন্ডি, ঢাকা-১২০৫🇬🇧 English

Oracle টেবিল Partitioning: বিলিয়ন-রো ERP টেবিল সামলানো

প্রতিটি বড় ERP সিস্টেম শেষ পর্যন্ত একই দেয়ালে গিয়ে ধাক্কা খায়: একটি sales, transaction বা audit টেবিল বেড়ে শত শত মিলিয়ন — তারপর বিলিয়ন — row-তে পৌঁছায়, আর যে query প্রথম বছরে তাৎক্ষণিক ছিল সেটিই এখন হামাগুড়ি দেয়। Partitioning হলো Oracle-এর সেই ফিচার যা এই সমস্যাটি সঠিকভাবে সমাধান করে। ঠিকভাবে করলে এটি একটি বিলিয়ন-রো টেবিলকে ছোট টেবিলের মতো অনুভব করায়, মাস-শেষের purge-কে এক-সেকেন্ডের metadata অপারেশনে পরিণত করে, আর বাকি টেবিলে হাত না দিয়েই ডেটা লোড করতে দেয়। ফার্মা sales history আর ERP fact টেবিলের জন্য partitioning ডিজাইন করার সময় আমি যে ব্যবহারিক গাইডটি অনুসরণ করি, এটি সেটাই।

মূল কথাগুলো

  • Transaction date দিয়ে range partitioning — INTERVAL সহ, যাতে partition নিজে নিজেই তৈরি হয় — ERP time-series টেবিলের মূল কর্মঘোড়া; list, hash ও composite বাকিটা সামলায়।
  • গতিটা আসে partition pruning থেকে। execution plan-এ এটি যাচাই করুন: Pstart/Pstop-এ পুরো টেবিল নয়, একটি সংকীর্ণ range দেখাতে হবে।
  • ডিফল্টে local index। global index-এর ক্ষেত্রে প্রতিটি partition operation-এ UPDATE INDEXES লাগবেই, নইলে সেগুলো UNUSABLE হয়ে যায় আর query ব্যর্থ হতে শুরু করে।
  • Rolling-window archival — পুরনো partition DROP বা EXCHANGE — বহু-ঘণ্টার DELETE জবকে এক-সেকেন্ডের metadata অপারেশনে বদলে দেয়।
  • বিদ্যমান টেবিল online-এ রূপান্তরিত হয় ALTER TABLE ... MODIFY PARTITION BY ... ONLINE দিয়ে (12.2+); কোনো outage লাগে না।
  • Partitioning হলো Enterprise Edition-এর আলাদাভাবে লাইসেন্স করা একটি option — এর ওপর ডিজাইন দাঁড় করানোর আগে লাইসেন্স নিশ্চিত করুন।
সারিতে সাজানো গুদামের তাক — Oracle টেবিল partitioning যেভাবে বিলিয়ন-রো টেবিলকে ব্যবস্থাপনাযোগ্য segment-এ ভাগ করে
Photo: Francesco Paggiaro / Pexels

১. Partitioning আসলে কী করে

Partitioning একটি লজিক্যাল টেবিলকে অনেকগুলো ফিজিক্যাল partition-এ ভাগ করে, প্রতিটি আলাদাভাবে সংরক্ষিত হয়, অথচ অ্যাপ্লিকেশন তখনও একটি একক টেবিলই দেখে। এর সুফল তিনটি:

  • পারফরম্যান্স — optimizer কেবল সেই partition-গুলোই পড়ে যেগুলোতে আপনার row থাকতে পারে (partition pruning)।
  • ব্যবস্থাপনাযোগ্যতা — একবারে এক partition করে ডেটা ব্যাকআপ, archive, compress বা drop করুন।
  • প্রাপ্যতা — একটি partition-এর রক্ষণাবেক্ষণ অন্যগুলোকে lock করে না।

২. একটি Partitioning কৌশল বেছে নেওয়া

Oracle চারটি মৌলিক পদ্ধতি এবং তাদের সমন্বয় দেয়। আমি বাস্তবে এগুলোর মধ্যে কীভাবে বাছাই করি — প্রতিটি কোথায় নিজের মূল্য প্রমাণ করে, সেই use-case সহ:

  • Range — একটি ধারাবাহিক key দিয়ে, প্রায় সবসময়ই একটি তারিখ। এটিই ERP-র কর্মঘোড়া: sales history, GL journal line, stock ledger, audit trail। ১৮+ বছরে আমার ডিজাইন করা প্রতি দশটি partitioned টেবিলের প্রায় নয়টিই ছিল range-by-date, সাধারণত মাসিক।
  • List — বিচ্ছিন্ন, জানা মান দিয়ে: company code, region, branch। group-of-companies ERP-তে আমি এটি ব্যবহার করি, যেখানে একটি legal entity-র ডেটা আলাদাভাবে ব্যবস্থাপনাযোগ্য (বা export-যোগ্য) হতে হয়। এটি কেবল তখনই কাজ করে যখন মানের সেটটি ছোট ও স্থিতিশীল।
  • Hash — যখন কোনো স্বাভাবিক range বা list key নেই তখন সমবণ্টন। যেসব বিশাল টেবিল সবসময় ID দিয়ে access হয় সেগুলোতে I/O ছড়িয়ে দিতে ভালো, আর একই key-তে hash করা দুটি টেবিলের মধ্যে partition-wise join সম্ভব করতেও। সবসময় দুইয়ের ঘাত (power of two) সংখ্যক partition ব্যবহার করুন।
  • Interval — নিজেকে নিজেই রক্ষণাবেক্ষণ করা range partitioning; প্রথম insert-এই Oracle প্রতিটি নতুন partition তৈরি করে। নিচে বিস্তারিত আছে, আর তারিখ-চালিত যেকোনো কিছুর জন্য এটিই আমার ডিফল্ট।
  • Composite — দুই স্তর। Range-hash হলো ধ্রুপদী: মাস অনুযায়ী partition, hot insert ছড়িয়ে দিতে ও parallel partition-wise join সম্ভব করতে product বা customer-এর hash দিয়ে sub-partition। Range-list কাজ করে যখন প্রতিটি মাসের ভেতরে region বা company ধরেও purge করতে হয়।

একটি composite range-hash সংজ্ঞা দেখতে এমন:

CREATE TABLE sales_history ( ... )
PARTITION BY RANGE (sale_date)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
SUBPARTITION BY HASH (product_code) SUBPARTITIONS 8 (
  PARTITION p_start VALUES LESS THAN (DATE '2026-01-01')
);

সোনালি নিয়ম: আপনার query যে column-এ filter করে সেই column-এ partition করুন। ৯০% query যদি transaction date দিয়ে সীমাবদ্ধ করে, তাহলে তারিখ দিয়ে partition করুন। দ্বিতীয় নিয়মটি মানুষ ভুলে যায়: key-টি আপনার archive করার ধরনের সঙ্গেও মিলতে হবে। retention নীতি যদি বলে "২৪ মাস রাখুন", তাহলে মাসিক range partition সেই নীতিকে এক লাইনের কমান্ড বানিয়ে দেয়।

৩. তারিখ অনুযায়ী Range Partitioning — মূল কর্মঘোড়া

মাস অনুযায়ী partition করা একটি sales-history টেবিল:

CREATE TABLE sales_history (
  sale_id      NUMBER,
  sale_date    DATE,
  product_code VARCHAR2(20),
  territory    VARCHAR2(40),
  amount       NUMBER(14,2)
)
PARTITION BY RANGE (sale_date) (
  PARTITION p_2026_01 VALUES LESS THAN (DATE '2026-02-01'),
  PARTITION p_2026_02 VALUES LESS THAN (DATE '2026-03-01'),
  PARTITION p_2026_03 VALUES LESS THAN (DATE '2026-04-01')
);

৪. Interval Partitioning — হাতে হাতে Partition বানানো বন্ধ করুন

সাধারণ range partitioning-এর জ্বালা হলো, ডেটা আসার আগেই কাউকে পরের মাসের partition যোগ করতে হয় — ভুলে গেলে insert ব্যর্থ হয়। Interval partitioning Oracle-কে নতুন range-এ প্রথম insert হওয়ামাত্র আপনাআপনি partition বানাতে দেয়:

CREATE TABLE sales_history (
  sale_id   NUMBER,
  sale_date DATE,
  amount    NUMBER(14,2)
)
PARTITION BY RANGE (sale_date)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH')) (
  PARTITION p_start VALUES LESS THAN (DATE '2026-01-01')
);

জুলাই ২০২৭ তারিখের একটি row insert করুন, আর Oracle সেই মাসের partition আপনার জন্য বানিয়ে দেবে। এই একটি ফিচারই "table not extending" ধরনের রাত ২টার কল-এর একটি গোটা শ্রেণিকে নির্মূল করে দেয়। আমি সেই কলগুলো ধরেছি; interval partitioning-ই কারণ যে এখন আর ধরতে হয় না।

একটি অভ্যাস রপ্ত করার মতো: auto-created partition-গুলো SYS_P4821-এর মতো system নাম পায়, যা রক্ষণাবেক্ষণ স্ক্রিপ্টে অকেজো। মাসিক housekeeping-এর অংশ হিসেবে এগুলোর নাম বদলে নিন, যাতে আপনার archival জব অর্থবহ নামে partition-কে ডাকতে পারে:

ALTER TABLE sales_history RENAME PARTITION SYS_P4821 TO p_2027_07;

৫. Partition Pruning — গতি যেখান থেকে আসে

এটাই তো পুরো ব্যাপার। একটি date filter দিয়ে Oracle পুরো টেবিল নয়, একটি partition পড়ে:

SELECT territory, SUM(amount)
FROM   sales_history
WHERE  sale_date >= DATE '2026-03-01'
AND    sale_date <  DATE '2026-04-01'
GROUP  BY territory;

কখনো ধরে নেবেন না যে pruning হচ্ছে — যাচাই করুন। plan চালিয়ে Pstart/Pstop column পড়ুন:

EXPLAIN PLAN FOR
SELECT territory, SUM(amount)
FROM   sales_history
WHERE  sale_date >= DATE '2026-03-01'
AND    sale_date <  DATE '2026-04-01'
GROUP  BY territory;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());

---------------------------------------------------------------------
| Id | Operation                | Name          | Pstart | Pstop |
---------------------------------------------------------------------
|  0 | SELECT STATEMENT         |               |        |       |
|  1 |  HASH GROUP BY           |               |        |       |
|  2 |   PARTITION RANGE SINGLE |               |      3 |     3 |
|  3 |    TABLE ACCESS FULL     | SALES_HISTORY |      3 |     3 |
---------------------------------------------------------------------

PARTITION RANGE SINGLE এবং Pstart = Pstop = 3 মানে Oracle ঠিক একটি partition ছুঁয়েছে — বিলিয়ন row-র বদলে এক মাসের ডেটা। bind variable থাকলে সংখ্যার বদলে KEY/KEY দেখবেন; সেটি ঠিক আছে, এর মানে pruning execution-এর সময় নির্ধারিত হচ্ছে।

একটি filtered query-তে যা কখনোই দেখতে চাইবেন না তা হলো PARTITION RANGE ALL, যেখানে Pstart = 1 এবং Pstop শেষ partition-এ। সেটি ঘটলে সাধারণ pruning-ঘাতকদের একটি কাজ করছে:

  • Partition key-র ওপর functionWHERE TRUNC(sale_date) = ... pruning অক্ষম করে দেয়। raw column-এর ওপর range predicate হিসেবে আবার লিখুন।
  • Implicit datatype conversion — একটি DATE key-কে string-এর সঙ্গে তুলনা করলে এমন conversion হয় যা optimizer-কে অন্ধ করে দেয়।
  • Filter-টি আসলে সেখানে নেই-ই — view-র মধ্য দিয়ে join করা রিপোর্ট কখনো কখনো নিচে নামার পথে date predicate হারিয়ে ফেলে। প্রকৃত SQL trace করে দেখুন।

এটি সেই একই plan-পড়ার শৃঙ্খলা যা আমি আমার Oracle পারফরম্যান্স টিউনিং গাইডে বর্ণনা করেছি — partitioned-টেবিল সংস্করণে কেবল নজর রাখার মতো দুটি column যোগ হয়।

ডেটা সেন্টারে server storage disk — partition pruning-এর কারণে Oracle কেবল query-র প্রয়োজনীয় disk segment-গুলোই পড়ে
Photo: panumas nikhomkhai / Pexels

৬. Local বনাম Global Index

  • Local index — টেবিলের মতোই একইভাবে partition করা। একটি table partition ড্রপ করলে তার index partition-ও সঙ্গে যায়। partitioned টেবিলের জন্য ডিফল্ট পছন্দ এবং রক্ষণাবেক্ষণের জন্য সবচেয়ে বন্ধুত্বপূর্ণ।
  • Global index — সব partition জুড়ে বিস্তৃত; যেসব query partition key অন্তর্ভুক্ত করে না তাদের জন্য ভালো (যেমন sale_id দিয়ে lookup)। খরচ: UPDATE INDEXES ব্যবহার না করলে partition রক্ষণাবেক্ষণ একে invalidate করে দিতে পারে।
CREATE INDEX sales_terr_lx ON sales_history(territory) LOCAL;
CREATE INDEX sales_id_gx   ON sales_history(sale_id) GLOBAL;

রক্ষণাবেক্ষণের এই ফাঁদটি আলাদা সতর্কবার্তার দাবি রাখে, কারণ আমি একে একটি live ERP নামিয়ে দিতে দেখেছি। যখন আপনি কোনো partition drop বা exchange করেন, statement-এ UPDATE INDEXES না থাকলে সেই টেবিলের প্রতিটি global index invalidate — UNUSABLE চিহ্নিত — হয়ে যায়। একটি UNUSABLE index মানে যেসব query তার ওপর নির্ভর করত সেগুলো হয় ORA-01502 দিয়ে ব্যর্থ হয়, নয়তো নিঃশব্দে বিলিয়ন-রো টেবিলের full scan-এ নেমে যায়। যেভাবেই হোক, আপনার ফোন বাজবে।

যে ঘটনাটি আমার সবচেয়ে মনে আছে: রাতের শিফটের একটি purge স্ক্রিপ্ট clause-টি ছাড়াই একটি পুরনো partition drop করেছিল, আর সকাল ৯টার মধ্যে order-entry স্ক্রিন time out করছিল, কারণ তার sale_id lookup তার global index হারিয়েছিল। সমাধান ছিল ব্যবসার সময়ের মধ্যে একটি দীর্ঘ online rebuild। তারপর থেকে আমার নিয়মটি যান্ত্রিক: global index আছে এমন টেবিলের প্রতিটি partition DDL-এ UPDATE INDEXES থাকবে, কোনো ব্যতিক্রম নেই — আর purge স্ক্রিপ্টগুলো এ জন্য code-review হয়।

Local index পুরো সমস্যাটিই পাশ কাটিয়ে যায়, সেজন্যই সেগুলো ডিফল্ট। আরও গভীর ডিজাইন প্রশ্ন — কোন column-গুলো আদৌ index-এর যোগ্য, এবং কোন আকারে — তা আমার Oracle indexing কৌশল গাইডের বিষয়; টেবিল partition হয়ে গেলে সেখানকার সবকিছু per-partition ভিত্তিতে প্রযোজ্য।

ফাইলিং ক্যাবিনেটের archive ড্রয়ার — rolling-window প্যাটার্ন row delete না করে পুরনো Oracle partition archive করে
Photo: Element5 Digital / Pexels

৭. Rolling Window — DELETE ছাড়াই Archiving

বিলিয়ন-রো টেবিল থেকে এক মাসের ডেটা সাধারণ উপায়ে delete করলে বিশাল undo ও redo তৈরি হয়, redo transport-এ standby ভেসে যায়, আর ঘণ্টার পর ঘণ্টা লাগে। Rolling-window প্যাটার্ন DELETE-কে পুরোপুরি প্রতিস্থাপন করে: প্রতি মাসে সামনের দিকে একটি নতুন partition ঢোকে আর পেছনের দিকে সবচেয়ে পুরনোটি বেরিয়ে যায় — metadata হিসেবে:

-- drop old data as pure metadata
ALTER TABLE sales_history DROP PARTITION p_2024_01 UPDATE INDEXES;

-- or move it to cheap storage / compress it first
ALTER TABLE sales_history MOVE PARTITION p_2024_01
  TABLESPACE archive_ts ROW STORE COMPRESS ADVANCED UPDATE INDEXES;

ডেটা হারিয়ে যাওয়ার আগে যদি অন্য কোথাও query-যোগ্য রাখতে হয়, তবে পুরনো partition-টিকে প্রথমে একটি standalone টেবিলে exchange করে বের করুন, সেই টেবিলটি export করুন বা একটি archive schema-তে সরান, তারপর এখন-খালি partition-টি drop করুন। regulator সন্তুষ্ট, storage পুনরুদ্ধার হলো, আর live টেবিল টেরও পেল না।

এভাবেই এমন একটি data-retention নীতি বাস্তবায়ন করবেন যা regulator ও storage বাজেট দুই-ই অনুমোদন করে — আর ফার্মায়, যেখানে আমার বেশিরভাগ ERP কাজ, retention নিয়ম নিয়ে দর-কষাকষির সুযোগ নেই।

৮. Partition Exchange — প্রায়-তাৎক্ষণিক Bulk Load

একটি live partitioned টেবিলে সরাসরি বড় batch লোড করা ধীর ও প্রতিযোগিতাপূর্ণ। বরং একটি আলাদা staging টেবিলে লোড করে সেটিকে metadata-only swap হিসেবে exchange করুন:

-- 1) load & index a plain staging table off to the side
-- 2) swap it into the partition instantly
ALTER TABLE sales_history
  EXCHANGE PARTITION p_2026_03
  WITH TABLE sales_stage
  INCLUDING INDEXES WITHOUT VALIDATION;

কোনো দীর্ঘ-চলা insert বা online query আটকানো ছাড়াই ডেটা সেকেন্ডের ভগ্নাংশে partitioned টেবিলে হাজির হয়।

৯. Downtime ছাড়া কি একটি বিদ্যমান টেবিল Partition করা যায়?

হ্যাঁ। Oracle 12.2 থেকে ALTER TABLE ... MODIFY PARTITION BY ... ONLINE একটি সাধারণ heap টেবিলকে partitioned টেবিলে রূপান্তর করে, অ্যাপ্লিকেশন যখন সেটি পড়ছে ও লিখছে তখনই। পুরনো ভার্সনে DBMS_REDEFINITION একটি interim টেবিলের মাধ্যমে একই ফল অর্জন করে। দুটোরই অপারেশন চলাকালে টেবিলের প্রায় দ্বিগুণ জায়গা লাগে।

একটি বড় live ERP টেবিল রূপান্তরের সময় আমি যে ক্রমটি অনুসরণ করি:

  1. জায়গা নিশ্চিত করুন। রূপান্তরটি একটি সম্পূর্ণ নতুন কপি লেখে, তাই tablespace-এ টেবিল-সহ-index-এর headroom আছে কি না দেখুন: SELECT SUM(bytes)/1024/1024/1024 FROM dba_segments WHERE segment_name = 'SALES_HISTORY';
  2. লক্ষ্য layout আগে কাগজে ঠিক করুন — partition key, interval, আর কোন index-গুলো local হবে। এটি একটি ডিজাইন সিদ্ধান্ত, syntax-এর মহড়া নয়।
  3. একটি clone-এ মহড়া দিন। আমি একটি সাম্প্রতিক RMAN কপি restore করি বা test refresh ব্যবহার করি এবং হুবহু statement-টি সেখানে চালাই — সময় মাপি আর ফলাফলের plan-গুলো পরীক্ষা করি।
  4. একটি শান্ত window-তে online রূপান্তরটি চালান (এটি online বটে, তবু I/O-র জন্য প্রতিযোগিতা করে):
    ALTER TABLE sales_history
      MODIFY PARTITION BY RANGE (sale_date)
      INTERVAL (NUMTOYMINTERVAL(1,'MONTH')) (
        PARTITION p_old VALUES LESS THAN (DATE '2024-01-01')
      ) ONLINE
      UPDATE INDEXES (
        sales_terr_lx LOCAL,
        sales_pk      GLOBAL
      );
  5. সঙ্গে সঙ্গে যাচাই করুন: USER_TAB_PARTITIONS-এ partition সংখ্যা, USER_INDEXES-এ প্রতিটি index VALID, আর শীর্ষ তিনটি query-তে Pstart/Pstop pruning।
  6. Statistics আবার সংগ্রহ করুন incremental mode চালু রেখে (পরের অংশ), তারপর কাজ শেষ ঘোষণার আগে এক সপ্তাহ AWR top-SQL-এ নজর রাখুন।

12.2-এর আগের সিস্টেমে আমি একই কাজ DBMS_REDEFINITION দিয়ে করেছি — একটি partitioned interim টেবিলে redefinition শুরু, dependent-গুলো কপি, sync, শেষ। ধাপ বেশি, ফল একই, তবুও কোনো outage নেই।

১০. Partitioned টেবিলে Statistics — Incremental-এ যান

একটি partitioned টেবিলের statistics দুই স্তরে থাকে: প্রতি partition এবং global (পুরো-টেবিল)। ডিফল্টে global stats refresh করা মানে পুরো টেবিল আবার scan করা — যা বিলিয়ন row-তে রাতের stats জবকে ডেটাবেজের সবচেয়ে দীর্ঘ-চলা কাজে পরিণত করে। Incremental statistics এটি ঠিক করে: Oracle প্রতি partition-এ একটি synopsis রাখে এবং সেগুলো থেকে global stats বের করে, ফলে কেবল পরিবর্তিত partition-গুলোই পুনরায় বিশ্লেষিত হয়।

EXEC DBMS_STATS.SET_TABLE_PREFS('ERP','SALES_HISTORY','INCREMENTAL','TRUE');
EXEC DBMS_STATS.SET_TABLE_PREFS('ERP','SALES_HISTORY','GRANULARITY','AUTO');

EXEC DBMS_STATS.GATHER_TABLE_STATS('ERP','SALES_HISTORY');

আমার ব্যবস্থাপনায় থাকা rolling-window টেবিলগুলোতে এটি stats window-কে ঘণ্টা থেকে মিনিটে নামিয়ে এনেছে, কারণ যেকোনো রাতে কেবল চলতি মাসের partition-ই বদলেছে। একটি সতর্কতা: synopsis-গুলো SYSAUX-এ জায়গা খায় — নজরে রাখুন। optimizer এই সংখ্যাগুলো কীভাবে ব্যবহার করে, আর কোন preference-গুলো সেট করা সার্থক — তার পূর্ণ গল্প আমার DBMS_STATS ও optimizer statistics গাইডে

ডেস্কে ক্যালেন্ডার পরিকল্পনা — মাসিক range partition Oracle ডেটা-জীবনচক্রকে ব্যবসায়িক ক্যালেন্ডারের সঙ্গে মেলায়
Photo: RDNE Stock project / Pexels

১১. একটি যুদ্ধ-কাহিনি: মাস-শেষ ঘণ্টা থেকে মিনিটে

আমার দেখাশোনায় থাকা একটি ফার্মাসিউটিক্যাল ERP-র sales-invoice detail টেবিল ষষ্ঠ বছরে বিলিয়ন-রো সীমা পেরিয়ে যায়। মাস-শেষের territory রিপোর্ট — যেগুলোর জন্য sales director-রা অপেক্ষা করেন — বিশ মিনিট থেকে বাড়তে বাড়তে প্রায় চার ঘণ্টায় পৌঁছেছিল, আর মাসিক purge জবটি চুপচাপ বন্ধ করে রাখা হয়েছিল, কারণ সেটি দুইবার undo tablespace উপচে দিয়েছিল।

রোগনির্ণয়ে লেগেছিল এক বিকেল: প্রতিটি রিপোর্ট invoice date-এ filter করছিল, অথচ টেবিলটি ছিল এক দৈত্যাকার heap। প্রতিটি রিপোর্ট এক মাসের যোগফল বের করতে ছয় বছরের ইতিহাস scan করছিল।

সমাধানটি হুবহু এই আর্টিকেলের ডিজাইন। সপ্তাহান্তের এক কম-ব্যবহারের window-তে আমরা টেবিলটিকে online-এ মাসিক interval partition-এ রূপান্তর করলাম, reporting index-গুলো local করলাম, invoice-number lookup স্ক্রিনের জন্য একটি global index রাখলাম, আর statistics-কে incremental-এ বদলালাম। পরের মাস-শেষে সেই একই territory রিপোর্ট আট মিনিটের কমে শেষ হলো — pruning ছয় বছরের scan-কে এক মাসের scan-এ পরিণত করেছিল। বন্ধ-করা purge-টি হয়ে গেল দুই লাইনের একটি drop-partition স্ক্রিপ্ট, যা চলে প্রায় এক সেকেন্ডে।

অ্যাপ্লিকেশনে কিছুই বদলায়নি। একটি query-ও নতুন করে লেখা হয়নি। ERP-র নিচের ফিজিক্যাল ডিজাইন ঠিক করার নীরব শক্তি এটাই — সেই একই নীতি যা আমার healthcare ERP ডিজাইন আর্টিকেলের schema সিদ্ধান্তগুলোকেও চালায়: ডেটাবেজের বিন্যাসকে প্রতিফলিত করতে হবে ব্যবসা আসলে কীভাবে প্রশ্ন করে, যা ERP-তে প্রায় সবসময়ই "সময়কাল ধরে"।

১২. লাইসেন্সিং নিয়ে সততা, আর কখন Partition করবেন না

প্রথমে সেই অংশটি, যা consultant-রা প্রায়ই এড়িয়ে যান: Partitioning হলো Oracle Enterprise Edition-এর ওপরে আলাদাভাবে লাইসেন্স করা একটি option। এটি Standard Edition-এ পাওয়া যায় না, আর EE-র সঙ্গে বিনামূল্যেও আসে না। লাইসেন্স ছাড়া ব্যবহার করলে DBA_FEATURE_USAGE_STATISTICS তা লিপিবদ্ধ করে, আর Oracle audit তা খুঁজে বের করবে। ফিচারটির ওপর ডিজাইন দাঁড় করানোর আগে আপনার entitlement নিশ্চিত করুন — আমি একটি partitioning প্রস্তাবকে লাইসেন্সিং লাইনে এসে মারা যেতে দেখেছি, আর সেখানে মারা যাওয়া audit settlement-এ মারা যাওয়ার চেয়ে ঢের ভালো।

দ্বিতীয়ত, partitioning কোনো প্রতিবর্ত-ক্রিয়া নয়। আমি এর বিপক্ষে পরামর্শ দিই যখন:

  • টেবিলটি আদৌ যথেষ্ট বড় নয়। কয়েক কোটি row-র নিচে একটি ভালোভাবে index করা heap টেবিল দিব্যি চলে, আর partitioning তখন অকারণে dictionary overhead ও পরিচালনগত জটিলতা যোগ করে।
  • কোনো প্রধান filter column নেই। query-গুলো যদি কোনো সাধারণ key ছাড়াই বিশটি ভিন্ন predicate দিয়ে টেবিলে আঘাত করে, কোনো partition scheme-ই prune করে না, আর আপনি সুফল ছাড়াই খরচটুকু উত্তরাধিকারে পান।
  • Access পুরোপুরি primary-key OLTP। একটি unique index lookup এমনিতেই এক-দুইটি I/O; partitioning তার চেয়ে ভালো করতে পারে না।
  • আপনি Standard Edition-এ আছেন। তখন সৎ বিকল্প হলো পর্যায়ক্রমিক archive টেবিল, একটি purge-শৃঙ্খলা আর ভালো indexing — কম মার্জিত, কিন্তু লাইসেন্সসম্মত।

Partition করুন যখন access pattern আর জীবনচক্র দুটোই একই দিকে ইশারা করে। যখন কেবল row-সংখ্যাই বড় কিন্তু বাকি সবকিছু বলে heap টেবিল, তখন একে একা থাকতে দিন।

১৩. সাধারণ ফাঁদ

  • ভুল partition key — query যে column-এ filter করে না সেটিতে partition করলে সব overhead পাবেন, কিন্তু pruning-এর কিছুই না।
  • অতিরিক্ত ছোট partition — পাঁচ বছরের দৈনিক partition মানে ১,৮০০টি partition; dictionary ও parsing overhead জমে ওঠে। granularity-কে query ও retention প্যাটার্নের সঙ্গে মেলান।
  • Global index UNUSABLE রেখে দেওয়া — drop/exchange-এর পর UPDATE INDEXES ভুলে গেলে query ভেঙে পড়ে।
  • Hash partitioning-এ skew — সমবণ্টনের জন্য সবসময় hash partition-এর সংখ্যা দুইয়ের ঘাত (power of two) রাখুন।
  • বাসি statistics — incremental stats সংগ্রহ করুন যাতে কেবল পরিবর্তিত partition-গুলোই পুনরায় বিশ্লেষিত হয়।

১৪. সেরা অনুশীলন

  • time-series ERP ডেটার জন্য interval সহ range-by-month — সেট করে ভুলে যান।
  • ডিফল্টে local index; global index যোগ করুন কেবল সেখানেই যেখানে non-key lookup দাবি করে।
  • বড় partitioned টেবিলে incremental statistics (INCREMENTAL = TRUE)।
  • retention নীতির অংশ হিসেবে পুরনো partition compress করুন এবং সস্তা storage-এ সরান।
  • load-এর জন্য exchangepurge-এর জন্য drop ব্যবহার করুন — দুটোই metadata অপারেশন।
  • নতুন কৌশল deploy করার পর DBMS_XPLAN দিয়ে সবসময় pruning যাচাই করুন

সাধারণ জিজ্ঞাসা (FAQ)

একটি টেবিল কত বড় হলে partitioning করা সার্থক?

কোনো জাদুকরী row-সংখ্যা নেই, তবে আমার অভিজ্ঞতায় কষ্টটা শুরু হয় ৫০–১০০ মিলিয়ন row বা ২০–৩০ GB পার হওয়ার পর — যখন full scan, index rebuild আর purge রক্ষণাবেক্ষণ window-তে আর ধরে না। কোনো টেবিল যদি মাসে মিলিয়ন মিলিয়ন row-তে বাড়ে এবং query তারিখ দিয়ে filter করে, তাহলে বিলিয়ন row-তে গিয়ে retrofit করার বদলে আগেভাগেই partitioning ডিজাইন করুন।

Partitioning কি সব query-কে দ্রুত করে?

না। কেবল যেসব query partition key-তে filter করে সেগুলোই pruning-এর সুফল পায়। index-এর মাধ্যমে primary key দিয়ে lookup আগে থেকেই দ্রুত ছিল, তার কোনো লাভ হয় না; আর যে query সব partition ছুঁয়ে যায় সেটি সামান্য ধীরও হতে পারে। Partitioning হলো প্রধান access pattern-এর জন্য একটি ডিজাইন-হাতিয়ার, ঢালাও accelerator নয়।

Partitioned টেবিলে local না global index ব্যবহার করব?

ডিফল্ট হিসেবে local index নিন: এগুলো নিজেদের table partition-এর সঙ্গেই drop, exchange ও rebuild হয়, ফলে রক্ষণাবেক্ষণ থাকে metadata-only। Global index কেবল সেখানেই ব্যবহার করুন যেখানে partition key-র সঙ্গে সম্পর্কহীন কোনো column-এ query দ্রুত হতেই হবে, এবং partition রক্ষণাবেক্ষণে সবসময় UPDATE INDEXES যুক্ত করুন যাতে এটি কখনো UNUSABLE না হয়।

Downtime ছাড়া কি একটি বিদ্যমান টেবিল partition করা যায়?

হ্যাঁ। Oracle 12.2 থেকে ALTER TABLE ... MODIFY PARTITION BY ... ONLINE একটি heap টেবিলকে partitioned টেবিলে রূপান্তর করে, অ্যাপ্লিকেশন চলতে চলতেই। পুরনো ভার্সনে DBMS_REDEFINITION একটি interim টেবিলের মাধ্যমে একই কাজ করে। দুটোরই রূপান্তরের সময় টেবিলের প্রায় দ্বিগুণ জায়গা লাগে।

Partition key কীভাবে বেছে নেব?

আপনার সবচেয়ে বড় ও সবচেয়ে ঘনঘন চলা query যে column-এ filter করে সেটিই নিন। ERP সিস্টেমে সেটি প্রায় সবসময়ই transaction date। key-টি আপনার archive করার ধরনের সঙ্গেও মিলতে হবে: মাস ধরে purge করলে মাস ধরে partition করুন। যে key-তে কেউ filter করে না, সেটি আপনাকে সব overhead দেবে, কিন্তু pruning-এর কিছুই দেবে না।

Partitioning কি আমার Oracle লাইসেন্সের অন্তর্ভুক্ত?

Standard Edition-এ নয়। Partitioning হলো Enterprise Edition-এর ওপরে আলাদাভাবে লাইসেন্স করা একটি option। এর ওপর নির্ভর করে ডিজাইন করার আগে লাইসেন্স নিশ্চিত করুন, কারণ লাইসেন্সবিহীন ব্যবহার audit-এর সময় DBA_FEATURE_USAGE_STATISTICS-এ ধরা পড়ে। Standard Edition-এ আলাদা archive টেবিল archival সুবিধার একটি আংশিক বিকল্প দেয়।

শেষ কথা

একটি ERP ডেটাবেজ যেটি সুন্দরভাবে বয়সী হয়, আর যেটির পাঁচ বছরে জরুরি উদ্ধার লাগে — এ দুইয়ের পার্থক্যই হলো partitioning। ফিচারটি নিজে সরল; মূল্য থাকে ডিজাইন সিদ্ধান্তে — সঠিক key, সঠিক granularity, সঠিক index কৌশল। শুরুতেই এগুলো ঠিক করুন, তাহলে বছরের পর বছর ধারাবাহিক পারফরম্যান্স ও ব্যথাহীন ডেটা-জীবনচক্র ব্যবস্থাপনা কিনে নিতে পারবেন। আতঙ্কে পরে bolt-on করলে মাঝরাতে index rebuild করতে হবে।

আপনার যদি এমন একটি বড় টেবিল থাকে যা ধীর হয়ে যাচ্ছে, একটি retention নীতি বাস্তবায়ন করার থাকে, কিংবা একটি ERP fact টেবিলের partitioning ডিজাইন দরকার হয়, চলুন কথা বলি। ফার্মা ও ERP workload-এর জন্য আমি খুব বড় Oracle টেবিল partition ও tune করেছি এবং প্রথমবারেই সঠিক আকার দিতে সাহায্য করতে পারি।

🔗 একটি বিশাল টেবিল নিয়ে ভুগছেন?

Partitioning ডিজাইন, online conversion, index কৌশল ও retention নীতি। বিনামূল্যে ৩০-মিনিটের পরামর্শ।

📩 বিনামূল্যে পরামর্শ মূল্য দেখুন
নাসির উদ্দিন খান — Oracle DBA কনসালট্যান্ট

লেখক পরিচিতি

নাসির উদ্দিন খান সিনিয়র আইটি কনসালট্যান্ট · Oracle DBA · ERP ও AI বিশেষজ্ঞ OCP · Red Hat Certified · MBA · CSV · ১৮+ বছরের অভিজ্ঞতা

নাসির একজন Oracle Certified Professional এবং CSV-সার্টিফায়েড আইটি কনসালট্যান্ট, অবস্থান ঢাকা, বাংলাদেশ। ম্যানুফ্যাকচারিং, ফার্মা, ব্যাংকিং ও হেলথকেয়ার প্রতিষ্ঠানে Oracle ডেটাবেজ অ্যাডমিনিস্ট্রেশন (RAC, Data Guard, RMAN, VLDB partitioning), WebLogic middleware, ERP সিস্টেম ডিজাইন এবং AI ইন্টিগ্রেশন নিয়ে তাঁর ১৮+ বছরের হাতে-কলমে অভিজ্ঞতা রয়েছে।

তথ্যসূত্র ও আরও পড়ুন

এই গাইডটি বড় ERP ও ফার্মা ডেটাসেটের জন্য হাতে-কলমে partitioning ডিজাইন এবং Oracle-এর অফিসিয়াল ডকুমেন্টেশনের ভিত্তিতে তৈরি।

সম্পর্কিত লেখা

💬