DDIA · Martin Kleppmann · Chương 2DDIA · Martin Kleppmann · Chương 2DDIA · Martin Kleppmann · Chapter 2
DDIA · Chương 2 · Mô hình dữ liệu & ngôn ngữ truy vấnDDIA · Chương 2 · Mô hình dữ liệu & ngôn ngữ truy vấnDDIA · Chapter 2 · Data Models & Query Languages

Mỗi tầng phần mềm là một lần kể lại dữ liệuCùng một dữ liệu, nhiều cách sắpEvery layer of software re-tells the data

Kleppmann mở Chương 2 bằng một khẳng định mạnh: mô hình dữ liệu có lẽ là phần quan trọng bậc nhất của việc phát triển phần mềm, vì nó không chỉ định đoạt phần mềm được viết ra sao mà còn định đoạt cách ta nghĩ về chính bài toán1. Phần mềm được xây thành nhiều tầng, mỗi tầng che tầng dưới bằng một mô hình sạch hơn: đối tượng trong ứng dụng → bảng hoặc tài liệu JSON → byte trên đĩa → dòng điện trong phần cứng. Chương này hỏi hai câu nối liền nhau: có những họ mô hình nào để chọn, và mỗi họ kéo theo ngôn ngữ truy vấn kiểu gì?

Dữ liệu giống như đồ đạc trong nhà. Cùng một mớ đồ, bạn có thể xếp vào ngăn kéo có nhãn, gói thành từng túi, hay treo lên một mạng lưới móc nối nhau. Cách xếp quyết định lúc tìm có nhanh không, và quyết định luôn cả cách bạn hỏi: "cái áo ở ngăn nào?" khác hẳn "lấy cho tôi mọi thứ nối với cái áo này". Nhớ nhé: chọn cách sắp dữ liệu là chọn luôn cách suy nghĩ về nó.

Kleppmann opens Chapter 2 with a strong claim: the data model is perhaps the single most important part of developing software, because it shapes not only how the software is written but how we think about the problem itself1. Software is built in layers, each hiding the one below behind a cleaner model: application objects → tables or JSON documents → bytes on disk → currents in hardware. This chapter asks two linked questions: which families of model are on offer, and what kind of query language does each one drag along with it?

∴
Luận đề của chương Điều quan trọng nhất The thesis of the chapter

Không có mô hình dữ liệu vạn năng. Quan hệ, tài liệu và đồ thị mỗi loại được sinh ra để đỡ một hình dạng quan hệ khác nhau — một-nhiều, nhiều-một, nhiều-nhiều — và chọn sai buộc lớp ứng dụng phải tự tay làm việc lẽ ra cơ sở dữ liệu nên làm1. Điều quyết định không phải nhãn "SQL" hay "NoSQL", mà là câu hỏi khiêm tốn hơn: dữ liệu của bạn thật ra có hình gì?

Không có kiểu sắp nào đúng cho mọi thứ. Có dữ liệu giống một cái cây (một người có nhiều việc làm), có dữ liệu giống một mạng lưới (ai cũng quen ai). Chọn nhầm kiểu thì sau này phải làm bù bằng tay rất mệt. Nhớ nhé: hãy nhìn hình dạng của dữ liệu trước khi chọn chỗ cất nó.

There is no universal data model. Relational, document and graph models each exist to carry a different shape of relationship — one-to-many, many-to-one, many-to-many — and choosing wrongly forces the application layer to hand-do work the database should have done1. What decides is not the "SQL" or "NoSQL" label but a humbler question: what shape is your data, really?

Trục 1 · Relational vs DocumentAxis 1 · Relational vs Document

Đối tượng trong ứng dụng không vừa với hàng trong bảngApplication objects don't fit rows in tables

Phần lớn ứng dụng ngày nay được viết bằng ngôn ngữ hướng đối tượng, nhưng dữ liệu lại nằm trong các bảng quan hệ; lớp mã phải dịch qua lại giữa hai thế giới đó được gọi là impedance mismatch6, và cả một họ framework ORM — Hibernate, ActiveRecord — ra đời chỉ để che bớt nó7. Kleppmann lấy một hồ sơ nghề nghiệp kiểu LinkedIn làm ví dụ chạy xuyên chương: một người, nhiều vị trí công việc, nhiều bằng cấp, nhiều thông tin liên hệ1. Chính cái hồ sơ ấy phơi ra ba hình dạng quan hệ mà toàn bộ cuộc tranh luận quan hệ-hay-tài liệu xoay quanh.

Trong chương trình, dữ liệu là những "đồ vật" lồng nhau: một người có một danh sách công việc, mỗi công việc lại có tên công ty, ngày tháng. Nhưng cơ sở dữ liệu kiểu cũ chỉ biết những bảng kẻ ô phẳng lì. Thế là lập trình viên phải ngồi tháo đồ vật ra thành nhiều bảng rồi lắp lại — mệt và dễ sai. Nhớ nhé: chỗ hai bên "không khớp nhau" chính là nơi sinh ra hầu hết rắc rối trong chương này.

Most applications today are written in object-oriented languages, yet the data lives in relational tables; the translation layer between those two worlds is called the impedance mismatch6, and a whole family of ORM frameworks — Hibernate, ActiveRecord — exists just to soften it7. Kleppmann uses a LinkedIn-style résumé as the chapter's running example: one person, many positions, many degrees, many pieces of contact information1. That single résumé exposes the three relationship shapes the whole relational-versus-document argument turns on.

Hình dạng 01 · Impedance mismatchShape 01 · Impedance mismatch

Lớp dịch giữa hai thế giớiA translation layer between two worlds

Bảng quan hệ là tập hợp các hàng phẳng; đối tượng trong bộ nhớ là cây lồng nhau có danh sách, tham chiếu và kế thừa. Sự vênh nhau giữa hai cấu trúc ấy là impedance mismatch6. ORM giảm bớt lượng mã bản mẫu phải viết7, nhưng Kleppmann nhấn rằng nó không xóa sự vênh — chỉ dời nó vào một lớp khác1.

Hình dung bạn có một con búp bê gỗ lồng nhau, nhưng cái tủ chỉ có những ngăn dẹt. Muốn cất thì phải tháo từng lớp ra, và muốn lấy thì phải lắp lại. Có người viết sẵn "máy tháo-lắp" giúp bạn, nhưng việc tháo-lắp vẫn còn đó. Nhớ nhé: công cụ làm đỡ mệt, chứ không làm cho hai hình dạng vừa nhau.

A relational table is a set of flat rows; an in-memory object is a nested tree with lists, references and inheritance. That gap is the impedance mismatch6. ORMs cut the boilerplate you must write7, but Kleppmann stresses they do not erase the mismatch — they only move it into another layer1.

ORM che bớt, không gỡ bỏ.
Nhớ nhé: vẫn phải tháo-lắp.
ORMs hide it, not remove it.
Hình dạng 02 · One-to-manyShape 02 · One-to-many

Một-nhiều: hồ sơ vốn là một cái câyOne-to-many: a résumé is a tree

Một hồ sơ có nhiều vị trí công việc, nhiều bằng cấp — quan hệ one-to-many. Trong SQL cổ điển, mỗi danh sách phải tách thành bảng con trỏ về hồ sơ bằng khóa ngoại. Trong mô hình tài liệu, cùng cấu trúc ấy được viết thẳng thành mảng lồng trong một tài liệu JSON1. Kleppmann nhận xét: ở đây mô hình tài liệu gần với cấu trúc dữ liệu của ứng dụng hơn, và cả cây được lấy về trong một lần đọc8.

Một người có nhiều chỗ làm, nhiều trường đã học. Kiểu bảng thì mỗi loại phải một cuốn sổ riêng rồi ghi mã số nối lại. Kiểu tài liệu thì cứ bỏ hết vào một túi mang tên người đó, mở túi là thấy đủ. Nhớ nhé: khi dữ liệu giống một cái cây, cất nguyên cây vào một túi là tiện nhất.

One résumé has many positions, many degrees — a one-to-many relationship. In classic SQL each list becomes a child table pointing back with a foreign key. In the document model the same structure is written directly as nested arrays inside one JSON document1. Kleppmann's point: here the document model is closer to the application's own data structure, and the whole tree comes back in a single read8.

Cây lồng nhau hợp với tài liệu.
Nhớ nhé: cả cây một túi.
Nested trees suit documents.
Hình dạng 03 · Many-to-one & many-to-manyShape 03 · Many-to-one & many-to-many

Nhiều-một và nhiều-nhiều: nơi tài liệu đuốiWhere documents run out of road

"Công ty", "trường", "vùng địa lý" nên là tham chiếu chuẩn hóa bằng mã định danh, không phải chuỗi tự do: nhiều hồ sơ cùng trỏ về một công ty — quan hệ many-to-one. Khi thêm lời giới thiệu giữa người với người thì thành many-to-many1. Chuẩn hóa đòi hỏi join, và Kleppmann chỉ ra rằng cơ sở dữ liệu tài liệu hỗ trợ join yếu — công việc nối phải dồn về mã ứng dụng, tức nhiều vòng truy vấn hơn8.

Nếu hàng nghìn hồ sơ đều ghi tên một công ty, đổi tên công ty ấy nghĩa là phải sửa hàng nghìn chỗ. Nên tốt hơn là ghi mã số công ty, rồi chỗ nào cần tên thì đi tra. Nhưng "đi tra" chính là việc mà kiểu-túi làm kém. Nhớ nhé: cái gì dùng chung cho nhiều người thì nên để một chỗ và trỏ tới, chứ đừng chép ra khắp nơi.

"Company", "school", "region" should be normalized references by identifier, not free text: many résumés point at one company — a many-to-one relationship. Add recommendations between people and it becomes many-to-many1. Normalization demands joins, and Kleppmann notes that document databases have weak join support — the joining work is pushed into application code, i.e. more round trips8.

Nhiều-nhiều là điểm yếu của tài liệu.
Nhớ nhé: dùng chung thì trỏ tới.
Many-to-many is the document's weak spot.
The debate is about shape, not about SQL vs NoSQL

Câu hỏi "quan hệ hay tài liệu?" thật ra là câu hỏi "dữ liệu của bạn có hình gì?". Nếu là cây một-nhiều và thường được nạp trọn vẹn, tài liệu thắng. Nếu có nhiều liên kết chéo giữa các thực thể, quan hệ — hoặc đồ thị — thắng.

Đừng hỏi "SQL hay NoSQL". Hãy hỏi: dữ liệu của tôi giống cái cây hay giống tấm lưới? Cây thì bỏ túi, lưới thì cần chỗ biết nối. Nhớ nhé: nhìn hình dạng trước, chọn công nghệ sau.

"Relational or document?" is really "what shape is your data?". If it is a one-to-many tree usually loaded whole, documents win. If it is full of cross-links between entities, relational — or graph — wins.

Cuộc tranh luận này đã diễn ra một lần, từ rất lâu trước NoSQL. Kleppmann kể lại rằng mô hình phân cấp — cây lồng nhau, gần hệt JSON ngày nay — được hiện thực trong IMS của IBM, hệ ra đời để quản lý bảng kê vật tư cho chương trình Apollo và phát tín hiệu sẵn sàng đầu tiên ngày 14/8/19684. Nó xử lý một-nhiều rất tốt và nhiều-nhiều rất tệ — đúng điểm yếu của mô hình tài liệu hôm nay. Hai lời đáp cạnh tranh xuất hiện: mô hình mạng của CODASYL, nơi bản ghi nối nhau bằng chuỗi con trỏ và lập trình viên phải men theo access path để tới dữ liệu5; và mô hình quan hệ mà E. F. Codd công bố trên Communications of the ACM năm 1970, đề xuất bày dữ liệu ra thành các quan hệ đơn giản để người dùng khỏi phải biết dữ liệu được tổ chức thế nào bên trong3. Đây là bối cảnh lịch sử, không phải hiện trạng: CODASYL nhạt dần khi quan tâm dồn sang quan hệ từ đầu thập niên 19805, còn IMS thì vẫn tồn tại trong các hệ mainframe kế thừa4.

Một-nhiều Cây tài liệu biểu diễn tự nhiên, lấy trọn một lần; quan hệ cần bảng con và khóa ngoại.
Nhiều-một Chuẩn hóa về mã định danh; quan hệ join rẻ, tài liệu phải nối thủ công trong mã ứng dụng.
Nhiều-nhiều Bảng nối hoặc mô hình đồ thị; đây là nơi mô hình tài liệu đuối nhất.
Một có nhiều Một người nhiều việc làm — bỏ chung một túi là gọn.
Nhiều về một Nghìn người cùng một công ty — ghi mã số rồi đi tra.
Ai cũng nối ai Bạn bè, giới thiệu, đề xuất — cần thứ biết đi theo đường nối.
One-to-many A document tree expresses it naturally and loads in one read; relational needs a child table and a foreign key.
Many-to-one Normalize to an identifier; relational joins are cheap, documents must stitch in application code.
Many-to-many A join table or a graph model; this is where the document model is weakest.
Trục 2 · Schema & localityAxis 2 · Schema & locality

Lược đồ đọc-khi-cần và cái giá của localitySchema-on-read and the price of locality

Hai lập luận còn lại bênh vực mô hình tài liệu là lược đồ linh hoạt và hiệu năng nhờ locality1. Kleppmann gỡ cả hai ra khỏi lớp khẩu hiệu: cơ sở dữ liệu tài liệu không phải "không có lược đồ" mà là lược đồ ngầm, kiểm khi đọc; còn locality chỉ có lợi khi bạn thật sự cần phần lớn tài liệu cùng lúc. Và ông kết phần này bằng quan sát quan trọng nhất của cả trục: hai phe đang hội tụ.

Người ta hay khen kiểu-túi vì hai điều: không bắt khai báo trước cái gì có trong túi, và mở một cái là thấy đủ. Cả hai đều đúng một nửa. Túi vẫn có "luật ngầm", chỉ là luật nằm trong đầu người viết chương trình thôi. Và mở nguyên túi chỉ lợi khi bạn cần gần hết đồ trong đó. Nhớ nhé: cái gọi là "tự do" thường chỉ là luật được dời sang chỗ khác.

The two remaining arguments for the document model are schema flexibility and performance from locality1. Kleppmann strips both of their slogan layer: a document database is not "schemaless" but has an implicit schema, checked on read; and locality only pays when you genuinely need most of the document at once. He closes the axis with its most important observation: the two camps are converging.

  1. Schema-on-read vs schema-on-write

    Không phải "không lược đồ", mà là lược đồ ngầmNot schemaless — an implicit schema

    Kleppmann đề nghị gọi đúng tên: cơ sở dữ liệu tài liệu là schema-on-read — cấu trúc chỉ được diễn giải lúc mã ứng dụng đọc dữ liệu; còn quan hệ là schema-on-write — cơ sở dữ liệu bắt tuân thủ ngay khi ghi. Ông ví von hai kiểu ấy với kiểm kiểu động và kiểm kiểu tĩnh trong ngôn ngữ lập trình1. Khác biệt lộ rõ nhất lúc đổi cấu trúc: bên tài liệu chỉ cần ghi dạng mới và để mã xử lý cả hai dạng; bên quan hệ cần một lệnh ALTER TABLE di trú.

    "Không có luật" thật ra là "luật không được viết ra". Khi bỏ đồ vào túi mà không dán nhãn, vẫn có một quy ước trong đầu bạn về cái gì nằm đâu — chỉ là máy không kiểm giúp. Khi nào máy kiểm: lúc cất, hay lúc lấy? Đó mới là khác biệt thật. Nhớ nhé: luật không biến mất, nó chỉ chuyển từ lúc cất sang lúc lấy.

    Kleppmann insists on the right name: document databases are schema-on-read — structure is interpreted only when application code reads the data; relational ones are schema-on-write — the database enforces it at write time. He likens the pair to dynamic versus static type checking in programming languages1. The difference shows up hardest during change: on the document side you write the new shape and let code handle both; on the relational side you need an ALTER TABLE migration.

    Khi nào hợpSchema-on-read hợp khi dữ liệu vốn không đồng nhất, hoặc cấu trúc do một hệ ngoài quyết định. Schema-on-write hợp khi mọi bản ghi đều cùng một dạng và bạn muốn máy phát hiện sai sót giúp.
    Chọn kiểu nàoĐồ lộn xộn thì đừng bắt dán nhãn cứng; đồ cùng loại thì dán nhãn cho máy kiểm giúp.
    When each fitsSchema-on-read fits heterogeneous data, or structure decided by an external system. Schema-on-write fits uniform records where you want the machine to catch mistakes for you.

    Cơ chế di trú

    Với schema-on-read, việc "di trú" chỉ là ghi tài liệu mới theo dạng mới và để mã đọc chấp nhận cả dạng cũ lẫn mới — chi phí dời sang mã ứng dụng và sẽ kéo dài chừng nào dữ liệu cũ còn tồn tại. Với schema-on-write, ALTER TABLE thường nhanh ở hầu hết hệ quan hệ, nhưng lệnh UPDATE ghi lại toàn bảng thì có thể rất nặng.

    Ranh giới đang mờ

    Ngay cả phe tài liệu cũng đã thêm ràng buộc tùy chọn: MongoDB cung cấp schema validation để đặt luật cho phép/chặn ở tầng cơ sở dữ liệu9. Đó là bằng chứng sớm cho luận điểm hội tụ ở thẻ 03.

  2. Locality — and its fine print

    Locality: lợi khi lấy trọn, hại khi tài liệu phìnhLocality: a win when you read it whole

    Một tài liệu thường được lưu như một chuỗi liên tục, nên nếu ứng dụng cần phần lớn nội dung cùng lúc thì một lần đọc là đủ — thay vì nhiều lần tra chỉ mục trên các bảng rải rác1. Nhưng Kleppmann kèm ngay phần chữ nhỏ: lợi ích ấy chỉ đúng khi bạn cần gần hết tài liệu, và vì nhiều cơ sở dữ liệu tài liệu phải ghi lại cả tài liệu khi cập nhật, nên nên giữ tài liệu nhỏ8.

    Để hết đồ của một người trong một túi thì lấy rất nhanh — chỉ cần với tay một lần. Nhưng nếu cái túi phình to mà mỗi lần thêm một món lại phải đổ ra xếp lại cả túi, thì cái nhanh lúc lấy sẽ bị trả giá lúc cất. Nhớ nhé: túi nên vừa tay, đừng để nó to quá.

    A document is usually stored as one contiguous string, so if the application needs most of it at once, a single read suffices — instead of several index lookups across scattered tables1. But Kleppmann attaches the fine print immediately: the benefit only holds when you need nearly the whole document, and because many document databases rewrite the entire document on update, you should keep documents small8.

    Hệ quả thiết kếNhúng dữ liệu con khi nó luôn được đọc cùng tài liệu cha và không tăng vô hạn; tách ra thành tham chiếu khi nó lớn dần hoặc được dùng chung8.
    Cách làmĐồ luôn đi cùng nhau thì để chung túi; đồ ngày càng nhiều hoặc dùng chung thì để riêng rồi trỏ tới.
    Design consequenceEmbed child data when it's always read with its parent and doesn't grow without bound; split it out into a reference when it grows or is shared8.

    Locality không độc quyền của tài liệu

    Kleppmann nhắc rằng ý tưởng gom dữ liệu liên quan nằm cạnh nhau trên đĩa cũng xuất hiện trong thế giới quan hệ dưới những tên khác — nhóm hàng của bảng con nằm lồng trong bảng cha, hay nhóm cột lưu chung một chỗ1. Nói cách khác, locality là một kỹ thuật lưu trữ, không phải một đặc quyền của mô hình tài liệu.

    Nối với Chương 3

    Vì sao một lần đọc liên tục lại rẻ hơn nhiều lần tra chỉ mục là câu chuyện của tầng lưu trữ — cấu trúc chỉ mục, chi phí tìm kiếm, ghi tuần tự. Đó chính là nội dung Chương 3 “Storage and Retrieval” sẽ mở ra2.

  3. Convergence of the two camps

    Hai phe đang mượn đồ của nhauThe two camps borrow from each other

    Kết luận thực dụng của Kleppmann: cơ sở dữ liệu quan hệ và tài liệu ngày càng giống nhau, và đó là một hướng đi tốt — mỗi bên xử lý phần dữ liệu hợp với mình1. Bằng chứng ở cả hai phía: PostgreSQL có kiểu json và jsonb cùng bộ toán tử và chỉ mục riêng10; còn RethinkDB — một cơ sở dữ liệu tài liệu — hỗ trợ phép nối giữa các bảng11, và MongoDB bổ sung ràng buộc lược đồ tùy chọn9.

    Hai phe từng cãi nhau giờ đang học lẫn nhau: bên ngăn kéo học cách chứa nguyên một túi trong ngăn, còn bên túi học cách nối các túi lại và đặt vài luật. Nhớ nhé: đừng chọn phe — chọn công cụ có đủ cả hai thứ bạn cần.

    Kleppmann's pragmatic conclusion: relational and document databases are becoming more alike, and that is a good direction — each handling the part of the data it suits1. The evidence runs both ways: PostgreSQL has json and jsonb types with their own operators and indexes10; RethinkDB — a document database — supports joins between tables11, and MongoDB added optional schema validation9.

    Đọc ngược lại nhãn"SQL" và "NoSQL" ngày càng là nhãn tiếp thị hơn là ranh giới kỹ thuật. Hỏi thẳng: hệ này có nhúng được cây không, có join được không, có ép lược đồ khi cần không?
    Cách hỏi đúngĐừng hỏi tên gọi. Hỏi: nó có để được cả cây không, có nối được không, có kiểm giúp không?
    Read past the label"SQL" and "NoSQL" are increasingly marketing labels rather than technical boundaries. Ask directly: can it nest a tree, can it join, can it enforce a schema when you want one?

    Mô hình lai

    Một hệ quan hệ lưu cột JSON cho phần dữ liệu không đồng nhất, đồng thời giữ khóa ngoại cho phần chuẩn hóa, là mô hình lai rất phổ biến trong thực tế10. Nó cho bạn locality ở chỗ cần và join ở chỗ cần.

    Giới hạn của hội tụ

    Hội tụ không có nghĩa là mọi hệ thành một. Đặc tính phân tán, mô hình giao dịch và cách xử lý nhất quán vẫn khác nhau rất nhiều — những chủ đề mà các phần sau của cuốn sách mới mổ xẻ2.

Trục 3 · Query languagesAxis 3 · Query languages

Nói cái mình muốn, đừng chỉ đường cho máyState what you want, don't steer the machine

Khi mô hình quan hệ ra đời, nó mang theo một cách hỏi dữ liệu hoàn toàn mới: khai báo3. Trước đó, truy vấn IMS hay CODASYL là những đoạn mã mệnh lệnh: lập trình viên phải men theo chuỗi con trỏ, di chuyển con trỏ bản ghi từng bước theo đúng access path đã định trước5. SQL lật ngược điều đó: bạn mô tả mẫu dữ liệu mình muốn, còn việc chọn đường đi để lấy nó là trách nhiệm của bộ tối ưu hóa. Kleppmann lập luận rằng chính sự ẩn giấu này — chứ không phải cú pháp — mới là nguồn sức mạnh1.

Có hai cách nhờ ai đó lấy đồ giúp bạn. Cách một: "đi thẳng, rẽ trái, mở ngăn thứ ba, lấy hộp màu xanh" — bạn phải biết đường. Cách hai: "lấy giúp tôi tất cả hộp màu xanh" — người kia tự tìm đường. Cơ sở dữ liệu hiện đại thích cách hai, vì nó tự nghĩ ra đường đi nhanh nhất. Nhớ nhé: nói kết quả bạn muốn, đừng đọc từng bước đi.

When the relational model arrived it brought an entirely new way of asking for data: declarative querying3. Before that, IMS and CODASYL queries were imperative code: the programmer followed pointer chains, stepping a record cursor along a predetermined access path5. SQL inverts this: you describe the pattern of data you want, and choosing the route to get it is the optimizer's job. Kleppmann argues that this hiding — not the syntax — is the source of the power1.

  1. Declarative vs imperative

    Khai báo tả mẫu, mệnh lệnh tả lối điDeclarative states a pattern, imperative states a path

    Một truy vấn khai báo nêu điều kiện mà kết quả phải thỏa và cách sắp xếp, biến đổi kết quả — không nêu cách đạt tới. Một đoạn mã mệnh lệnh nêu từng thao tác theo thứ tự. Để minh họa, Kleppmann rời khỏi thế giới cơ sở dữ liệu và dùng trình duyệt web: chọn phần tử bằng CSS hay XSL là khai báo — bạn tả cái gì cần tô — còn tự tay duyệt cây DOM bằng JavaScript để đổi lớp là mệnh lệnh1. Chính vì CSS là ngôn ngữ khai báo mà quy tắc kiểu vẫn đúng khi phần tử mới xuất hiện12.

    Muốn tô đỏ mọi tiêu đề trên trang, có hai cách. Cách mệnh lệnh: đi từng chữ một, thấy tiêu đề thì tô. Cách khai báo: viết một dòng luật "tiêu đề thì màu đỏ" rồi để máy lo. Cái hay của cách hai: tiêu đề mới thêm vào cũng tự đỏ, không cần sửa gì. Nhớ nhé: đặt luật thì bền hơn làm từng việc.

    A declarative query states the conditions results must satisfy and how to sort or transform them — not how to get there. Imperative code states each operation in order. To illustrate, Kleppmann leaves the database world for the web browser: selecting elements with CSS or XSL is declarative — you describe what to style — while hand-walking the DOM in JavaScript to flip classes is imperative1. Precisely because CSS is declarative, a style rule still applies when new elements appear12.

    Vì sao khác biệt lớnMã mệnh lệnh khóa chặt một cách thực thi; truy vấn khai báo để ngỏ mọi cách thực thi tương đương, nên engine có thể đổi chiến lược mà không cần bạn viết lại gì.
    Ý chínhRa lệnh từng bước thì phải sửa mỗi khi đổi; đặt luật thì máy tự lo.
    Why the difference is bigImperative code locks in one execution; a declarative query leaves every equivalent execution open, so the engine can change strategy without you rewriting anything.

    Gốc lịch sử

    Codd đề xuất mô hình quan hệ chính là để người dùng và chương trình không phải biết dữ liệu được tổ chức bên trong ra sao, tức tách biểu diễn logic khỏi biểu diễn vật lý3. Truy vấn khai báo là hệ quả trực tiếp của sự tách đó — một điểm thuộc lịch sử thập niên 1970, nhưng vẫn là nền của mọi hệ hôm nay.

    Cái giá phải trả

    Khai báo cũng có mặt trái: bạn mất quyền kiểm soát chi tiết, và khi bộ tối ưu hóa chọn nhầm kế hoạch thì khó can thiệp. Đổi lại, mọi cải tiến trong engine đều tự động chảy vào truy vấn cũ của bạn mà không phải sửa một dòng nào.

  2. Why declarative won

    Giấu chi tiết thì mới song song hóa đượcHiding the details is what enables parallelism

    Kleppmann nêu ba lợi thế nối nhau1. Một: ngôn ngữ khai báo ngắn và dễ hơn vì giấu phần hiện thực. Hai: nó cho phép đổi cách thực thi mà không đổi truy vấn — thêm một chỉ mục, đổi cấu trúc lưu trữ, và truy vấn cũ tự hưởng lợi. Ba, và quan trọng nhất: vì không quy định thứ tự thao tác, nó dễ song song hóa trên nhiều lõi và nhiều máy; mã mệnh lệnh thì ràng buộc thứ tự nên rất khó chia ra chạy song song.

    Khi bạn chỉ nói "lấy giúp tôi tất cả hộp xanh", mười người có thể chia nhau đi tìm cùng lúc. Nhưng nếu bạn đọc từng bước "rẽ trái, mở ngăn ba…" thì chỉ một người làm theo được, người thứ hai đứng chờ. Nhớ nhé: nói kết quả thì nhiều người cùng làm được, đọc từng bước thì phải xếp hàng.

    Kleppmann lists three linked advantages1. One: declarative languages are shorter and easier because they hide implementation. Two: they let the engine change execution without changing the query — add an index, change the storage layout, and old queries benefit automatically. Three, and most important: since they don't fix the order of operations, they parallelize easily across cores and machines; imperative code pins the order and resists being split up.

    Vì sao còn đúng hôm nayTốc độ một lõi CPU đã gần chững lại, nên sức mạnh tới từ chạy song song. Đó là lý do ngôn ngữ khai báo càng ngày càng có giá, chứ không phải chỉ là di sản của SQL.
    Vì sao quan trọngMáy giờ mạnh lên bằng cách làm nhiều việc cùng lúc, nên cách nói "kết quả tôi muốn" hợp thời hơn.
    Why it still holdsSingle-core speed has largely plateaued, so power comes from running in parallel. That is why declarative languages keep gaining value, rather than being a mere SQL legacy.

    Không chỉ là SQL

    Lập luận này không gắn với cú pháp SQL. Nó áp cho mọi ngôn ngữ tả-mẫu: CSS trên trình duyệt12, Cypher trên đồ thị thuộc tính16, SPARQL trên bộ ba19. Mô hình dữ liệu khác nhau, tinh thần khai báo thì giống nhau.

    Ranh giới thực tế

    Thực tế hiếm khi thuần một phe: hàm do người dùng viết, thủ tục lưu trữ, hay các pha xử lý trong ứng dụng đều là mảnh mệnh lệnh cắm vào giữa một khung khai báo. Thẻ 03 là ví dụ được đặt tên hẳn hoi cho kiểu lai đó.

  3. MapReduce — the hybrid in the middle

    MapReduce: nửa khai báo, nửa mệnh lệnhMapReduce: half declarative, half imperative

    MapReduce là mô hình lập trình do Jeffrey Dean và Sanjay Ghemawat của Google công bố năm 2004 để xử lý và sinh dữ liệu quy mô lớn trên cụm máy13. Kleppmann xếp nó vào khoảng giữa: bạn viết map và reduce bằng mã thường — phần mệnh lệnh — nhưng bị buộc phải viết chúng như hàm thuần khiết, chỉ dùng dữ liệu đầu vào và không có tác dụng phụ; nhờ vậy khung nền được tự do gọi lại, sắp xếp lại và chạy song song1.

    Có kiểu ở giữa: bạn tự viết hai việc nhỏ — "với mỗi món, rút ra một mẩu" và "gộp các mẩu giống nhau lại" — rồi máy tự chia cho cả trăm người cùng làm. Bí quyết là hai việc ấy không được đụng vào gì khác ngoài món đang cầm. Nhớ nhé: việc nào không gây ảnh hưởng lung tung thì mới chia cho nhiều người làm cùng lúc được.

    MapReduce is a programming model published in 2004 by Google's Jeffrey Dean and Sanjay Ghemawat for processing and generating large datasets on clusters13. Kleppmann places it in between: you write map and reduce in ordinary code — the imperative part — but you must write them as pure functions, using only their input and causing no side effects; that constraint frees the framework to re-run, reorder and parallelize them1.

    Đoạn kết của ví dụKleppmann đối chiếu MapReduce của MongoDB với aggregation pipeline khai báo của chính hệ đó14 — và kể từ MongoDB 5.0, map-reduce đã bị coi là lỗi thời, nhường chỗ cho pipeline14.
    Kết cụcVề sau người ta làm cách gọn hơn để nói cùng việc đó, nên kiểu tự viết hai hàm dần ít dùng.
    How the example endsKleppmann contrasts MongoDB's MapReduce with that same system's declarative aggregation pipeline14 — and since MongoDB 5.0, map-reduce is deprecated in favour of the pipeline14.

    Vì sao phải thuần khiết

    Trên một cụm máy, một tác vụ có thể bị chạy lại vì máy hỏng hoặc chậm. Nếu hàm map có tác dụng phụ — ghi vào đâu đó, đọc trạng thái ngoài — thì chạy lại sẽ cho kết quả khác. Ràng buộc thuần khiết chính là cái giá để đổi lấy quyền song song hóa và tự phục hồi13.

    Vòng quay về khai báo

    Aggregation pipeline là chuỗi các giai đoạn tả-mẫu nối tiếp nhau, gần với tinh thần SQL hơn hẳn14. Kleppmann dùng nó để cho thấy rằng, ngay cả trong thế giới NoSQL, sức hút của ngôn ngữ khai báo vẫn kéo mọi thứ về cùng một hướng.

Trục 4 · Graph-like data modelsAxis 4 · Graph-like data models

Khi nhiều-nhiều trở thành chuyện thường ngàyWhen many-to-many becomes the norm

Nếu ứng dụng của bạn có rất nhiều quan hệ nhiều-nhiều, Kleppmann đề nghị bước hẳn sang mô hình đồ thị: dữ liệu là đỉnh và cạnh, và cạnh — chứ không phải bảng — mới là công dân hạng nhất1. Ông lấy ví dụ đồ thị mạng xã hội, đồ thị web, và đồ thị đường bộ hoặc đường sắt; cũng nhắc rằng một đồ thị không nhất thiết đồng nhất — Facebook lưu người, địa điểm, sự kiện, lượt check-in và bình luận trong cùng một đồ thị1. Chương giới thiệu hai kiểu cấu trúc — property graph và triple-store — cùng ba ngôn ngữ hỏi chúng.

Có loại dữ liệu mà phần quan trọng nhất chính là những sợi dây: ai quen ai, đường nào nối thành phố nào, trang web nào dẫn tới trang nào. Với loại đó, đừng cố nhét vào ngăn kéo hay túi — hãy vẽ hẳn một tấm lưới gồm các nút và các sợi dây, rồi hỏi "đi từ nút này tới nút kia được không". Nhớ nhé: khi sợi dây quan trọng hơn cái nút, hãy chọn tấm lưới.

If your application has lots of many-to-many relationships, Kleppmann suggests moving all the way to a graph model: data is vertices and edges, and the edge — not the table — is the first-class citizen1. He cites social graphs, the web graph, and road or rail networks; he also notes a graph need not be homogeneous — Facebook keeps people, locations, events, check-ins and comments in one graph1. The chapter introduces two structures — the property graph and the triple-store — and three languages for asking questions of them.

  1. Property graph & Cypher

    Đồ thị thuộc tính: đỉnh, cạnh, và nhãnProperty graphs: vertices, edges, labels

    Trong mô hình property graph, mỗi đỉnh có một định danh, tập cạnh đi ra, đi vào và một tập cặp khóa-giá trị; mỗi cạnh cũng có định danh, đỉnh đầu, đỉnh cuối, một nhãn mô tả quan hệ và các thuộc tính riêng15. Kleppmann lấy Neo4j, Titan và InfiniteGraph làm ví dụ hiện thực1. Ngôn ngữ đi kèm phổ biến nhất là Cypher, mà Neo4j mô tả thẳng là ngôn ngữ truy vấn khai báo cho cơ sở dữ liệu đồ thị thuộc tính16; đặc tả mở của nó được công bố dưới tên openCypher17.

    Tưởng tượng một tấm bản đồ đầy chấm tròn và mũi tên. Mỗi chấm là một thứ (một người, một thành phố), mỗi mũi tên là một mối liên hệ và có tên hẳn hoi: "là bạn của", "sinh ra ở". Bạn hỏi bằng cách vẽ hình mẫu mình muốn tìm — "chấm nào có mũi tên sinh ra ở chỉ tới Huế?" — rồi máy đi dò. Nhớ nhé: hỏi đồ thị là mô tả hình mẫu, không phải chỉ đường.

    In the property graph model each vertex has an identifier, a set of outgoing and incoming edges, and a set of key-value properties; each edge has an identifier, a tail and head vertex, a label describing the relationship, and its own properties15. Kleppmann names Neo4j, Titan and InfiniteGraph as implementations1. The best-known companion language is Cypher, which Neo4j describes outright as a declarative query language for property graph databases16; its open specification is published as openCypher17.

    Sức mạnh thậtĐồ thị chịu được tiến hóa: thêm một loại quan hệ mới chỉ là thêm một nhãn cạnh, không phải đổi lược đồ. Với dữ liệu mà cấu trúc còn đang lớn lên, đó là ưu thế lớn1.
    Điểm mạnhMuốn thêm kiểu quan hệ mới thì chỉ cần vẽ thêm một loại mũi tên, không phải làm lại cả tấm bản đồ.
    The real strengthGraphs absorb evolution: adding a new kind of relationship is just a new edge label, not a schema change. For data whose structure is still growing, that is a big advantage1.

    Truy vấn nhiều bước

    Điều đồ thị làm gọn mà SQL cổ điển làm cồng kềnh là đi nhiều bước với số bước chưa biết trước: "tìm mọi người cách tôi từ một tới ba bước quen biết". Trong Cypher đó là một mẫu đường đi có độ dài thay đổi16; trong SQL phải viết truy vấn đệ quy phức tạp hơn nhiều1.

    Sau khi sách xuất bản

    Cuốn sách ra năm 2017 khi mỗi hệ đồ thị còn một phương ngữ riêng. Từ đó, nỗ lực chuẩn hóa GQL — kế thừa trực tiếp tinh thần của Cypher và openCypher — đã hình thành một ngôn ngữ truy vấn đồ thị chung24. Đây là diễn biến sau nội dung chương, nêu ra để bạn không đọc chương như bản đồ hiện trạng.

  2. Triple-stores, RDF & SPARQL

    Bộ ba: chủ thể — vị từ — đối tượngTriples: subject — predicate — object

    Mô hình triple-store ghi mọi thứ thành câu ba thành phần: chủ thể, vị từ, đối tượng. Đối tượng có thể là một giá trị nguyên thủy (khi đó bộ ba là một thuộc tính của đỉnh) hoặc là một đỉnh khác (khi đó bộ ba là một cạnh)1. Kleppmann nêu Datomic và AllegroGraph làm ví dụ1, và nối mô hình này với RDF — chuẩn biểu diễn dữ liệu dưới dạng bộ ba của Web ngữ nghĩa18. Ngôn ngữ hỏi bộ ba là SPARQL, một ngôn ngữ tả-mẫu trên đồ thị RDF1920, được W3C công nhận thành khuyến nghị năm 2008 và bản 1.1 năm 20131921.

    Cách này ghi mọi thứ thành câu ba chữ, kiểu "Lan — thích — Toán" hay "Lan — bạn của — Minh". Chỉ cần ba ô cho mọi loại thông tin, nên thêm kiểu thông tin mới rất dễ: cứ viết thêm một câu. Muốn hỏi thì để trống một ô: "ai — bạn của — Minh?". Nhớ nhé: ba ô đơn giản mà kể được gần như mọi chuyện.

    The triple-store model records everything as a three-part statement: subject, predicate, object. The object may be a primitive value — making the triple a property of the vertex — or another vertex, making it an edge1. Kleppmann names Datomic and AllegroGraph as examples1, and ties the model to RDF, the Semantic Web's standard for representing data as triples18. The language for asking about triples is SPARQL, a pattern-matching language over RDF graphs1920, made a W3C Recommendation in 2008 with version 1.1 following in 20131921.

    Quan hệ với đồ thị thuộc tínhKleppmann chỉ ra hai mô hình gần như tương đương về sức biểu đạt: cùng một dữ liệu viết được bằng bộ ba hay bằng đỉnh-cạnh. Khác nhau ở truyền thống và công cụ, không ở khả năng1.
    Giống nhau ở đâuBản đồ chấm-mũi tên và câu ba chữ nói được cùng những thứ, chỉ là cách viết khác nhau.
    Relation to property graphsKleppmann points out the two models are near-equivalent in expressive power: the same data can be written as triples or as vertices and edges. They differ in tradition and tooling, not capability1.

    Web ngữ nghĩa: tham vọng và thực tế

    Kleppmann tách rõ mô hình bộ ba khỏi dự án Web ngữ nghĩa: ý tưởng để các website công bố dữ liệu máy đọc được nhằm dựng nên một "cơ sở dữ liệu của cả Internet" đã không thành hiện thực như kỳ vọng, nhưng bản thân mô hình dữ liệu bộ ba thì vẫn là một công cụ tốt cho ứng dụng nội bộ1. Đừng vì thất vọng với cái thứ nhất mà bỏ cái thứ hai.

    Định danh bằng URI

    RDF dùng URI làm định danh cho chủ thể, vị từ và đối tượng, để dữ liệu từ nhiều nguồn khác nhau ghép lại được mà không đụng tên18. Đây chính là chi tiết khiến RDF trông lạ mắt so với đồ thị thuộc tính, dù nội dung bên dưới tương đương.

  3. Datalog — the oldest and the most composable

    Datalog: nền móng cũ, sức ghép nối mớiDatalog: an old foundation, new composability

    Datalog là ngôn ngữ truy vấn khai báo bắt nguồn từ logic vị từ, được nghiên cứu kỹ trong giới hàn lâm từ thập niên 198023 — nghĩa là cũ hơn cả Cypher lẫn SPARQL. Kleppmann xếp nó cuối phần đồ thị vì nó là nền móng mà các ngôn ngữ sau dựng lên1. Nó ghi dữ liệu gần như bộ ba nhưng viết theo lối vị từ, và cho phép định nghĩa quy tắc dẫn xuất rồi dùng lại quy tắc đó trong quy tắc khác — kể cả đệ quy. Datomic dùng Datalog làm ngôn ngữ truy vấn của mình22.

    Kiểu này giống như dạy cho máy vài "luật suy ra": nếu A là cha của B, và B là cha của C, thì A là ông của C. Dạy xong, bạn chỉ cần hỏi "ai là ông của C?" và máy tự lần theo luật. Cái hay là luật này dùng lại được trong luật khác. Nhớ nhé: dạy luật một lần rồi hỏi được rất nhiều câu.

    Datalog is a declarative query language rooted in predicate logic, studied thoroughly in academia since the 1980s23 — that is, older than both Cypher and SPARQL. Kleppmann puts it last in the graph section because it is the foundation the later languages build on1. It stores data much like triples but written as predicates, and it lets you define derived rules and reuse them inside other rules, recursion included. Datomic uses Datalog as its query language22.

    Vì sao đáng họcKhả năng ghép quy tắc khiến Datalog mạnh với truy vấn phức tạp: bạn chia bài toán thành từng quy tắc nhỏ đặt tên được, thay vì viết một truy vấn khổng lồ một lần22.
    Vì sao hayChia câu hỏi lớn thành nhiều luật nhỏ có tên, dễ hiểu hơn một câu hỏi dài ngoằng.
    Why it's worth learningRule composition makes Datalog strong for complex queries: you split the problem into small named rules instead of writing one giant query at once22.

    Quy tắc và đệ quy

    Một quy tắc Datalog nói: nếu các mệnh đề bên phải đúng thì mệnh đề bên trái cũng đúng. Vì bên phải có thể gọi lại chính quy tắc đang định nghĩa, ta biểu diễn được những câu hỏi "đi bao nhiêu bước cũng được" một cách tự nhiên23 — đúng loại câu hỏi mà đồ thị hay gặp.

    Trả giá bằng độ quen thuộc

    Datalog đòi người viết đổi lối nghĩ và không có mấy công cụ phổ thông, nên nó ít gặp hơn SQL hay Cypher. Kleppmann trình bày nó không phải để bạn dùng ngay, mà để thấy nền lý thuyết chung bên dưới các ngôn ngữ đồ thị1.

  4. Graphs are not CODASYL returning

    Đồ thị không phải mô hình mạng sống lạiGraphs are not the network model reborn

    Vì cả hai đều nói chuyện "bản ghi nối bản ghi", người ta dễ tưởng cơ sở dữ liệu đồ thị là CODASYL quay lại. Kleppmann bác bỏ bằng những khác biệt cụ thể1: CODASYL quy định trước những kiểu bản ghi nào được lồng vào kiểu nào và bắt lập trình viên men theo access path bằng chuỗi con trỏ5; đồ thị thì cho phép bất kỳ đỉnh nào nối bất kỳ đỉnh nào, cho tra cứu trực tiếp một đỉnh qua định danh hoặc chỉ mục, và hỏi bằng ngôn ngữ khai báo thay vì mã điều hướng16.

    Ngày xưa cũng có kiểu "nối bản ghi bằng dây", nhưng bạn buộc phải đi đúng đường đã vạch sẵn, như đi trong mê cung có một lối. Tấm lưới hôm nay thì khác: nút nào nối nút nào cũng được, và bạn có thể nhảy thẳng tới một nút bất kỳ rồi mới đi tiếp. Nhớ nhé: giống nhau ở chỗ có dây, khác nhau ở chỗ được tự do đi.

    Because both talk about records linked to records, it's tempting to read graph databases as CODASYL returning. Kleppmann rejects that with concrete differences1: CODASYL fixed in advance which record types could nest inside which, and made the programmer follow an access path along pointer chains5; a graph lets any vertex link to any other vertex, allows direct lookup of a vertex by identifier or index, and is queried in a declarative language instead of navigation code16.

    Bài học rộng hơnHai hệ giống nhau về cấu trúc lưu trữ vẫn có thể khác hẳn nhau về mô hình lập trình. Điều quyết định trải nghiệm không phải "có con trỏ hay không", mà là ai chịu trách nhiệm chọn đường đi.
    Ý chínhNhìn giống nhau chưa chắc đã giống: quan trọng là ai phải tìm đường — bạn hay máy.
    The wider lessonTwo systems can share a storage structure and still differ utterly in programming model. What decides the experience is not "are there pointers" but who is responsible for choosing the route.

    Khung lịch sử

    Mọi điều nói về IMS và CODASYL trong chương này thuộc về bối cảnh lịch sử của thập niên 1960–1980, được Kleppmann kể lại để giải thích vì sao mô hình quan hệ thắng, chứ không mô tả cách các hệ ấy đang được dùng hôm nay45.

    Cùng một bài học, lặp lại

    Kleppmann nhắc rằng mô hình tài liệu hôm nay lặp lại đúng điểm mạnh và điểm yếu của mô hình phân cấp ngày xưa: giỏi một-nhiều, kém nhiều-nhiều1. Lịch sử không lặp lại y nguyên, nhưng các đánh đổi thì có.

Ba ngộ nhận thường gặp về Chương 2Three common misreadings of Chapter 2

“NoSQL nghĩa là không có lược đồ”"NoSQL means no schema"

Sai: lược đồ vẫn tồn tại, chỉ là ngầm và được kiểm lúc đọc. Tên đúng là schema-on-read, và nó tương ứng với kiểm kiểu động chứ không phải vô luật1. Thậm chí các hệ tài liệu nay còn cho bật ràng buộc lược đồ tùy chọn9.

Wrong: the schema still exists, it is just implicit and checked on read. The right name is schema-on-read, which corresponds to dynamic type checking, not to lawlessness1. Document systems now even let you switch on optional schema constraints9.

“Tài liệu luôn nhanh hơn vì không join”"Documents are always faster — no joins"

Sai: lợi thế locality chỉ đúng khi cần gần trọn tài liệu, và việc nối vẫn phải làm — chỉ là làm trong mã ứng dụng, thường tốn nhiều vòng truy vấn hơn1. Tài liệu phình to còn khiến mỗi lần cập nhật phải ghi lại nhiều hơn8.

Wrong: the locality win only holds when you need nearly the whole document, and the joining still happens — just in application code, usually across more round trips1. Bloated documents also make every update rewrite more8.

“Đồ thị chỉ dành cho mạng xã hội”"Graphs are only for social networks"

Sai: Kleppmann nêu đồ thị web, mạng đường bộ và đường sắt, và cả những đồ thị không đồng nhất trộn nhiều loại đối tượng trong một cấu trúc1. Tiêu chí chọn là mật độ quan hệ nhiều-nhiều, không phải lĩnh vực ứng dụng15.

Wrong: Kleppmann cites the web graph, road and rail networks, and heterogeneous graphs mixing many object types in one structure1. The criterion is the density of many-to-many relationships, not the application domain15.

Tự đọc mìnhRead yourself

Soi mô hình dữ liệu của chính hệ bạnTest your own system's data model

Chương 2 không bảo bạn nên chọn gì; nó đưa cho bạn những câu hỏi để tự biết mình đang ở đâu. Hãy lấy một hệ bạn đang làm và trả lời thật — phần lớn nợ kỹ thuật về dữ liệu bắt đầu từ một câu trả lời bị bỏ qua ở đây.

Lấy một app hay một dịch vụ bạn đang làm, rồi thử trả lời mấy câu dưới. Không phải để chọn công nghệ mới, mà để biết cách bạn đang sắp dữ liệu có hợp với hình dạng thật của nó không. Nhớ nhé: trả lời thật thì mới soi ra được chỗ đang lệch.

Chapter 2 doesn't tell you what to pick; it hands you questions to locate yourself. Take a system you work on and answer honestly — most data-shaped technical debt starts from one question skipped here.

Dữ liệu của bạn nghiêng về hình dạng nào: cây, bảng nối, hay lưới?Đếm thật: có bao nhiêu quan hệ một-nhiều được nạp trọn vẹn, bao nhiêu quan hệ nhiều-một cần chuẩn hóa, bao nhiêu quan hệ nhiều-nhiều? Tỉ lệ ấy — chứ không phải sở thích công nghệ — mới là căn cứ chọn mô hình.
Chỗ nào trong mã của bạn đang làm việc mà cơ sở dữ liệu lẽ ra nên làm?Nếu bạn đang lặp qua danh sách ID rồi gọi truy vấn từng cái để "nối" dữ liệu, đó chính là một phép join bị dời vào ứng dụng. Nó có ý thức hay chỉ là hệ quả của một lựa chọn mô hình cũ?
Lược đồ của bạn được kiểm lúc ghi hay lúc đọc — và bạn có chủ động chọn điều đó không?Nếu là schema-on-read, hãy chỉ ra chỗ mã đang xử lý các dạng dữ liệu cũ. Nếu là schema-on-write, hãy chỉ ra lần di trú gần nhất tốn bao lâu. Cả hai đều ổn, miễn là một lựa chọn chứ không phải một tai nạn.
Mỗi lần đọc, bạn dùng bao nhiêu phần trăm tài liệu lấy về?Locality chỉ trả cổ tức khi con số này cao. Nếu bạn kéo về một tài liệu lớn chỉ để lấy hai trường, bạn đang trả tiền cho locality mà không tiêu nó — và đang làm mỗi lần cập nhật đắt lên.
Tài liệu lớn nhất của bạn to tới đâu, và nó có xu hướng tăng vô hạn không?Một mảng lồng chỉ tăng mà không bao giờ giảm là dấu hiệu cần tách ra thành tham chiếu. Hãy đặt một ngưỡng cụ thể và một cảnh báo, thay vì đợi tới lúc một tài liệu chạm giới hạn của hệ.
Truy vấn của bạn nói cái gì hay nói làm thế nào?Tìm những chỗ bạn đã tự tay viết vòng lặp, sắp xếp, lọc trong mã ứng dụng cho dữ liệu vốn nằm sẵn trong cơ sở dữ liệu. Mỗi chỗ như thế là một cơ hội bị bỏ lỡ để bộ tối ưu hóa làm hộ — và là một chỗ khó song song hóa.
Nếu phải đi ba bước quan hệ, truy vấn của bạn trông thế nào?Đây là phép thử nhanh nhất xem bạn có đang cần mô hình đồ thị không. Nếu câu trả lời là một chuỗi join dài hoặc một truy vấn đệ quy khó đọc, và loại câu hỏi đó lặp lại thường xuyên, hãy nghiêm túc cân nhắc đồ thị.
Bạn có đang chọn công nghệ theo nhãn thay vì theo khả năng?Hai phe đang hội tụ: hệ quan hệ lưu được JSON, hệ tài liệu nối và ràng buộc được. Thay vì hỏi "SQL hay NoSQL", hãy liệt kê ba khả năng bạn thật sự cần và kiểm từng ứng viên theo danh sách đó.
Dữ liệu của bạn giống cái cây hay giống tấm lưới?Cây thì bỏ chung một túi; lưới thì cần thứ biết đi theo dây nối.
Có thứ gì bạn đang chép ra nhiều chỗ không?Cái gì dùng chung thì để một chỗ rồi trỏ tới, đổi một lần là xong.
Mỗi lần lấy đồ, bạn có dùng gần hết túi không?Nếu chỉ lấy hai món mà phải bê cả túi, thì cách cất đang chưa hợp.
Bạn đang nói kết quả mình muốn, hay đọc từng bước cho máy?Nói kết quả thì máy tự tìm đường nhanh nhất, và nhiều máy làm cùng lúc được.
Which shape dominates your data: tree, join table, or web?Count honestly: how many one-to-many relationships loaded whole, how many many-to-one needing normalization, how many many-to-many? That ratio, not taste in technology, is the basis for choosing a model.
Where is your code doing work the database should do?If you loop over IDs issuing one query each to "stitch" data together, that is a join displaced into the application. Is it deliberate, or a leftover of an old model choice?
Is your schema checked on write or on read — and did you choose that?If schema-on-read, point at the code handling older shapes. If schema-on-write, point at how long your last migration took. Either is fine, as long as it is a choice rather than an accident.
What fraction of each document you fetch do you actually use?Locality only pays a dividend when that number is high. Pulling a large document for two fields means you pay for locality without spending it — and make every update dearer.
How large is your biggest document, and does it grow without bound?A nested array that only ever grows is a signal to split it into a reference. Set a concrete threshold and an alert rather than waiting for a document to hit the system limit.
Do your queries say what or say how?Find the places where you hand-wrote loops, sorts and filters in application code over data already in the database. Each is a missed chance to let the optimizer do it — and a place that resists parallelism.
What does a three-hop relationship query look like in your system?This is the fastest test for whether you need a graph model. If the answer is a long join chain or an unreadable recursive query, and such questions recur, consider a graph seriously.
Are you choosing technology by label rather than by capability?The camps are converging: relational systems store JSON, document systems join and constrain. Instead of "SQL or NoSQL", list the three capabilities you actually need and test each candidate against that list.
Kết luận cô đọngThe conclusion, distilled
Cuộc tranh cãi quan hệ hay tài liệu không phải chuyện công nghệ mới thắng công nghệ cũ, mà là chuyện dữ liệu của bạn có hình gì: cây một-nhiều, lưới nhiều-nhiều, hay đâu đó ở giữa. Mỗi hình dạng kéo theo một mô hình, mỗi mô hình kéo theo một cách hỏi — và bước ngoặt lớn nhất của lịch sử cơ sở dữ liệu là lúc ta thôi chỉ đường cho máy và bắt đầu nói ra điều mình muốn.Không có kiểu sắp dữ liệu nào đúng cho mọi thứ. Có dữ liệu giống cái cây, có dữ liệu giống tấm lưới, và mỗi kiểu có cách hỏi riêng. Nhớ nhé: nhìn hình dạng của dữ liệu trước, rồi hãy nói điều mình muốn thay vì chỉ từng bước cho máy.The relational-versus-document argument is not new technology beating old, but a question of what shape your data has: a one-to-many tree, a many-to-many web, or somewhere between. Each shape drags a model behind it, each model a way of asking — and the biggest turn in database history was the moment we stopped steering the machine and began stating what we want.