PostgreSQL-এ শূন্য-ডাউনটাইম ইনডেক্স পরিবর্তন: একটি নিরাপদ প্লেবুক
CONCURRENTLY, সহজ লক চেক এবং স্পষ্ট রোলব্যাক ধাপ ব্যবহার করে PostgreSQL-এ শূন্য-ডাউনটাইম ইনডেক্স পরিবর্তন — যাতে প্রোডাকশন ট্রাফিক চলতেই থাকে।

কেন ইনডেক্স পরিবর্তনগুলো ডাউনটাইম সৃষ্টি করে (এবং কিভাবে এড়াবেন)
ইনডেক্সের কাজ শুনতে নিরীহ লাগে। আপনি "শুধু" একটি সহায়ক স্ট্রাকচার যোগ করছেন। কিন্তু PostgreSQL-এ ইনডেক্স তৈরি, ড্রপ বা বদলানোর সময় এমন লক লাগতে পারে যা অন্যান্য সেশনকে ব্লক করে। টেবিল ব্যস্ত হলে ঐ অপেক্ষাগুলো জমে যায় এবং অ্যাপলিকেশন ভেঙে পড়ার মতো অনুভূত হয়।
ডাউনটাইম সাধারণত পরিষ্কার আউটেজ ব্যানারের মতো দেখা যায় না। এটি প্রায়ই পেজ হ্যাং করা, ব্যাকগ্রাউন্ড জব পিছিয়ে পড়া, এবং ডাটাবেসে আটকে থাকা অনুরোধের একটি বেড়েছে কিউ হিসেবে প্রকাশ পায়। কেউ "Search" চাপলে টাইমআউট পায়, আর সাপোর্ট টুল ও অ্যাডমিন স্ক্রিনগুলো ধীর হয়ে পড়ে কারণ সাধারণ কুয়েরিগুলো প্রয়োজনীয় লক পায় না।
"রাত্রে চালান" বলা দুইটি সাধারণ কারণে ব্যর্থ হয়। বহু সিস্টেম কখনও সত্যিই নিস্তব্ধ থাকে না (গ্লোবাল ইউজার্স, ব্যাচ জব, ETL, ব্যাকআপ)। আর ইনডেক্স অপারেশনগুলো প্রত্যাশার চেয়ে বেশি সময় নিতে পারে কারণ সেগুলো অনেক ডেটা পড়তে হয় এবং CPU ও ডিস্কের সাথে প্রতিযোগিতা করে। যদি উইন্ডো নির্মাণের মাঝেই শেষ হয়ে যায় না, তাহলে আপনাকে অপেক্ষা বা কাজ বাতিলের মধ্যে পছন্দ করতে হয়।
শূন্য-ডাউনটাইম ইনডেক্স পরিবর্তন কোনও জাদু নয়। এটা আসে কম ব্লকিং অপারেশন বেছে নেওয়া, গার্ডরেল (টাইমআউট এবং ডিস্ক চেক) সেট করা, এবং রান চলাকালীন ডাটাবেস মনিটর করার মাধ্যমে।
এই প্লেবুকটি ব্যবহারিক প্রোডাকশন অভ্যাসের দিকে ফোকাস করে:
- পড়া ও লেখা চলতেই থাকলে concurrent ইনডেক্স বিল্ড প্রাধান্য দিন।
- লক ও বিল্ড প্রগ্রেস মনিটর করুন যেন প্রথমে প্রতিক্রিয়া করা যায়।
- পরিবর্তন কোনো রিগ্রেশন বা সময় অধিক হলে রোলব্যাক পাথ রাখুন।
এতে কভার করা নেই: গভীর ইনডেক্স ডিজাইন তত্ত্ব, বিস্তৃত কুয়েরি টিউনিং, অথবা স্কিমা রিফ্যাক্টর যা অনেক ডেটা রিরাইট করে।
ইনডেক্স কাজের পিছনের সরল লক মডেল
PostgreSQL অনেক সেশন একই টেবিলে টাচ করলে ডেটা সঠিক রাখার জন্য লক ব্যবহার করে। একটি লক হচ্ছে শুধু একটি নিয়ম যা বলে কবে কে কোনো অবজেক্ট পড়তে বা লিখতে পারবে, আর কাকে অপেক্ষা করতে হবে।
অধিকাংশ সময় আপনি লক অনুভব করেন না কারণ PostgreSQL হালকা মোড ব্যবহার করতে পারে যা সাধারণ কুয়েরিগুলোকে চলতে দেয়। DDL ভিন্ন। ইনডেক্স তৈরি বা ড্রপ করার সময় PostgreSQL-কে ক্যাটালগ ও ডেটা কনসিস্টেন্ট রাখতে টেবিলের ওপর পর্যাপ্ত নিয়ন্ত্রণ দরকার। যত বেশি নিয়ন্ত্রণ লাগবে, তত বেশি অন্যান্য সেশনকে অপেক্ষা করতে হতে পারে।
ইনডেক্স তৈরি বনাম ইনডেক্স ব্যবহার করা
ইনডেক্স ব্যবহার সাধারণত লক দিক থেকে সস্তা। SELECT, UPDATE, এবং DELETE কুয়েরি অন্যান্য সেশন চলাকালীন ইনডেক্স পড়তে বা রক্ষণাবেক্ষণ করতে পারে।
ইনডেক্স তৈরি আলাদা। PostgreSQL টেবিল স্ক্যান করে, কীগুলো sort বা hash করে, এবং একটি নতুন স্ট্রাকচার ডিস্কে লিখে। সেই কাজটা সময় সাপেক্ষ, এবং সময়ই ছোট লকগুলোকে প্রোডাকশনে বড় সমস্যায় পরিণত করে।
CONCURRENTLY কী বদলে দেয় (এবং কী না)
একটি সাধারণ CREATE INDEX শক্ত লক নেয় যা বিল্ডের পুরো সময় ধরে writes ব্লক করে। CREATE INDEX CONCURRENTLY ডিজাইন করা হয়েছে যাতে ইনডেক্স বিল্ড চলাকালীন normal reads ও writes সচল থাকে।
কিন্তু "concurrent" মানে "কোনো লক নেই" নয়। শুরু ও শেষের দিকে সংক্ষিপ্ত লক উইন্ডো থাকে, এবং বিল্ড ব্যর্থ বা অপেক্ষিত হতে পারে যদি অন্য কেউ incompatible লক ধরে থাকে।
যেগুলো সবচেয়ে গুরুত্বপূর্ণ ফলাফল:
- Non-concurrent বিল্ডগুলো inserts, updates, এবং deletes ব্লক করতে পারে।
- Concurrent বিল্ড সাধারণত reads ও writes চালাতে দেয়, কিন্তু দীর্ঘ ট্রানজেকশনগুলোর কারণে ধীর বা আটকে যেতে পারে।
- শেষ করার ধাপগুলো এখনও সংক্ষিপ্ত লক চায়, তাই খুব ব্যস্ত সিস্টেমে ছোট অপেক্ষা দেখা যেতে পারে।
সঠিক পদ্ধতি বেছে নিন: concurrent নাকি normal
ইনডেক্স বদলের সময় আপনার দুটি প্রধান অপশন আছে: স্বাভাবিকভাবে ইনডেক্স তৈরি করা (দ্রুত, কিন্তু ব্লকিং), অথবা CONCURRENTLY দিয়ে তৈরি করা (সাধারণত অ্যাপ ট্র্যাফিকের জন্য নন-ব্লকিং, কিন্তু ধীরে এবং দীর্ঘ ট্রানজেকশনের প্রতি সংবেদনশীল)।
কখন CONCURRENTLY সঠিক পছন্দ
CREATE INDEX CONCURRENTLY ব্যবহার করুন যখন টেবিল বাস্তব ট্রাফিক সার্ভ করে এবং আপনি writes থামাতে পারবেন না। এটি সাধারণত নিরাপদ পছন্দ যখন:
- টেবিল বড় এবং স্বাভাবিক বিল্ডে মিনিট বা ঘণ্টা লাগতে পারে।
- টেবিল steady writes পায়, শুধু পড়া নয়।
- আপনি একটি বাস্তব রক্ষণাবেক্ষণ উইন্ডো নির্ধারণ করতে পারেন না।
- আপনাকে প্রথমে বিল্ড করতে হবে, যাচাই করতে হবে, এবং পরে পুরোনো ইনডেক্স ড্রপ করতে হবে।
কখন normal ইনডেক্স বিল্ড গ্রহণযোগ্য
টেবিল ছোট, ট্রাফিক কম, বা আপনার কাছে নিয়ন্ত্রিত উইন্ডো থাকলে সাধারণ CREATE INDEX ঠিক থাকে। এটি প্রায়ই দ্রুত শেষ হয়ে যায় এবং চালাতে সহজ।
স্টেজিংয়ে বিল্ড যদি ধারাবাহিকভাবে দ্রুত হয় এবং আপনি সাময়িকভাবে writes বন্ধ রাখতে পারেন (সামান্য বিরতি পর্যন্ত), তখন normal পন্থা বিবেচনা করুন।
ইউনিকনেস দরকার হলে শুরুতেই সিদ্ধান্ত নিন। CREATE UNIQUE INDEX CONCURRENTLY কাজ করবে, কিন্তু duplicate ভ্যালু থাকলে ব্যর্থ হবে। অনেক প্রোডাকশনে duplicate খুঁজে বের করা ও ঠিক করা বাস্তবে বড় কাজ হয়ে থাকে।
প্রোডাকশনে স্পর্শ করার আগে প্রিফ্লাইট চেকগুলি
অধিকাংশ সমস্যা কমান্ড শুরু হওয়ার আগে ঘটে। কিছু চেক আপনাকে দুইটি বড় চমকে ওঠা থেকে রক্ষা করবে: অপ্রত্যাশিত ব্লকিং এবং ইনডেক্স বিল্ড যা আপনি পরিকল্পনা চেয়ে অনেক বেশি সময় বা স্পেস নেয়।
-
নিশ্চিত করুন আপনি কোনো ট্রানজেকশনের ভিতরে নেই।
CREATE INDEX CONCURRENTLYযদিBEGIN-এর পরে চালান তখন ব্যর্থ হবে, এবং কিছু GUI টুল চুপচাপ স্টেটমেন্টগুলোকে ট্রানজেকশনে আবৃত করে। নিশ্চিত না হলে একটি নতুন সেশন খুলে শুধু ইনডেক্স কমান্ড সেখানে রান করুন। -
সময় ও ডিস্কের জন্য প্রত্যাশা সেট করুন। Concurrent বিল্ড সাধারণত normal বিল্ডের চেয়ে ধীরে চলে এবং চলাকালীন অতিরিক্ত কাজের জায়গা দরকার হয়। নতুন ইনডেক্স ও সাময়িক ওভারহেডের জন্য পরিকল্পনা করুন এবং যথেষ্ট ফ্রি ডিস্ক আছে কি না নিশ্চিত করুন।
-
আপনার লক্ষ্য অনুযায়ী টাইমআউট সেট করুন। আপনি চান যদি লক না পাওয়া যায় তাহলে বিল্ড দ্রুত ব্যর্থ হোক, তবে খুব আগ্রাসী statement timeout-এ সেশন বিল্ডের মাঝেই না মরে।
-
একটি বেসলাইন ক্যাপচার করুন। আপনি চাইবেন প্রমাণ যে পরিবর্তন কাজ করেছে এবং regressions দ্রুত ধরতে পারবেন। একটি "আগে" স্ন্যাপশট নিন: ধীর কুয়েরির সময়, একটি প্রতিনিধিত্বমূলক
EXPLAIN (ANALYZE, BUFFERS), এবং CPU, IO, কানেকশন এবং ফ্রি ডিস্কের স্ন্যাপশট।
অনেক দল যেসব সেশন সেটিং ব্যবহার করে (আপনার নিয়মে সমন্বয় করুন):
-- Run in the same session that will build the index
SET lock_timeout = '2s';
SET statement_timeout = '0';
ধাপে ধাপে: CONCURRENTLY দিয়ে ইনডেক্স তৈরি করা
যখন আপনাকে অ্যাপ ট্র্যাফিক চালাতে রাখতে হবে এবং বেশিক্ষণ বিল্ড সামলে নিতে পারেন তখন CREATE INDEX CONCURRENTLY ব্যবহার করুন।
প্রথমে সিদ্ধান্ত নিন ঠিক কী বানাতে যাচ্ছেন:
- কলামের অর্ডার স্পষ্ট করুন (এটি গুরুত্বপূর্ণ)।
- partial index যথেষ্ট কিনা বিবেচনা করুন। যদি অধিকাংশ কুয়েরি "active" রো গুলোতে ফিল্টার করে, একটি partial index ছোট, দ্রুত এবং রক্ষণাবেক্ষণে সস্তা হতে পারে।
একটি নিরাপদ রান এরকম লাগে: লক্ষ্য ও ইনডেক্স নাম লিখে রাখুন, ইনডেক্স বিল্ডটি ট্রানজেকশন ব্লকের বাইরে চালান, সম্পন্ন হওয়া পর্যন্ত দেখুন, তারপর planner সেটি ব্যবহার করতে পারে কিনা যাচাই করুন পুরোনো কিছু মুছবার আগে।
-- Example: speed up searches by email for active users
CREATE INDEX CONCURRENTLY idx_users_active_email
ON public.users (email)
WHERE status = 'active';
-- Validate it exists
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'users';
-- Check the plan can use it
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM public.users
WHERE status = 'active' AND email = '[email protected]';
অডিটের জন্য প্রগ্রেস নোট (উপযোগী): শুরু সময়, শেষ সময়, এবং আপনি যে কোনো অপেক্ষা দেখেছেন তা রেকর্ড করুন। চলাকালীন, আপনি অন্য একটি সেশন থেকে pg_stat_progress_create_index কুয়েরি করে দেখতে পারেন।
যাচাই শুধুই "ইনডেক্স আছে" নয়। নিশ্চিত করুন planner সেটি বেছে নিতে পারে, তারপর ডিপ্লয়মেন্টের পরে বাস্তবে কুয়েরি টাইমিং দেখুন। যদি নতুন ইনডেক্স ব্যবহৃত না হয়, তখন পুরোনোটা দ্রুত ড্রপ করতে যেও না — আগে কুয়েরি বা ইনডেক্স ডিফিনিশন ঠিক করুন।
ধাপে ধাপে: ব্লক ছাড়া ইনডেক্স বদলানো বা সরানো
সবচেয়ে নিরাপদ প্যাটার্ন হলো প্রথমে নতুনটি যোগ করা, ট্রাফিককে নতুন ইনডেক্স থেকে লাভ করাতে দেয়া, এবং শুধুমাত্র তখনই পুরোনোটি সরানো। এভাবে আপনার কাছে একটি কাজ করা ফলব্যাক থাকে।
পুরোনো ইনডেক্স বদলানোর নিরাপদ অর্ডার
-
CREATE INDEX CONCURRENTLYদিয়ে নতুন ইনডেক্স তৈরি করুন। -
যাচাই করুন এটি ব্যবহৃত হচ্ছে। ধীর কুয়েরিগুলোতে
EXPLAINচেক করুন এবং সময়ের সাথে ইনডেক্স ইউসেজ মনিটর করুন। -
তার পরে কেবলমাত্র পুরোনো ইনডেক্সটি concurrent করে ড্রপ করুন। ঝুঁকি বেশি হলে দুইটি ইনডেক্স একটি পূর্ণ ব্যবসায়িক সাইকেল ধরে রেখে দিন।
ইনডেক্স ড্রপ করা: কখন CONCURRENTLY কাজ করে (এবং কখন করে না)
আপনি নিজে তৈরি করা সাধারণ ইনডেক্সের জন্য DROP INDEX CONCURRENTLY সাধারণত সঠিক পছন্দ। দুইটি গটচা: এটি ট্রানজেকশন ব্লকের ভিতরে চলবে না, এবং শুরু ও শেষেও সংক্ষিপ্ত লক লাগবে, তাই দীর্ঘ চলমান ট্রানজেকশনের কারণে এটি বিলম্বিত হতে পারে।
ইনডেক্স যদি PRIMARY KEY বা UNIQUE কনস্ট্রেন্টের জন্য বিদ্যমান থাকে, সাধারণত সরাসরি ড্রপ করা যাবে না। আপনাকে কনস্ট্রেন্টটি ALTER TABLE দিয়ে বদলাতে হবে, যা বহু ক্ষেত্রে শক্ত লক নেয়। এটাকে আলাদা পরিকল্পিত রক্ষণাবেক্ষণ হিসেবে আটেন।
ইনডেক্সের নাম বদলানো
ALTER INDEX ... RENAME TO ... সাধারণত দ্রুত, কিন্তু টুলিং বা মাইগ্রেশনগুলো ইনডেক্স নামের উপর নির্ভর করলে এটি এড়ান। নিরাপদ অভ্যাস হলো শুরুর দিকে পরিষ্কার নাম বেছে নেওয়া।
যদি পুরনো ইনডেক্স দরকার হয়
কখনো কখনো দুইটি ভিন্ন কুয়েরি প্যাটার্নের জন্য দুইটি ইনডেক্স দরকার হতে পারে। গুরুত্বপূর্ণ কুয়েরিগুলো এখনও পুরানো ইনডেক্সে নির্ভর করলে সেটি রেখে দিন। নতুন ইনডেক্সকে (কলাম অর্ডার, partial শর্ত) মিলিয়ে দেখুন পরিবর্তন করুন, না হলে পুরোনোটা ফেলবেন না।
বিল্ড চলাকালীন লক ও প্রগ্রেস মনিটর করা
CREATE INDEX CONCURRENTLY থাকলেও আপনাকে বাস্তবে কী হচ্ছে তা দেখে রাখা উচিত। বেশিরভাগ অবাক করা ঘটনা দুই বিষয় থেকে আসে: একটি blocking session আপনি লক্ষ্য করেননি, অথবা একটি দীর্ঘ চলমান ট্রানজেকশন যে বিল্ডটিকে অপেক্ষায় রাখছে।
ব্লক করা সেশন চিহ্নিত করা (কে কাকে ব্লক করছে)
লক-ওয়েটের ওপর অপেক্ষা করছে এমন সেশনগুলো খুঁজুন:
SELECT
a.pid,
a.usename,
a.application_name,
a.state,
a.wait_event_type,
a.wait_event,
now() - a.xact_start AS xact_age,
left(a.query, 120) AS query
FROM pg_stat_activity a
WHERE a.wait_event_type = 'Lock'
ORDER BY xact_age DESC;
সঠিক ব্লকার জানতে blocked_pid থেকে blocking_pid অনুসরণ করুন:
SELECT
blocked.pid AS blocked_pid,
blocking.pid AS blocking_pid,
now() - blocked.xact_start AS blocked_xact_age,
left(blocked.query, 80) AS blocked_query,
left(blocking.query, 80) AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
ON blocking.pid = ANY (pg_blocking_pids(blocked.pid))
WHERE blocked.wait_event_type = 'Lock';
বিল্ড প্রগ্রেস ও "স্টাক" সিগন্যাল দেখা
PostgreSQL ইনডেক্স বিল্ড প্রগ্রেস উন্মোচন করে। যদি দীর্ঘ সময় কোনো অগ্রগতি না দেখেন, তাহলে একটি দীর্ঘ ট্রানজেকশন আছে কিনা দেখুন (অften একটি idle সেশন পুরোনো snapshot ধরে রাখে)।
SELECT
pid,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index;
সিস্টেম প্রেশারের ওপরও নজর রাখুন: ডিস্ক IO, replication lag, এবং বাড়তে থাকা কুয়েরি টাইম। Concurrent বিল্ড আপটাইমের অনুগত হলেও সেগুলো অনেক ডেটা পড়ে।
প্রোডাকশনে কার্যকর কিছু সরল নিয়ম:
- প্রগ্রেস চলমান থাকলে এবং ইউজার প্রভাব কম থাকলে অপেক্ষা করুন।
- যদি বিল্ড দীর্ঘ ট্রানজেকশনের কারণে আটকে থাকে এবং সেটি নিরাপদে শেষ করা না যায়, বাতিল করে পুনঃনির্ধারণ করুন।
- যদি আইও পিক সময়ে কাস্টমার-ফেসিং কুয়েরি ক্ষতিগ্রস্ত হয়, তখন বিরতি দিন।
- কেবলমাত্র বাইরের উপায় হিসেবে টার্মিনেট করুন, এবং সেটা করার আগে সেশনটি ঠিক কী করছে নিশ্চিত করুন।
টিম কমিউনিকেশনের জন্য আপডেটগুলো সংক্ষিপ্ত রাখুন: শুরু সময়, বর্তমান ফেজ, কী ব্লক করছে (যদি কিছু থাকে), এবং পরবর্তী চেক কখন করবেন।
রোলব্যাক প্ল্যান: নিরাপদভাবে কিভাবে পেছনে ফিরবেন
ইনডেক্স পরিবর্তনগুলো তখনই কম ঝুঁকির থাকে যখন আপনি শুরু করার আগে বেরিয়ে আসার পরিকল্পনা রাখেন। সবচেয়ে নিরাপদ রোলব্যাক প্রায়ই নাটকীয়ভাবে পূর্বাবস্থায় ফিরানো নয় — এটা সাধারণত নতুন কাজ বন্ধ করে পুরোনো ইনডেক্স রেখে দেওয়া।
ইনডেক্স কাজ ব্যর্থ হওয়ার সাধারণ উপায়
অধিকাংশ প্রোডাকশন ব্যর্থতা ভবিষ্যদ্বাণীযোগ্য: বিল্ড টাইমআউটে পড়ে, কেউ ইন্টার্ভেন করে এবং বাতিল করে, সার্ভারে ডিস্ক কমে যায়, বা বিল্ড সাধারণ ট্রাফিকের সাথে প্রতিযোগিতা করে ব্যবহারকারী-সম্মুখে লেটেন্সি বাড়িয়ে দেয়।
CREATE INDEX CONCURRENTLY থাকলে বিল্ড বাতিল করা অ্যাপের জন্য সাধারণত নিরাপদ কারণ কুয়েরিগুলো চল্তেই থাকে। বদলে দরকার হয় ক্লিনআপ: বাতিল বা ব্যর্থ concurrent বিল্ড একটি invalid ইনডেক্স রেখে যেতে পারে।
নিরাপদ ক্যানসেল ও ক্লিনআপ নিয়ম
Concurrent বিল্ড বাতিল করলে সেটি সাধারণ ট্রানজেকশনের মতো রোলব্যাক করে না। PostgreSQL এমন একটি ইনডেক্স রাখতে পারে যা আছে কিন্তু planner-এ ভ্যালিড নয়।
-- Cancel the session building the index (use the PID you identified)
SELECT pg_cancel_backend(<pid>);
-- If the index exists but is invalid, remove it without blocking writes
DROP INDEX CONCURRENTLY IF EXISTS your_index_name;
ড্রপের আগে নিশ্চিত করুন আপনি যা দেখছেন তা:
SELECT
c.relname AS index_name,
i.indisvalid,
i.indisready
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'your_index_name';
যদি indisvalid = false হয়, সেটা ব্যবহার হচ্ছে না এবং মুছে ফেলাই নিরাপদ।
পুরানো ইনডেক্স প্রতিস্থাপনের সময় ব্যবহারিক রোলব্যাক চেকলিস্ট:
- নতুনটা পুরোটা build এবং valid না হওয়া পর্যন্ত পুরোনো ইনডেক্স রাখুন।
- নতুন বিল্ড ব্যর্থ বা বাতিল হলে invalid নতুন ইনডেক্সকে concurrent করে ড্রপ করুন।
- যদি আপনি আগে থেকেই পুরোনো ইনডেক্স ড্রপ করে ফেলেন, পূর্বাবস্থা ফিরিয়ে আনতে সেটি
CREATE INDEX CONCURRENTLYদিয়ে পুনরায় তৈরি করুন। - ডিস্ক প্রেসার যদি ব্যর্থতার কারণ হয়, আগে জায়গা মুক্ত করুন, তারপর পুনরায় চেষ্টা করুন।
- টাইমআউট যদি ব্যর্থতার কারণ হয়, তখন একটি শান্ত উইন্ডো শিডিউল করুন বদলে জোর করে চালানোর চেয়ে।
উদাহরণ: আপনি একটি অ্যাডমিন সার্চের জন্য নতুন ইনডেক্স শুরু করলেন, এটি 20 মিনিট চলল, তারপর ডিস্ক অ্যালার্ট বাজে। বিল্ড বাতিল করুন, invalid ইনডেক্স concurrent করে ড্রপ করুন, আর পুরোনো ইনডেক্স টারাফিক সার্ভ করে যেতে দিন। জায়গা ফাঁকা করে পরে আবার চেষ্টা করুন, কোনো ব্যবহারকারীর দৃশ্যমান আউটেজ ছাড়াই।
ইনডেক্স সংক্রান্ত সাধারণ ভুল যা আকস্মিক আউটেজ সৃষ্টি করে
ইনডেক্স সংক্রান্ত বেশিরভাগ আউটেজ PostgreSQL "ধীর" হওয়ার কারণে নয়। সেগুলো ঘটে যখন এক ছোট বিবরণ একটি নিরাপদ পরিবর্তনকে ব্লকিং করে দেয়।
1) concurrent বিল্ডকে একটি ট্রানজেকশনের ভিতরে রাখা
CREATE INDEX CONCURRENTLY ট্রানজেকশন ব্লকের ভিতরে চালানো যাবে না। অনেক মাইগ্রেশন টুল ডিফল্টভাবে প্রতিটি পরিবর্তনকে একটি ট্রানজেকশনে আবৃত করে। ফলাফল হয় বা তো কঠিন ত্রুটি (শ্রেষ্ঠ ক্ষেত্রে) বা একটি গোলমেলে ডেপ্লয় যেটা রিট্রাই করে।
মাইগ্রেশন চালানোর আগে নিশ্চিত করুন আপনার টুল একটি স্টেটমেন্ট বাইরের ট্রানজেকশনে চালাতে পারে, অথবা মাইগ্রেশনকে একটি বিশেষ non-transactional ধাপে ভাগ করুন।
2) পীক ট্রাফিক চলাকালীন শুরু করা
Concurrent ইনডেক্স বিল্ড ব্লক কমায়, কিন্তু এগুলো লোড বাড়ায়: অতিরিক্ত রিড, অতিরিক্ত রাইট, এবং autovacuum-এ চাপ। ডেপ্লয় উইন্ডোতে ট্রাফিক স্পাইক থাকলে বিল্ড চালানো ধীরতা তৈরি করে যা আউটেজের মতো অনুভূত হতে পারে।
একটি শান্ত সময় বেছে নিন এবং এটাকে যেকোনো প্রোডাকশন রক্ষণাবেক্ষণের মতো বিবেচনা করুন।
3) দীর্ঘ চলমান ট্রানজেকশন উপেক্ষা করা
একটি একক দীর্ঘ ট্রানজেকশন concurrent বিল্ডের ক্লিনআপ ধাপে বাধা দিতে পারে। ইনডেক্স লাগাতে গেলে প্রগ্রেস চলতে দেখালেও শেষের কাছে সেটি পুরোনো স্ন্যাপশটগুলোর জন্য অপেক্ষা করে বসে থাকতে পারে।
অভ্যাস বানান: শুরু করার আগে দীর্ঘ চলমান ট্রানজেকশন চেক করুন, এবং যদি প্রগ্রেস আটকে থাকে আবার চেক করুন।
4) ভুল জিনিস ড্রপ করা (বা কনস্ট্রেন্ট ভাঙা)
টিমগুলো মাঝে মাঝে স্মৃতিতে থাকা নাম দিয়ে ইনডেক্স ড্রপ করে ফেলে, অথবা এমন একটি ইনডেক্স মুছে ফেলে যেটি একটি uniqueness রুল সাপোর্ট করে। ভুল অবজেক্ট ড্রপ করলে enforcement (unique constraints) হারাতে পারেন বা কুয়েরি পারফরম্যান্স তৎক্ষণাৎ খারাপ হয়ে যেতে পারে।
দ্রুত সিকিউরিটি চেকলিস্ট: ক্যাটালগে ইনডেক্স নাম ভেরিফাই করুন, নিশ্চিত করুন এটি কোনো কনস্ট্রেন্ট সাপোর্ট করে কি না, স্কিমা এবং টেবিল দুটো যাচাই করুন, এবং "create new" আলাদা রাখুন "drop old" থেকে। শুরু করার আগে রোলব্যাক কমান্ড রেডি রাখুন।
বাস্তবসম্মত উদাহরণ: একটি অ্যাডমিন সার্চকে দ্রুত করা
একটি সাধারণ কষ্টকর জায়গা হলো স্টেজিংয়ে ছোট লাগে কিন্তু প্রোডাকশনে ধীর একটি অ্যাডমিন সার্চ। ধরুন আপনার কাছে একটি বড় tickets টেবিল (কয়েক কোটি রো) আছে, অভ্যন্তরীণ অ্যাডমিন প্যানেল পিছনে, এবং এজেন্টরা প্রায়ই খোঁজ করেন "একটি গ্রাহকের খোলা টিকিটগুলো, নবীনতম প্রথম।"
কুয়েরিটি দেখতে এমন:
SELECT id, customer_id, subject, created_at
FROM tickets
WHERE customer_id = $1
AND status = 'open'
ORDER BY created_at DESC
LIMIT 50;
(customer_id, status, created_at) এর উপর একটি পূর্ণ ইনডেক্স সাহায্য করে, কিন্তু এটি প্রতিটি টিকিট আপডেটে লেখার অতিরিক্ত ওভারহেড যোগ করে, এমনকি যখন টিকিটগুলি closed হয়। যদি অধিকাংশ রো open না হয়, একটি partial index সাধারণত সহজ ও কার্যকরী জয় করে:
CREATE INDEX CONCURRENTLY tickets_open_by_customer_created_idx
ON tickets (customer_id, created_at DESC)
WHERE status = 'open';
প্রোডাকশনে একটি নিরাপদ টাইমলাইন:
- প্রিফ্লাইট: নিশ্চিত করুন কুয়েরির আকার স্থিতিশীল এবং টেবিলে নতুন ইনডেক্স বিল্ডের জন্য পর্যাপ্ত ফ্রি ডিস্ক আছে।
- বিল্ড: আলাদা সেশনে
CREATE INDEX CONCURRENTLYচালান, স্পষ্ট টাইমআউট সেটিংস দিয়ে। - যাচাই:
ANALYZE tickets;চালান এবং নিশ্চিত করুন planner নতুন ইনডেক্স ব্যবহার করছে। - ক্লিনআপ: আত্মবিশ্বাসী হলে, এখন অপ্রয়োজনীয় পুরোনো ইনডেক্স
DROP INDEX CONCURRENTLYকরুন।
সাফল্যের চিত্র:
- অ্যাডমিন সার্চ সাধারণ কেসে সেকেন্ড থেকে মিলিসেকেন্ড স্তরে নেমে আসে।
- বিল্ড চলাকালীন সাধারণ পাঠ্য ও লেখাগুলো কাজ করে থাকে।
- বিল্ড চলাকালীন CPU ও ডিস্ক IO বাড়ে কিন্তু আপনার স্বাভাবিক নিরাপত্তা সীমানার মধ্যে থাকে।
- আপনি স্পষ্ট আগে/পরে সংখ্যা দেখাতে পারেন: কুয়েরি সময়, স্ক্যান করা রো, এবং লক ইতিহাস।
দ্রুত চেকলিস্ট এবং পরবর্তী ধাপ
ইনডেক্স কাজ নিরাপদ হবে যখন আপনি এটাকে একটি ছোট প্রোডাকশন রিলিজের মতো আচরণ করবেন: প্রস্তুত, চালানোর সময় দেখবেন, তারপর ফল যাচাই করে ক্লিনআপ করবেন।
শুরু করার আগে:
- একটি surprise লক চিরকারীর জন্য টাইমআউট সেট করুন।
- নতুন ইনডেক্স তৈরির জন্য পর্যাপ্ত ফ্রি ডিস্ক আছে কি না নিশ্চিত করুন।
- দীর্ঘ চলমান ট্রানজেকশন আছে কি না দেখুন।
- একটি কম-ট্রাফিক উইন্ডো বেছে নিন এবং "ডোন" কবে সেটা সংজ্ঞায়িত করুন।
- এখনই আপনার রোলব্যাক প্ল্যান লিখে রাখুন।
চালানোর সময়:
- ব্লকিং ও লক ওয়েট চেক করুন।
pg_stat_progress_create_indexদিয়ে বিল্ড প্রগ্রেস ট্র্যাক করুন।- অ্যাপ লক্ষণের দিকে তাকান: এরর রেট, টাইমআউট, এবং টেবিল-সংক্রান্ত ধীর এন্ডপয়েন্টগুলো।
- যদি লক ওয়েট বেড়ে যায় বা ইউজার-ফেসিং টাইমআউট স্পাইক করে, বাতিল করার জন্য প্রস্তুত থাকুন।
- কি ঘটল তা লগ করুন: শুরু সময়, শেষ সময়, এবং কোনো অ্যালার্ট।
শেষ হলে ইনডেক্সটি valid কিনা নিশ্চিত করুন, আপনার মূল কুয়েরিগুলো চালিয়ে প্ল্যান ও টাইমিং উন্নত হয়েছে কি না দেখুন, এবং তারপরই পুরোনো ইনডেক্সগুলো non-blocking ভাবে সরান।
যদি আপনি এটি একাধিকবার করেন, তাহলে এটিকে একটি পুনরাবৃত্তিযোগ্য ডেলিভারি ধাপে পরিণত করুন: একটি ছোট রানবুক, প্রোডাকশন-সমমানের ডেটা নিয়ে স্টেজিং রিহার্সাল, এবং স্পষ্ট মালিক যিনি বিল্ড দেখবেন।
If you're building internal tools or admin panels with AppMaster (appmaster.io), it helps to treat database changes like index builds as part of the same release checklist as your backend updates: measured, monitored, and with a rollback you can execute quickly.
প্রশ্নোত্তর
ডাউনটাইম সাধারণত পূর্ণ আউটেজের মতো নয় — সেটা লক ওয়েট হিসেবে প্রকাশ পায়। একটি স্বাভাবিক CREATE INDEX পুরো নির্মাণকালে writes (INSERT/UPDATE/DELETE) ব্লক করতে পারে, ফলে সেই ধরনের অনুরোধগুলো অপেক্ষা করে টাইমআউট হতে পারে; ফলত পেজ হ্যাং করে এবং ব্যাকলগ তৈরি হয়।
CREATE INDEX CONCURRENTLY ব্যবহার করুন যখন টেবিল বাস্তব ট্রাফিক সার্ভ করে এবং আপনি writes থামাতে পারবেন না। বড় বা ব্যস্ত টেবিলের জন্য এটি সাধারণত নিরাপদ ডিফল্ট, যদিও এটা ধীরে চলে এবং দীর্ঘ ট্রানজেকশনগুলোতে আটকে যেতে পারে।
না। এটি ব্লকিং কমায়, কিন্তু সম্পূর্ণ-lock-free নয়। শুরু ও শেষের দিকে সংক্ষিপ্ত লক দরকার হয়, এবং যদি অন্য সেশনগুলো অন compatibles লক ধরে রাখে বা দীর্ঘ ট্রানজেকশন থাকে, তাহলে বিল্ড অপেক্ষা করতে পারে।
কারণ প্রোডাকশন সিস্টেম প্রায়ই সত্যিই নিস্তব্ধ থাকে না, এবং ইনডেক্স বিল্ড টেবিল সাইজ, CPU ও ডিস্ক আইও-এর কারণে বেশ সময় নিতে পারে। যদি বিল্ড আপনার নির্ধারিত উইন্ডো পার করে, তাহলে বা তো ঝুঁকি বাড়িয়ে ব্যবসায়িক সময়ে চালিয়ে যেতে হবে, না হলে কাজ মাঝপথে বাতিল করতে হবে।
প্রথমে নিশ্চিত করুন আপনি কোনো ট্রানজেকশনের ভেতরে নেই — CREATE INDEX CONCURRENTLY ট্রানজেকশন ব্লকের ভেতরে চলবে না। এরপর নতুন ইনডেক্স ও অস্থায়ী ওভারহেডের জন্য পর্যাপ্ত ডিস্ক আছে কি না দেখুন, এবং lock_timeout ছোট রাখুন যাতে আপনি দ্রুত ব্যর্থ হন যদি লক না পাওয়া যায়।
অনেক দল শুরু করার জন্য SET lock_timeout = '2s'; এবং SET statement_timeout = '0'; ব্যবহার করে। প্রথমটি লকের জন্য দ্রুত ব্যর্থ হওয়ার সুযোগ দেয়; দ্বিতীয়টি নিশ্চিত করে বিল্ড মাঝপথে অ্যাগ্রেসিভ স্ট্যাটমেন্ট টাইমআউটের কারণে না থেমে যায়।
প্রথমে pg_stat_progress_create_index দেখুন — ফেজ এবং ব্লক/টিউপল কাউন্টারগুলো এগোছ কি না। যদি প্রগ্রেস আটকে থাকে, pg_stat_activity দিয়ে লক ও দীর্ঘতর ট্রানজেকশন (বিশেষত idle sessions যেগুলি পুরোনো snapshot ধরে রাখে) খুঁজুন।
নিরাপদ উপায় হলো প্রথমে নতুন ইনডেক্স CREATE INDEX CONCURRENTLY দিয়ে তৈরি করা, নিশ্চিত হওয়া যে planner সেটি ব্যবহার করছে (এবং বাস্তবে কুয়েরি টাইমিং উন্নত হয়েছে), তারপর মাত্র পুরোনো ইনডেক্স DROP INDEX CONCURRENTLY করে ফেলা। এই “add first, remove later” প্যাটার্নে ব্যর্থ হলে ফলব্যাক থাকে।
DROP INDEX CONCURRENTLY সাধারণত স্বাভাবিক ইনডেক্সের জন্য নিরাপদ, কিন্তু এটাও ট্রানজেকশন ব্লকের ভেতরে চলবে না এবং শুরু/শেষে সংক্ষিপ্ত লক লাগে, তাই দীর্ঘ ট্রানজেকশনগুলোর কারণে বিলম্ব হতে পারে। যদি ইনডেক্সটি PRIMARY KEY বা UNIQUE কনস্ট্রেন্টের কারণ হয়, তাহলে সাধারণত আপনাকে ALTER TABLE করে কনস্ট্রেন্ট বদলাতে হবে — যা শক্ত লক নেবে এবং আলাদা পরিকল্পনা দরকার।
বিল্ড সেশন বাতিল করুন, তারপর দেখুন একটি invalid ইনডেক্স রেখে গেছে কি না। যদি indisvalid = false থাকে, তাহলে সেটি ব্যবহার হচ্ছে না এবং DROP INDEX CONCURRENTLY দিয়ে মুছে ফেলুন; পুরোনো ইনডেক্স ইতোমধ্যে মুছে ফেলা হয়ে থাকলে পুরোনো অবস্থা ফিরিয়ে আনতে সেটি ফের CREATE INDEX CONCURRENTLY করে তৈরি করুন।


