我正在做一个处理 struct 化文档数据库的项目.我有一个类别树(大约1,000个类别,每个级别最多50个类别),每个类别包含数千个(比方说,最多10000个) struct 化文档.每个文档都是一些 struct 化形式的几千字节的数据(我更喜欢YAML,但也可以是JSON或XML).

此系统的用户执行几种类型的操作:

  • 按ID检索这些文档
  • 通过文档中的某些 struct 化属性搜索文档
  • 编辑文档(即添加/删除/重命名/合并);每个编辑操作都应记录为带有注释的事务
  • 查看特定文档记录的更改历史记录(包括查看更改文档的人、时间和原因,获取早期版本,如果需要,可能会恢复到此版本)

当然,传统的解决方案是使用某种类型的文档数据库(如CouchDB或Mongo)来解决这个问题-然而,这种版本控制(历史)的事情让我产生了一个疯狂的 idea -为什么我不应该使用git存储库作为该应用程序的数据库后端呢?

乍一看,可以这样解决:

  • 类别=目录,文档=文件
  • getting document by ID => changing directories + reading a file in a working copy
  • editing documents with edit comments => making commits by various users + storing commit messages
  • history => normal git log and retrieval of older transactions
  • search => that'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 allow to search by

此解决方案中是否还有其他常见的trap ?有没有人已经try 过实现这样的后端(比如,对于任何流行的框架--RoR、node.js、Django、CakePHP)?此解决方案是否对性能或可靠性有任何可能的影响-即,是否已证明git将比传统数据库解决方案慢得多,或者会存在任何可伸缩性/可靠性缺陷?我假设这样一群相互推送/拉入对方存储库的服务器应该是相当健壮和可靠的.

基本上,告诉我if这个解决方案有效,why这个解决方案有效还是无效?

推荐答案

回答我自己的问题不是最好的做法,但是,当我最终放弃这个 idea 时,我想分享一下在我的 case 中起作用的理由.我想强调的是,这一基本原理可能并不适用于所有情况,因此应由架构师决定.

一般来说,我的问题忽略的第一个要点是,我正在处理multi-user system个并行、并行工作的问题,使用我的服务器和瘦客户机(即仅一个web浏览器).这样的话,我必须为所有的人保持state.有几种方法可以实现这一点,但它们要么对资源要求太高,要么太复杂而无法实现(因此,首先要把所有难以实现的东西都转移到git上,这有点扼杀了最初的目的):

  • "钝"方法:1个用户=1个状态=1个服务器为用户维护的存储库的完整工作副本.即使我们谈论的是相当小的文档数据库(例如,100s MIB),用户数量约为100K,为所有用户维护完整的存储库克隆也会使光盘的使用量激增(即100K用户乘以100MB~10TIB).更糟糕的是,每次克隆100个MiB存储库都需要几秒钟的时间,即使是以相当有效的方式完成(即git不使用和解包重新打包的内容),这是不可接受的,我认为.更糟糕的是,我们应用于主树的每一次编辑都应该被拉到每个用户的存储库,这是(1)资源占用,(2)在一般情况下可能会导致未解决的编辑冲突.

    Basically, it might be as bad as O(number of edits × data × number of users) in terms of disc usage, and such disc usage automatically means pretty high CPU usage.

  • "仅限活动用户"方法:仅为活动用户维护工作副本.这样,您通常不会按用户存储完整的repo-clone-,而是:

    • 用户登录时,可以克隆存储库.每个活动用户需要几秒钟和大约100 MiB的磁盘空间.
    • 当用户继续在站点上工作时,他将使用给定的工作副本.
    • 当用户注销时,他的存储库克隆会作为一个分支复制回主存储库,因此只存储他的"未应用的更改"(如果有),这相当节省空间.

    Thus, disc usage in this case peaks at O(number of edits × data × number of active users), which is usually ~100..1000 times less than number of total users, but it makes logging in/out more complicated and slower, as it involves cloning of a per-user branch on every login and pulling these changes back on logout or session expiration (which should be done transactionally => adds another layer of complexity). In absolute numbers, it drops 10 TiBs of disc usage down to 10..100 GiBs in my case, that might be acceptable, but, yet again, we're now talking about fairly small database of 100 MiBs.

  • "稀疏签出"方法:对每个活动用户进行"稀疏签出"而不是全面的回购克隆并没有多大帮助.它可能会节省约10倍的磁盘空间使用,但代价是在涉及操作的历史记录上,CPU/磁盘的负载要高得多,这就扼杀了它的用途.

  • "工作人员池"方法:我们可能会保留一个"工作人员"克隆池,以备使用,而不是每次都为活动人员进行全面克隆.这样,每次用户登录时,他都会占用一个"worker",将其分支从主repo拉到那里,然后在他注销时释放"worker",这会使git进行巧妙的硬重置,再次成为一个主repo克隆,供另一个登录用户使用.这对光盘使用没有多大帮助(它仍然很高——每个活动用户只能进行完整克隆),但至少它可以加快登录/注销速度,从而降低复杂性.

也就是说,请注意,我特意计算了相当小的数据库和用户群的数量:10万个用户,1万个活动用户,100个MIB总数据库+编辑历史,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 │

显然,对于如此大量的数据/活动,这种方法是完全不可接受的.

一般来说,如果你能把网络浏览器当作一个"胖"客户端使用,也就是说,发出git操作并在客户端(而不是在服务器端)存储几乎全部的 checkout 记录,那么这种方法是可行的.

我还漏掉了其他几点,但与第一点相比,它们并没有那么糟糕:

  • 就普通ORM(如ActiveRecord、Hibernate、DataMapper、Tower等)而言,具有"密集"用户编辑状态的模式本身是有争议的.
  • 在我搜索的范围内,现有的免费代码库是零,可以从流行的框架中使用这种方法来实现git.
  • 至少有一个服务能以某种方式高效地做到这一点--显然是github个--但是,唉,他们的代码库是封闭源代码的,我强烈怀疑他们没有在内部使用普通的git服务器/repo存储技术,也就是说,他们基本上实现了替代的" Big Data "git.

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

Database相关问答推荐

如何避免在模式更改时重新同步微服务数据库之间的整个表?

如何在同一个表的派生部分引用主键?

如何在保持相同 Flyway 校验和的同时更正语法?

一个强大的 MySQL 管理工具,具有与 SQL Server Management Studio 类似的功能

数据库设计 - 类别(categories)和子类别(sub-categories)

MySQL解释更新

递归关系的数据库设计

Sql更新查询

如何将正在使用的数据库复制到django中的其他数据库?

当使用多个 WHEN MATCHED 语句时,它们是全部执行,还是只执行一个?

SQLite3 的动态类型

什么是 Scalar标量查询?

将少量信息保存为 android 中的设置

触发器、断言和判断之间有什么区别?

从旧数据 struct 到新数据 struct 的数据迁移

在 MySQL 中 Select 浮点数

将文本列设为唯一键

在连接表中,Rails 缺少组合键的最佳解决方法是什么?

归一化 - 2NF 与 3NF

使用 Sinatra 时与数据库对话的最佳方式是什么?