データベースのバックエンドとしてのgitリポジトリの使用


119

私は構造化文書データベースを扱うプロジェクトをやっています。カテゴリのツリー(〜1000カテゴリ、各レベルで最大〜50カテゴリ)があり、各カテゴリには数千(たとえば、最大〜10000)の構造化ドキュメントが含まれています。各ドキュメントは、いくつかの構造化された形式で数キロバイトのデータです(私はYAMLを好みますが、JSONまたはXMLでもかまいません)。

このシステムのユーザーは、いくつかのタイプの操作を行います。

  • IDによるこれらのドキュメントの取得
  • 内部のいくつかの構造化属性でドキュメントを検索する
  • ドキュメントの編集(つまり、追加/削除/名前変更/マージ); 各編集操作は、コメント付きのトランザクションとして記録する必要があります
  • 特定のドキュメントの記録された変更の履歴を表示する(誰が、いつ、なぜドキュメントを変更したか、以前のバージョンを取得したり、要求に応じてこのバージョンに戻したりすることを含む)

もちろん、従来の解決策は、この問題にある種のドキュメントデータベース(CouchDBやMongoなど)を使用することでしgitた。このアプリケーションのデータベースバックエンド?

一見すると、次のように解決できます。

  • カテゴリ=ディレクトリ、ドキュメント=ファイル
  • IDによるドキュメントの取得=>ディレクトリの変更+作業コピーのファイルの読み取り
  • コメントを編集してドキュメントを編集する=>さまざまなユーザーによるコミット+コミットメッセージの保存
  • 履歴=>通常のgitログと古いトランザクションの取得
  • search =>これは少しトリッキーな部分です。検索を許可する列のインデックス付けを使用して、カテゴリをリレーショナルデータベースに定期的にエクスポートする必要があると思います

このソリューションには他によくある落とし穴はありますか?そのようなバックエンドを実装しようとした人はいますか(つまり、一般的なフレームワーク-RoR、node.js、Django、CakePHP)。このソリューションは、パフォーマンスや信頼性に影響を与える可能性がありますか?つまり、gitが従来のデータベースソリューションよりもはるかに遅くなるか、スケーラビリティ/信頼性の落とし穴があることが証明されていますか?お互いのリポジトリをプッシュ/プルするそのようなサーバーのクラスターは、かなり堅牢で信頼できるはずだと思います。

基本的に、私に言うならば、このソリューションが動作すると、なぜそうなるか、しないだろうか?


回答:


58

自分の質問に答えることは最善のことではありませんが、最終的にはそのアイデアを捨てたので、私のケースで機能した理論的根拠について共有したいと思います。この根拠はすべてのケースに当てはまるわけではないため、決定するのはアーキテクト次第です。

一般的に、私の質問が最初に見逃している主な点は、サーバーをシンクライアント(つまり、Webブラウザー)で使用して、並行して同時に動作するマルチユーザーシステムを扱っていることです。このように、私はそれらすべての状態を維持する必要があります。これにはいくつかのアプローチがありますが、それらはすべて、リソースに負荷がかかりすぎるか、複雑すぎて実装できません(したがって、最初からgitにすべてのハード実装をオフロードするという本来の目的をやめます)。

  • 「鈍い」アプローチ:1人のユーザー= 1つの状態=サーバーがユーザーのために維持するリポジトリの1つの完全な作業用コピー。ユーザー数が10万人程度のかなり小さなドキュメントデータベース(たとえば、100万MiB)について話している場合でも、すべてのユーザーに対して完全なリポジトリクローンを維持すると、ディスク使用量が制限されます(つまり、100 Kのユーザー数に100 MiBから10 TiBが掛けられます)。 。さらに悪いことに、かなり効果的な方法(つまり、gitやunpacking-repackingなどを使用しない)で行ったとしても、毎回100 MiBリポジトリのクローン作成には数秒の時間がかかります。これは許容できないIMOです。さらに悪いことに、メインツリーに適用するすべての編集は、すべてのユーザーのリポジトリにプルする必要があります。これは、(1)リソースの消費、(2)一般に、未解決の編集の競合につながる可能性があります。

    基本的には、ディスク使用量の点でO(編集数×データ×ユーザー数)と同じくらい悪くなる可能性があり、そのようなディスク使用量は自動的にかなり高いCPU使用量を意味します。

  • 「アクティブユーザーのみ」のアプローチ:アクティブユーザーのみの作業コピーを維持します。この方法では、通常、ユーザーごとの完全なレポクローンではなく、次のように保存します。

    • ユーザーがログインすると、リポジトリのクローンが作成されます。アクティブユーザーごとに数秒、最大100 MiBのディスク容量がかかります。
    • ユーザーがサイトで作業を続けると、指定された作業用コピーで作業します。
    • ユーザーがログアウトすると、彼のリポジトリクローンはブランチとしてメインリポジトリにコピーされ、「適用されていない変更」がある場合は、それだけが格納されます。これはスペース効率がかなり良い方法です。

    したがって、この場合のディスク使用量はO(編集数×データ×アクティブユーザーの数)でピークに達します。これは通常、合計ユーザー数の約100..1000分の1ですが、ログイン/ログアウトがより複雑で遅くなります、ログインごとにユーザーごとのブランチのクローンを作成し、ログアウトまたはセッションの期限切れ時にこれらの変更を元に戻す必要があるため(トランザクションで実行する必要がある=>別の複雑なレイヤーが追加されます)。絶対数では、ディスクの使用量が10 TiBから10..100 GiBに下がります。これは許容できるかもしれませんが、ここでも、100 MiBのかなり小さいデータベースについて話しています。

  • 「スパースチェックアウト」アプローチ:アクティブユーザーごとに本格的なレポクローンの代わりに「スパースチェックアウト」を作成しても、あまり役に立ちません。これにより、ディスク領域の使用量が最大で10倍節約されますが、履歴を伴う操作でのCPU /ディスクの負荷が非常に高くなるため、目的が失われます。

  • 「ワーカープール」アプローチ:アクティブな人のために毎回本格的なクローンを作成する代わりに、「ワーカー」クローンのプールを保持して、すぐに使用できるようにすることができます。このように、ユーザーがログインするたびに、彼は1つの「ワーカー」を占有し、メインリポジトリから自分のブランチをプルし、ログアウトすると、「ワーカー」を解放します。ログインしている別のユーザーが使用できるメインリポジトリクローン。ディスクの使用にはあまり役立ちません(それでもかなり高いです。アクティブユーザーごとのフルクローンのみ)。少なくとも、ログイン/ログアウトが速くなります。さらに複雑になります。

とはいえ、かなり小さなデータベースとユーザーベースの数を意図的に計算したことに注意してください。ユーザー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 │

明らかに、その量のデータ/アクティビティでは、このアプローチはまったく受け入れられません。

一般的に、Webブラウザを「シック」クライアントとして使用できれば、つまり、gitオペレーションを発行し、サーバー側ではなくクライアント側でほぼすべてのチェックアウトを保存できれば、うまくいきました。

私が見逃した他のポイントもありますが、それらは最初のものと比較してそれほど悪くはありません:

  • 「シック」ユーザーの編集状態を持つというまさにそのパターンは、ActiveRecord、Hibernate、DataMapper、Towerなどの通常のORMに関して議論の余地があります。
  • 私が検索したように、人気のあるフレームワークからgitへのアプローチを行うための既存の無料のコードベースはありません。
  • どういうわけかそれを効率的に行うことができるサービスが少なくとも1つあります。それは明らかにgithubですが、悲しいことに、それらのコードベースはクローズドソースであり、内部で通常のgitサーバー/リポジトリストレージ技術を使用していないことを強く疑っています。代替の「ビッグデータ」git。

だから、一番下の行:それはある可能性が、ほとんどの現在のユースケースのために、それはどこでも最適なソリューション近くではありません。独自のdocument-edit-history-to-SQL実装をロールアップするか、既存のドキュメントデータベースを使用しようとすることは、おそらくより良い代替手段です。


16
おそらくパーティーには少し遅れましたが、私はこれと同様の要件を持っていて、実際にgit-routeを降りました。gitの内部を少し調べた後、私はそれを動作させる方法を見つけました。アイデアは、裸のリポジトリで動作することです。いくつかの欠点がありますが、私はそれが実行可能であると思います。あなたがチェックしたいと思うかもしれない投稿にすべてを書きました(もしあれば、興味のために):kenneth-truyers.net/2016/10/13/git-nosql-database
Kenneth

これを行わないもう1つの理由は、クエリ機能です。多くの場合、ドキュメントストアはドキュメントにインデックスを付けて、ドキュメント内の検索を容易にします。これはgitでは簡単ではありません。
FrankyHollywood 2017年

12

確かに興味深いアプローチ。データを保存する必要がある場合は、非常に特定のタスク用に設計されたソースコードリポジトリではなく、データベースを使用してください。Gitをそのまま使用できる場合は問題ありませんが、おそらくその上にドキュメントリポジトリレイヤーを構築する必要があります。つまり、従来のデータベースの上に構築することもできますよね?また、組み込みのバージョン管理に興味がある場合は、オープンソースのドキュメントリポジトリツールの 1つを使用しないのはなぜですか。たくさんの選択肢があります。

とにかく、とにかくGitバックエンドを採用することにした場合、基本的には、説明どおりに実装すれば要件に対応できます。だが:

1)「相互にプッシュ/プルするサーバーのクラスター」について説明しました-私はしばらく考えていましたが、まだわかりません。複数のリポジトリをアトミック操作としてプッシュ/プルすることはできません。並行作業中にマージが混乱する可能性はあるのでしょうか。

2)おそらく必要ないかもしれませんが、リストしていないドキュメントリポジトリの明らかな機能はアクセス制御です。サブモジュールを介して一部のパス(= categories)へのアクセスを制限することもできますが、おそらくドキュメントレベルでのアクセスを簡単に許可することはできません。


11

私の2ペンス相当。少し憧れですが……インキュベーションプロジェクトの1つでも同様の要件がありました。あなたと同様に、ドキュメントのバージョン管理を行うドキュメントデータベース(私の場合はxml)での私の主要な要件。これは、コラボレーションのユースケースが多数あるマルチユーザーシステム用でした。私の好みは、主要な要件のほとんどをサポートする利用可能なオープンソースソリューションを使用することでした。

追いかけるために、十分にスケーラブルな方法(ユーザー数、使用量、ストレージ、およびコンピューティングリソース)で両方を提供する製品を見つけることができませんでした。私はすべての有望な機能についてgitに偏っていました。 (可能性のある)そこから作成できるソリューション。私がgitオプションをさらにいじるにつれて、シングルユーザーの視点からマルチ(ミリ)ユーザーの視点に移動することは明らかな課題になりました。残念ながら、私はあなたのように実質的なパフォーマンス分析を行うことができませんでした。(.. lazy /早期終了....バージョン2の場合、マントラ)Power to you !. とにかく、私の偏見のあるアイデアは、次の(まだ偏った)代替案に変形しました:個別の球、データベース、バージョン管理で最高のツールのメッシュアップ。

まだ作業中ですが(...少し無視されます)、モーフィングバージョンはこれだけです。

  • フロントエンド:(userfacing)第1レベルのストレージにデータベースを使用(ユーザーアプリケーションとのインターフェース)
  • バックエンドで、バージョンコントロールシステム(VCS)(gitなど)を使用して、データベース内のデータオブジェクトのバージョン管理を実行します。

本質的には、データベースにバージョン管理プラグインを追加することになります。統合グルーを使用すると、開発が必要になる場合がありますが、はるかに簡単です。

それがどのように機能するか(想定される)は、主要なマルチユーザーインターフェイスのデータ交換がデータベースを介して行われることです。DBMSは、マルチユーザー、同時実行、アトミック操作などのすべての楽しく複雑な問題を処理します。バックエンドでは、VCSは単一のデータオブジェクトのセットでバージョン管理を実行します(同時実行、またはマルチユーザーの問題はありません)。データベース上の有効なトランザクションごとに、バージョン管理は、効果的に変更されたデータレコードに対してのみ実行されます。

インターフェース用接着剤に関しては、データベースとVCS間の単純なインターワーキング機能の形式になります。設計に関しては、データベースからのデータ更新がバージョン管理手順をトリガーするシンプルなアプローチがイベントドリブンインターフェースであるため(ヒント:Mysqlを想定し 、トリガーとsys_exec()を使用) 何とか何とか...)。実装の複雑さの点で、それは単純で効果的な(スクリプトなど)から複雑で素晴らしい(いくつかのプログラムされたコネクタインターフェイス)までの範囲になります。すべては、どれだけクレイジーになりたいか、そしてどれだけの資金を費やすかによって決まります。簡単なスクリプトで魔法をかけるべきだと思います。そして、最終結果であるさまざまなデータバージョンにアクセスするための簡単な方法は、VCSのバージョンタグ/ ID /ハッシュによって参照されるデータをデータベースのクローン(データベース構造のクローン)に入力することです。繰り返しますが、このビットは、インターフェースの単純なクエリ/変換/マップジョブになります。

まだ対処すべき課題や不明な点がいくつかありますが、影響はあると思います。これらのほとんどの関連性は、アプリケーションの要件とユースケースに大きく依存します。一部は単に問題にならないかもしれません。いくつかの問題には、2つの主要モジュール、データベースとVCSの間のパフォーマンスの一致、高頻度のデータ更新アクティビティを伴うアプリケーションの場合、データとしてのgit側でのリソース(ストレージと処理能力)の経時的なスケーリング、およびユーザーが含まれます。成長:安定した、指数関数的、または最終的にはプラトー

上記のカクテルのうち、私が現在醸造しているのはここです

  • VCSにGitを使用する(2つのバージョン間のチェンジセットまたはデルタのみを使用するため、当初は古き良きCVSと見なされていました)
  • mysqlを使用する(私のデータ、厳密なxmlスキーマを持つxmlの高度に構造化された性質による)
  • MongoDBをいじる(gitで使用されるネイティブのデータベース構造と厳密に一致するNoSQlデータベースを試すため)

いくつかの面白い事実-gitは圧縮などのストレージを最適化するために実際にクリアし、オブジェクトのリビジョン間のデルタのみのストレージを保存します-はい、gitはデータオブジェクトのリビジョン間のチェンジセットまたはデルタのみを格納します。いつ、どのように)。参照:packfiles、Git内部内臓の奥深く-git のオブジェクトストレージ(コンテンツアドレス可能なファイルシステム)のレビューは、mongoDBなどのnoSQLデータベースとの(概念の観点から)際立った類似点を示しています。繰り返しになりますが、汗の資本を犠牲にして、2を統合するためのより興味深い可能性を提供し、パフォーマンスを調整することができます。

ここまで来たら、上記があなたのケースに当てはまるかどうかを考えてみてください。そうであると仮定すると、最後の包括的なパフォーマンス分析のいくつかの側面にどのように対応するでしょうか。


4

Rubyライブラリを上に実装したlibgit2ので、これを実装して探索するのはかなり簡単です。明らかな制限がいくつかありますが、完全なgitツールチェーンを入手できるため、これはかなり解放的なシステムでもあります。

ドキュメントには、パフォーマンス、トレードオフなどに関するいくつかのアイデアが含まれています。


2

あなたが述べたように、マルチユーザーの場合は少し扱いに​​くいです。可能な解決策の1つは、ユーザー固有の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ジョブを使用して新しい参照をマージすることもできますmasterが、おそらく競合の解決がここで最も難しい部分です。

簡単にするためのアイデアは大歓迎です。


手動の競合解決のためのトランザクションとUIの本格的なコンセプトが必要でない限り、これは一般的にどこにも先を行くアプローチではありません。競合の一般的な考え方は、コミット時にユーザーが正しく解決できるようにすることです(つまり、「申し訳ありませんが、編集中のドキュメントを他の誰かが編集しました->彼の編集内容と編集内容を確認してマージしてください」)。2人のユーザーが正常にコミットすることを許可してから、非同期cronjobで状況が悪化したことを発見した場合、通常、問題を解決できる人は誰もいません。
GreyCat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.