使用git仓库作为数据库后端
我正在做一个处理结构化文档数据库的项目。 我有一个类别树(〜1000个类别,每个级别最多〜50个类别),每个类别包含数千个(最多约10000个)结构化文档。 每个文档都是几千字节的数据(我更喜欢YAML,但也可能是JSON或XML)。
这个系统的用户执行几种types的操作:
- 通过ID检索这些文件
- 通过内部的一些结构化属性来search文档
- 编辑文档(即添加/删除/重命名/合并); 每个编辑操作都应该logging下来,并附带一些注释
- 查看logging的特定文档更改的历史logging(包括查看谁更改了文档,何时更改以及为什么更改文档,获取更早的版本 – 如果需要,可能还原为这一版本)
当然,传统的解决scheme是使用某种文档数据库(比如CouchDB或者Mongo)来解决这个问题 – 然而,这个版本控制(历史)的东西引起了我一个疯狂的想法 – 为什么我不应该使用git
仓库这个应用程序的数据库后端?
乍一看,可以这样解决:
- category =目录,document =文件
- 通过ID获取文档=>更改目录+读取工作副本中的文件
- 用编辑注释编辑文档=>由各种用户进行提交+存储提交消息
- 历史=>正常的git日志和旧的交易的检索
- search=>这是一个稍微棘手的部分,我想这将需要一个类别定期导出到关系数据库索引的列,我们将允许search
这个解决scheme还有其他常见的缺陷吗? 有没有人试图实现这样的后端(即任何stream行的框架 – RoR,node.js,Django,CakePHP)? 这个解决scheme是否对性能或可靠性有任何可能的影响 – 也就是说,git会比传统的数据库解决scheme慢得多,否则就会有可扩展性/可靠性的缺陷? 我认为,推送/拉对方存储库的这种服务器集群应该相当健壮和可靠。
基本上,告诉我, 如果这个解决scheme将工作, 为什么会或不会做?
回答我自己的问题并不是最好的做法,但是,当我最终放弃了这个想法时,我想分享一下我的理论。 我想强调的是,这个基本原理可能不适用于所有情况,所以由build筑师决定。
一般来说,我的问题的第一个要点是,我正在处理并行工作的多用户系统 ,同时使用我的服务器与瘦客户端(即只是一个Web浏览器)。 这样,我必须为所有人维护国家 。 这个方法有好几种方法,但是所有的方法都是资源太难或者实现太复杂了(这样就把原来把所有难以实现的东西卸载到原来的东西的原始目的):
-
“Blunt”方法:1个用户= 1个状态= 1个服务器为用户维护的存储库的完整工作副本。 即使我们谈论的是拥有大约100K用户的相当小的文档数据库(例如100个MiB),为它们全部维护完整的存储库克隆也会使得磁盘使用率达到顶峰(即100K用户100MiB〜10TiB) 。 甚至更糟糕的是,每次克隆100 MiB存储库需要花费几秒钟的时间,即使在相当有效的maneer(即不使用git和拆包 – 重新打包的东西)中完成,这是不可接受的,IMO。 更糟糕的是,我们应用到主树上的每一个编辑都应该被拉到每个用户的存储库,这是(1)资源浪费,(2)在一般情况下可能导致无法解决的编辑冲突。
基本上,就光盘使用而言,它可能与O(编辑数量×数据量×用户数量)一样差,而这样的光盘使用率自动意味着相当高的CPU使用率。
-
“只有活动用户”的方法:只为活动用户维护工作副本。 这样,您通常不会存储每个用户的完整回购克隆,但是:
- 在用户login时,您可以克隆存储库。 每个活动用户需要几秒钟的时间和大约100Mb的磁盘空间。
- 当用户继续在网站上工作时,他使用给定的工作副本。
- 在用户注销时,他的存储库克隆将作为分支被复制回主存储库,因此只存储他的“未应用的更改”(如果有的话),这是相当节省空间的。
因此,在这种情况下,光盘使用量在O(编辑×数据×活动用户数量)达到峰值,这通常是总用户数量的100-1000倍,但是使login/退出更加复杂和更慢,因为它涉及在每次login时克隆每个用户分支,并在退出或会话到期时(这应该以事务方式完成=>增加了另一层复杂性)拉回这些更改。 从绝对数字来看,在我的情况下,它将10个TiB的光盘使用量降低到10..100 GiBs,这可能是可以接受的,但是现在我们又谈到一个相当小的100 MiB的数据库。
-
“稀疏结帐”方法:使“稀疏结帐”,而不是每个活跃用户的全面回购克隆没有什么帮助。 它可以节省约10倍的磁盘空间使用量,但是牺牲历史涉及的操作的CPU /磁盘负载高得多,这种杀死目的。
-
“工人池”的方法:不是每次为活跃的人做完整的克隆,我们可能会留下一堆“工人”克隆,随时可以使用。 这样,每当用户login时,他就占用一个“工人”,从主回购站拉他的分支,当他注销时,他释放了“工人”,这聪明的git硬重置成为又一次一个主要的repo克隆,准备被另一个用户login使用。对光盘的使用没有什么帮助(它仍然非常高 – 每个活动用户只有完整的克隆),但是至less它使login/登出速度更快,甚至更复杂。
这就是说,我注意到我有意计算了一些相当小的数据库和用户群:100K用户,1K活跃用户,100 MiBs总数据库+编辑历史,10 MiBs的工作副本。 如果你看看更为重要的众包项目,那里有更多的人数:
│ │ 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 │
显然,对于这些数据/活动来说,这种做法是完全不可接受的。
一般来说,如果可以使用networking浏览器作为“厚”客户端,即发出git操作,并在客户端而不是在服务器端存储几乎全部的结帐,那么它一直工作。
还有其他一些我错过的点,但是和第一点相比,并没有那么糟糕:
- 具有“厚”用户编辑状态的模式在正常ORM方面是有争议的,比如ActiveRecord,Hibernate,DataMapper,Tower等。
- 就像我search的一样,没有现成的免费代码来做这种方法来从stream行的框架git。
- 至less有一个服务可以有效地做到这一点 – 这显然是github – 但是,唉,他们的代码库是封闭的源码,我强烈怀疑他们不使用正常的git servers / repo存储技术,即他们基本上实现替代“大数据”混帐。
所以, 底线 :这是可能的,但对于大多数目前的使用情况,它不会在任何地方接近最佳的解决scheme。 将自己的文档编辑历史转换为SQL实现或尝试使用任何现有的文档数据库可能是更好的select。
确实有趣的方法。 我会说,如果你需要存储数据,使用一个数据库,而不是一个源代码库,这是专门为一个非常具体的任务而devise的。 如果你可以使用Git开箱即用,那么很好,但你可能需要在它上面build立一个文档库。 所以你可以把它build立在传统的数据库上,对吗? 如果您感兴趣的是内置的版本控制,为什么不使用开源文档存储库工具之一呢? 有很多select。
那么,如果你决定继续使用Git后端,那么基本上它将适用于你的要求,如果你按照描述实现它的话。 但:
1)你提到了“推/拉对方的服务器集群” – 我想了一会儿,但我还不确定。 作为一个primefaces操作你不能推/拉几个回购。 我想知道在并行工作中是否会有一些合并混乱的可能性。
2)也许你不需要它,但是你没有列出的一个明显的文档库function是访问控制。 您可以通过子模块限制对某些path(=类别)的访问,但可能无法轻松地在文档级别授予访问权限。
我的2便士值得。 有点渴望,但是……在我的一个孵化项目中,我有类似的要求。 与你的类似,我的关键要求是文档数据库(在我的情况下是xml)和文档版本。 这是一个有很多协作用例的多用户系统。 我的首选是使用支持大多数关键需求的可用开源解决scheme。
为了削减成本,我找不到任何一种产品都能够提供足够的可扩展性(用户数,使用量,存储和计算资源)。我偏向于所有有前途的function的git, (可能的)解决scheme。 当我玩弄git选项时,从单一的用户angular度转到多(毫)用户的angular度成为一个明显的挑战。 不幸的是,我没有像你那样做实质性的分析。 (..懒惰/退出早期….版本2,口头禅)力量给你! 无论如何,我偏见的想法已经演变成下一个(仍然是有偏见的)select:在各自的领域,数据库和版本控制中最好的工具的网格化。
虽然仍在进行中(…并略有忽略)变形版本就是这样。
- 在前端:(userfacing)使用一级数据库(与用户应用程序接口)
- 在后端,使用版本控制系统(VCS)(如git)来执行数据库中数据对象的版本控制
实际上,这相当于将一个版本控制插件添加到数据库中,使用一些集成粘合剂,您可能需要开发这些插件,但是可能要简单得多。
它应该如何工作,主要的多用户界面数据交换是通过数据库。 DBMS将处理所有有趣和复杂的问题,例如多用户,并发e,primefaces操作等。在后端,VCS将对一组数据对象(没有并发或多用户问题)执行版本控制。 对于数据库上的每个有效事务,版本控制仅在有效更改的数据logging上执行。
对于接口连接,它将以数据库和VCS之间的简单互通function的forms出现。 在devise方面,简单的方法将是一个事件驱动的接口,从数据库更新数据触发版本控制程序(提示:假设Mysql,使用触发器和sys_exec()等等等等)。在实现方面复杂性,范围从简单而有效(例如脚本)到复杂和美妙(一些程序化的连接器接口)。 所有这一切取决于你想要如何发疯,以及你愿意花费多less汗水。 我认为简单的脚本应该做的魔术。 而要访问最终结果,各种数据版本,一个简单的select是用VCS中版本标记/ id / hash引用的数据来填充数据库的克隆(更多的是数据库结构的克隆)。 这一点将是一个简单的查询/翻译/地图作业的接口。
还有一些挑战和未知的问题需要处理,但是我认为这些影响和相关性大部分取决于你的应用需求和用例。 有些人可能最终不是问题。 其中一些问题包括2个关键模块,数据库和VCS之间的性能匹配,用于具有高频率数据更新活动的应用程序,随着时间在git端的资源(存储和处理能力)作为数据缩放,以及用户成长:稳定,指数或最终高原
上面的鸡尾酒,这是我目前正在酝酿
- 使用Git的VCS(由于只使用2版本之间的变更集或变化量,最初被认为是旧的CVS)
- 使用MySQL(由于我的数据高度结构化的性质,具有严格的XML模式的XML)
- 玩弄MongoDB(尝试一个NoSQl数据库,它与git中使用的本地数据库结构非常匹配)
一些有趣的事实 – git实际上确实清除了优化存储的东西,比如压缩,以及在对象修订之间只存储增量值 – 是的,git只存储数据对象修订之间的变更集或变化量,它在哪里适用何时以及如何)。 参考: Git内部深处的packfiles – 对git的对象存储(内容可寻址的文件系统)的回顾,在noSQL数据库(如mongoDB)中展示了非常类似的概念(从概念的angular度来看)。 再次,以牺牲汗水为代价,它可能会提供更有趣的可能性来整合2和性能调整
如果你有这么多的话,让我想想看,如果上面的情况可能适用于你的情况,并且假设情况是这样,那么在你上一次的综合performance分析
正如你所提到的,多用户案例有点棘手。 一个可能的解决scheme是使用用户特定的Git索引文件
- 不需要单独的工作副本(磁盘使用仅限于更改的文件)
- 不需要耗时的准备工作(每个用户会话)
诀窍是将Git的GIT_INDEX_FILE
环境variables与工具手动合并来创buildGit提交:
- git散列对象
- git update-index
- git写入树
- git commit-tree
下面是一个解决scheme概要(命令中省略了实际的SHA1散列):
# Initialize the index # NB 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 # NB 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 # NB 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作业将新的参考文件合并到master
但冲突解决无疑是最难的部分。
想法使其更容易,是受欢迎的。
我在libgit2
之上实现了一个Ruby库 ,这使得这个实现和探索变得非常容易。 有一些明显的限制,但它也是一个相当自由的系统,因为你得到完整的Git工具链。
文档包括一些有关性能,折衷等的想法。