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.