首页 文章

使用git repository作为数据库后端

提问于
浏览
99

我正在做一个处理结构化文档数据库的项目 . 我有一个类别树(约1000个类别,每个级别最多约50个类别),每个类别包含数千个(最多,比如说,~10000)结构化文档 . 每个文档都是几千字节的数据,采用某种结构化形式(我更喜欢YAML,但它也可能是JSON或XML) .

该系统的用户可以进行多种操作:

  • 按ID检索这些文件

  • 通过其中的一些结构化属性搜索文档

  • 编辑文件(即添加/删除/重命名/合并);每个编辑操作都应记录为具有一些注释的事务

  • 查看特定文档记录更改的历史记录(包括查看更改文档的人员,时间和原因,获取更早版本 - 如果需要,可能还原为此版本)

当然,传统的解决方案是使用某种文档数据库(例如CouchDB或Mongo)来解决这个问题 - 然而,这个版本控制(历史)的东西诱惑我一个疯狂的想法 - 为什么我不应该使用 git 存储库作为这个应用程序的数据库后端?

乍一看,它可以像这样解决:

  • category = directory,document = file

  • 通过ID获取文档=>更改目录读取工作副本中的文件

  • 使用编辑注释编辑文档=>由存储提交消息的各种用户进行提交

  • history =>正常的git日志和旧事务的检索

  • search => 's a slightly trickier part, I guess it would require periodic export of a category into relational database with indexing of columns that we' ll允许搜索

这个解决方案还有其他常见的陷阱吗?有没有人试图实现这样的后端(即任何流行的框架--RoR,node.js,Django,CakePHP)?这个解决方案是否会对性能或可靠性产生任何影响 - 即它是否证明git比传统数据库解决方案慢得多,或者存在任何可扩展性/可靠性缺陷?我认为推送/拉取彼此存储库的这类服务器集群应该相当强大和可靠 .

基本上,请告诉我这个解决方案是否有效以及为什么会这样做?

5 回答

  • 12

    我的2便士 Value . 有点渴望,但......我的一个孵化项目有类似的要求 . 与你的类似,我的关键要求是文档数据库(在我的情况下为xml),文档版本控制 . 它适用于具有大量协作用例的多用户系统 . 我倾向于使用支持大多数关键要求的可用开源解决方案 .

    为了减少追逐,我找不到任何提供两者的产品,其方式足够可扩展(用户数量,使用量,存储和计算资源) . 我偏向于所有有前途的功能git,并且(可能的)解决方案可以制造出来 . 随着我更多地使用git选项,从单用户角度转向多(毫)用户角度成为一个明显的挑战 . 不幸的是,我没有像你那样做大量的性能分析 . (..懒惰/退出早......对于版本2,口头禅)给你力量!无论如何,我的偏见已经转变为下一个(仍有偏见的)替代方案:在各自的领域,数据库和版本控制中最好的工具网格化 .

    虽然仍在进行中(......并且稍微忽略了),但变形版本就是这样 .

    前端

    • :( userfacing)使用数据库进行第一级存储(与用户应用程序连接)

    • 在后端,使用版本控制系统(VCS)(如git)在数据库中执行数据对象的版本控制

    从本质上讲,它相当于向数据库添加一个版本控制插件,使用一些集成胶水,您可能需要开发,但可能要容易得多 .

    它(应该)如何工作是主要的多用户界面数据交换是通过数据库 . DBMS将处理所有有趣和复杂的问题,如多用户,并发e,原子操作等 . 在后端,VCS将对一组数据对象执行版本控制(无并发或多用户问题) . 对于数据库上的每个有效事务,仅对已经有效更改的数据记录执行版本控制 .

    对于接口胶,它将采用数据库和VCS之间简单的互通功能的形式 . 在设计方面,简单的方法就是一个事件驱动的接口,数据库中的数据更新触发版本控制程序(提示:假设Mysql, use of triggers and sys_exec()等等......) . 在实现复杂性方面,它将从简单的范围并且有效(例如编写脚本)复杂而精彩(一些编程的连接器接口) . 一切都取决于你想要用它多疯狂,以及你愿意花多少汗水资金 . 我认为简单的脚本应该这样做魔法 . 并且为了访问最终结果,各种数据版本,一个简单的替代方案是使用VCS中版本标记/ id / hash引用的数据填充数据库的克隆(更多数据库结构的克隆) . 再次,这个位将是一个简单的查询/翻译/ Map 作业的接口 .

    仍然存在一些挑战和未知因素需要处理,但我认为其中大部分的影响和相关性在很大程度上取决于您的应用程序需求和用例 . 有些人可能最终会成为非问题 . 一些问题包括2个关键模块,数据库和VCS之间的性能匹配,用于具有高频数据更新活动的应用程序,git端的资源(存储和处理能力)随时间的缩放作为数据,以及用户成长:稳定,指数或最终高原

    在上面的鸡尾酒中,这是我正在酝酿的

    • 使用Git作为VCS(由于仅使用2个版本之间的更改集或增量,最初被认为是好的旧CVS)

    • 使用mysql(由于我的数据具有高度结构化的特性,xml具有严格的xml模式)

    • 使用MongoDB进行操作(尝试使用与git中使用的本机数据库结构非常匹配的NoSQl数据库)

    一些有趣的事实 - git实际上确实可以清楚地优化存储,例如压缩和仅存储对象修订之间的增量 - 是的,git只存储数据对象修订版之间的变更集或增量,它在哪里适用(它知道何时以及如何) . 参考:packfiles,深入guts of Git internals - 回顾git的对象存储(内容可寻址文件系统),显示与非数据库mongoDB相似的(从概念角度来看) . 同样,以汗水资本为代价,它可能为整合2和性能调整提供更有趣的可能性

    如果你到目前为止,如果以上内容可能适用于你的情况,并且假设它会是,那么它将如何与你上一次综合性能分析中的某些方面相提并论

  • 13

    正如您所提到的,多用户案例处理起来有点棘手 . 一种可能的解决方案是使用特定于用户的Git索引文件

    • 不需要单独的工作副本(磁盘使用仅限于更改的文件)

    • 无需耗时的准备工作(每个用户会话)

    诀窍是将Git的GIT_INDEX_FILE环境变量与手动创建Git提交的工具结合起来:

    下面是解决方案概述(命令中省略了实际的SHA1哈希值):

    # Initialize the index
    # N.B. Use the commit hash since refs might changed during the session.
    $ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>
    
    #
    # Change data and save it to `changed_file`
    #
    
    # Save changed data to the Git object database. Returns a SHA1 hash to the blob.
    $ cat changed_file | git hash-object -t blob -w --stdin
    da39a3ee5e6b4b0d3255bfef95601890afd80709
    
    # Add the changed file (using the object hash) to the user-specific index
    # N.B. When adding new files, --add is required
    $ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file
    
    # Write the index to the object db. Returns a SHA1 hash to the tree object
    $ GIT_INDEX_FILE=user_index_file git write-tree
    8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53
    
    # Create a commit from the tree. Returns a SHA1 hash to the commit object
    # N.B. Parent commit should the same commit as in the first phase.
    $ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
    3f8c225835e64314f5da40e6a568ff894886b952
    
    # Create a ref to the new commit
    git update-ref refs/heads/users/user_x_change_y <new_commit_hash>
    

    根据您的数据,您可以使用cron作业将新refs合并到 master ,但冲突解决方案可以说是最困难的部分 .

    欢迎提出更容易的想法 .

  • 2

    回答我自己的问题不是最好的办法,但是,当我最终放弃这个想法时,我想分享一下在我的案例中有用的理由 . 我想强调的是,这个理由可能并不适用于所有情况,因此需要由架构师来决定 .

    一般来说,我的问题遗漏的第一个要点是我正在处理多用户系统并行工作,同时使用我的服务器和瘦客户端(即只是一个Web浏览器) . 这样,我必须维持所有这些状态 . 有几种方法可以解决这个问题,但是所有这些方法要么资源太难,要么实现太复杂(因此最终会破坏将所有硬实现内容卸载到git的最初目的):

    • “Blunt”方法:1 user = 1 state =服务器为用户维护的存储库的完整工作副本 . 即使我们正在讨论具有~100K用户的相当小的文档数据库(例如,100s MiB),为所有这些用户维护完整的存储库克隆也会使磁盘使用在屋顶上运行(即100K用户乘以100MiB~10 TiB) . 更糟糕的是,每次克隆100 MiB存储库需要几秒钟的时间,即使是在相当有效的maneer(即不使用git和拆包重新打包的东西)中完成的,这是不可接受的,IMO . 更糟糕的是 - 我们应用于主树的每个编辑都应该被提取到每个用户的存储库,这是(1)资源占用,(2)在一般情况下可能导致未解决的编辑冲突 .

    基本上,就光盘使用而言,它可能与O(编辑数量×数据×用户数量)一样糟糕,并且这种光盘使用自动意味着相当高的CPU使用率 .

    • “仅活动用户”方法:仅为活动用户维护工作副本 . 这样,您通常不会存储每个用户的完全repo-clone,但是:

    • 当用户登录时,您将克隆存储库 . 每个活动用户需要几秒钟和大约100 MiB的磁盘空间 .

    • 当用户继续在网站上工作时,他使用给定的工作副本 .

    • 当用户注销时,他的存储库克隆将作为分支复制回主存储库只存储他的"unapplied changes",如果有的话,这是相当节省空间的 .

    因此,在这种情况下,磁盘使用率达到峰值O(编辑数量×数据×活跃用户数量),这通常比总用户数少~100..1000倍,但它使登录/退出更复杂,更慢,因为它涉及在每次登录时克隆每个用户的分支,并在注销或会话到期时将这些更改拉回来(这应该在事务上完成=>增加了另一层复杂性) . 在绝对数字中,在我的情况下,它会将10 TiB的光盘使用量降低到10..100 GiB,这可能是可以接受的,但是,我们现在再次讨论相当小的100 MiB数据库 .

    • “稀疏结账”方法:对每个活跃用户进行“稀疏结账”而不是完整的回购克隆并没有多大帮助 . 它可以节省大约10倍的磁盘空间使用量,但是在历史参与操作中以更高的CPU /磁盘负载为代价,这种目的是什么 .

    • “ Worker 池”方法:我们不是每次为活跃的人做完全克隆,而是保留一堆“ Worker ”克隆,随时可以使用 . 这样,每次用户登录时,他都会占用一个“ Worker ”,从主要仓库拉出他的分支,并且当他退出时,他释放了“ Worker ”,这使得聪明的git硬重置再次变得刚好一个主要的repo克隆,准备好由另一个用户登录使用 . 对光盘使用没有多大帮助(它仍然很高 - 每个活动用户只有完全克隆),但至少它会使登录/退出更快,因为更复杂 .

    也就是说,请注意我有意计算了相当小的数据库和用户群的数量:100K用户,1K活跃用户,100 MiBs总数据库编辑历史,10 MiB的工作副本 . 如果你看一下更为突出的众包项目,那里有更高的数字:

    │              │ Users │ Active users │ DB+edits │ DB only │
    ├──────────────┼───────┼──────────────┼──────────┼─────────┤
    │ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
    │ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
    │ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │
    

    显然,对于那些数据/活动,这种方法是完全不可接受的 .

    一般情况下,如果可以使用Web浏览器作为“厚”客户端,即发出git操作并在客户端存储几乎完整的结账,而不是在服务器端,它就会工作 .

    我还错过了其他一些观点,但与第一点相比,它们并没有那么糟糕:

    • 具有"thick"用户编辑状态的模式在正常ORM方面存在争议,例如ActiveRecord,Hibernate,DataMapper,Tower等 .

    • 尽管我没有现成的免费代码库来从流行的框架中实现git的这种方法 .

    • 至少有一种服务以某种方式设法有效地做到这一点 - 显然github - 但是,唉,他们的代码库是封闭的来源,我强烈怀疑他们内部不使用普通的git服务器/ repo存储技术,即他们基本上实施替代"big data" git .

    所以, bottom line :这是可能的,但对于大多数当前的用例来说,它不会接近最佳解决方案 . 汇总您自己的文档编辑历史到SQL实现或尝试使用任何现有文档数据库可能是更好的选择 .

  • 47

    一个有趣的方法确实 . 我想说,如果你需要存储数据,请使用数据库,而不是源代码存储库,它是专为特定任务而设计的 . 如果您可以使用Git开箱即用,那么它是您感兴趣的内置版本控件,为什么不使用open source document repository tools?有很多可供选择 .

    好吧,如果你决定去Git后端,那么如果你按照描述实现它,基本上它可以满足你的要求 . 但:

    1)你提到“服务器集群互相推/拉” - 我已经考虑了一段时间,但我仍然不确定 . 你不能推/拉几个repos作为原子操作 . 我想知道在并发工作中是否会出现一些合并混乱的可能性 .

    2)也许您不需要它,但是您未列出的文档存储库的明显功能是访问控制 . 您可以通过子模块限制对某些路径(=类别)的访问,但可能您无法轻松地在文档级别上授予访问权限 .

  • 2

    我在 libgit2 之上实现了Ruby library,这使得它很容易实现和探索 . 有一些明显的限制,但它也是一个相当自由的系统,因为你得到了完整的git工具链 .

    该文档包括一些关于性能,权衡等的想法 .

相关问题