Thay đổi chỉ mục trong PostgreSQL không gián đoạn: sổ tay an toàn
Thay đổi chỉ mục trong PostgreSQL không gián đoạn bằng CONCURRENTLY, kiểm tra khóa đơn giản và bước rollback rõ ràng để giữ luồng traffic production liên tục.

Tại sao thay đổi chỉ mục gây gián đoạn (và cách tránh)
Công việc với chỉ mục nghe có vẻ vô hại. Bạn "chỉ" thêm một cấu trúc trợ giúp. Tuy nhiên trong PostgreSQL, việc xây dựng, xóa hoặc hoán đổi chỉ mục có thể lấy các khóa chặn các session khác. Nếu bảng bận, các lần chờ đó chất lên và ứng dụng bắt đầu có cảm giác hỏng.
Gián đoạn hiếm khi xuất hiện dưới dạng banner ngắt kết nối rõ ràng. Nó thường biểu hiện bằng các trang tải chậm, job nền bị trễ, và hàng đợi yêu cầu chờ database. Ai đó bấm "Tìm kiếm" rồi gặp timeout, trong khi công cụ hỗ trợ và màn hình quản trị đột nhiên cảm thấy ì ạch vì các truy vấn đơn giản không lấy được khóa cần thiết.
"Chạy ban đêm thôi" thất bại vì hai lý do phổ biến. Nhiều hệ thống không thật sự yên tĩnh (người dùng toàn cầu, job theo lịch, ETL, backup). Và thao tác với chỉ mục có thể kéo dài hơn bạn nghĩ vì phải đọc nhiều dữ liệu và cạnh tranh CPU/đĩa. Nếu cửa sổ bảo trì kết thúc khi quá trình còn dang dở, bạn phải chọn giữa chờ lâu hơn hoặc ngắt công việc.
Các thay đổi chỉ mục không gián đoạn không phải phép màu. Bản chất là chọn thao tác ít chặn nhất, đặt rào chắn (timeout và kiểm tra đĩa), và giám sát database khi chạy.
Playbook này tập trung vào thói quen thực tế cho production:
- Ưu tiên xây dựng chỉ mục theo chế độ concurrent khi cần giữ luồng đọc/ghi.
- Giám sát khóa và tiến độ để phản ứng sớm.
- Có đường lùi nếu thay đổi gây suy giảm hoặc kéo dài quá lâu.
Những gì không đề cập: lý thuyết sâu về thiết kế chỉ mục, tối ưu truy vấn rộng, hay refactor schema viết lại nhiều dữ liệu.
Mô hình khóa đơn giản phía sau công việc với chỉ mục
PostgreSQL dùng khóa để giữ dữ liệu đúng khi nhiều session cùng tác động trên một bảng. Khóa chỉ là một quy tắc cho biết ai được phép đọc/ghi đối tượng ngay bây giờ, và ai phải chờ.
Phần lớn thời gian bạn không nhận ra khóa vì PostgreSQL dùng các chế độ nhẹ cho phép truy vấn bình thường chạy. DDL thì khác. Khi tạo hoặc xóa chỉ mục, PostgreSQL cần đủ quyền kiểm soát trên bảng để giữ catalog và dữ liệu nhất quán. Càng cần nhiều quyền kiểm soát, càng nhiều session khác có thể bị buộc chờ.
Xây dựng chỉ mục khác với sử dụng chỉ mục
Sử dụng chỉ mục thường ít tốn khóa. SELECT, UPDATE và DELETE có thể đọc hoặc duy trì chỉ mục trong khi các session khác cũng làm vậy.
Xây dựng chỉ mục thì khác. PostgreSQL phải quét bảng, sắp xếp hoặc băm các khóa, và ghi cấu trúc mới ra đĩa. Công việc đó tốn thời gian, và thời gian biến "khóa nhỏ" thành "vấn đề lớn" trong production.
CONCURRENTLY thay đổi gì (và không thay đổi gì)
Một CREATE INDEX bình thường lấy khóa mạnh và chặn ghi trong suốt quá trình. CREATE INDEX CONCURRENTLY được thiết kế để giữ cho các đọc và ghi bình thường vẫn chạy trong khi index được xây dựng.
Nhưng "concurrent" không có nghĩa là "không khóa". Vẫn có cửa sổ khóa ngắn ở đầu và cuối, và quá trình có thể thất bại hoặc chờ nếu có thứ khác giữ khóa không tương thích.
Kết quả quan trọng cần biết:
- Xây dựng không concurrent có thể chặn insert, update và delete trên bảng.
- Xây dựng concurrent thường cho phép đọc và ghi, nhưng có thể bị chậm hoặc dừng bởi các transaction chạy lâu.
- Các bước hoàn tất vẫn cần khóa ngắn, nên hệ thống quá bận có thể thấy chờ ngắn.
Chọn cách phù hợp: concurrent hay không
Bạn có hai lựa chọn chính khi thay đổi chỉ mục: xây dựng chỉ mục bình thường (nhanh nhưng chặn), hoặc xây dựng với CONCURRENTLY (thường không chặn lưu lượng ứng dụng, nhưng chậm hơn và nhạy cảm với transaction lâu).
Khi nào nên chọn CONCURRENTLY
Dùng CREATE INDEX CONCURRENTLY khi bảng phục vụ lưu lượng thực và bạn không thể tạm dừng ghi. Đây thường là lựa chọn an toàn khi:
- Bảng đủ lớn khiến một build bình thường có thể mất phút hoặc giờ.
- Bảng có ghi ổn định, không chỉ đọc.
- Bạn không thể lên lịch cửa sổ bảo trì thực thụ.
- Bạn cần xây dựng trước, kiểm tra, rồi xóa chỉ mục cũ sau.
Khi nào build bình thường chấp nhận được
CREATE INDEX bình thường có thể là hợp lý khi bảng nhỏ, lưu lượng thấp, hoặc bạn có cửa sổ kiểm soát. Nó thường hoàn thành nhanh hơn và đơn giản để chạy.
Cân nhắc cách bình thường nếu build luôn nhanh trong staging và bạn có thể tạm dừng ghi (dù chỉ một lúc).
Nếu bạn cần ràng buộc uniq, quyết định sớm. CREATE UNIQUE INDEX CONCURRENTLY hoạt động nhưng sẽ thất bại nếu tồn tại giá trị trùng lặp. Trong nhiều hệ thống production, tìm và sửa trùng lặp mới là phần việc chính.
Kiểm tra trước khi chạm production
Hầu hết vấn đề xảy ra trước khi câu lệnh bắt đầu. Một vài kiểm tra giúp bạn tránh hai bất ngờ lớn: khóa bất ngờ và build chỉ mục chạy lâu hoặc dùng nhiều không gian hơn dự tính.
-
Đảm bảo bạn không đang trong transaction.
CREATE INDEX CONCURRENTLYsẽ thất bại nếu bạn chạy nó sauBEGIN, và một số công cụ GUI lặng lẽ bọc các câu lệnh trong transaction. Nếu không chắc, mở session mới và chỉ chạy câu lệnh tạo index ở đó. -
Thiết lập kỳ vọng về thời gian và đĩa. Build concurrent thường lâu hơn build bình thường và cần không gian làm việc thêm khi chạy. Lên kế hoạch cho index mới cộng chi phí tạm thời, và xác nhận bạn có đủ dung lượng đĩa trống thoải mái.
-
Đặt timeout phù hợp với mục tiêu. Bạn muốn build thất bại nhanh nếu không lấy được khóa, nhưng không muốn session chết giữa chừng vì
statement_timeoutquá chặt. -
Lưu baseline. Bạn cần bằng chứng thay đổi có hiệu quả và cách nhanh để phát hiện suy giảm. Ghi lại snapshot trước: thời gian truy vấn chậm, một
EXPLAIN (ANALYZE, BUFFERS)đại diện, và thống kê CPU, IO, kết nối, và dung lượng đĩa trống.
Thiết lập session an toàn nhiều đội dùng làm điểm khởi đầu (tùy chỉnh theo quy tắc của bạn):
-- Run in the same session that will build the index
SET lock_timeout = '2s';
SET statement_timeout = '0';
Bước theo bước: tạo index với CONCURRENTLY
Dùng CREATE INDEX CONCURRENTLY khi bạn cần lưu lượng ứng dụng tiếp tục hoạt động và bạn có thể chấp nhận thời gian build lâu hơn.
Trước hết, quyết định chính xác bạn sẽ xây dựng gì:
- Cụ thể về thứ tự cột (quan trọng).
- Xem xét một partial index có đủ không. Nếu hầu hết truy vấn lọc trên hàng "active", partial index có thể nhỏ hơn, nhanh hơn và rẻ hơn để duy trì.
Một thao tác an toàn trông như sau: ghi rõ mục tiêu và tên index, chạy build ngoài block transaction, giám sát đến khi hoàn thành, rồi xác minh planner có thể dùng trước khi bạn xóa thứ khác.
-- 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]';
Để ghi chú tiến độ (hữu ích cho audit), lưu lại giờ bắt đầu, giờ kết thúc, và bất kỳ chỗ chờ nào bạn thấy. Khi chạy, bạn có thể query pg_stat_progress_create_index từ session khác.
Xác minh không chỉ là "index tồn tại." Xác nhận planner có thể chọn nó, rồi theo dõi thời gian truy vấn thực tế sau khi triển khai. Nếu index mới không được dùng, đừng vội xóa index cũ. Sửa truy vấn hoặc định nghĩa index trước.
Bước theo bước: thay thế hoặc xóa index mà không chặn
Mẫu an toàn nhất là add trước, để traffic hưởng lợi từ index mới, rồi chỉ khi đó gỡ index cũ. Bằng cách đó, bạn giữ được fallback hoạt động.
Hoán đổi index cũ sang index mới (thứ tự an toàn)
-
Tạo index mới với
CREATE INDEX CONCURRENTLY. -
Xác minh nó được dùng. Kiểm tra
EXPLAINtrên các truy vấn chậm bạn quan tâm, và giám sát sử dụng index theo thời gian. -
Chỉ sau đó hãy drop index cũ bằng concurrent. Nếu rủi ro cao, giữ cả hai index trong một chu kỳ kinh doanh trước khi xóa.
Xóa index: khi CONCURRENTLY phù hợp (và khi không)
Với index bình thường bạn tự tạo, DROP INDEX CONCURRENTLY thường là lựa chọn đúng. Hai lưu ý: nó không thể chạy trong transaction block, và vẫn cần khóa ngắn ở đầu và cuối, nên có thể bị trì hoãn bởi transaction chạy lâu.
Nếu index tồn tại do PRIMARY KEY hoặc UNIQUE constraint, bạn thường không thể drop trực tiếp. Phải thay đổi constraint bằng ALTER TABLE, thao tác này có thể lấy khóa mạnh hơn. Xem đó là một hoạt động bảo trì có kế hoạch riêng.
Đổi tên index cho rõ ràng
Đổi tên (ALTER INDEX ... RENAME TO ...) thường nhanh, nhưng tránh nếu công cụ hoặc migration tham chiếu tên index. Thói quen an toàn hơn là chọn tên rõ ngay từ đầu.
Nếu vẫn cần index cũ
Đôi khi hai pattern truy vấn khác nhau cần hai index khác nhau. Nếu truy vấn quan trọng vẫn phụ thuộc vào index cũ, hãy giữ nó. Cân nhắc điều chỉnh index mới (thứ tự cột, điều kiện partial) thay vì cố gắng gỡ bỏ.
Giám sát khóa và tiến độ khi index đang xây
Ngay cả với CREATE INDEX CONCURRENTLY, bạn nên theo dõi điều gì đang xảy ra theo thời gian thực. Phần lớn sự cố bất ngờ đến từ hai nguyên nhân: một session chặn bạn mà bạn không để ý, hoặc một transaction chạy lâu khiến build bị kẹt chờ.
Tìm các session đang bị chặn (ai chặn ai)
Bắt đầu bằng cách tìm các session đang chờ khóa:
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;
Nếu cần biết chính xác blocker, theo blocked_pid đến 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';
Theo dõi tiến độ build và dấu hiệu "kẹt"
PostgreSQL cung cấp tiến độ xây dựng index. Nếu bạn thấy không có chuyển động lâu, hãy tìm transaction chạy lâu (thường là session idle giữ snapshot cũ).
SELECT
pid,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index;
Cũng theo dõi áp lực hệ thống: IO đĩa, độ trễ replication, và thời gian truy vấn tăng. Build concurrent thân thiện hơn với uptime, nhưng vẫn đọc nhiều dữ liệu.
Một vài quy tắc đơn giản hiệu quả trong production:
- Chờ nếu tiến độ đang chạy và tác động người dùng thấp.
- Hủy và lên lịch lại nếu build bị kẹt bởi transaction dài bạn không thể kết thúc an toàn.
- Tạm dừng trong giờ cao điểm nếu IO ảnh hưởng tới truy vấn khách hàng.
- Chỉ terminate như phương sách cuối cùng, và chỉ sau khi xác nhận session đó đang làm gì.
Về giao tiếp trong team, giữ cập nhật ngắn gọn: giờ bắt đầu, giai đoạn hiện tại, cái gì đang chặn (nếu có), và khi nào bạn sẽ kiểm tra lại.
Kế hoạch rollback: cách lùi an toàn
Thay đổi chỉ mục chỉ giữ được rủi ro thấp nếu bạn lên kế hoạch thoát trước khi bắt đầu. Lùi an toàn thường không phải undo ầm ĩ. Đó thường là dừng công việc mới và giữ index cũ tại chỗ.
Các cách phổ biến khiến công việc chỉ mục thất bại
Hầu hết lỗi production có thể đoán trước: build vượt timeout, ai đó hủy giữa sự cố, server thiếu đĩa, hoặc build cạnh tranh đủ để làm tăng độ trễ khách hàng.
Với CREATE INDEX CONCURRENTLY, hủy thường an toàn cho app vì truy vấn vẫn chạy. Đổi lại là dọn dẹp: build concurrent bị hủy hoặc thất bại có thể để lại index không hợp lệ.
Quy tắc hủy và dọn dẹp an toàn
Hủy một build concurrent không rollback như transaction bình thường. PostgreSQL có thể để lại index tồn tại nhưng không hợp lệ cho planner.
-- Cancel the session building the index (use the PID you identified)
SELECT pg_cancel_backend(\u003cpid\u003e);
-- If the index exists but is invalid, remove it without blocking writes
DROP INDEX CONCURRENTLY IF EXISTS your_index_name;
Trước khi xóa, xác nhận trạng thái bạn đang nhìn:
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';
Nếu indisvalid = false, nó không được planner dùng và an toàn để xóa.
Checklist rollback thực tế khi thay thế index tồn tại:
- Giữ index cũ cho tới khi index mới được xây xong và hợp lệ.
- Nếu build mới thất bại hoặc bị hủy, drop index mới không hợp lệ bằng
DROP INDEX CONCURRENTLY. - Nếu bạn đã xóa index cũ, tạo lại nó với
CREATE INDEX CONCURRENTLYđể phục hồi trạng thái trước đó. - Nếu áp lực đĩa gây lỗi, giải phóng dung lượng trước rồi thử lại.
- Nếu timeout gây lỗi, lên lịch cửa sổ yên tĩnh hơn thay vì ép chạy.
Ví dụ: bạn bắt đầu tạo index mới cho tìm kiếm admin, nó chạy 20 phút rồi cảnh báo đĩa bật. Hủy build, drop index không hợp lệ bằng concurrent, và giữ index cũ phục vụ traffic. Bạn có thể thử lại sau khi giải phóng dung lượng mà không gây outage cho người dùng.
Sai lầm thường gặp gây outage bất ngờ
Hầu hết outage quanh chỉ mục không phải vì PostgreSQL "chậm." Chúng xảy ra vì một chi tiết nhỏ biến thay đổi an toàn thành chặn.
1) Chạy build concurrent trong transaction
CREATE INDEX CONCURRENTLY không thể chạy trong transaction block. Nhiều công cụ migration bọc mọi thay đổi trong một transaction theo mặc định. Kết quả là một lỗi cứng (trường hợp tốt nhất) hoặc deploy lộn xộn với retry.
Trước khi chạy migration, xác nhận công cụ của bạn có thể chạy câu lệnh mà không có transaction bên ngoài, hoặc tách migration thành bước không transaction riêng.
2) Bắt đầu trong giờ cao điểm
Build concurrent giảm chặn nhưng vẫn tăng tải: đọc thêm, ghi thêm, và áp lực autovacuum. Bắt đầu trong cửa sổ deploy khi traffic tăng là cách thông thường gây chậm giống như outage.
Chọn thời điểm yên tĩnh và đối xử như mọi bảo trì production khác.
3) Bỏ qua transaction chạy lâu
Một transaction duy nhất chạy lâu có thể giữ lại giai đoạn cleanup của build concurrent. Index có thể tiến rất nhanh rồi dừng gần cuối vì chờ snapshot cũ biến mất.
Tạo thói quen: kiểm tra transaction chạy lâu trước khi bắt đầu, và kiểm tra lại nếu tiến độ dừng.
4) Xóa nhầm thứ (hoặc phá rối constraint)
Teams đôi khi xóa index bằng tên nhớ trong đầu, hoặc gỡ index đang hỗ trợ ràng buộc unique. Nếu xóa nhầm đối tượng, bạn có thể mất enforcement (unique constraint) hoặc làm suy giảm hiệu năng ngay lập tức.
Danh sách kiểm tra an toàn nhanh: xác minh tên index trong catalog, xác nhận nó có hỗ trợ constraint không, kiểm tra schema và bảng, và tách biệt rõ ràng "tạo mới" và "xóa cũ." Có lệnh rollback sẵn trước khi bắt đầu.
Ví dụ thực tế: tăng tốc tìm kiếm admin
Một điểm đau phổ biến là tìm kiếm admin nhanh ở staging nhưng chậm ở production. Giả sử bạn có bảng tickets lớn (hàng chục triệu hàng) phía sau panel admin nội bộ, và agents thường tìm "ticket mở cho một khách hàng, mới nhất trước."
Truy vấn trông như sau:
SELECT id, customer_id, subject, created_at
FROM tickets
WHERE customer_id = $1
AND status = 'open'
ORDER BY created_at DESC
LIMIT 50;
Index toàn phần trên (customer_id, status, created_at) giúp, nhưng nó tăng chi phí ghi cho mọi update ticket, kể cả ticket đã đóng. Nếu hầu hết hàng không phải open, partial index thường là lựa chọn đơn giản hơn:
CREATE INDEX CONCURRENTLY tickets_open_by_customer_created_idx
ON tickets (customer_id, created_at DESC)
WHERE status = 'open';
Lộ trình an toàn trên production:
- Preflight: xác nhận hình dạng truy vấn ổn định và bảng có đủ đĩa trống cho build index mới.
- Build: chạy
CREATE INDEX CONCURRENTLYtrong session riêng với timeout rõ ràng. - Validate: chạy
ANALYZE tickets;và xác nhận planner dùng index mới. - Cleanup: khi đã chắc chắn, drop các index thừa bằng
DROP INDEX CONCURRENTLY.
Thành công trông như thế nào:
- Tìm kiếm admin giảm từ vài giây xuống vài chục mili giây cho khách hàng điển hình.
- Đọc và ghi vẫn hoạt động trong khi build.
- CPU và IO đĩa tăng trong lúc build nhưng ở trong giới hạn an toàn của bạn.
- Bạn có số liệu rõ ràng trước/sau: thời gian truy vấn, số hàng quét, và lịch sử khóa.
Checklist nhanh và bước tiếp theo
Công việc với chỉ mục an toàn hơn khi bạn coi nó như một release nhỏ trên production: chuẩn bị, giám sát khi chạy, rồi xác minh trước khi dọn dẹp.
Trước khi bắt đầu:
- Đặt timeout để một khóa bất ngờ không treo mãi.
- Xác nhận đủ đĩa trống cho build index mới.
- Tìm transaction chạy lâu có thể làm chậm build.
- Chọn cửa sổ lưu lượng thấp và định nghĩa rõ khi nào là "xong."
- Ghi sẵn kế hoạch rollback.
Khi chạy:
- Theo dõi chặn và chuỗi chờ khóa.
- Dùng
pg_stat_progress_create_indexđể theo dõi tiến độ. - Quan sát triệu chứng ứng dụng: tỷ lệ lỗi, timeout, và endpoint liên quan đến bảng đó.
- Sẵn sàng hủy nếu chờ khóa tăng hoặc timeout người dùng tăng vọt.
- Ghi log những gì xảy ra: giờ bắt đầu, giờ kết thúc, và bất kỳ cảnh báo nào.
Sau khi hoàn thành, xác nhận index hợp lệ, chạy một hai truy vấn chính để thấy plan và thời gian cải thiện, rồi chỉ khi chắc chắn mới gỡ index cũ theo cách không chặn.
Nếu bạn làm việc này nhiều lần, biến nó thành bước giao hàng lặp lại: một runbook nhỏ, rehearsals trên staging với dữ liệu giống production, và một người chịu trách nhiệm theo dõi quá trình.
Nếu bạn đang xây công cụ nội bộ hoặc bảng quản trị với AppMaster (appmaster.io), tốt hơn là coi thay đổi cơ sở dữ liệu như build index là một phần của cùng checklist phát hành với cập nhật backend: có đo lường, giám sát, và kế hoạch rollback thực thi nhanh.
Câu hỏi thường gặp
Downtime thường xuất hiện dưới dạng chờ khóa chứ không phải một sự cố hoàn toàn. Một CREATE INDEX thông thường có thể chặn các thao tác ghi trong suốt quá trình xây dựng, nên các yêu cầu cần INSERT, UPDATE, hoặc DELETE sẽ phải chờ rồi timeout, khiến trang web treo và hàng đợi tăng lên.
Dùng CREATE INDEX CONCURRENTLY khi bảng có lưu lượng thực và bạn không thể tạm dừng ghi. Đây thường là lựa chọn an toàn cho các bảng lớn hoặc bận, mặc dù nó chạy chậm hơn và có thể bị chậm lại bởi các transaction chạy lâu.
Không. Nó giảm hiện tượng chặn nhưng không phải là không khoá. Vẫn có các khoảng khóa ngắn ở đầu và cuối, và quá trình xây dựng có thể phải chờ nếu các session khác giữ các khóa tương thích kém hoặc nếu có transaction chạy lâu ngăn bước cuối hoàn thành.
Bởi vì môi trường production thường không thật sự yên tĩnh, và việc tạo index có thể kéo dài hơn dự kiến do kích thước bảng, CPU và IO. Nếu quá trình chạy vượt quá cửa sổ bảo trì của bạn, bạn phải chọn giữa kéo dài rủi ro vào giờ làm việc hoặc hủy giữa chừng.
Đầu tiên, đảm bảo bạn không đang nằm trong một transaction, vì CREATE INDEX CONCURRENTLY sẽ thất bại nếu chạy trong transaction. Tiếp đó, xác nhận bạn có đủ dung lượng đĩa trống cho index mới và chi phí tạm thời, và đặt lock_timeout ngắn để thất bại nhanh nếu không thể lấy khóa cần thiết.
Một điểm khởi đầu thông dụng là chạy trong cùng session sẽ tạo index:
SET lock_timeout = '2s';
SET statement_timeout = '0';
Điều này giúp tránh chờ khóa vô hạn mà không làm chết phiên xây dựng vì statement_timeout quá nghiêm ngặt.
Bắt đầu bằng pg_stat_progress_create_index để xem giai đoạn và liệu số block/tuple có tiếp tục tăng hay không. Nếu tiến trình dừng, kiểm tra pg_stat_activity để tìm chờ khóa và tìm các transaction chạy lâu, đặc biệt là các session idle giữ snapshot cũ.
Tạo index mới bằng CREATE INDEX CONCURRENTLY, xác minh planner có thể dùng nó (và thời gian truy vấn thực tế được cải thiện), rồi chỉ sau đó mới DROP INDEX CONCURRENTLY index cũ. Thứ tự “thêm trước, gỡ sau” này giữ được phương án dự phòng hoạt động nếu index mới không được dùng hoặc gây suy giảm.
DROP INDEX CONCURRENTLY thường an toàn cho các index thông thường, nhưng nó vẫn cần các khoảnh khóa ngắn và không thể chạy trong transaction block. Nếu index hỗ trợ PRIMARY KEY hoặc UNIQUE, bạn thường phải thay đổi constraint bằng ALTER TABLE, thao tác này có thể cần các khóa mạnh hơn và cần lên kế hoạch.
Hủy phiên đang xây dựng index, rồi kiểm tra xem có index không hợp lệ còn sót lại. Nếu indisvalid là false, hãy gỡ nó bằng DROP INDEX CONCURRENTLY và giữ index cũ. Nếu bạn đã gỡ index cũ, tạo lại nó bằng CREATE INDEX CONCURRENTLY để khôi phục trạng thái trước đó.


