CouchDB 문서 모델링 원칙


120

지금 당분간 답변을 시도했지만 알아낼 수없는 질문이 있습니다.

CouchDB 문서를 어떻게 디자인하거나 분할합니까?

예를 들어 블로그 게시물을 살펴보십시오.

이를 수행하는 반 "관계형"방법은 몇 가지 개체를 만드는 것입니다.

  • 게시하다
  • 사용자
  • 논평
  • 꼬리표
  • 단편

이것은 상당한 의미가 있습니다. 하지만 나는 같은 것을 모델링하기 위해 (대단한 모든 이유로) couchdb를 사용하려고 노력하고 있으며 매우 어려웠습니다.

대부분의 블로그 게시물은이를 수행하는 방법에 대한 쉬운 예를 제공합니다. 그들은 기본적으로 같은 방식으로 나누지 만 각 문서에 '임의'속성을 추가 할 수 있다고 말하는데, 확실히 좋습니다. 따라서 CouchDB에 다음과 같은 것이 있습니다.

  • 게시물 (문서에 "의사"모델 태그 및 스 니펫 포함)
  • 논평
  • 사용자

어떤 사람들은 거기에 Comment와 User를 던질 수 있다고 말할 것입니다.


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 문서에서 댓글 만 추출한 뷰를 작성하여 사용자 및 태그와 마찬가지로 댓글 모델로 가져 오는 방법도 이해합니다.

하지만 "내 전체 사이트를 하나의 문서에 넣지 않는 이유는 무엇입니까?"라고 생각합니다.


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"
                    ...
                }
            }
        }
    }
}

원하는 것을 쉽게 찾을 수 있습니다.

그러면 제가 가진 질문은 문서를 더 작은 문서로 나눌 때 또는 문서간에 "관계"를 만들 때 어떻게 결정합니까?

나는 그것이 훨씬 더 "객체 지향적"일 것이라고 생각하고 다음과 같이 나눈다면 Value Objects에 매핑하기가 더 쉬울 것입니다.


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
    }
}

...하지만 관계형 데이터베이스처럼 보이기 시작합니다. 그리고 종종 나는 "전체 사이트-내-문서"처럼 보이는 것을 물려 받기 때문에 관계로 모델링하기가 더 어렵습니다.

관계형 데이터베이스와 문서 데이터베이스를 사용하는 방법 /시기에 대해 많은 것을 읽었으므로 여기서는 주요 문제가 아닙니다. CouchDB에서 데이터를 모델링 할 때 적용 할 좋은 규칙 / 원칙이 무엇인지 궁금합니다.

또 다른 예는 XML 파일 / 데이터입니다. 일부 XML 데이터에는 10 개 이상의 수준이 중첩되어 있으며 ActiveRecord, CouchRest 또는 기타 Object Relational Mapper에서 JSON을 렌더링하는 것과 동일한 클라이언트 (예 : Ajax on Rails 또는 Flex)를 사용하여 시각화하고 싶습니다. 때로는 아래처럼 전체 사이트 구조 인 거대한 XML 파일을 얻고 Rails 앱에서 사용하기 위해 Value Objects에 매핑해야하므로 데이터를 직렬화 / 역 직렬화하는 다른 방법을 작성할 필요가 없습니다. :


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

따라서 일반적인 CouchDB 질문은 다음과 같습니다.

  1. 문서를 나누는 데 사용하는 규칙 / 원칙 (관계 등)은 무엇입니까?
  2. 전체 사이트를 하나의 문서에 넣어도 괜찮습니까?
  3. 그렇다면 임의의 깊이 수준으로 문서를 직렬화 / 역 직렬화하는 방법 (위의 큰 json 예제 또는 xml 예제)을 어떻게 처리합니까?
  4. 아니면 그것들을 VO로 바꾸지 않습니까? "이것들이 객체-관계형 맵에 너무 중첩되어 있으므로 원시 XML / JSON 메서드를 사용하여 액세스 할 것"이라고 결정합니까?

도와 주셔서 감사합니다. CouchDB로 데이터를 나누는 방법에 대한 문제는 "지금부터 이렇게해야합니다"라고 말하기 어려웠습니다. 곧 도착하기를 바랍니다.

다음 사이트 / 프로젝트를 공부했습니다.

  1. CouchDB의 계층 적 데이터
  2. CouchDB 위키
  3. 소파-CouchDB 앱
  4. CouchDB 최종 가이드
  5. PeepCode CouchDB 스크린 캐스트
  6. 소파 휴식
  7. CouchDB 읽어보기

... 그러나 그들은 여전히이 질문에 대답하지 않았습니다.


2
와우 당신은 여기에 전체 에세이를 작성했습니다 ... :-)
Eero

8
안녕하세요, 좋은 질문입니다
elmarco

답변:


26

이미 이에 대한 몇 가지 훌륭한 답변이 있었지만, viatropos에서 설명한 원래 상황과 함께 작업하기위한 옵션 조합에 최신 CouchDB 기능을 추가하고 싶었습니다.

문서를 분할하는 핵심 포인트는 충돌이있을 수있는 부분입니다 (앞서 언급했듯이). 완전히 관련되지 않은 업데이트 (예 : 전체 사이트 문서에 개정을 추가하는 주석 추가)에 대한 단일 개정 경로를 얻을 수 있으므로 단일 문서에 대량으로 "얽힌"문서를 함께 보관해서는 안됩니다. 다양하고 작은 문서 간의 관계 또는 연결을 관리하는 것은 처음에는 혼란 스러울 수 있지만 CouchDB는 서로 다른 부분을 단일 응답으로 결합하는 여러 옵션을 제공합니다.

첫 번째는 뷰 데이터 정렬입니다. 맵 / 축소 쿼리 결과에 키 / 값 쌍을 내보낼 때 키는 UTF-8 데이터 정렬 ( "a"가 "b"앞에 옴)을 기반으로 정렬됩니다. 맵 / 축소에서 복잡한 키를 JSON 배열로 출력 할 수도 있습니다 ["a", "b", "c"].. 그렇게하면 배열 키로 만들어진 일종의 "트리"를 포함 할 수 있습니다. 위의 예를 사용하여 post_id, 참조하는 유형, ID (필요한 경우)를 출력 할 수 있습니다. 그런 다음 참조 된 문서의 ID를 반환 된 값의 객체에 출력하면 'include_docs'쿼리 매개 변수를 사용하여 해당 문서를 map / reduce 출력에 포함 할 수 있습니다.

{"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"}}
]}

'? include_docs = true'로 동일한 뷰를 요청하면 'value'개체에서 참조 된 '_id'를 사용하거나 'value'개체에없는 경우 'doc'키가 추가됩니다. 행이 생성 된 문서의 '_id'(이 경우 'post'문서) 이러한 결과에는 방출이 만들어진 소스 문서를 참조하는 'id'필드가 포함됩니다. 공간과 가독성을 위해 생략했습니다.

그런 다음 'start_key'및 'end_key'매개 변수를 사용하여 결과를 단일 게시물의 데이터로 필터링 할 수 있습니다.

? start_key = [ "123412804910820"] & end_key = [ "123412804910820", {}, {}]
또는 특정 유형에 대한 목록을 구체적으로 추출하십시오.
? start_key = [ "123412804910820", "comment"] & end_key = [ "123412804910820", "comment", {}]
이러한 쿼리 매개 변수 조합은 빈 개체 ( " {}")가 항상 데이터 정렬의 맨 아래에 있고 null 또는 ""가 항상 맨 위에 있기 때문에 가능합니다.

이러한 상황에서 CouchDB의 두 번째 유용한 추가 기능은 _list 함수입니다. 이를 통해 HTML, XML, CSV 등을 원할 경우 어떤 종류의 템플릿 시스템을 통해 위의 결과를 실행하거나 전체 게시물의 콘텐츠 (포함)를 요청할 수있는 경우 통합 된 JSON 구조를 출력 할 수 있습니다. 작성자 및 주석 데이터)를 단일 요청으로 처리하고 클라이언트 측 / UI 코드에 필요한 것과 일치하는 단일 JSON 문서로 반환됩니다. 이렇게하면 다음과 같은 방식으로 게시물의 통합 출력 문서를 요청할 수 있습니다.

/ db / _design / app / _list / posts / unified ?? start_key = [ "123412804910820"] & end_key = [ "123412804910820", {}, {}] & include_docs = true
_list 함수 (이 경우 "unified")는 view map / reduce (이 경우 "posts")의 결과를 가져 와서 콘텐츠 유형으로 HTTP 응답을 다시 보내는 JavaScript 함수를 통해 실행합니다. 필요합니다 (JSON, HTML 등).

이러한 것들을 결합하면 업데이트, 충돌 및 복제에 유용하고 "안전"하다고 판단되는 수준에서 문서를 분할 한 다음 요청시 필요에 따라 다시 통합 할 수 있습니다.

도움이되기를 바랍니다.


2
이것이 랜스에게 도움이되었는지는 확실하지 않지만 한 가지를 알고 있습니다. 그것은 확실히 저에게 큰 도움이되었습니다! 굉장합니다!
Mark

17

나는 이것이 오래된 질문이라는 것을 알고 있지만 똑같은 문제에 대한 최선의 접근 방식을 찾으려고 노력했습니다. Christopher Lenz는 CouchDB에서 "조인"을 모델링 하는 방법에 대한 멋진 블로그 게시물을 작성했습니다 . 필자의 시사점 중 하나는 "관련 데이터의 충돌하지 않는 추가를 허용하는 유일한 방법은 관련 데이터를 별도의 문서에 저장하는 것입니다." 따라서 단순성을 위해 "비정규 화"로 기울이는 것이 좋습니다. 그러나 특정 상황에서 쓰기 충돌로 인해 자연스러운 장벽에 부딪 힐 것입니다.

게시물 및 댓글의 예에서 단일 게시물과 모든 댓글이 하나의 문서에 있으면 두 사람이 동시에 (즉, 문서의 동일한 개정판에 대해) 댓글을 게시하려고하면 충돌이 발생합니다. 이것은 "단일 문서의 전체 사이트"시나리오에서 더욱 악화 될 것입니다.

그래서 저는 경험의 법칙이 "아플 때까지 비정규 화"라고 생각하지만 "아파"될 지점은 동일한 문서 개정판에 대해 여러 편집이 게시 될 가능성이 높은 지점입니다.


흥미로운 반응입니다. 이를 염두에두고 트래픽이 상당히 많은 사이트가 하나의 문서에 단일 블로그 게시물에 대한 모든 댓글을 포함 할 수 있는지 여부에 대해 질문해야합니다. 내가이 권리를 읽었다면 사람들이 빠르게 연속해서 댓글을 추가 할 때마다 갈등을 해결해야 할 수도 있음을 의미합니다. 물론, 나는 그들이 이것을 촉발시키기 위해 얼마나 빨리 연속적으로 있어야 할 것인지 모릅니다.
pc1oad1etter

1
Couch에서 댓글이 문서의 일부인 경우 버전 관리 범위가 모든 댓글이있는 "게시물"이기 때문에 동시 댓글 게시가 충돌 할 수 있습니다. 각 객체가 문서 모음 인 경우, 이는 단순히 게시물로 돌아가는 링크가 있고 충돌의 우려가없는 두 개의 새로운 '댓글'문서가됩니다. 또한 "객체 지향"문서 디자인에 대한 뷰를 작성하는 것은 간단하다는 점을 지적합니다. 예를 들어 게시물의 키를 전달한 다음 해당 게시물에 대해 어떤 방법으로 정렬 된 모든 주석을 내 보냅니다.
Riyad Kalla 2011 년

16

은 내가 올바르게 기억한다면 문서가 업데이트 될 수있는 빈도를 염두에두고 "아플 때까지"비정규 화하라고 말합니다.

  1. 문서를 나누는 데 사용하는 규칙 / 원칙 (관계 등)은 무엇입니까?

경험상 문제의 항목과 관련된 페이지를 표시하는 데 필요한 모든 데이터를 포함합니다. 즉, 누군가에게 건네 줄 실제 종이에 인쇄하는 모든 것입니다. 예를 들어 주식 시세 문서에는 숫자 외에 회사 이름, 거래소, 통화가 포함됩니다. 계약 문서에는 상대방의 이름과 주소, 날짜 및 서명인에 대한 모든 정보가 포함됩니다. 그러나 다른 날짜의 주식 시세는 별도의 문서를 형성하고 별도의 계약은 별도의 문서를 형성합니다.

  1. 전체 사이트를 하나의 문서에 넣어도 괜찮습니까?

아니요, 그 이유는 다음과 같습니다.

  • 업데이트 할 때마다 전체 사이트 (문서)를 읽고 써야하는데 이는 매우 비효율적입니다.
  • 뷰 캐싱의 이점이 없습니다.

3
저와 함께 해주셔서 감사합니다. 나는 "문제의 항목에 관한 페이지를 표시하는 데 필요한 모든 데이터를 포함한다"라는 아이디어를 얻었지만 여전히 구현하기가 매우 어렵습니다. "페이지"는 댓글 페이지, 사용자 페이지, 게시물 페이지, 댓글 및 게시물 페이지 등이 될 수 있습니다. 그러면 주로 어떻게 나누겠습니까? 사용자에게 계약을 표시 할 수도 있습니다. 나는 '형식과 유사한'문서를 얻습니다.
Lance Pollard

6

Jake의 응답은 CouchDB 작업에서 가장 중요한 측면 중 하나이며 범위 지정 결정을 내리는 데 도움이 될 수 있습니다.

게시물 자체의 배열 속성으로 주석이 있고 거대한 'post'문서가 포함 된 'post'DB가있는 경우 Jake와 다른 사람들이 올바르게 지적했듯이 시나리오를 상상할 수 있습니다. 두 명의 사용자가 동시에 포스트 문서에 대한 편집 내용을 제출하여 해당 문서에 대한 충돌과 버전 충돌이 발생하는 매우 인기있는 블로그 게시물입니다.

제외 : 이 문서 포인트 아웃 , 또한 당신이 요청하는 때마다 / 당신이 얻을 수있는 그 문서를 업데이트 / 너무 많은 전체 사이트 또는 게시물을 표현하거나 대규모 문서를 주위에 전달 전체에서 문서를 설정하는 것이 고려 그것에 대한 댓글이 당신이 피하고 싶은 문제가 될 수 있습니다.

게시물이 댓글과 별도로 모델링되고 두 사람이 스토리에 대한 댓글을 제출하는 경우, 해당 게시물은 충돌 문제없이 해당 DB에서 두 개의 "댓글"문서가됩니다. 두 개의 새 주석을 "comment"db에 추가하는 두 개의 PUT 작업 만 있습니다.

그런 다음 게시물에 대한 댓글을 다시 제공하는 뷰를 작성하려면 postID를 전달한 다음 논리적 순서로 정렬 된 상위 게시물 ID를 참조하는 모든 댓글을 내 보냅니다. 어쩌면 [postID, byUsername]과 같은 것을 'comments'보기의 키로 전달하여 상위 게시물과 결과 정렬 방법 또는 해당 줄을 따라 원하는 것을 표시 할 수도 있습니다.

MongoDB는 문서를 약간 다르게 처리하여 문서의 하위 요소에 색인을 만들 수 있으므로 MongoDB 메일 링리스트에서 동일한 질문을 볼 수 있으며 누군가 "댓글을 상위 게시물의 속성으로 만드십시오"라고 말할 수 있습니다.

Mongo의 쓰기 잠금 및 단일 마스터 특성으로 인해 두 사람이 주석을 추가하는 충돌 수정 문제가 발생하지 않으며 앞서 언급했듯이 콘텐츠의 쿼리 가능성이 하위 항목으로 인해 너무 나쁘게 영향을받지 않습니다. 색인.

귀하의 하위 요소 경우 그 존재는 말했다 DB는 (코멘트 수천의 말에 10 초) 내가 그 별도의 요소를 만들기 위해 두 진영의 추천을 믿고 거대거야; 문서와 그 하위 요소의 크기에 대한 상한선이 있기 때문에 Mongo의 경우에 해당합니다.


매우 유용합니다. 감사합니다
Ray Suelzer 2013 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.