Học lập trình MongoDB trực tuyến cơ bản đến nâng cao

Một câu hỏi thường gặp của lập trình viên có kinh nhiệm với SQL mới chuyển sang Mongo là làm thế nào để có thể model được quan hệ 1 : nhiều. Có rất nhiều cách để có thể làm được điều này. Trong phần đầu tiên của loạt bài này , chúng ra sẽ cùng nhau tìm hiểu 3 cách cơ bản để có thể model quan hệ 1: nhiều.

Với rất nhiều người mới học MongoDB, giải pháp duy nhất họ nghĩ đến khi gặp vấn đề "One to N" là nhúng 1 mảng các sub-document vào trong thằng document gốc. Tuy nhiên điều này không thưc sự đúng. Bạn có thể nhúng document trong MongoDB nhưng đây là 1 bad pattern không nên theo.

Khi thiết kế một MongoDB schema, điều đầu tiên ta cần quan tâm là định lượng được mối quan hệ. Nghe có vẻ kỳ cục so với việc thiết kế schema với SQL. Trong MongoDB, tùy vào thuộc tính của mối quan hệ "one to many" mà ta có phân ra làm 3 loại khác nhau : "one to few", "one to many", "one to squillions (rất nhiều)" . Với từng loại ta có cách thiết kế schema khác nhau.

One to few

Một ví dụ của "One to few" là việc bạn mô tả địa chỉ của một người. Trong trường hợp này bạn hoàn toàn có thể sử dụng việc nhúng dữ liệu. Ở đây ta sẽ để các địa chỉ dưới dạng array nằm trong đối tượng Person.

> db.person.findOne()
{
  name: 'Kate Monster',
  ssn: '123-456-7890',
  addresses : [
     { street: '123 Sesame St', city: 'Anytown', cc: 'USA' },
     { street: '123 Avenue Q', city: 'New York', cc: 'USA' }
  ]
}

Ưu điểm của việc nhúng dữ liệu là bạn chỉ cần 1 câu lệnh query để có thể lấy đầy đủ thông tin chi tiết bên trong tuy nhiên dữ liệu của bạn phụ thuộc vào thằng Person nên bạn không thể xử lý nó như 1 thực thể độc lập Address được.

Ví dụ: Nếu bạn cần model một hệ thống quản lý tác vụ. Mỗi người nhận một số lượng tác vụ nhất định. Với cách làm trên khi bạn gặp vấn đề truy vấn như : Hãy cho tối biết số các tác vụ cần hoàn thành vào ngày mai ?. Giải pháp của bạn sẽ là vào từng người trong hệ thống , kiểm tra từng tác vụ của họ xem có cái nào cần hoàn thành vào ngày mai không , gom hết lại trả về ... Truy vấn hơi phức tạp phải không . Nào ta cần tới loại thứ 2 rồi.

One to Many

Ví dụ: Bạn có 1 sản phẩm như Smartphone chẳng hạn. Nó bao gồm rất nhiều các linh kiện bên trong và các link kiên này có thể thay thế được. Số linh kiên ở đây bị giới hạn cỡ vài trăm thôi nhé ( vài nghìn là thành loại khác rồi). Giải pháp cho vấn đề này là đặt một mảng các ObjectID của từng link kiện trong dữ liệu của sản phẩm. (Trong ví dụ này tôi sử dụng các ObjectID 2-byte vì nó dễ đọc, trong ứng dụng thật bạn hoàn toàn có thể sử dụng ObjectID 12-byte)

Với mỗi link kiện đều có một bản dữ liệu riêng :

> db.parts.findOne()
{
    _id : ObjectID('AAAA'),
    partno : '123-aff-456',
    name : '#4 grommet',
    qty: 94,
    cost: 0.94,
    price: 3.99
}

Với mỗi sản phẩm sẽ có một mảng các ObjectID để tham chiếu đến các linh kiện tạo nên sản phẩm:

> db.products.findOne()
{
    name : 'left-handed smoke shifter',
    manufacturer : 'Acme Corp',
    catalog_number: 1234,
    parts : [     // array of references to Part documents
        ObjectID('AAAA'),    
        ObjectID('F17C'),   
        ObjectID('D2AA'),
        // etc
    ]

Để có thể truy vấn dữ liệu từng linh kiện trong một sản phẩm xác định bạn có thể sử dụng cú pháp sau:

// lấy thông tin sản phẩm qua catalog_number
> product = db.products.findOne({catalog_number: 1234});
   // Lấy toàn bộ linh kiện trong sản phẩm
> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;

Để tối ưu việc truy vấn, bạn cần đặt index cho thằng "products.catalog_number".Ở trong hệ thống đã có sắn index cho thằng "parts._id", nên không cần phải tối ưu thằng đó nữa.

Với loại thứ 2 này, bạn có 2 đối tượng riêng rẽ dễ dàng truy vấn tuy nhiên điều này làm cho việc bạn lấy dữ liệu của thằng linh kiện trong một thằng sản phẩm chậm hơn một chút. Tất nhiên chẳng bao giờ có giải pháp nào hoàn hảo cả. Thêm vào đó, bạn hoàn thành có thể thấy các sản phẩm thì có nhiều linh kiện giống nhau. Từ đó mối quan hệ "one to Many " thành "Many to Many " rồi.

Để có kinh nghiệm lập trình Node.js thực tế, hãy tham khảo khóa học "Node.js xây dựng web site tốc độ cao"

One to Squillions

Một ví dụ cho cái loại cuối cùng này à.. Chẹp.. Cái này dành cho những cái gì siêu rác ... Ví dụ Hệ thống logging , nơi bạn lưu lại các message từ nhiều server khác nhau. Từng server tạo ra lượng message đủ để tràn kích thước của dữ liệu là 16 MB kể cả khi bạn sử dụng ObjectID . Giải pháp cho tính huống này là "parent-referencing" (Tạo tham chiếu đến thằng cha). Bạn có một bản dữ liệu cho từng server, tiếp đó bạn lưu ObjectJD của server trên từng mesage

> db.hosts.findOne()
{
    _id : ObjectID('AAAB'),
    name : 'goofy.example.com',
    ipaddr : '127.66.66.66'
}

>db.logmsg.findOne()
{
    time : ISODate("2014-03-28T09:42:41.382Z"),
    message : 'cpu is on fire!',
    host: ObjectID('AAAB')       // Reference to the Host document
}

Làm cách nào để lấy được toàn bộ message từ một server:

// tìm thằng server
> host = db.hosts.findOne({ipaddr : '127.66.66.66'});  // assumes unique index
   // liệt kê 5000 message của thằng server
> last_5k_msg = db.logmsg.find({host: host._id}).sort({time : -1}).limit(5000).toArray()

Tổng kết

Ngay cả ở cấp độ cơ bản, rõ ràng là việc thiết kế Mongo Schema có nhiều vấn đề cần phải suy nghĩ hơn. Có hai câu hỏi bạn cần trả lời khi thiết kế :

  • Có cần coi thằng Many trong "one to Many" là một thực thể hay không ?
  • Số lượng của thằng Many là bao nhiêu ? Sau khi trả lời được 2 câu hỏi trên bạn có thể chọn 3 loại ở trên để áp dung :
  • Nhúng toàn bộ dữ liệu "Many" trong thằng Object "One".
  • Sử dụng mảng các ObjectID đại diện cho các thằng "Many" trong thằng Object "One".
  • Tạo tham chiếu bằng ObjectID đến thằng "One" trong từng thằng "Many"

Bài viết được lược dịch theo bản gốc : http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1