0

Elasticsearch Bài học 22: Reindex API - Bài toán đau đầu khi phải thay đổi Mapping trên Production (Zero-downtime)

Chúng ta đã đi đến bài học cuối cùng của Phần 4. Ở Bài 21, bạn đã tạo ra một Index mới cực kỳ xịn sò mang tên hasaki_products_v2 tích hợp sẵn từ điển tiếng Việt (Vietnamese Analyzer).

Nhưng ngoài đời thực, hệ thống đang chạy live. Index cũ hasaki_products_v1 (dùng Analyzer mặc định) đang chứa 1 triệu sản phẩm và vẫn đang phục vụ hàng nghìn request tìm kiếm mỗi giây từ khách hàng.

Bạn không thể dùng lệnh UPDATE để sửa Mapping hay Analyzer của Index v1. Mọi thứ đã bị "đóng băng" dưới dạng Inverted Index. Cách duy nhất là copy toàn bộ 1 triệu bản ghi từ v1 sang v2. Câu hỏi đặt ra là: Làm sao để copy và chuyển đổi luồng traffic mà API Backend không hề bị gián đoạn dù chỉ 1 mili-giây?

1. Reindex API: Cỗ máy "dọn nhà" mẫn cán

Elasticsearch cung cấp một API chuyên dụng để copy dữ liệu từ Index này sang Index khác, gọi là _reindex.

Bản chất của _reindex là nó tự động thực hiện một vòng lặp Scroll (lấy lô dữ liệu từ Source) và Bulk (ghi lô dữ liệu vào Destination) hoàn toàn ở background, bạn không cần phải viết code PHP hay Golang để tự kéo đẩy dữ liệu.

Cú pháp cơ bản:

JSON

POST _reindex
{
  "source": {
    "index": "hasaki_products_v1"
  },
  "dest": {
    "index": "hasaki_products_v2"
  }
}

Khi chạy lệnh này, Elasticsearch sẽ bốc từng cục JSON _source từ v1 và ném sang v2. Tại v2, dữ liệu sẽ tự động đi qua luồng Pipeline mới (Vietnamese Analyzer) và tạo ra Inverted Index mới chuẩn xác hơn.

Tối ưu hóa tốc độ Reindex:

Copy 1 triệu bản ghi có thể tốn khá nhiều thời gian. Để tiến trình này diễn ra với tốc độ ánh sáng, bạn cần chuẩn bị Index v2 thật kỹ:

  1. Tắt Refresh: Set "refresh_interval": "-1" cho v2 để nó không mất công tạo Segment phục vụ search trong lúc đang copy.

  2. Tắt Replica: Set "number_of_replicas": 0 cho v2. Chỉ ghi vào Primary Shard, sau khi copy xong mới bật Replica lên lại để nó tự đồng bộ.

  3. Chạy ngầm (Async): Thêm tham số ?wait_for_completion=false vào URL. Lệnh sẽ trả về ngay một task_id. Bạn có thể dùng ID này để kiểm tra tiến độ copy % thay vì bị treo Terminal.

2. Bí kíp Zero-downtime: Thiết kế hệ thống với Index Alias

Nếu API Backend của bạn đang hard-code (viết cứng) tên Index là hasaki_products_v1 vào trong URL, thì khi reindex xong, bạn sẽ phải sửa code thành hasaki_products_v2, build lại app, và deploy. Quá trình deploy chắc chắn gây ra downtime vài giây hoặc vài phút.

Các hệ thống chuyên nghiệp giải quyết bài toán này bằng Index Alias (Bí danh).

Alias giống như một Symlink (shortcut) trong hệ điều hành, hoặc một CNAME trong DNS. Nó là một tên giả trỏ đến một Index thật.

Quy tắc vàng: API Backend không bao giờ được gọi trực tiếp tên Index thật. Phải luôn gọi thông qua Alias.

Kịch bản chuyển đổi hoàn hảo:

Bước 1: Trạng thái hiện tại Backend của bạn chỉ biết gọi vào POST /hasaki_products/_search. Trong Elasticsearch, bạn cấu hình Alias hasaki_products trỏ tới Index gốc hasaki_products_v1. Mọi thứ chạy bình thường.

Bước 2: Tạo Index V2 và Reindex Bạn tạo hasaki_products_v2 với Mapping mới. Kích hoạt lệnh _reindex từ v1 sang v2. Lúc này, khách hàng vẫn đang search trên v1 thông qua Alias. Chả ai nhận ra hệ thống đang copy dữ liệu ở dưới.

Bước 3: Cú lừa thế kỷ - Atomic Switch (Đảo công tắc) Sau khi _reindex chạy xong 100%, v2 đã đầy đủ dữ liệu. Bạn chạy lệnh Đổi Alias. Lệnh này phải được gộp vào chung một block _aliases để đảm bảo tính Atomic (Nguyên tử) – tức là việc gỡ alias khỏi v1 và gắn sang v2 diễn ra đồng thời, không có khoảng hở (gap) thời gian nào ở giữa.

JSON

POST _aliases
{
  "actions": [
    {
      "remove": {
        "index": "hasaki_products_v1",
        "alias": "hasaki_products"
      }
    },
    {
      "add": {
        "index": "hasaki_products_v2",
        "alias": "hasaki_products"
      }
    }
  ]
}

Và bùm! Ngay mili-giây tiếp theo, mọi API request từ Backend (vẫn đang gọi vào hasaki_products) đã tự động được định tuyến sang hasaki_products_v2. Khách hàng lập tức cảm nhận được kết quả tìm kiếm tiếng Việt thông minh hơn. Không có downtime. Không cần sửa code Backend. Không cần deploy lại server.

3. Cạm bẫy cuối cùng: Dữ liệu bị thay đổi trong lúc Reindex (Dual-write problem)

Đây là bài toán vận hành khó nhất. Nếu quá trình _reindex mất 30 phút, thì trong 30 phút đó, hệ thống thật vẫn đang có khách hàng mua hàng (sản phẩm tồn kho thay đổi). Những update mới nhất đó sẽ ghi vào v1 (vì Alias vẫn trỏ về v1). Khi bạn Switch sang v2, v2 sẽ bị thiếu mất những cập nhật diễn ra trong 30 phút đó.

Cách giải quyết (Mô hình Dual-write): Trong môi trường siêu nghiêm ngặt về dữ liệu (CDC từ MySQL qua), chúng ta thường áp dụng kỹ thuật:

  1. Alias đọc (Read Alias) trỏ về v1.

  2. Alias ghi (Write Alias) trỏ về CẢ v1 và v2. Hoặc đơn giản nhất, sau khi Reindex lần 1 xong, bạn chạy lại API _reindex lần 2 nhưng thêm điều kiện "query": { "range": { "updated_at": { "gte": "thời gian bắt đầu reindex lần 1" } } } để chỉ copy phần dữ liệu "cặn" (delta) thay đổi, sau đó mới Switch.

Chúc mừng bạn! Bạn đã sống sót qua 4 Module đầu tiên, từ kiến trúc phân tán lõi, cơ chế Inverted Index, phân tích Token cho đến kỹ năng vận hành Migration Zero-downtime.

Bạn đã có một cái móng nhà cực kỳ vững chắc. Kể từ Bài 23 trở đi, chúng ta sẽ chính thức bước vào Phần 5: Nghệ thuật Tìm kiếm (Query DSL). Nơi chúng ta sẽ biến những chuỗi JSON vô tri thành các thuật toán "hút hồn" khách hàng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí