草庐IT

mongodb - 文档DB和模拟ACID

coder 2023-11-06 原文

见最后的结果
我想使用文档数据库(出于各种原因)-可能是couchdb或mongodb。但是,我还需要acid处理多个文档事务。
但是,我确实计划使用“仅添加”模型-更改将作为新文档添加(添加是添加,更新是添加副本+转换数据,删除是添加具有相同ID+删除标志的空文档)。我将定期对数据库运行压缩以删除非当前文档。
有鉴于此,以下观点是否存在漏洞:
维护当前正在进行的事务的集合。此集合将保存具有正在进行的事务的事务ID(guid+时间戳)的文档。

Atomicity:
    On a transaction:
        Add a document to the transactions in progress collection.
        Add the new documents (add is add, update is copy+add, delete is add with ID and “deleted” flag).
            Each added document will have the following management fields:
            Transaction ID.
            Previous document ID (linked list).
        Remove the document added to the transactions in progress collection.
    On transaction fail:
        Remove all added documents
        Remove the document from the transactions in progress collection.
    Periodically:
        Go over all transaction in progress, get ones that have been abandoned (>10 minutes?), remove the associated documents in the DB (index on transaction ID) and then remove the transaction in progress.
Read transaction consistency (read only committed transactions):
    On data retrieval:
        Load transactions in progress set.
        Load needed documents.
        For all documents, if the document transaction ID is in “transactions in progress” or later (using timestamp), load the previous document in the linked list (recursive).

有点像mvcc,有点像git。我通过在开始之前成功完成的事务设置检索上下文。我通过保留“正在进行的事务”而不是“事务修订”的列表来避免单个序列(因此是单个执行)。当然,我也避免阅读未授权的事务,并提供冲突回滚。
这里面有洞吗?我的表演会受到严重影响吗?
Edit1:请不要敲打“如果您需要多文档事务,请不要使用文档数据库”。我知道,我还需要一个文档数据库。
edit2:添加了时间戳,以避免来自检索事务启动后启动的事务的数据。可能会将时间戳更改为序列ID。
edit3:这是我想到的另一个算法——它可能比上面的算法更好:
新算法-更容易理解(这次可能会更正:)
Support structures:
transaction_support_tempalte {
    _created-by-transaction: <txid>
    _made-obsolete-by-transaction: <txid>
}

transaction_record { //
    transaction_id: <txid>
    timestamp: <tx timestamp>
    updated_documents: {
        [doc1_id, doc2_id...]
    }   
}

transaction_numer { //atomic counter - used for ordering transactions.
    _id: "transaction_number"
    next_transaction_id: 0 //initial.
}

Note: all IDs are model object IDs, not DB ids (don't confuse with logical IDs which are different).
DB ID - different for each document - but multiple DB documents are revisions of one model object.
Model object ID - same for all revisions of the model object.
Logical ID - client-facing ID.


First time setup:
1. Create the transaction_number document:

Commit process:
1. Get new transaction ID by atomic increment on the transaction number counter.
2. Insert a new transaction record with the transaction id, the timestamp and the updated documents.
3. Create the new version for each document. Make sure the _created-by-transaction is set.
4. Update the old version of each updated or deleted document as 
   "_made-obsolete-by-transaction" with the transaction id.
   This is the time to detect conflicts! if seen a conflict, rollback.
   Note - this can be done as find-and-modify rather then by serializing the entire document again.
5. Remove the transaction record.

Cleanup process:
1. Go over transaction record, sorted by id, ascending (oldest transaction first).
2. For each transaction, if it expired (by timestamp), do rollback(txid).

Rollback(txid) process:
1. Get the transaction record for the given transaction id.
2. For each document id in the "updated documents":
    2.1 If the document exists and has "_made-obsolete-by-transaction" with 
        the correct transaction id, remove the _made-obsolete-by-transaction data.
3. For each document with the _created-by-transaction-id:
    3.1 remove the document.
4. Remove the transaction record document.

Retrieval process:
1. Top-transaction-id = transaction ID counter.
2. Read all transactions from the transactions collection. 
   Current-transaction-ids[] = Get all transaction IDs.
3. Retrieve documents as needed. Always use "sort by transaction_id, desc" as last sort clause.
    3.1 If a document "_created-by-transaction-id" is in the Current-transaction-ids[] 
        or is >= Top-transaction-id - ignore it (not yet committed).
    3.2 If a document "_made-obsolete-by-transaction" is not in the Current-transaction-ids[] 
        and is < Top-transaction-id - ignore it (a newer version was committed).
4. We may have to retrieve more chunks to satisfy original requests if documents were ignored.

我们开始的时候文件提交了吗?
如果我们在当前执行的事务中看到一个带有事务ID的文档-它是一个
在我们开始检索之前就开始了,但那时还没有提交-所以我们不需要它。
如果我们看到一个事务id>=top事务id的文档-它是在
我们开始了检索-所以我们不想要它。
文件是否为最新版本?
如果我们看到一个过时的文档不在当前事务ID中(事务已启动
在我们开始之前)并且是
过去有一个事务完成了commit,这使得这个文档过时了,所以我们不需要它。
为什么分类不受损害?
因为我们将排序作为最后一个子句添加,所以我们总是首先看到真正的排序工作。每一个真实的
排序“bucket”我们可能会得到多个文档,它们在不同版本上表示模型对象。
但是,模型对象之间的排序顺序保持不变。
为什么计数器不使事务按顺序执行(一次一个)?
因为这不是rdbms-我们没有真正的事务,所以我们不等待事务
像我们在“选择更新”中所做的那样提交。
一旦我们完成另一个事务,它就可以进行原子更改。
压实度:
每隔一段时间就必须进行压缩—获取所有真正旧的文档并将它们删除到另一个数据存储区。
这不应影响任何正在运行的检索或事务。
优化:
将条件放入查询本身。
将事务ID添加到所有索引。
确保具有相同模型对象id的文档不会被切分到不同的节点。
费用是多少?
假设我们需要多个文档版本用于历史记录和审核,那么额外的成本是
原子更新计数器,创建事务记录,“密封”每个模型对象的以前版本
(标记为已过时)并删除交易文档。这个不应该太大。
请注意,如果上述假设无效,则额外成本相当高,尤其是对于检索而言。
结果:
我已经实现了上面的算法(修改后的算法有一些小改动)。从功能上说,它起作用了。但是,性能(至少在主从复制拓扑中有3个节点的MongoDB上,没有fsync,但是在“提交”结束之前需要复制)是非常糟糕的。我一直在读我刚从不同的线程写的东西。我得到了事务集合上的常量集合锁,我的索引无法跟上常量滚动更新。对于带有10个feeder线程的微小事务,性能上限为20 tps。
简而言之-不是一个好的通用解决方案。

最佳答案

在不详细介绍您的计划的情况下,我认为首先讨论一下MongoDB对ACID需求的支持可能是有用的。
原子性:mongo支持对单个文档进行原子更改。通常,最重要的原子操作是“$set”和find,并在mongodb中修改关于这些操作和原子性的一些文档:

http://www.mongodb.org/display/DOCS/Atomic+Operations
[http://www.mongodb.org/display/DOCS/Updating#Updating-%24set][1]
http://www.mongodb.org/display/DOCS/findAndModify+Command

一致性:难以实现且相当复杂。我不想在这篇文章中总结,但是有一系列关于这个主题的文章:
http://blog.mongodb.org/post/475279604/on-distributed-consistency-part-1
[http://blog.mongodb.org/post/498145601/on-distributed-consistency-part-2-some-eventual][2]

隔离:MongoDB中的隔离对于文档是存在的,但对于任何更高级别的文档都不存在。同样,这是一个复杂的主题;除了上面的原子操作链接之外,我找到的最佳资源是以下堆栈溢出线程:
Why doesn't MongoDB use fsync()?(对于这个主题来说,尽管一些关于耐久性的信息已经过时,但总的来说,最重要的答案还是有点像金矿)
持久性:用户确保数据持久性的主要方法是使用getlasterror命令(有关更多信息,请参见下面的链接)来确认副本集中的大多数节点在调用返回之前已经写入了数据。
http://www.mongodb.org/display/DOCS/getLastError+Command#getLastErrorCommand-majority 
http://docs.mongodb.org/manual/core/replication-internals/ (linked to in the above document)

了解了mongo中acid的所有这些知识,查看一些在mongo中已经解决的类似问题的示例将非常有用。我希望下面的两个链接对您非常有用,因为它们是非常完整和正确的主题。
Two-Phase Commits: http://cookbook.mongodb.org/patterns/perform-two-phase-commits/

Transactions for e-commerce work: http://www.slideshare.net/spf13/mongodb-ecommerce-and-transactions-10524960

最后,我不得不问:你为什么想要交易?MongoDB的用户很少发现他们真正需要ACID来实现他们的目标。在您继续在mongo上实现一个完整的层以获取事务之前,可能值得退后一步,尝试从另一个角度来处理这个问题。

关于mongodb - 文档DB和模拟ACID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12375289/

有关mongodb - 文档DB和模拟ACID的更多相关文章

  1. ruby-on-rails - Railstutorial : db:populate vs. 工厂女孩 - 2

    在railstutorial中,作者为什么选择使用这个(代码list10.25):http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-usersnamespace:dbdodesc"Filldatabasewithsampledata"task:populate=>:environmentdoRake::Task['db:reset'].invokeUser.create!(:name=>"ExampleUser",:email=>"example@railstutorial.org",:passwo

  2. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  3. Matlab imread()读到了什么 (浅显 当复习文档了) - 2

    matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1

  4. ruby-on-rails - 在这种情况下我如何模拟一个对象?没有明显的方法可以用模拟替换对象 - 2

    假设我在Store的模型中有这个非常简单的方法:defgeocode_addressloc=Store.geocode(address)self.lat=loc.latself.lng=loc.lngend如果我想编写一些不受地理编码服务影响的测试脚本,这些脚本可能已关闭、有限制或取决于我的互联网连接,我该如何模拟地理编码服务?如果我可以将地理编码对象传递到该方法中,那将很容易,但我不知道在这种情况下该怎么做。谢谢!特里斯坦 最佳答案 使用内置模拟和stub的rspecs,你可以做这样的事情:setupdo@subject=MyCl

  5. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  6. ruby - 在 RSpec 中 stub /模拟全局常量 - 2

    我有一个gem,它有一个根据Rails.env的不同行为的方法:defself.envifdefined?(Rails)Rails.envelsif...现在我想编写一个规范来测试这个代码路径。目前我是这样做的:Kernel.const_set(:Rails,nil)Rails.should_receive(:env).and_return('production')...没关系,只是感觉很丑。另一种方法是在spec_helper中声明:moduleRails;end而且效果也很好。但也许有更好的方法?理想情况下,这应该有效:rails=double('Rails')rails.sho

  7. Ruby 等同于 Sphinx 文档生成器? - 2

    Ruby有一些不错的文档生成器,例如Yard、rDoc,甚至Glyph。问题是Sphinx可以做网站、PDF、epub、LaTex等。它在重组文本中完成所有这些事情。在Ruby世界中有替​​代方案吗?也许是程序的组合?如果我也能使用Markdown就更好了。 最佳答案 自1.0版以来,Sphinx有了“域”的概念,它是从Python和/或C以外的语言标记代码实体(如方法调用、对象、函数等)的方法。有一个rubydomain,所以你可以只使用Sphinx本身。您唯一会缺少的(我认为)是Sphinx使用autodoc从源代码自动创建文档

  8. ruby-on-rails - 撤消 "rails generate scaffold"后是否需要撤消 "db:migrate"? - 2

    我是RoR的新手,我正在学习MichaelHartl的教程(所以请随意更正我在您认为合适的地方使用的术语)。在第2章中,我通过运行以下行创建了一个Users表:$railsgeneratescaffoldUsername:stringemail:string$bundleexecrakedb:migrate然后,我运行了下面的代码来尝试创建一个Microposts表(但是,我拼错了没有“r”的Micropost!)...$railsgeneratescaffoldMiropostcontent:stringuser_id:integer$bundleexecrakedb:migrate

  9. ruby-on-rails - rspec 模拟对象属性赋值 - 2

    我有一个rspec模拟对象,一个值赋给了属性。我正在努力在我的rspec测试中满足这种期望。只是想知道语法是什么?代码:defcreate@new_campaign=AdCampaign.new(params[:new_campaign])@new_campaign.creationDate="#{Time.now.year}/#{Time.now.mon}/#{Time.now.day}"if@new_campaign.saveflash[:status]="Success"elseflash[:status]="Failed"endend测试it"shouldabletocreat

  10. ruby - 如何使用 rspec stub /模拟对命令行的调用? - 2

    我正在尝试测试命令行工具的输出。如何使用rspec来“伪造”命令行调用?执行以下操作不起作用:it"shouldcallthecommandlineandreturn'text'"do@p=Pig.new@p.should_receive(:run).with('my_command_line_tool_call').and_return('resulttext')end如何创建stub? 最佳答案 使用newmessageexpectationsyntax:规范/虚拟规范.rbrequire"dummy"describeDummy

随机推荐