Nguyên tắc Lập mô hình Tài liệu CouchDB


120

Tôi có một câu hỏi mà tôi đã cố gắng trả lời một thời gian nhưng không thể tìm ra:

Bạn thiết kế hoặc phân chia tài liệu CouchDB như thế nào?

Lấy một bài đăng trên blog làm ví dụ.

Cách bán "quan hệ" sẽ là tạo một vài đối tượng:

  • Bài đăng
  • Người sử dụng
  • Bình luận
  • Nhãn
  • Đoạn trích

Điều này rất có ý nghĩa. Nhưng tôi đang cố gắng sử dụng couchdb (vì tất cả các lý do là nó tuyệt vời) để tạo mô hình tương tự và điều đó cực kỳ khó khăn.

Hầu hết các bài đăng trên blog đều cung cấp cho bạn một ví dụ dễ dàng về cách thực hiện điều này. Về cơ bản họ phân chia nó theo cùng một cách, nhưng nói rằng bạn có thể thêm các thuộc tính 'tùy ý' vào mỗi tài liệu, điều này chắc chắn rất hay. Vì vậy, bạn sẽ có một cái gì đó như thế này trong CouchDB:

  • Đăng (với các thẻ và đoạn mã mô hình "giả" trong tài liệu)
  • Bình luận
  • Người sử dụng

Một số người thậm chí sẽ nói rằng bạn có thể ném Nhận xét và Người dùng vào đó, vì vậy bạn sẽ có điều này:


post {
    id: 123412804910820
    title: "My Post"
    body: "Lots of Content"
    html: "<p>Lots of Content</p>"
    author: {
        name: "Lance"
        age: "23"
    }
    tags: ["sample", "post"]
    comments {
        comment {
            id: 93930414809
            body: "Interesting Post"
        } 
        comment {
            id: 19018301989
            body: "I agree"
        }
    }
}

Điều đó trông rất đẹp và dễ hiểu. Tôi cũng hiểu cách bạn có thể viết các dạng xem chỉ trích xuất Nhận xét từ tất cả các tài liệu Bài đăng của bạn, để đưa chúng vào các mô hình Nhận xét, giống với Người dùng và Thẻ.

Nhưng sau đó tôi nghĩ, "tại sao không chỉ đặt toàn bộ trang web của tôi vào một tài liệu duy nhất?":


site {
    domain: "www.blog.com"
    owner: "me"
    pages {
        page {
            title: "Blog"
            posts {
                post {
                    id: 123412804910820
                    title: "My Post"
                    body: "Lots of Content"
                    html: "<p>Lots of Content</p>"
                    author: {
                        name: "Lance"
                        age: "23"
                    }
                    tags: ["sample", "post"]
                    comments {
                        comment {
                            id: 93930414809
                            body: "Interesting Post"
                        } 
                        comment {
                            id: 19018301989
                            body: "I agree"
                        }
                    }
                }
                post {
                    id: 18091890192984
                    title: "Second Post"
                    ...
                }
            }
        }
    }
}

Bạn có thể dễ dàng thực hiện các lượt xem để tìm những gì bạn muốn với điều đó.

Sau đó, câu hỏi tôi có là, làm thế nào để bạn xác định khi nào để chia tài liệu thành các tài liệu nhỏ hơn, hoặc khi nào tạo "MỐI QUAN HỆ" giữa các tài liệu?

Tôi nghĩ rằng nó sẽ "Hướng đối tượng" hơn nhiều và dễ ánh xạ tới Đối tượng giá trị hơn, nếu nó được phân chia như vậy:


posts {
    post {
        id: 123412804910820
        title: "My Post"
        body: "Lots of Content"
        html: "<p>Lots of Content</p>"
        author_id: "Lance1231"
        tags: ["sample", "post"]
    }
}
authors {
    author {
        id: "Lance1231"
        name: "Lance"
        age: "23"
    }
}
comments {
    comment {
        id: "comment1"
        body: "Interesting Post"
        post_id: 123412804910820
    } 
    comment {
        id: "comment2"
        body: "I agree"
        post_id: 123412804910820
    }
}

... nhưng sau đó nó bắt đầu trông giống Cơ sở dữ liệu quan hệ hơn. Và thường thì tôi kế thừa một thứ gì đó trông giống như "toàn bộ trang web trong một tài liệu", vì vậy việc mô hình hóa nó bằng các quan hệ sẽ khó hơn.

Tôi đã đọc rất nhiều điều về cách / khi nào sử dụng Cơ sở dữ liệu quan hệ so với Cơ sở dữ liệu tài liệu, vì vậy đó không phải là vấn đề chính ở đây. Tôi chỉ đang tự hỏi, đâu là quy tắc / nguyên tắc tốt để áp dụng khi lập mô hình dữ liệu trong CouchDB.

Một ví dụ khác là với các tệp / dữ liệu XML. Một số dữ liệu XML có độ sâu hơn 10 cấp lồng nhau và tôi muốn hình dung rằng bằng cách sử dụng cùng một ứng dụng khách (ví dụ: Ajax trên Rails hoặc Flex) mà tôi sẽ kết xuất JSON từ ActiveRecord, CouchRest hoặc bất kỳ Trình lập bản đồ quan hệ đối tượng nào khác. Đôi khi tôi nhận được các tệp XML khổng lồ là toàn bộ cấu trúc trang web, như hình dưới đây và tôi cần ánh xạ nó thành Đối tượng giá trị để sử dụng trong ứng dụng Rails của mình để tôi không phải viết một cách khác để tuần tự hóa / giải mã dữ liệu :


<pages>
    <page>
        <subPages>
            <subPage>
                <images>
                    <image>
                        <url/>
                    </image>
                </images>
            </subPage>
        </subPages>
    </page>
</pages>

Vì vậy, các câu hỏi chung của CouchDB là:

  1. Bạn sử dụng các quy tắc / nguyên tắc nào để phân chia các tài liệu của mình (các mối quan hệ, v.v.)?
  2. Có ổn không khi đặt toàn bộ trang web vào một tài liệu?
  3. Nếu vậy, làm cách nào để xử lý việc tuần tự hóa / giải mã hóa tài liệu với các mức độ sâu tùy ý (như ví dụ json lớn ở trên hoặc ví dụ xml)?
  4. Hoặc bạn không biến chúng thành VO, bạn chỉ quyết định "những cái này quá lồng vào Bản đồ quan hệ đối tượng, vì vậy tôi sẽ chỉ truy cập chúng bằng các phương thức XML / JSON thô"?

Cảm ơn sự giúp đỡ của bạn rất nhiều, vấn đề làm thế nào để phân chia dữ liệu của bạn với CouchDB đã khó đối với tôi để nói "đây là cách tôi nên làm điều đó từ bây giờ". Tôi hy vọng sẽ sớm đạt được điều đó.

Tôi đã nghiên cứu các trang web / dự án sau đây.

  1. Dữ liệu phân cấp trong CouchDB
  2. CouchDB Wiki
  3. Sofa - Ứng dụng CouchDB
  4. CouchDB Hướng dẫn cuối cùng
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... nhưng họ vẫn chưa trả lời câu hỏi này.


2
wow bạn đã viết một bài luận nguyên ở đây ... :-)
Eero

8
hey, đó là một câu hỏi hay
elmarco

Câu trả lời:


26

Đã có một số câu trả lời tuyệt vời cho điều này, nhưng tôi muốn thêm một số tính năng CouchDB mới hơn vào hỗn hợp các tùy chọn để làm việc với tình huống ban đầu được mô tả bởi viatropos.

Điểm mấu chốt để tách các tài liệu là nơi có thể có xung đột (như đã đề cập trước đó). Bạn không bao giờ nên giữ hàng loạt các tài liệu "rối rắm" với nhau trong một tài liệu vì bạn sẽ nhận được một đường dẫn sửa đổi duy nhất cho các bản cập nhật hoàn toàn không liên quan (ví dụ: bổ sung nhận xét thêm bản sửa đổi vào toàn bộ tài liệu trang web). Ban đầu, việc quản lý các mối quan hệ hoặc kết nối giữa các tài liệu nhỏ hơn khác nhau có thể gây nhầm lẫn, nhưng CouchDB cung cấp một số tùy chọn để kết hợp các phần khác nhau thành các phản hồi duy nhất.

Điều quan trọng đầu tiên là đối chiếu chế độ xem. Khi bạn phát ra các cặp khóa / giá trị vào kết quả của một truy vấn ánh xạ / thu nhỏ, các khóa được sắp xếp dựa trên đối chiếu UTF-8 ("a" đứng trước "b"). Bạn cũng có thể khóa phức tạp ra khỏi bản đồ của bạn / giảm như mảng JSON: ["a", "b", "c"]. Làm điều đó sẽ cho phép bạn bao gồm một "cây" các loại được xây dựng từ các khóa mảng. Sử dụng ví dụ của bạn ở trên, chúng tôi có thể xuất ra post_id, sau đó là loại thứ mà chúng tôi đang tham chiếu, sau đó là ID của nó (nếu cần). Sau đó, nếu chúng tôi xuất id của tài liệu được tham chiếu thành một đối tượng trong giá trị được trả về, chúng tôi có thể sử dụng tham số truy vấn 'include_docs' để đưa các tài liệu đó vào bản đồ / đầu ra:

{"rows":[
  {"key":["123412804910820", "post"], "value":null},
  {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
  {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
  {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

Yêu cầu cùng một chế độ xem với '? Include_docs = true' sẽ thêm một khóa 'doc' sẽ sử dụng "_id" được tham chiếu trong đối tượng "value" hoặc nếu nó không có trong đối tượng "value", nó sẽ sử dụng '_id' của tài liệu mà từ đó hàng được phát ra (trong trường hợp này là tài liệu 'post'). Xin lưu ý, các kết quả này sẽ bao gồm trường 'id' tham chiếu đến tài liệu nguồn mà từ đó tạo ra. Tôi bỏ nó ra để có không gian và khả năng đọc.

Sau đó, chúng tôi có thể sử dụng các thông số 'start_key' và 'end_key' để lọc kết quả thành dữ liệu của một bài đăng:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
Hoặc thậm chí trích xuất cụ thể danh sách cho một loại nhất định:
? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "comment", {}]
Các kết hợp tham số truy vấn này có thể thực hiện được vì một đối tượng trống (" {}") luôn ở cuối đối chiếu và null hoặc "" luôn ở trên cùng.

Bổ sung hữu ích thứ hai từ CouchDB trong những trường hợp này là hàm _list. Điều này sẽ cho phép bạn chạy các kết quả ở trên thông qua một hệ thống tạo khuôn mẫu nào đó (nếu bạn muốn HTML, XML, CSV hoặc bất kỳ thứ gì trở lại) hoặc xuất ra một cấu trúc JSON thống nhất nếu bạn muốn có thể yêu cầu toàn bộ nội dung của bài đăng (bao gồm dữ liệu tác giả và nhận xét) với một yêu cầu duy nhất và được trả lại dưới dạng một tài liệu JSON duy nhất phù hợp với những gì mã giao diện người dùng / phía máy khách của bạn cần. Làm điều đó sẽ cho phép bạn yêu cầu tài liệu đầu ra thống nhất của bài đăng theo cách này:

/ db / _design / app / _list / posts / united ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
Hàm _list của bạn (trong trường hợp này có tên là "hợp nhất") sẽ lấy kết quả của bản đồ / thu nhỏ chế độ xem (trong trường hợp này có tên là "bài đăng") và chạy chúng thông qua một hàm JavaScript sẽ gửi lại phản hồi HTTP trong loại nội dung mà bạn cần (JSON, HTML, v.v.).

Kết hợp những điều này, bạn có thể chia nhỏ tài liệu của mình ở bất kỳ cấp độ nào mà bạn thấy hữu ích và "an toàn" để cập nhật, xung đột và sao chép, sau đó ghép chúng lại với nhau nếu cần khi chúng được yêu cầu.

Hy vọng rằng sẽ giúp.


2
Không chắc liệu điều này có giúp ích cho Lance hay không, nhưng tôi biết một điều; nó chắc chắn đã giúp tôi rất nhiều! Điều này thật tuyệt!
Đánh dấu

17

Tôi biết đây là một câu hỏi cũ, nhưng tôi đã xem qua nó để cố gắng tìm ra cách tiếp cận tốt nhất cho vấn đề tương tự chính xác này. Christopher Lenz đã viết một bài đăng trên blog rất hay về các phương pháp tạo mô hình "gia nhập" trong CouchDB . Một trong những cách làm của tôi là: "Cách duy nhất để cho phép bổ sung dữ liệu liên quan một cách không xung đột là đặt dữ liệu liên quan đó vào các tài liệu riêng biệt." Vì vậy, vì lợi ích đơn giản, bạn muốn nghiêng về "không chuẩn hóa". Nhưng bạn sẽ gặp phải rào cản tự nhiên do viết mâu thuẫn trong một số trường hợp nhất định.

Trong ví dụ về Bài đăng và Nhận xét của bạn, nếu một bài đăng và tất cả các nhận xét của nó nằm trong một tài liệu, thì hai người cố gắng đăng nhận xét cùng một lúc (tức là chống lại cùng một bản sửa đổi của tài liệu) sẽ gây ra xung đột. Điều này thậm chí sẽ trở nên tồi tệ hơn trong kịch bản "toàn bộ trang web trong một tài liệu duy nhất" của bạn.

Vì vậy, tôi nghĩ quy tắc ngón tay cái sẽ là "không chuẩn hóa cho đến khi nó đau", nhưng điểm mà nó sẽ "đau" là nơi bạn có nhiều khả năng đăng nhiều bản chỉnh sửa so với cùng một bản sửa đổi của một tài liệu.


Phản hồi thú vị. Với ý nghĩ đó, người ta nên đặt câu hỏi liệu bất kỳ trang web có lưu lượng truy cập cao hợp lý nào thậm chí sẽ có tất cả các nhận xét cho một bài đăng blog trong một tài liệu. Nếu tôi đọc đúng điều này, có nghĩa là mỗi lần bạn có mọi người thêm nhận xét liên tiếp, bạn có thể phải giải quyết xung đột. Tất nhiên, tôi không biết họ sẽ phải kích hoạt điều này nhanh như thế nào.
pc1oad1etter 18/02

1
Trong trường hợp các nhận xét là một phần của tài liệu trong Couch, các bài đăng nhận xét đồng thời có thể xung đột vì phạm vi lập phiên bản của bạn là "bài đăng" với tất cả các nhận xét của nó. Trong trường hợp mỗi đối tượng của bạn là bộ sưu tập tài liệu, chúng chỉ đơn giản là trở thành hai tài liệu 'bình luận' mới với các liên kết trở lại bài đăng và không lo va chạm. Tôi cũng sẽ chỉ ra rằng việc xây dựng các quan điểm về thiết kế tài liệu "hướng đối tượng" là dễ dàng - bạn chuyển vào khóa của một bài đăng chẳng hạn, sau đó gửi tất cả các nhận xét, được sắp xếp theo một số phương pháp, cho bài đăng đó.
Riyad Kalla

16

Các cuốn sách nói, nếu tôi nhớ chính xác, để denormalize cho đến khi "một điều đau khổ", trong khi vẫn giữ trong tâm trí các tần số mà tài liệu của bạn có thể được cập nhật.

  1. Bạn sử dụng các quy tắc / nguyên tắc nào để phân chia các tài liệu của mình (các mối quan hệ, v.v.)?

Theo quy tắc chung, tôi bao gồm tất cả dữ liệu cần thiết để hiển thị một trang liên quan đến mục được đề cập. Nói cách khác, mọi thứ bạn sẽ in trên một tờ giấy trong thế giới thực mà bạn sẽ giao cho ai đó. Ví dụ: một tài liệu báo giá cổ phiếu sẽ bao gồm tên của công ty, sàn giao dịch, đơn vị tiền tệ, ngoài các con số; một tài liệu hợp đồng sẽ bao gồm tên và địa chỉ của các bên đối tác, tất cả thông tin về ngày tháng và các bên ký kết. Nhưng báo giá cổ phiếu từ các ngày khác nhau sẽ tạo thành các tài liệu riêng biệt, các hợp đồng riêng biệt sẽ tạo thành các tài liệu riêng biệt.

  1. Có ổn không khi đặt toàn bộ trang web vào một tài liệu?

Không, điều đó thật ngớ ngẩn, bởi vì:

  • bạn sẽ phải đọc và viết toàn bộ trang web (tài liệu) trên mỗi bản cập nhật, và điều đó rất kém hiệu quả;
  • bạn sẽ không được lợi từ bất kỳ bộ nhớ đệm chế độ xem nào.

3
Cảm ơn vì đã tham gia với tôi một chút. Tôi có ý tưởng "bao gồm tất cả dữ liệu cần thiết để hiển thị một trang liên quan đến mục được đề cập", nhưng điều đó vẫn rất khó thực hiện. Một "trang" có thể là trang Nhận xét, trang Người dùng, trang Bài đăng hoặc trang Nhận xét và Bài đăng, v.v. Khi đó, bạn sẽ phân chia chúng như thế nào về cơ bản? Bạn cũng có thể hiển thị Hợp đồng của mình với Người dùng. Tôi nhận được các tài liệu 'giống như biểu mẫu', điều đó hợp lý khi giữ chúng riêng biệt.
Lance Pollard

6

Tôi nghĩ phản hồi của Jake là một trong những khía cạnh quan trọng nhất khi làm việc với CouchDB có thể giúp bạn đưa ra quyết định phạm vi: xung đột.

Trong trường hợp bạn có nhận xét như một thuộc tính mảng của chính bài đăng và bạn chỉ có một DB 'bài đăng' với một loạt các tài liệu 'bài đăng' khổng lồ trong đó, như Jake và những người khác đã chỉ ra một cách chính xác, bạn có thể tưởng tượng một kịch bản trên một bài đăng blog thực sự phổ biến trong đó hai người dùng gửi chỉnh sửa đồng thời cho tài liệu bài đăng, dẫn đến xung đột và xung đột phiên bản cho tài liệu đó.

HƯỚNG DẪN: Như bài viết này đã chỉ ra , cũng nên lưu ý rằng mỗi khi bạn yêu cầu / cập nhật tài liệu đó, bạn phải lấy / đặt toàn bộ tài liệu, vì vậy hãy chuyển một lượng lớn tài liệu đại diện cho toàn bộ trang web hoặc một bài đăng có nhiều nhận xét về nó có thể trở thành một vấn đề mà bạn muốn tránh.

Trong trường hợp các bài đăng được lập mô hình tách biệt với các bình luận và hai người gửi bình luận về một câu chuyện, chúng chỉ đơn giản trở thành hai tài liệu "bình luận" trong DB đó, không có vấn đề gì xung đột; chỉ cần hai thao tác PUT để thêm hai nhận xét mới vào db "nhận xét".

Sau đó, để viết các lượt xem cung cấp cho bạn nhận xét cho một bài đăng, bạn sẽ chuyển vào ID post và sau đó gửi tất cả các nhận xét tham chiếu đến ID bài đăng chính đó, được sắp xếp theo một số thứ tự hợp lý. Có thể bạn thậm chí chuyển một cái gì đó như [postID, byUsername] làm chìa khóa cho chế độ xem 'nhận xét' để chỉ ra bài đăng chính và cách bạn muốn sắp xếp kết quả hoặc một cái gì đó dọc theo những dòng đó.

MongoDB xử lý các tài liệu hơi khác một chút, cho phép các chỉ mục được xây dựng trên các phần tử con của tài liệu, vì vậy bạn có thể thấy câu hỏi tương tự trên danh sách gửi thư MongoDB và ai đó nói rằng "chỉ cần đặt các nhận xét thành thuộc tính của bài đăng chính".

Do tính chất khóa ghi và bản chất một chủ của Mongo, vấn đề sửa đổi xung đột giữa hai người thêm nhận xét sẽ không xuất hiện ở đó và khả năng truy vấn của nội dung, như đã đề cập, không bị ảnh hưởng quá kém vì phụ các chỉ mục.

Điều đó đang được nói, nếu các yếu tố phụ của bạn trong một trong hai DB sẽ rất lớn (giả sử 10 nghìn bình luận), tôi tin rằng cả hai phe đều khuyến nghị tạo các yếu tố riêng biệt đó; Tôi chắc chắn đã thấy điều đó xảy ra với Mongo vì có một số giới hạn trên về mức độ lớn của một tài liệu và các thành phần phụ của nó.


Rất hữu ích. Cảm ơn bạn
Ray Suelzer 17/10/13
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.