少し前までは、大量のデータをディスクにすばやく頻繁にダンプし、時々そこからデータを読み取る必要がある機能を開発する必要がありました。 このデータを保存する場所、方法、および支援方法を見つける必要がありました。 この記事では、問題の簡単な分析、解決策の調査および比較を提供します。
タスクコンテキスト
私は、リレーショナルデータベース開発者(SqlServer、MySql、Oracle)向けの開発ツールを開発するチームで働いています。その中には、個別のアプリケーションと、Microsoft Management Studioのような32ビット「ドレッドノート」に組み込まれたものがあります。
挑戦する
次回起動時に閉じるときにIDEで開いているドキュメントを復元します。
ユースケース
保存するドキュメントと保存しないドキュメントを考えずに、家に帰る前にIDEをすばやく閉じます。 次回環境を起動するときに、環境も取得します。これは、終了時に作業を続けていた環境です。
クラッシュ時の開発者の作業のすべての結果(プログラムまたはオペレーティングシステムのクラッシュ、電源オフ)を保存します。
タスク解析
同様の機能はブラウザにあります。 技術的な観点から見ると、彼らにとって生活はずっと楽です:たくさんのURLアドレスを保存するだけでよく、100を超えるタブはめったにありませんが、URL自体は平均200文字です。 したがって、ブラウザの動作に似た動作を取得したいのですが、ドキュメントのコンテンツ全体を保存する必要があります。 どこかに頻繁に、すべてのユーザードキュメントをすばやく保存する必要があることがわかりました。 他の言語とは異なる方法でSQLを使用する場合があるという事実も、タスクを複雑にしました。 私がC#開発者として1000行以上のコードのクラスを書く
ストレージ要件
タスクを分析した後、次のストレージ要件を策定しました。
- 組み込みの軽量ソリューションである必要があります。
- 書き込み速度。
- マルチプロセッサアクセスの可能性。 要件は重要ではありません。同期オブジェクトの助けを借りて私たち自身がそれを提供できるからです。
役割の応募者
最初の正面で不器用なオプション:すべてをAppDataのどこかにあるフォルダーに保存します。
明らかなオプションはSQLiteです。 組み込みデータベース標準。 非常に包括的で人気のあるプロジェクト。
3番目はLiteDBデータベースでした。 Googleの質問に対する最初の回答:「.netの埋め込みデータベース」
初見
ファイルシステム-ファイルはファイルです。 彼らは従わなければならず、名前を考え出す必要があります。 ファイルのコンテンツに加えて、小さなプロパティセット(ディスク上の元のパス、接続文字列、開かれたIDEバージョン)を保存する必要があります。つまり、ドキュメントごとに2つのファイルを作成するか、コンテンツからプロパティを分離するためにフォーマットを作成する必要があります。
SQLiteは、古典的なリレーショナルデータベースです。 データベースは、ディスク上の単一のファイルで表されます。 データスキーマはこのファイルにロールされます。その後、SQLを使用してデータスキーマとやり取りする必要があります。 2つのテーブルを作成できます。1つはプロパティ用、もう1つはコンテンツ用です。一方が必要なタスクがある場合、もう一方は必要ありません。
LiteDBは非リレーショナルデータベースです。 SQLiteと同様に、データベースは単一のファイルで表されます。 完全にC#で記述されています。 魅力的な使いやすさ:ライブラリにオブジェクトを与えるだけで、すでにシリアル化が処理されます。
性能試験
コードを提供する前に、一般的な概念を説明し、比較の結果を提供します。
一般的な考え方は次のとおりです。データベースに書き込まれる小さなファイルの数、中サイズのファイルの平均数、およびいくつかの非常に大きなファイルを比較します。 中サイズのファイルを使用するオプションは実際のファイルに最も近く、小さいファイルと大きいファイルは境界線のケースであり、これらも考慮する必要があります。
FileStreamを介して標準のバッファーサイズでファイルに書き込みました。
SQLiteには、注意を集中する必要があると考えるニュアンスが1つありました。 1つのデータベースセルにドキュメントのすべてのコンテンツ(これらは非常に大きくなる可能性があることを上で書きました)を配置できませんでした。 実際には、最適化のために、ドキュメントのテキストを1行ずつ保存します。つまり、テキストを1つのセルに配置するには、使用するRAMの2倍の量ではなく、すべてのテキストを1行にマージする必要があります。 同じ問題のもう一方は、データベースからデータを読み取ることで取得できます。 そのため、SQLiteには個別のラベルがあり、データは行ごとに格納され、外部キーによってドキュメントのプロパティのみが配置されるテーブルに接続されていました。 さらに、同期モードをオフにして、ログを記録せずに、単一のトランザクションの一部としてデータを数千行のバッチで挿入することにより、データベースをわずかに高速化することがわかりました(このトリックはこことここで見ました )。
LiteDBは、プロパティリスト<string>のいずれかを持つオブジェクトを返し、ライブラリ自体がそれをディスクに保存しました。
テストアプリケーションの開発中であっても、LiteDBの方が好きだということに気付きました。実際、SQLiteのテストコードは120行以上かかり、LiteDBの同じ問題を解決したコードは20未満でした。
テストデータ生成
Filestrings.cs
Program.cs
internal class FileStrings { private static readonly Random random = new Random(); public List<string> Strings { get; set; } = new List<string>(); public int SomeInfo { get; set; } public FileStrings() { } public FileStrings(int id, int minLines, decimal lineIncrement) { SomeInfo = id; int lines = minLines + (int)(id * lineIncrement); for (int i = 0; i < lines; i++) { Strings.Add(GetString()); } } private string GetString() { int length = 250; StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; i++) { builder.Append(random.Next((int)'a', (int)'z')); } return builder.ToString(); } }
Program.cs
List<FileStrings> files = Enumerable.Range(1, NUM_FILES + 1) .Select(f => new FileStrings(f, MIN_NUM_LINES, (MAX_NUM_LINES - MIN_NUM_LINES) / (decimal)NUM_FILES)) .ToList();
Sqlite
private static void SaveToDb(List<FileStrings> files) { using (var connection = new SQLiteConnection()) { connection.ConnectionString = @"Data Source=data\database.db;FailIfMissing=False;"; connection.Open(); var command = connection.CreateCommand(); command.CommandText = @"CREATE TABLE files ( id INTEGER PRIMARY KEY, file_name TEXT ); CREATE TABLE strings ( id INTEGER PRIMARY KEY, string TEXT, file_id INTEGER, line_number INTEGER ); CREATE UNIQUE INDEX strings_file_id_line_number_uindex ON strings(file_id,line_number); PRAGMA synchronous = OFF; PRAGMA journal_mode = OFF"; command.ExecuteNonQuery(); var insertFilecommand = connection.CreateCommand(); insertFilecommand.CommandText = "INSERT INTO files(file_name) VALUES(?); SELECT last_insert_rowid();"; insertFilecommand.Parameters.Add(insertFilecommand.CreateParameter()); insertFilecommand.Prepare(); var insertLineCommand = connection.CreateCommand(); insertLineCommand.CommandText = "INSERT INTO strings(string, file_id, line_number) VALUES(?, ?, ?);"; insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter()); insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter()); insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter()); insertLineCommand.Prepare(); foreach (var item in files) { using (var tr = connection.BeginTransaction()) { SaveToDb(item, insertFilecommand, insertLineCommand); tr.Commit(); } } } } private static void SaveToDb(FileStrings item, SQLiteCommand insertFileCommand, SQLiteCommand insertLinesCommand) { string fileName = Path.Combine("data", item.SomeInfo + ".sql"); insertFileCommand.Parameters[0].Value = fileName; var fileId = insertFileCommand.ExecuteScalar(); int lineIndex = 0; foreach (var line in item.Strings) { insertLinesCommand.Parameters[0].Value = line; insertLinesCommand.Parameters[1].Value = fileId; insertLinesCommand.Parameters[2].Value = lineIndex++; insertLinesCommand.ExecuteNonQuery(); } }
Litedb
private static void SaveToNoSql(List<FileStrings> item) { using (var db = new LiteDatabase("data\\litedb.db")) { var data = db.GetCollection<FileStrings>("files"); data.EnsureIndex(f => f.SomeInfo); data.Insert(item); } }
表は、テストコードの複数の実行の平均結果を示しています。 測定したとき、統計的偏差は無視できました。
SQLiteに対するLiteDBの勝利には驚きませんでしたが、この勝利の順序には驚きました。 LiteDBがファイルを勝ち取ったので圧倒されました。 たとえば、ライブラリリポジトリを少し調べて、ディスク上で非常に有能に実装されたページネーションを見つけたので、これで落ち着きましたが、これはそこで使用されている多くのパフォーマンストリックの1つに過ぎないと確信しています。 また、フォルダ内に多数のファイルがある場合に、ファイルシステムへのアクセス速度がどれほど速く低下するかに注意を払いたいと思います。
LiteDBは、この機能の開発に選ばれましたが、将来は後悔することはほとんどありませんでした。 ライブラリはすべての人のためにネイティブc#で書かれていることが保存され、何かが完全に明確でない場合は、常にソースを読むことができます。
短所
LiteDBが競合他社より優れているという上記の利点に加えて、開発時に欠陥が出現し始めましたが、そのほとんどは図書館の若さに起因します。 「通常の」シナリオの範囲をわずかに超えてライブラリの使用を開始すると、いくつかの問題が見つかりました( #419 、 #420 、 #483 、 #496 )。ライブラリの作成者は常に質問に非常に迅速に答え、ほとんどの問題は非常に迅速に修正されました。 これで、残っているのは1つだけになりました(閉じた状態が気にならないようにします)。 これは、競合するアクセスの問題です。 どうやら、図書館の奥のどこかに、非常に厄介な競合状態が隠されていたようです。 私たち自身のために、このバグをかなり興味深い方法で回避しました。これについては別に書きます。
また、便利なエディターとビューアーの欠如に言及する価値があります。 LiteDBShellがありますが、これはコンソールファン向けです。
UPD:最近ツールを見つけました
まとめ
LiteDBの上に大規模で重要な機能を構築しました。現在、このライブラリを使用する別の主要な機能を開発しています。 ニーズに合わせてインプロセスベースを探している人は、LiteDBとそれがあなたのタスクにどのように当てはまるかを検討することをお勧めします。 。