Hai cái store xứng đáng có mặt
Dữ liệu danh tính khách sạn nằm trong một bảng quan hệ: khoảng 1.9 triệu row, chừng 46 cột supplier-ID, mỗi row là một khách sạn mà platform biết đến. Câu hỏi mà nghiệp vụ đặt cho nó nghe rất đơn giản — khách sạn 123 của supplier A có phải cùng một toà nhà với khách sạn 456 của supplier B không? — nhưng danh tính có tính bắc cầu. A khớp B, B khớp C, suy ra A khớp C, qua một chuỗi mà độ dài bạn không biết trước. Model chuyện đó bằng một bảng join từng cặp thì mỗi lượt tra bắc cầu biến thành một cú self-join với độ sâu không xác định: bạn cứ join cái bảng với chính nó cho tới khi không lòi ra gì mới nữa. Engine quan hệ sinh ra cho những cú join có hình dạng biết trước, không phải cho kiểu "đi tiếp cho tới khi hết biên".
Thế nên các lượt tra chạy trên một graph store — Neo4j, traversal độ sâu thay đổi — còn bản thân các row vẫn nằm ở bảng quan hệ. Cùng một lý lẽ đó, ở công việc trước, đặt một search index cạnh transactional store: những câu mà một ô search hỏi — text mờ, facet, xếp hạng — là những câu mà một row store trả lời rất tệ ở bất kỳ quy mô nào đáng có.
Cả hai quyết định đều đúng. Cho làm lại mình vẫn làm cả hai. Chính vì thế chúng mới đáng viết ra, vì cái giá của chúng rất dễ bị bỏ ra khỏi câu chuyện.
Cái giá thật
Mỗi derived store là một bản sao, và một bản sao là một món nợ với ba kỳ trả không bao giờ ngừng đáo hạn.
Rebuild từ nguồn thế nào? Bảng quan hệ là sự thật; graph chỉ soi gương nó. Đến ngày cái gương bị hỏng, hay schema xê dịch, hay đơn giản là bạn hết tin nó, bạn cần một con đường dựng lại toàn bộ từ row gốc. Con đường đó là code, phải có người viết, và phải còn chạy được — một script rebuild từng chạy ổn ở một nửa số row hiện tại là một giả thuyết, không phải một công cụ.
Phát hiện nó trôi bằng cách nào? Một cái gương không tự khai là nó đang chậm. Một row được update trong bảng mà không update trong graph không sinh ra lỗi ở đâu cả — chỉ có một lượt tra lặng lẽ trả lời bằng dữ liệu của hôm qua. Phải có một thứ gì đó so hai bên theo lịch, hoặc đếm những gì đáng ra phải bằng nhau ở cả hai phía. Graph đã từng trôi khỏi bảng xa đến đâu — mình không nói được, vì bọn mình không đo liên tục, và chính lời thú nhận đó cũng là một phần chi phí gánh nợ: một độ trôi chưa đo không đồng nghĩa với độ trôi bằng 0.
Trong lúc nó chậm, hệ thống làm gì? Giữa một đợt rebuild, hay một khoảng lag, query vẫn cứ đến. Trả lời bằng dữ liệu cũ, trả lời thiếu, hay từ chối — phải có người chọn một, một cách chủ đích, trước lần đầu tiên nó xảy ra chứ không phải ngay giữa lúc đó.
Cái graph gánh món nợ này từ 2022 và cho tới giờ mọi thứ vẫn giữ được. Nhưng "vẫn giữ được" là một phát biểu về kỷ luật, không phải về kiến trúc — cái sync là một thứ có người nuôi, mãi mãi, và nó không bao giờ tốt nghiệp thành đồ miễn phí.
Trước cái store tiếp theo
Những câu phải trả lời trước khi thêm một derived store, theo thứ tự bây giờ mình sẽ hỏi:
- Rebuild lại từ đầu, từ nguồn, bằng một script đang tồn tại được không? Không phải trên nguyên tắc — trong một file, chạy được ngay hôm nay.
- Rebuild đó mất bao lâu? Với quy mô của bọn mình, gần đây mình chưa bấm giờ một lần rebuild đầy đủ nào, và đó là câu trả lời thật của phần lớn các team — bản thân nó đã là lời cảnh báo. Một cú rebuild không ai biết mất bao lâu là một kế hoạch khôi phục không ai biết mất bao lâu.
- Trong lúc nó lag, người dùng nhìn thấy gì? Cũ, thiếu, hay bị từ chối — được chọn và viết xuống, không phải được khám phá trong một cái incident.
Nếu một cái store không trả lời nổi câu đầu tiên, nó không phải derived store — nó là một nguồn sự thật thứ hai, và thế là có hai nguồn, tức là bài học trước đó trên site này lặp lại từ đầu.
Điều rút ra
Thêm một cái store thì dễ; cái giá là con đường rebuild mà đằng nào bạn cũng phải viết. Viết nó trước. Nếu con đường rebuild đắt đến mức không viết nổi, thì cái store đã đắt đến mức không nên thêm — bạn chỉ vừa học được điều đó trước cái incident thay vì ở giữa nó.
Một bản sao không ai audit là một bản sao nên mặc định là sai. Câu hỏi duy nhất là sai bao nhiêu, và một độ trôi chưa đo không phải là 0.
Derived store là thứ mua, không phải thứ được cho. Cái graph và cái index đều đáng đồng tiền. Sai lầm không nằm ở việc trả giá — sai lầm là không nhận ra mình đang trả.