MongoDB / NoSQL : 문서 변경 기록 유지


134

데이터베이스 응용 프로그램에서 매우 일반적인 요구 사항은 데이터베이스에서 하나 이상의 특정 엔터티에 대한 변경 내용을 추적하는 것입니다. 이것을 행 버전 관리, 로그 테이블 또는 기록 테이블이라고 들었습니다 (다른 이름이 있다고 확신합니다). RDBMS에서 여러 가지 방법으로 접근 할 수 있습니다. 모든 소스 테이블의 모든 변경 사항을 단일 테이블 (더 많은 로그)에 쓰거나 각 소스 테이블에 대해 별도의 히스토리 테이블을 가질 수 있습니다. 응용 프로그램 코드 또는 데이터베이스 트리거를 통해 로깅을 관리하는 옵션도 있습니다.

NoSQL / 문서 데이터베이스 (특히 MongoDB)에서 동일한 문제에 대한 솔루션이 어떻게 보이는지, 어떻게 균일하게 해결되는지 생각하려고합니다. 문서의 버전 번호를 작성하고 덮어 쓰지 않는 것만 큼 간단합니까? "실제"문서와 "로그 된"문서에 대한 별도의 컬렉션을 만드시겠습니까? 이것이 쿼리 및 성능에 어떤 영향을 미칩니 까?

어쨌든, 이것은 NoSQL 데이터베이스에서 일반적인 시나리오입니까? 그렇다면 일반적인 해결책이 있습니까?


어떤 언어 드라이버를 사용하고 있습니까?
여호수아 Partogi

여전히 땜질하고 (MongoDB를 보이는 있지만 아직 후면 끝의 검색을 완료하지 않은 - 아직 결정 extrememly 가능성). 나는 NoRM (C #)을 고민하고 있었고 그 프로젝트와 관련된 이름 중 일부를 좋아하므로 선택이 될 것 같습니다.
Phil Sandler

2
나는 이것이 오래된 질문이라는 것을 알고 있지만 MongoDB로 버전을 찾고있는 사람에게는이 SO 질문 이 관련 이 있으며 더 나은 답변과 함께 내 의견에 있습니다.
AWolf

답변:


107

좋은 질문입니다. 나는 이것도 직접 조사하고있었습니다.

변경 될 때마다 새 버전을 만듭니다.

Ruby 용 Mongoid 드라이버 의 버전 관리 모듈 을 발견했습니다. 나는 그것을 직접 사용하지는 않았지만 찾을 수있는 것에서 각 문서에 버전 번호를 추가합니다. 이전 버전은 문서 자체에 포함되어 있습니다. 가장 큰 단점은 변경때마다 전체 문서가 복제 되어 큰 문서를 처리 할 때 많은 중복 컨텐츠가 저장된다는 것입니다. 이 방법은 작은 크기의 문서를 처리하거나 문서를 자주 업데이트하지 않는 경우에 좋습니다.

변경 사항을 새 버전으로 만 저장

또 다른 방법은 변경된 필드 만 새 버전 으로 저장 하는 것 입니다. 그런 다음 내역을 '평평하게'하여 모든 버전의 문서를 재구성 할 수 있습니다. 그러나 모델의 변경 사항을 추적하고 응용 프로그램이 최신 문서를 재구성 할 수있는 방식으로 업데이트 및 삭제를 저장해야하기 때문에 다소 복잡합니다. 플랫 SQL 테이블이 아닌 구조화 된 문서를 처리 할 때 까다로울 수 있습니다.

문서 내에 변경 사항 저장

각 필드에는 개별 기록이있을 수 있습니다. 이 방법으로 문서를 지정된 버전으로 재구성하는 것이 훨씬 쉽습니다. 애플리케이션에서 변경 사항을 명시 적으로 추적 할 필요는 없지만 값을 변경할 때 새 버전의 특성을 작성하기 만하면됩니다. 문서는 다음과 같이 보일 수 있습니다.

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { version: 1, value: "Hello world" },
    { version: 6, value: "Foo" }
  ],
  body: [
    { version: 1, value: "Is this thing on?" },
    { version: 2, value: "What should I write?" },
    { version: 6, value: "This is the new body" }
  ],
  tags: [
    { version: 1, value: [ "test", "trivial" ] },
    { version: 6, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { version: 3, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { version: 4, value: "Spam" },
        { version: 5, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { version: 7, value: "Not bad" },
        { version: 8, value: "Not bad at all" }
      ]
    }
  ]
}

버전에서 삭제 된 것으로 문서의 일부를 표시하는 것은 여전히 ​​다소 어색합니다. state애플리케이션에서 삭제 / 복원 할 수있는 부품 필드를 소개 할 수 있습니다.

{
  author: "xxx",
  body: [
    { version: 4, value: "Spam" }
  ],
  state: [
    { version: 4, deleted: false },
    { version: 5, deleted: true }
  ]
}

이러한 각 접근 방식을 사용하면 하나의 컬렉션에 최신의 병합 된 버전을 저장하고 별도의 컬렉션에 기록 데이터를 저장할 수 있습니다. 최신 버전의 문서에만 관심이있는 경우 쿼리 시간이 향상됩니다. 그러나 최신 버전과 기록 데이터가 모두 필요한 경우 하나가 아닌 두 개의 쿼리를 수행해야합니다. 따라서 단일 컬렉션과 두 개의 개별 컬렉션 중 어느 것을 사용할지는 응용 프로그램에 이전 버전이 필요한 빈도에 따라 달라집니다 .

이 답변의 대부분은 내 생각의 두뇌 덤프 일뿐입니다. 실제로는 아직 시도하지 않았습니다. 이를 되돌아 보면, 첫 번째 옵션은 아마도 중복 데이터의 오버 헤드가 애플리케이션에 매우 중요하지 않은 한 가장 쉽고 최상의 솔루션 일 것입니다. 두 번째 옵션은 매우 복잡하며 아마도 노력할 가치가 없습니다. 세 번째 옵션은 기본적으로 옵션 2의 최적화이며 구현하기가 쉽지만 실제로 옵션 1을 사용할 수 없다면 구현 노력의 가치가 없습니다.

이 문제에 대한 피드백과 다른 사람들의 문제에 대한 해결책을 기대합니다 :)


어딘가에 델타를 저장하면 역사적인 문서를 가져와 항상 최신 버전을 사용할 수 있도록 평평해야합니다.
jpmc26

@ jpmc26 두 번째 접근 방식과 비슷하지만 델타를 저장하여 최신 버전으로 가져 오는 대신 델타를 저장하여 이전 버전으로 가져옵니다. 사용할 방법은 이전 버전이 필요한 빈도에 따라 다릅니다.
Niels van der Rest

문서를 현재 상태에 대한보기로 사용하고 타임 스탬프 (초기 값이이 로그에 표시되어야 함)를 포함하여 각 변경 사항을 추적하는 변경 로그로 두 번째 문서를 갖는 것에 관한 단락을 추가 할 수 있습니다. '를 임의의 특정 시점에 적용하고, 예를 들어 알고리즘이 알고리즘을 터치했을 때 발생한 상황을 상관 시키거나 사용자가 클릭했을 때 항목이 어떻게 표시되는지 확인합니다.
Manuel Arwed Schmidt

인덱스 필드가 배열로 표시되는 경우 성능에 영향을 줍니까?
DmitriD

@All-이것을 달성하기 위해 코드를 공유해 주시겠습니까?
Pra_A

8

우리는 이것을 사이트에서 부분적으로 구현했으며 '개정 문서를 별도의 문서에 저장합니다'(및 별도의 데이터베이스)를 사용합니다. 우리는 diff를 반환하고 저장하기위한 사용자 지정 함수를 작성했습니다.


2
같은 코드를 공유해 주시겠습니까? 이 접근법은 유망 해 보인다
Pra_A

1
@smilyface-Spring Boot Javers 통합이이를 달성하는 것이 가장 좋습니다
Pra_A

@PAA-질문을했습니다 (거의 같은 개념). stackoverflow.com/questions/56683389/… 입력 내용이 있습니까?
smilyface 2016 년

6

문서 내에서 변경 사항저장 하지 않는 이유는 무엇 입니까?

각 키 페어에 대해 버전을 저장하는 대신 문서의 현재 키 페어는 항상 최신 상태를 나타내며 변경 기록은 기록 배열 내에 저장됩니다. 작성 이후 변경된 키만 로그에 항목을 갖습니다.

{
  _id: "4c6b9456f61f000000007ba6"
  title: "Bar",
  body: "Is this thing on?",
  tags: [ "test", "trivial" ],
  comments: [
    { key: 1, author: "joe", body: "Something cool" },
    { key: 2, author: "xxx", body: "Spam", deleted: true },
    { key: 3, author: "jim", body: "Not bad at all" }
  ],
  history: [
    { 
      who: "joe",
      when: 20160101,
      what: { title: "Foo", body: "What should I write?" }
    },
    { 
      who: "jim",
      when: 20160105,
      what: { tags: ["test", "test2"], comments: { key: 3, body: "Not baaad at all" }
    }
  ]
}

2

하나는 현재 NoSQL 데이터베이스와 히스토리 NoSQL 데이터베이스를 가질 수 있습니다. 매일 야간 ETL이 실행됩니다. 이 ETL은 모든 값을 타임 스탬프와 함께 기록하므로 값 대신 항상 튜플 (버전이 지정된 필드)이됩니다. 현재 값이 변경된 경우에만 새 값을 기록하여 프로세스 공간을 절약합니다. 예를 들어,이 역사적인 NoSQL 데이터베이스 json 파일은 다음과 같습니다.

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { date: 20160101, value: "Hello world" },
    { date: 20160202, value: "Foo" }
  ],
  body: [
    { date: 20160101, value: "Is this thing on?" },
    { date: 20160102, value: "What should I write?" },
    { date: 20160202, value: "This is the new body" }
  ],
  tags: [
    { date: 20160101, value: [ "test", "trivial" ] },
    { date: 20160102, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { date: 20160301, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { date: 20160101, value: "Spam" },
        { date: 20160102, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { date: 20160101, value: "Not bad" },
        { date: 20160102, value: "Not bad at all" }
      ]
    }
  ]
}

0

Python 사용자 (python 3 이상) 는 pymongo의 Collection 객체의 확장 인 HistoricalCollection 이 있습니다.

문서의 예 :

from historical_collection.historical import HistoricalCollection
from pymongo import MongoClient
class Users(HistoricalCollection):
    PK_FIELDS = ['username', ]  # <<= This is the only requirement

# ...

users = Users(database=db)

users.patch_one({"username": "darth_later", "email": "darthlater@example.com"})
users.patch_one({"username": "darth_later", "email": "darthlater@example.com", "laser_sword_color": "red"})

list(users.revisions({"username": "darth_later"}))

# [{'_id': ObjectId('5d98c3385d8edadaf0bb845b'),
#   'username': 'darth_later',
#   'email': 'darthlater@example.com',
#   '_revision_metadata': None},
#  {'_id': ObjectId('5d98c3385d8edadaf0bb845b'),
#   'username': 'darth_later',
#   'email': 'darthlater@example.com',
#   '_revision_metadata': None,
#   'laser_sword_color': 'red'}]

전체 공개, 저는 패키지 작성자입니다. :)

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