構造化文書データベースを扱うプロジェクトをやっています。カテゴリツリー(〜1000カテゴリ、各レベルで〜50カテゴリ)があり、各カテゴリには数千(たとえば、〜10000)の構造化文書が含まれています。各ドキュメントは、何らかの構造化された形式の数キロバイトのデータです(YAMLの方が好きですが、JSONまたはXMLでもかまいません)。
このシステムのユーザーは、いくつかのタイプの操作を実行します。
もちろん、従来のソリューションでは、この問題に対して何らかの種類のドキュメントデータベース(CouchDBやMongoなど)を使用していましたが、このバージョン管理(履歴)のことで私は思い切ったアイデアになりました-なぜgit
リポジトリをこのアプリケーションのデータベースバックエンド?
一見すると、次のように解決できます。
このソリューションに他の一般的な落とし穴はありますか?誰もがすでにそのようなバックエンドを実装しようとしましたか?(つまり、一般的なフレームワーク-RoR、node.js、Django、CakePHP)このソリューションは、パフォーマンスや信頼性に影響を与える可能性がありますか?つまり、gitが従来のデータベースソリューションよりもはるかに遅いことや、スケーラビリティ/信頼性の落とし穴があることが証明されていますか?互いのリポジトリをプッシュ/プルするそのようなサーバーのクラスタは、かなり堅牢で信頼性が高いはずです。
基本的に、ifこのソリューションが機能するかwhyそれが機能するかしないかを教えてください。
自分の質問に答えることは最善のことではありませんが、最終的にアイデアを落としたので、私の場合に働いた理論的根拠を共有したいと思います。この理論的根拠がすべての場合に当てはまるわけではないことを強調したいので、決定するのは建築家次第です。
一般的に、私の質問が見逃す最初の主なポイントは、シンクライアントでサーバーを使用して、並行して、同時に動作するマルチユーザーシステムを扱っているということです(つまり、ウェブブラウザ)。この方法では、それらすべてについてstateを維持する必要があります。これにはいくつかのアプローチがありますが、それらはすべてリソースが難しすぎるか、実装するには複雑すぎます(したがって、そもそもすべてのハードな実装をgitにオフロードするという本来の目的を殺してしまいます):
「鈍的」アプローチ:1ユーザー= 1状態= 1サーバーがユーザーのために維持するリポジトリの完全な作業コピー。 〜100Kのユーザーを含むかなり小さなドキュメントデータベース(たとえば、100s MiB)について話している場合でも、すべてのユーザーの完全なリポジトリクローンを維持すると、ディスクの使用が屋根を駆け巡ります(つまり、100Kユーザー〜100 TiBの時間) 。さらに悪いことに、かなり効果的な方法(つまり、gitやunpacking-repackingで使用しない場合)でも、100 MiBリポジトリを毎回複製すると数秒の時間がかかります。さらに悪いことに、メインツリーに適用するすべての編集は、すべてのユーザーのリポジトリにプルされる必要があります。これは、(1)リソースの大量消費、(2)一般的な場合、未解決の編集競合につながる可能性があります。
基本的に、ディスク使用量に関してはO(編集数×データ×ユーザー数)と同じくらい悪いかもしれません。そのようなディスク使用量は自動的にかなり高いCPU使用率を意味します。
「アクティブユーザーのみ」アプローチ:アクティブユーザーのみの作業コピーを維持します。この方法では、通常、ユーザーごとにフルリポジトリクローンを保存するのではなく、次のように保存します。
したがって、この場合のディスク使用量は、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操作を発行し、サーバー側ではなくクライアント側でほぼすべてのチェックアウトを保存できれば、うまくいきます。
私が見逃した他のポイントもありますが、それらは最初のものと比べてそれほど悪くはありません:
したがって、bottom line:itisですが、現在のほとんどのユースケースではどこにもありません最適なソリューションの近く。独自のdocument-edit-history-to-SQL実装をロールアップするか、既存のドキュメントデータベースを使用しようとするのが、おそらくより良い選択肢です。
興味深いアプローチです。データを保存する必要がある場合は、非常に特定のタスク用に設計されたソースコードリポジトリではなく、データベースを使用してください。 Gitをそのまま使用できる場合は問題ありませんが、おそらくその上にドキュメントリポジトリレイヤーを構築する必要があります。したがって、従来のデータベース上にも構築できますか?そして、あなたが興味を持っている組み込みのバージョン管理であれば、なぜ オープンソースドキュメントリポジトリツール の1つを使用しないのですか?たくさんの選択肢があります。
まあ、とにかくGitバックエンドを使用することに決めた場合、基本的には、説明されているように実装すれば、要件を満たすことができます。しかし:
1)「お互いにプッシュ/プルするサーバーのクラスター」について言及しました-私はしばらくの間それについて考えてきましたが、まだよくわかりません。アトミック操作として複数のリポジトリをプッシュ/プルすることはできません。並行作業中に、いくつかのマージが混乱する可能性があるのではないかと思います。
2)必要ないかもしれませんが、リストにないドキュメントリポジトリの明らかな機能はアクセス制御です。サブモジュールを介して一部のパス(=カテゴリ)へのアクセスを制限することもできますが、ドキュメントレベルで簡単にアクセスを許可することはできないでしょう。
私の2ペンスの価値。少し憧れていましたが......インキュベーションプロジェクトの1つで同様の要件がありました。 yoursと同様に、ドキュメントデータベース(この場合はxml)とドキュメントのバージョン管理に関する重要な要件です。これは、多くのコラボレーションユースケースを持つマルチユーザーシステム用でした。私の好みは、主要な要件のほとんどをサポートする利用可能なオープンソースソリューションを使用することでした。
追いかけるために、十分にスケーラブルな方法(ユーザー数、使用量、ストレージ、およびコンピューティングリソース)で両方を提供する製品を見つけることができませんでした。すべての有望な機能についてgitに偏っていました。 (可能性のある)解決策。 gitオプションをさらにいじったように、シングルユーザーの視点からマルチ(ミリ)ユーザーの視点に移行することは明らかな課題になりました。残念ながら、私はあなたのように実質的なパフォーマンス分析を行うことができませんでした。 (.. lazy /早期に終了.... forバージョン2、マントラ)Power to you !.とにかく、私の偏った考えは、次の(まだ偏った)代替案に変化しました:別々の領域、データベース、およびバージョン管理で最高のツールのメッシュアップ。
まだ進行中の作業(...およびわずかに無視されています)の間に、モーフィングされたバージョンは単にこれです。
本質的には、データベースにバージョン管理プラグインを追加することになります。統合グルーは、開発する必要があるかもしれませんが、はるかに簡単かもしれません。
どのように動作するか(想定)は、プライマリマルチユーザーインターフェイスのデータ交換がデータベースを介して行われることです。 DBMSは、マルチユーザー、同時実行性e、アトミック操作などのすべての楽しく複雑な問題を処理します。バックエンドでは、VCSは単一のデータオブジェクトのセットに対してバージョン管理を実行します(同時実行性なし、またはマルチユーザーの問題)。データベース上の有効なトランザクションごとに、バージョン管理は、効果的に変更されたデータレコードに対してのみ実行されます。
インターフェースのりについては、データベースとVCSの間の単純なインターワーキング機能の形式になります。設計に関しては、単純なアプローチはイベント駆動型インターフェースであり、データベースからのデータ更新がバージョン管理手順をトリガーします(ヒント: Mysql、トリガーとsys_exec() 何とか...)。実装の複雑さに関しては、シンプルで効果的な(スクリプトなど)から、複雑で素晴らしい(プログラムされたコネクタインターフェイス)まであります。すべては、あなたがどれだけクレイジーになりたいか、そしてどれだけの汗をかいているかにかかっています。私は簡単なスクリプトが魔法をするはずだと考えています。そして、最終結果であるさまざまなデータバージョンにアクセスするための簡単な方法は、VCSのバージョンタグ/ ID /ハッシュで参照されるデータをデータベースのクローン(データベース構造のクローン)に追加することです。繰り返しますが、このビットは、インターフェイスの単純なクエリ/翻訳/マップジョブになります。
まだ対処すべき課題と未知の問題がいくつかありますが、それらの影響と関連性の大部分は、アプリケーションの要件とユースケースに大きく依存すると思います。いくつかは、単に非問題になる可能性があります。いくつかの問題には、2つの主要モジュール、データベースとVCS、高頻度のデータ更新アクティビティを伴うアプリケーションのパフォーマンスマッチング、データとしてのgit側での経時的なリソース(ストレージと処理能力)のスケーリング、およびユーザーが含まれます成長:安定、指数関数的、または最終的にプラトーの
上記のカクテルのうち、ここに私が現在醸造しているものがあります
いくつかの楽しい事実-gitは圧縮などのストレージを最適化するために実際に物事をクリアし、オブジェクトのリビジョン間のデルタのみのストレージ-はい、gitは適用可能なデータオブジェクトのリビジョン間のチェンジセットまたはデルタのみを格納します(いつ、どのように)。参照:packfiles、Git内部の gutsの奥深く -gitのオブジェクトストレージ(content-addressable filesystem)のレビュー、驚くべき類似点(概念から)パースペクティブ)mongoDBなどのnoSQLデータベースを使用します。繰り返しますが、汗の資本を犠牲にして、2を統合し、パフォーマンスを調整するためのより興味深い可能性を提供する可能性があります
これまでのところ、上記があなたのケースに当てはまるかどうか、それがであると仮定して、最後の包括的なパフォーマンス分析の側面のいくつかにどのように二乗するかを教えてください
libgit2
の上に Rubyライブラリ を実装したため、実装と探索が非常に簡単になりました。明らかな制限がいくつかありますが、完全なgitツールチェーンを取得できるため、非常に解放的なシステムでもあります。
ドキュメントには、パフォーマンス、トレードオフなどに関するいくつかのアイデアが含まれています。
既に述べたように、マルチユーザーのケースは少し扱いにくいです。可能な解決策の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
にマージできますが、ここではおそらく競合の解決が最も難しい部分です。
簡単にするためのアイデアは大歓迎です。