Tự trap bằng jsonb trong PostgreSQL
Bài này để nói về lỗi ngớ ngẩn của mình khi làm việc với jsonb trong PostgreSQL, không phải một bài giới thiệu về jsonb. Mình xin phép bắt đầu luôn nha.
jsonb là gì?
Hiểu ngắn gọn là lưu trữ json document trong PostgreSQL, còn chi tiết ở đây nhé
Thao tác với jsonb bằng Golang
- Tạo bảng với cột jsonb:
CREATE TABLE example_tbl (
id SERIAL PRIMARY KEY,
data jsonb
);
- Đẩy dữ liệu vào
data := map[string]any{
"name": "Qui Vo",
"age": 24,
"country": "Vietnam",
}
query := "INSERT INTO example_tbl (data) VALUES ($1)"
_, err = conn.Exec(ctx, query, data)
if err != nil {
panic(err)
}
- Kiểm tra dữ liệu vừa insert
- Oke ngon! Giờ thử thêm một field mới vào jsonb nào
// Retrieve
query := "SELECT id, data FROM example_tbl WHERE id = $1"
var eTbl ExampleTbl
err = pgxscan.Get(ctx, conn, &eTbl, query, 1)
if err != nil {
panic(err)
}
// Update
eTbl.Data["github"] = "vkhanhqui"
query = "UPDATE example_tbl SET data = $1 WHERE id = $2"
_, err = conn.Exec(ctx, query, eTbl.Data, eTbl.ID)
if err != nil {
panic(err)
}
Field mới đã vô
Question: Điều gì xảy ra nếu nhiều field update đồng thời? Liệu Atomicity có được đảm bảo?
Vấn đề mình gặp
Nếu thực tế các requests mà chạy được như trên thì đỡ quá. Đoạn code update chắc chắn sẽ xảy ra race condition
khi chạy đồng thời. Ví dụ vừa update các social urls
, vừa đổi name
trong cùng một field jsonb.
Giả lập lại quá trình update:
- Retrieve a single record by id
- Update a single key value of the jsonb field by record id
Và quá trình này đồng thời cũng chạy song song:
updates := map[string]string{
"github": "new vkhanhqui",
"website": "new khanhqui.com",
"linkedin": "new khanhqui",
"name": "new Qui Vo",
}
// Run in parallel
var wg sync.WaitGroup
for k, v := range updates {
wg.Add(1)
go func() {
// Retrieve
eTbl, err := retrieve(ctx, conn, 1)
if err != nil {
panic(err)
}
// Update
eTbl.Data[k] = v
err = update(ctx, conn, 1, eTbl.Data)
if err != nil {
panic(err)
}
defer wg.Done()
}()
}
wg.Wait()
Vì bị dính race condition
nên chỉ có name
và github
được update
Cách xử lý
Đổi qua update từng field trong jsonb thay vì cả cụm
// Run in parallel
var wg sync.WaitGroup
for k, v := range updates {
wg.Add(1)
go func() {
query := fmt.Sprintf(`
UPDATE example_tbl
SET data = jsonb_set(
COALESCE(data, '{}'),
'{%s}', '"%s"'::jsonb
)
WHERE id = $1`, k, v)
_, err = conn.Exec(ctx, query, 1)
if err != nil {
panic(err)
}
defer wg.Done()
}()
}
wg.Wait()
Atomicity đã được đảm bảo:
Toàn bộ code của bài này được lưu ở đây. Như thường lệ, hy vọng mọi người không gặp phải sai lầm của mình.