program tip

git 저장소를 데이터베이스 백엔드로 사용

radiobox 2020. 8. 2. 18:03
반응형

git 저장소를 데이터베이스 백엔드로 사용


구조화 된 문서 데이터베이스를 다루는 프로젝트를 수행하고 있습니다. 나는 카테고리 트리 (~ 1000 카테고리, 각 레벨에서 최대 ~ 50 카테고리)를 가지고 있으며 각 카테고리에는 수천 (최대 ~ 10000)의 구조화 된 문서가 포함되어 있습니다. 각 문서는 구조화 된 형태로 몇 킬로바이트의 데이터입니다 (YAML을 선호하지만 JSON 또는 XML 일 수도 있습니다).

이 시스템의 사용자는 여러 유형의 작업을 수행합니다.

  • ID로 이러한 문서 검색
  • 내부의 일부 구조적 속성으로 문서 검색
  • 문서 편집 (즉, 추가 / 제거 / 이름 바꾸기 / 병합); 각 편집 작업은 주석이 포함 된 트랜잭션으로 기록되어야합니다.
  • 특정 문서에 대해 기록 된 변경 내역보기 (문서를 변경 한 사람,시기 및 이유보기, 이전 버전 가져 오기 및 요청시이 문서로 되돌릴 수 있음)

물론 전통적인 해결책은이 문제에 일종의 문서 데이터베이스 (예 : CouchDB 또는 Mongo)를 사용하는 것입니다. 그러나이 버전 관리 (역사)는 저에게 야생 아이디어를 유혹했습니다. 왜 git리포지토리를 저장소로 사용해서는 안 됩니까? 이 응용 프로그램의 데이터베이스 백엔드?

언뜻보기에 다음과 같이 해결할 수 있습니다.

  • 카테고리 = 디렉토리, 문서 = 파일
  • ID로 문서 가져 오기 => 디렉토리 변경 + 작업 사본에서 파일 읽기
  • 편집 주석이있는 문서 편집 => 다양한 사용자가 커밋 + 커밋 메시지 저장
  • history => 정상적인 트랜잭션 로그 및 오래된 트랜잭션 검색
  • search => 그것은 약간 까다로운 부분입니다. 우리가 검색 할 수있는 열을 색인화하여 관계형 데이터베이스로 범주를 주기적으로 내 보내야한다고 생각합니다

이 솔루션에 다른 일반적인 함정이 있습니까? 누구든지 이미 이러한 백엔드를 구현하려고 했습니까 (예 : RoR, node.js, Django, CakePHP와 같은 인기있는 프레임 워크)? 이 솔루션이 성능 또는 안정성에 영향을 줄 수 있습니까? 즉, git이 기존 데이터베이스 솔루션보다 훨씬 느리거나 확장 성 / 신뢰성 함정이 있음이 입증 되었습니까? 서로의 저장소를 밀거나 당기는 서버의 클러스터는 상당히 강력하고 안정적이어야합니다.

기본적으로 말해 경우 이 솔루션이 작동하며 그것을 것 또는하지 않을 것이다?


내 자신의 질문에 대답하는 것이 최선의 방법은 아니지만 궁극적으로 아이디어를 버리면서 내 경우에 효과가 있었던 근거를 공유하고 싶습니다. 이 이론적 근거가 모든 경우에 적용되는 것은 아니라는 점을 강조하고 싶습니다. 따라서 결정하는 것은 건축가의 몫입니다.

일반적으로 질문에서 놓친 첫 번째 요점 은 씬 클라이언트 (예 : 웹 브라우저)로 서버를 사용하여 동시에 병렬로 작동 하는 다중 사용자 시스템을 처리 한다는 것입니다. 이런 식으로, 나는 그들 모두의 상태 를 유지 해야합니다. 이것에 대한 몇 가지 접근 방식이 있지만 모든 자원에 너무 어렵거나 구현하기가 너무 복잡합니다 (따라서 처음에는 git에 모든 어려운 구현 항목을 오프로드하는 원래 목적을 없애십시오).

  • "블런트"접근 방식 : 1 사용자 = 1 상태 = 1 서버가 사용자를 위해 유지 관리하는 저장소의 전체 작업 사본. ~ 100K의 사용자가있는 매우 작은 문서 데이터베이스 (예 : 100s MiB)에 대해 이야기하더라도 모든 저장소를 전체 저장소 복제본으로 유지하면 디스크 사용이 지붕을 통해 실행됩니다 (예 : 100K 사용자 수 100MiB ~ 10TiB) . 더 나쁜 것은, IMO가 받아 들일 수없는 상당히 효과적인 관리자 (즉, git 및 unpacking-repacking 물건에 의해 사용되지 않음)에서 수행 되더라도 매번 100 MiB 저장소를 복제하는 데 몇 초의 시간이 걸립니다. 더 나쁜 것은 – 메인 트리에 적용한 모든 편집 내용은 모든 사용자의 저장소로 가져와야합니다. (1) 리소스 호그 (2) 일반적인 경우 해결되지 않은 편집 충돌이 발생할 수 있습니다.

    기본적으로 디스크 사용량 측면에서 O (편집 수 × 데이터 × 사용자 수)만큼 나쁠 수 있으며 이러한 디스크 사용량은 자동으로 CPU 사용량이 매우 높습니다.

  • "활성 사용자 만"접근 : 활성 사용자에 대해서만 작업 사본을 유지하십시오. 이 방법으로 일반적으로 전체 사용자 당 복제본이 아닌 다음을 저장합니다.

    • 사용자가 로그인하면 저장소를 복제합니다. 활성 사용자 당 몇 초와 최대 100MiB의 디스크 공간이 필요합니다.
    • 사용자가 사이트에서 계속 작업하면서 주어진 작업 복사본으로 작업합니다.
    • 사용자가 로그 아웃 할 때 그의 저장소 복제본은 기본 저장소로 분기로 다시 복사되므로 "적용되지 않은 변경 사항"(있는 경우) 만 저장하면 상당히 공간 효율적입니다.

    따라서이 경우 디스크 사용량은 O (편집 수 × 데이터 × 활성 사용자 수)에서 가장 높으며 일반적으로 총 사용자 수보다 ~ 100..1000 배 적지 만 로그인 / 로그 아웃을 더 복잡하고 느리게 만듭니다. 모든 로그인에서 사용자 별 분기를 복제하고 로그 아웃 또는 세션 만료시 이러한 변경 사항을 다시 가져와야합니다 (트랜잭션으로 수행해야 함 => 또 다른 복잡성 계층 추가). 절대 숫자로는 10TiB의 디스크 사용량을 10..100GiB로 줄였습니다.이 경우 허용 가능할 수 있지만 이제는 100MiB의 매우 작은 데이터베이스에 대해 이야기하고 있습니다.

  • "스파 스 체크 아웃"접근 방식 : 활성 사용자 당 전체 리포 복제본 대신 "스파 스 체크 아웃"을 만드는 것은 큰 도움이되지 않습니다. 디스크 공간 사용량을 약 10 배 절약 할 수 있지만 기록 관련 작업에서 CPU / 디스크로드가 훨씬 높아져 목적이 사라집니다.

  • "Workers Pool"접근 방식 : 활동적인 사람을 위해 매번 본격적인 클론을 수행하는 대신 "Worker"클론의 풀을 사용할 수 있습니다. 이 방법으로 사용자가 로그인 할 때마다 한 명의 "작업자"를 점유하고 메인 리포지토리에서 지점을 가져오고, 로그 아웃 할 때 그는 "작업자"를 해제합니다. 다른 사용자가 로그인 할 준비가 된 기본 리포지토리 클론. 디스크 사용량에 큰 도움이되지는 않지만 (여전히 활성 사용자 당 전체 복제본 만), 최소한의 비용으로 로그인 / 로그 아웃이 더 빠릅니다. 훨씬 더 복잡한.

즉, 100K 사용자, 1K 활성 사용자, 100MiB 총 데이터베이스 + 편집 기록, 10MiB의 작업 복사본과 같이 상당히 작은 데이터베이스 및 사용자 기반의 수를 의도적으로 계산했습니다. 보다 눈에 띄는 크라우드 소싱 프로젝트를 살펴 보려면 훨씬 더 많은 숫자가 있습니다.

│              │ 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 작업을 실행하고 서버 측이 아닌 클라이언트 측에서 거의 전체 체크 아웃을 저장하는 것이 효과적 일 것입니다.

내가 놓친 다른 점들도 있지만, 첫 번째 점에 비해 그리 나쁘지는 않습니다.

  • The very pattern of having "thick" user's edit state is controversial in terms of normal ORMs, such as ActiveRecord, Hibernate, DataMapper, Tower, etc.
  • As much as I've searched for, there's zero existing free codebase for doing that approach to git from popular frameworks.
  • There is at least one service that somehow manages to do that efficiently — that is obviously github — but, alas, their codebase is closed source and I strongly suspect that they do not use normal git servers / repo storage techniques inside, i.e. they basically implemented alternative "big data" git.

So, bottom line: it is possible, but for most current usecases it won't be anywhere near the optimal solution. Rolling up your own document-edit-history-to-SQL implementation or trying to use any existing document database would be probably a better alternative.


An interesting approach indeed. I would say that if you need to store data, use a database, not a source code repository, which is designed for a very specific task. If you could use Git out-of-the-box, then it's fine, but you probably need to build a document repository layer over it. So you could build it over a traditional database as well, right? And if it's built-in version control that you're interested in, why not just use one of open source document repository tools? There are plenty to choose from.

Well, if you decide to go for Git backend anyway, then basically it would work for your requirements if you implemented it as described. But:

1) You mentioned "cluster of servers that push/pull each other" - I've thought about it for a while and still I'm not sure. You can't push/pull several repos as an atomic operation. I wonder if there could be a possibility of some merge mess during concurrent work.

2) Maybe you don't need it, but an obvious functionality of a document repository you did not list is access control. You could possibly restrict access to some paths(=categories) via submodules, but probably you won't be able to grant access on document level easily.


my 2 pence worth. A bit longing but ...... I had a similar requirement in one of my incubation projects. Similar to yours , my key requirements where a document database ( xml in my case),with document versioning. It was for a multi-user system with a lot of collaboration use cases. My preference was to use available opensource solutions that support most of the key requirements.

To cut to the chase, I could not find any one product that provided both, in a way that was scalable enough ( number of users, usage volumes, storage and compute resources).I was biased towards git for all the promising capability, and (probable) solutions one could craft out of it. As I toyed with git option more, moving from a single user perspective to a multi ( milli) user perspective became an obvious challenge. Unfortunately, I did not get to do substantial performance analysis like you did. ( .. lazy/ quit early ....for version 2, mantra) Power to you!. Anyway, my biased idea has since morphed to the next (still biased ) alternative: a mesh-up of tools that are the best in their separate spheres, databases and version control.

While still work in progress ( ...and slightly neglected ) the morphed version is simply this .

  • on the frontend: (userfacing ) use a database for the 1st level storage ( interfacing with user applications )
  • on the backend, use a version control system (VCS)(like git ) to perform versioning of the data objects in database

In essence it would amount to adding a version control plugin to the database, with some integration glue, which you may have to develop, but may be a lot much easier.

How it would (supposed to ) work is that the primary multi-user interface data exchanges are through the database. The DBMS will handle all the fun and complex issues such as multi-user , concurrency e, atomic operations etc. On the backend the VCS would perform version control on a single set of data objects ( no concurrency, or multi-user issues). For each effective transactions on the database, version control is only performed on the data records that would have effectively changed.

As for the interfacing glue, it will be in the form of a simple interworking function between the database and the VCS. In terms of design, as simple approach would be an event driven interface, with data updates from the database triggering the version control procedures ( hint : assuming Mysql, use of triggers and sys_exec() blah blah ...) .In terms of implementation complexity, it will range from the simple and effective ( eg scripting ) to the complex and wonderful ( some programmed connector interface) . All depends on how crazy you want to go with it , and how much sweat capital you are willing to spend. I reckon simple scripting should do the magic. And to access the end result, the various data versions, a simple alternative is to populate a clone of the database ( more a clone of the database structure) with the data referenced by the version tag/id/hash in the VCS. again this bit will be a simple query/translate/map job of an interface.

There are still some challenges and unknowns to be dealt with, but I suppose the impact, and relevance of most of these will largely depend on your application requirements and use cases. Some may just end up being non issues. Some of the issues include performance matching between the 2 key modules, the database and the VCS, for an application with high frequency data update activity, Scaling of resources (storage and processing power ) over time on the git side as the data , and users grow: steady, exponential or eventually plateau's

Of the cocktail above, here is what I'm currently brewing

  • using Git for the VCS ( initially considered good old CVS for the due to the use of only changesets or deltas between 2 version )
  • using mysql ( due to the highly structured nature of my data, xml with strict xml schemas )
  • toying around with MongoDB (to try a NoSQl database, which closely matches the native database structure used in git )

Some fun facts - git actually does clear things to optimize storage, such as compression, and storage of only deltas between revision of objects - YES, git does store only changesets or deltas between revisions of data objects, where is it is applicable ( it knows when and how) . Reference : packfiles, deep in the guts of Git internals - Review of the git's object storage ( content-addressable filesystem), shows stricking similarities ( from the concept perspective) with noSQL databases such mongoDB. Again, at the expense of sweat capital, it may provide more interesting possibilities for integrating the 2, and performance tweaking

If you got this far, let me if the above may be applicable to your case, and assuming it would be , how it would square up to some of the aspect in your last comprehensive performance analysis


As you mentioned, the multi-user case is a bit trickier to handle. One possible solution would be to use user-specific Git index files resulting in

  • no need for separate working copies (disk usage is restricted to changed files)
  • no need for time-consuming preparatory work (per user session)

The trick is to combine Git's GIT_INDEX_FILE environmental variable with the tools to create Git commits manually:

A solution outline follows (actual SHA1 hashes omitted from the commands):

# 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>

Depending on your data you could use a cron job to merge the new refs to master but the conflict resolution is arguably the hardest part here.

Ideas to make it easier are welcome.


I implemented a Ruby library on top of libgit2 which makes this pretty easy to implement and explore. There are some obvious limitations, but it's also a pretty liberating system since you get the full git toolchain.

The documentation includes some ideas about performance, tradeoffs, etc.

참고URL : https://stackoverflow.com/questions/20151158/using-git-repository-as-a-database-backend

반응형