# Lần đầu vào database của khách hàng: đọc schema, truy vấn trên bản sao và khoá phiên chỉ đọc

> Trước khi gõ truy vấn đầu tiên ở khách hàng, hãy tìm một bản sao để đọc; nếu buộc phải dùng primary, hãy khoá phiên ở chế độ chỉ đọc và đặt thời hạn cho mọi câu lệnh.

Bản gốc: https://fdetimes.net/vi/bach-khoa/postgresql-va-nosql-o-khach-hang/

Sáng thứ hai ở văn phòng khách hàng. Đội IT đưa bạn một connection string kèm lời dặn: "Database đây, anh cứ xem thoải mái". Bạn mở DBeaver, thấy hơn 200 bảng không có tài liệu, và việc đầu tiên bạn muốn làm là `SELECT COUNT(*)` trên bảng lớn nhất.

Bạn nên dừng tay ở đây. Nếu connection string trỏ vào primary, câu lệnh vô hại đó vẫn chạy chung tài nguyên với đơn hàng thật của khách. Lời "cứ xem thoải mái" là phép lịch sự của đội IT, còn hệ thống của họ thì không vì thế mà chịu tải tốt hơn.

Ở một khách hàng mới, bạn cần hiểu dữ liệu của họ thật nhanh nhưng không được làm hỏng thứ gì. Bài này hướng dẫn một quy trình bạn dùng được ngay ngày mai: chọn đúng nơi để truy vấn, khoá phiên làm việc, đọc schema của PostgreSQL và của NoSQL, rồi tránh những lỗi mà người mới hay mắc.

## Truy vấn ở đâu là câu hỏi đầu tiên

PostgreSQL có một chế độ gọi là hot standby. Ở chế độ này bạn kết nối được vào máy chủ bản sao và chạy truy vấn chỉ đọc trong lúc nó vẫn đang nhận dữ liệu từ primary. Đây là nơi FDE nên làm việc, và ngay từ buổi đầu bạn nên hỏi đội IT: "Bên mình có standby nào cho em kết nối không?"

Hot standby còn cho bạn một lớp bảo vệ có sẵn. Tài liệu PostgreSQL nói mọi kết nối vào standby đều chỉ đọc tuyệt đối, đến bảng tạm cũng không ghi được. Lỡ tay chạy một câu `UPDATE` thì câu đó bị từ chối chứ không chạm vào dữ liệu.

MongoDB thì khác. Theo mặc định, ứng dụng gửi mọi thao tác đọc tới primary của replica set, nên bạn phải tự đổi read preference sang secondary để không đè thêm tải lên production. Việc này chỉ cần một dòng, như bạn sẽ thấy trong ví dụ ở phần sau.

## Khoá phiên làm việc trước khi gõ câu đầu tiên

Có những khách hàng không có standby, hoặc chỉ đưa bạn một user trên primary. Lúc đó bạn phải tự dựng hàng rào, và nên dán bốn dòng sau vào đầu mọi phiên làm việc:

```sql
SET default_transaction_read_only = on;
SET statement_timeout = '30s';
SET lock_timeout = '2s';
SET idle_in_transaction_session_timeout = '60s';
```

Mỗi dòng chặn một kiểu tai nạn. `default_transaction_read_only` mặc định là off, tức là mọi transaction đều được ghi, nên phải bật lên để transaction mới mặc định chỉ đọc. `statement_timeout` huỷ mọi câu lệnh chạy quá thời gian bạn đặt, nhờ vậy một phép JOIN thiếu điều kiện không thể chạy suốt buổi chiều.

Hai dòng còn lại xử lý vấn đề khoá. `lock_timeout` huỷ câu lệnh nếu nó chờ khoá trên bảng, index hay dòng quá lâu, để truy vấn của bạn không nằm trong hàng đợi chặn đường giao dịch thật. `idle_in_transaction_session_timeout` ngắt phiên nào mở transaction rồi bỏ đó, chẳng hạn khi bạn mở công cụ GUI, chạy một câu, rồi đi ăn trưa.

**Điểm mấu chốt:** READ ONLY chỉ cấm bạn sửa dữ liệu. Truy vấn của bạn vẫn tốn CPU, vẫn gây I/O và vẫn chiếm chỗ trong hàng đợi khoá.

Tài liệu PostgreSQL còn ghi rõ READ ONLY chỉ là khái niệm chỉ đọc ở mức cao, không chặn mọi thao tác ghi xuống đĩa. Nhưng lý do chính để cần cả bốn dòng nằm ở chỗ khác: read-only không giới hạn một truy vấn được chiếm CPU, I/O hay chờ khoá trong bao lâu. Ba dòng timeout mới đặt ra giới hạn đó.

## Ví dụ: đọc schema của một công ty giao hàng

Thử hình dung khách hàng là một công ty giao hàng. Đơn hàng nằm trong PostgreSQL, còn log sự kiện của tài xế nằm trong MongoDB. Bạn được nhờ tìm hiểu vì sao báo cáo tỉ lệ giao trễ không khớp giữa hai phòng ban.

Với PostgreSQL, bạn bắt đầu từ information_schema. Đây là tập view theo chuẩn SQL nên câu truy vấn quen thuộc với bất kỳ ai từng làm việc với RDBMS:

```sql
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;
```

Xuất kết quả ra file rồi đọc từng cột theo tên. Trong ví dụ này, cặp `orders.driver_id` và `drivers.id` đã gợi ra quan hệ giữa hai bảng dù không có foreign key. Có một giới hạn: information_schema không chứa những tính năng riêng của PostgreSQL, muốn tìm hiểu chúng bạn phải truy vấn system catalogs trong `pg_catalog`.

MongoDB thì không có bảng nào để liệt kê. Schema của NoSQL linh hoạt, mỗi document có thể chứa kiểu dữ liệu khác nhau, vì vậy cấu trúc thật chỉ hiện ra khi bạn lấy mẫu:

```javascript
db.getMongo().setReadPref("secondaryPreferred")
db.driver_events.aggregate([
{ $sample: { size: 1000 } },
{ $project: { kv: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$kv" },
{ $group: { _id: "$kv.k", n: { $sum: 1 } } },
{ $sort: { n: -1 } }
])
```

Giả sử kết quả cho thấy `delivered_at` có trong 1.000 document, còn `delivered_ts` chỉ có trong 300. Nhiều khả năng ứng dụng đã đổi tên field ở một phiên bản nào đó, và hai phòng ban đang đọc hai field khác nhau. Đó là giả thuyết đầu tiên bạn mang đến cuộc họp, kèm con số cụ thể để mọi người kiểm tra lại.

## Cái giá của việc đọc trên bản sao

Bản sao an toàn hơn primary nhưng vẫn có những giới hạn riêng. Trên hot standby, một truy vấn dài có thể xung đột với thay đổi mà standby đang replay từ primary, chẳng hạn một lệnh DROP TABLE. Khi thời gian chờ vượt quá `max_standby_streaming_delay` hoặc `max_standby_archive_delay`, PostgreSQL sẽ huỷ truy vấn của bạn.

Gặp lỗi đó thì đừng nhờ DBA tăng tham số. Hãy chia truy vấn nhỏ lại, chẳng hạn chạy theo từng tuần thay vì cả năm. Truy vấn ngắn ít bị huỷ hơn, và nếu bị huỷ thì bạn cũng mất ít công hơn.

Với MongoDB, mọi read preference trừ primary đều có thể trả về dữ liệu cũ, vì secondary sao chép từ primary theo cơ chế bất đồng bộ. Bạn có thể đặt `maxStalenessSeconds` để giới hạn độ trễ chấp nhận được.

Nói rộng hơn, đa số NoSQL chỉ đảm bảo eventual consistency, nên khi báo con số cho khách, hãy ghi rõ số đó được đọc từ đâu và vào lúc nào.

## Những lỗi khiến FDE mất lòng tin

Lỗi phổ biến nhất là tin vào connection string mà không kiểm tra. Hãy chạy `SELECT pg_is_in_recovery();` để biết mình đang ở standby hay primary, mất có hai giây.

Kế đến là kết luận về schema NoSQL chỉ từ vài document mở bằng `find()`. Vì mỗi document có thể mang cấu trúc khác nhau, hãy dùng `$sample` hoặc sắp xếp theo `_id` để nhìn thấy cả document cũ lẫn mới trước khi kết luận.

Một lỗi khác là dùng số liệu đọc từ secondary trong một cuộc họp cần số liệu thời gian thực mà không nói trước. Khi khách phát hiện con số lệch, họ dễ nghi ngờ cả những phân tích khác của bạn. Còn chuyện để transaction treo trong công cụ GUI thì `idle_in_transaction_session_timeout` sẽ tự xử lý giúp bạn, miễn là bạn đã cài nó.

## Biến kỹ năng này thành điểm cộng khi đi phỏng vấn

Khi đọc JD của một vị trí FDE, hãy để ý những cụm như "work directly with customer data" hay "integrate with existing systems". Đó là dấu hiệu tuần đầu tiên của bạn sẽ trông giống như cảnh mở đầu bài này.

Trong CV, đừng chỉ ghi "thành thạo SQL". Hãy viết rằng bạn đã xây một quy trình truy cập chỉ đọc gồm phiên có timeout, truy vấn trên replica và lấy mẫu schema NoSQL. Một dòng như vậy cho thấy cụ thể bạn đã làm gì với dữ liệu thật, điều mà "thành thạo SQL" không nói được.

Khách hàng có thể quên bạn đã viết bao nhiêu truy vấn hay. Nhưng nếu bạn từng làm chậm hệ thống đặt hàng của họ dù chỉ một lần, họ sẽ nhớ chuyện đó rất lâu.

**Thử ngay tuần này:**

- Viết một file safe_session.sql gồm bốn lệnh SET trong bài, chạy nó trên một PostgreSQL ở máy mình rồi thử một câu ghi để chắc chắn nó bị từ chối.
- Dùng information_schema.columns liệt kê toàn bộ cột của một database mẫu và tự vẽ sơ đồ quan hệ giữa các bảng trong 30 phút.
- Viết một aggregation MongoDB lấy mẫu 1.000 document và đếm số lần mỗi field xuất hiện, sau đó ghi lại field nào chỉ có ở một phần document.

## Nguồn

- [What Is NoSQL? NoSQL Databases Explained | MongoDB](https://www.mongodb.com/nosql-explained)

- [26.4. Hot Standby (PostgreSQL 18 Documentation)](https://www.postgresql.org/docs/current/hot-standby.html)

- [19.11. Client Connection Defaults (PostgreSQL 18 Documentation)](https://www.postgresql.org/docs/current/runtime-config-client.html)

- [SET TRANSACTION (PostgreSQL 18 Documentation)](https://www.postgresql.org/docs/current/sql-set-transaction.html)

- [Chapter 35. The Information Schema (PostgreSQL 18 Documentation)](https://www.postgresql.org/docs/current/information-schema.html)

- [Read Preference - Database Manual - MongoDB Docs](https://www.mongodb.com/docs/manual/core/read-preference/)

- [SQL Tutorial: Learn SQL from Scratch for Beginners](https://www.sqltutorial.org/)
