webdevqa.jp.net

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

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

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

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

もちろん、従来のソリューションでは、この問題に対して何らかの種類のドキュメントデータベース(CouchDBやMongoなど)を使用していましたが、このバージョン管理(履歴)のことで私は思い切ったアイデアになりました-なぜgitリポジトリをこのアプリケーションのデータベースバックエンド?

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

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

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

基本的に、ifこのソリューションが機能するかwhyそれが機能するかしないかを教えてください。

107
GreyCat

自分の質問に答えることは最善のことではありませんが、最終的にアイデアを落としたので、私の場合に働いた理論的根拠を共有したいと思います。この理論的根拠がすべての場合に当てはまるわけではないことを強調したいので、決定するのは建築家次第です。

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

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

    基本的に、ディスク使用量に関してはO(編集数×データ×ユーザー数)と同じくらい悪いかもしれません。そのようなディスク使用量は自動的にかなり高いCPU使用率を意味します。

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

    • ユーザーがログインすると、リポジトリを複製します。アクティブなユーザーごとに数秒と100 MiBのディスク容量が必要です。
    • ユーザーがサイトで作業を続けると、指定された作業コピーで作業します。
    • ユーザーがログアウトすると、リポジトリクローンがブランチとしてメインリポジトリにコピーされるため、「適用されていない変更」がある場合はそれだけが保存され、スペース効率が大幅に向上します。

    したがって、この場合のディスク使用量は、O(編集数×データ×アクティブユーザー数)でピークになります。これは通常、合計ユーザー数の約100..1000倍ですが、ログイン/ログアウトがより複雑で遅くなります。 、ログインごとにユーザーごとのブランチのクローンを作成し、ログアウトまたはセッションの有効期限でこれらの変更を元に戻す必要があるため(トランザクションで行う必要があります=>別の複雑さを追加します)。絶対数では、ディスク使用量の10 TiBを私の場合は10..100 GiBに落としますが、それは許容できるかもしれませんが、今でもかなりsmall100 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操作を発行し、サーバー側ではなくクライアント側でほぼすべてのチェックアウトを保存できれば、うまくいきます。

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

  • Active Record、Hibernate、DataMapper、Towerなどの通常のORMに関しては、「厚い」ユーザーの編集状態を持つというまさにパターンは論争の的です。
  • 私が検索した限りでは、人気のあるフレームワークからgitへのアプローチを行うための既存の無料のコードベースはありません。
  • なんとかしてそれを効率的に行うサービスが少なくとも1つあります。これは明らかに github です。内部のテクニック、すなわち、彼らは基本的に代替の「ビッグデータ」gitを実装しました。

したがって、bottom line:itisですが、現在のほとんどのユースケースではどこにもありません最適なソリューションの近く。独自のdocument-edit-history-to-SQL実装をロールアップするか、既存のドキュメントデータベースを使用しようとするのが、おそらくより良い選択肢です。

48
GreyCat

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

まあ、とにかくGitバックエンドを使用することに決めた場合、基本的には、説明されているように実装すれば、要件を満たすことができます。しかし:

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

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

12

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

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

まだ進行中の作業(...およびわずかに無視されています)の間に、モーフィングされたバージョンは単にこれです。

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

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

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

インターフェースのりについては、データベースとVCSの間の単純なインターワーキング機能の形式になります。設計に関しては、単純なアプローチはイベント駆動型インターフェースであり、データベースからのデータ更新がバージョン管理手順をトリガーします(ヒント: Mysql、トリガーとsys_exec() 何とか...)。実装の複雑さに関しては、シンプルで効果的な(スクリプトなど)から、複雑で素晴らしい(プログラムされたコネクタインターフェイス)まであります。すべては、あなたがどれだけクレイジーになりたいか、そしてどれだけの汗をかいているかにかかっています。私は簡単なスクリプトが魔法をするはずだと考えています。そして、最終結果であるさまざまなデータバージョンにアクセスするための簡単な方法は、VCSのバージョンタグ/ ID /ハッシュで参照されるデータをデータベースのクローン(データベース構造のクローン)に追加することです。繰り返しますが、このビットは、インターフェイスの単純なクエリ/翻訳/マップジョブになります。

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

上記のカクテルのうち、ここに私が現在醸造しているものがあります

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

いくつかの楽しい事実-gitは圧縮などのストレージを最適化するために実際に物事をクリアし、オブジェクトのリビジョン間のデルタのみのストレージ-はい、gitは適用可能なデータオブジェクトのリビジョン間のチェンジセットまたはデルタのみを格納します(いつ、どのように)。参照:packfiles、Git内部の gutsの奥深く -gitのオブジェクトストレージ(content-addressable filesystem)のレビュー、驚くべき類似点(概念から)パースペクティブ)mongoDBなどのnoSQLデータベースを使用します。繰り返しますが、汗の資本を犠牲にして、2を統合し、パフォーマンスを調整するためのより興味深い可能性を提供する可能性があります

これまでのところ、上記があなたのケースに当てはまるかどうか、それがであると仮定して、最後の包括的なパフォーマンス分析の側面のいくつかにどのように二乗するかを教えてください

11
young chisango

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

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

2
ioquatix

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

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

2
7mp