MongoDB / NoSQL: การเก็บประวัติการเปลี่ยนแปลงเอกสาร


134

ข้อกำหนดทั่วไปในแอปพลิเคชันฐานข้อมูลคือการติดตามการเปลี่ยนแปลงของเอนทิตีเฉพาะอย่างน้อยหนึ่งรายการในฐานข้อมูล ฉันเคยได้ยินสิ่งนี้เรียกว่าการกำหนดเวอร์ชันแถวตารางบันทึกหรือตารางประวัติ (ฉันแน่ใจว่ามีชื่ออื่นอยู่) มีหลายวิธีในการเข้าถึงใน RDBMS - คุณสามารถเขียนการเปลี่ยนแปลงทั้งหมดจากตารางต้นทางทั้งหมดไปยังตารางเดียว (บันทึกเพิ่มเติม) หรือมีตารางประวัติแยกต่างหากสำหรับแต่ละตารางต้นทาง นอกจากนี้คุณยังมีตัวเลือกในการจัดการการล็อกอินรหัสแอปพลิเคชันหรือผ่านทริกเกอร์ฐานข้อมูล

ฉันกำลังพยายามคิดว่าวิธีแก้ปัญหาเดียวกันจะเป็นอย่างไรในฐานข้อมูล NoSQL / document (โดยเฉพาะ MongoDB) และจะแก้ไขอย่างไรในลักษณะเดียวกัน มันจะง่ายเหมือนการสร้างหมายเลขเวอร์ชันสำหรับเอกสารและไม่เขียนทับหรือไม่ การสร้างคอลเล็กชันแยกต่างหากสำหรับเอกสาร "ของจริง" กับ "บันทึก" สิ่งนี้จะส่งผลต่อการสืบค้นและประสิทธิภาพอย่างไร

อย่างไรก็ตามนี่เป็นสถานการณ์ทั่วไปสำหรับฐานข้อมูล NoSQL และถ้าเป็นเช่นนั้นมีวิธีแก้ปัญหาทั่วไปหรือไม่?


คุณใช้ไดรเวอร์ภาษาอะไร
Joshua Partogi

ไม่ได้ตัดสินใจยัง - ยังคง tinkering และยังไม่ได้แม้กระทั่งสรุปทางเลือกของปลายด้านหลังเลย (แม้ว่า MongoDB ดูextrememlyแนวโน้ม) ฉันกำลังซ่อมแซม NoRM (C #) และฉันชอบชื่อบางชื่อที่เกี่ยวข้องกับโครงการนั้นจึงดูเหมือนว่าจะเป็นทางเลือกมาก
Phil Sandler

2
ฉันรู้ว่านี่เป็นคำถามเก่า แต่สำหรับใครก็ตามที่กำลังมองหาการกำหนดเวอร์ชันด้วย MongoDB คำถาม SOนี้เกี่ยวข้องและในความคิดเห็นของฉันพร้อมคำตอบที่ดีกว่า
AWolf

คำตอบ:


107

เป็นคำถามที่ดีฉันกำลังมองหาสิ่งนี้ด้วยตัวเองเช่นกัน

สร้างเวอร์ชันใหม่สำหรับการเปลี่ยนแปลงแต่ละครั้ง

ฉันเจอโมดูล Versioningของไดรเวอร์ Mongoid สำหรับ Ruby ฉันไม่ได้ใช้มันเอง แต่จากสิ่งที่ฉันหาได้มันเพิ่มหมายเลขเวอร์ชันให้กับเอกสารแต่ละฉบับ เวอร์ชันเก่าจะฝังอยู่ในเอกสาร ข้อเสียเปรียบที่สำคัญคือเอกสารทั้งหมดจะซ้ำกันในการเปลี่ยนแปลงแต่ละครั้งซึ่งจะส่งผลให้มีการจัดเก็บเนื้อหาที่ซ้ำกันจำนวนมากเมื่อคุณจัดการกับเอกสารขนาดใหญ่ วิธีนี้ใช้ได้ดีแม้ว่าคุณจะจัดการกับเอกสารขนาดเล็กและ / หรือไม่ได้อัปเดตเอกสารบ่อยนัก

จัดเก็บเฉพาะการเปลี่ยนแปลงในเวอร์ชันใหม่

อีกวิธีหนึ่งที่จะเก็บเฉพาะสาขาที่มีการเปลี่ยนแปลงในเวอร์ชันใหม่ จากนั้นคุณสามารถ "แบน" ประวัติของคุณเพื่อสร้างเอกสารเวอร์ชันใดก็ได้ สิ่งนี้ค่อนข้างซับซ้อนเนื่องจากคุณต้องติดตามการเปลี่ยนแปลงในโมเดลของคุณและจัดเก็บการอัปเดตและการลบเพื่อให้แอปพลิเคชันของคุณสามารถสร้างเอกสารที่เป็นปัจจุบันขึ้นมาใหม่ได้ อาจเป็นเรื่องยุ่งยากเนื่องจากคุณกำลังจัดการกับเอกสารที่มีโครงสร้างแทนที่จะเป็นตาราง 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 }
  ]
}

ด้วยวิธีการเหล่านี้แต่ละวิธีคุณสามารถจัดเก็บเวอร์ชันที่ทันสมัยและแบนราบไว้ในคอลเลคชันเดียวและข้อมูลประวัติในคอลเลกชันแยกต่างหาก สิ่งนี้ควรปรับปรุงเวลาในการสืบค้นหากคุณสนใจเฉพาะเอกสารเวอร์ชันล่าสุด แต่เมื่อคุณต้องการทั้งเวอร์ชันล่าสุดและข้อมูลประวัติคุณจะต้องดำเนินการค้นหาสองครั้งแทนที่จะเป็นหนึ่งคำค้นหา ดังนั้นการเลือกใช้คอลเลกชั่นเดียวกับสองคอลเลกชันที่แยกจากกันควรขึ้นอยู่กับความถี่ที่แอปพลิเคชันของคุณต้องการเวอร์ชันที่ผ่านมา

คำตอบนี้ส่วนใหญ่เป็นเพียงการระบายความคิดของฉันฉันยังไม่ได้ลองทำอะไรเลย เมื่อมองย้อนกลับไปตัวเลือกแรกน่าจะเป็นทางออกที่ง่ายและดีที่สุดเว้นแต่ว่าข้อมูลที่ซ้ำกันจะมีความสำคัญมากสำหรับแอปพลิเคชันของคุณ ตัวเลือกที่สองค่อนข้างซับซ้อนและอาจไม่คุ้มกับความพยายาม ตัวเลือกที่สามโดยพื้นฐานแล้วเป็นการเพิ่มประสิทธิภาพของตัวเลือกที่สองและควรจะใช้งานได้ง่ายกว่า แต่อาจไม่คุ้มกับความพยายามในการนำไปใช้เว้นแต่คุณจะไม่สามารถใช้ตัวเลือกที่หนึ่งได้จริงๆ

รอข้อเสนอแนะเกี่ยวกับเรื่องนี้และแนวทางแก้ไขปัญหาของผู้อื่น :)


เกี่ยวกับการจัดเก็บเดลต้าไว้ที่ไหนสักแห่งเพื่อที่คุณจะต้องแบนเพื่อรับเอกสารทางประวัติศาสตร์และมีข้อมูลปัจจุบันอยู่เสมอ?
jpmc26

@ jpmc26 นั่นคล้ายกับแนวทางที่สอง แต่แทนที่จะบันทึกเดลต้าเพื่อไปยังเวอร์ชันล่าสุดคุณจะประหยัดเดลต้าเพื่อไปยังเวอร์ชันที่ผ่านมา จะใช้แนวทางใดขึ้นอยู่กับความถี่ที่คุณต้องการเวอร์ชันในอดีต
Niels van der Rest

คุณสามารถเพิ่มย่อหน้าเกี่ยวกับการใช้เอกสารเป็นมุมมองในสถานะปัจจุบันของสิ่งต่าง ๆ และมีเอกสารที่สองเป็นบันทึกการเปลี่ยนแปลงที่จะติดตามการเปลี่ยนแปลงแต่ละครั้งรวมถึงการประทับเวลา (ค่าเริ่มต้นจะต้องปรากฏในบันทึกนี้) จากนั้นคุณสามารถ 'เล่นซ้ำได้ 'ไปยังจุดใดเวลาหนึ่งและเช่นสัมพันธ์กับสิ่งที่เกิดขึ้นเมื่ออัลกอริทึมของคุณสัมผัสหรือดูว่ารายการแสดงผลอย่างไรเมื่อผู้ใช้คลิกที่มัน
Manuel Arwed Schmidt

สิ่งนี้จะส่งผลต่อประสิทธิภาพหรือไม่หากฟิลด์ที่จัดทำดัชนีแสดงเป็นอาร์เรย์
DmitriD

@All - คุณช่วยแบ่งปันรหัสเพื่อบรรลุสิ่งนี้ได้ไหม
Pra_A

8

เราได้นำสิ่งนี้ไปใช้บางส่วนบนไซต์ของเราและเราใช้ "การแก้ไขร้านค้าในเอกสารแยกต่างหาก" (และฐานข้อมูลแยกต่างหาก) เราเขียนฟังก์ชันที่กำหนดเองเพื่อส่งคืนความแตกต่างและเราจัดเก็บสิ่งนั้นไว้ไม่ยากและสามารถอนุญาตให้กู้คืนอัตโนมัติได้


2
คุณช่วยกรุณาแบ่งปันรหัสที่เหมือนกันได้ไหม แนวทางนี้มีแนวโน้มที่ดี
Pra_A

1
@smilyface - การรวม Spring Boot Javers เป็นสิ่งที่ดีที่สุดในการบรรลุเป้าหมายนี้
Pra_A

@PAA - ฉันถามคำถาม (เกือบจะเป็นแนวคิดเดียวกัน) stackoverflow.com/questions/56683389/…คุณมีข้อมูลสำหรับสิ่งนั้นหรือไม่?
smilyface

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 นี้จะบันทึกทุกค่าด้วยการประทับเวลาดังนั้นแทนที่จะเป็นค่าจะเป็นทูเปิลเสมอ (ช่องที่กำหนดเวอร์ชัน) จะบันทึกค่าใหม่ก็ต่อเมื่อมีการเปลี่ยนแปลงค่าปัจจุบันซึ่งจะช่วยประหยัดพื้นที่ในกระบวนการ ตัวอย่างเช่นไฟล์ json ฐานข้อมูล NoSQL ในอดีตนี้อาจมีลักษณะดังนี้:

{
  _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+ ขึ้นไป) มีHistoricalCollectionซึ่งเป็นส่วนขยายของวัตถุ Collection ของ pymongo

ตัวอย่างจากเอกสาร:

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.