ディレクトリをキャッシュするいくつかの手順

ドキュメント管理システムでの作業の過程で、クライアント側で使用されるディレクトリをキャッシュするというタスクが発生しました。 このシステムは3リンク(DB-アプリケーションサーバー-クライアントパート)として設計されたため、想像する余地がたくさんありました。

初期条件:数十のディレクトリ、それぞれが数レコードから数万レコードまでのボリュームが異なる。 ほとんどのディレクトリの各エントリには、有用なデータ(通常は文字列)とレコード識別子(整数)が格納されます。



最初のステップ


クライアント上のディレクトリにアクセスする最も簡単な方法は、最初の呼び出しの前にそれらをキャッシュすることです。 そして、これらのディレクトリでの作業が終了したら、メモリから削除します。 たとえば、このディレクトリが必要なウィンドウに入るたびにロードされ、ウィンドウを閉じると削除されます。 ソリューションは簡単ですが、クライアントとサーバー間のトラフィックが大きすぎます。



第二段階


最適化は、サーバー上で変更されていない場合、更新時にすべてのデータを再度受信しないようにします。 これを行うには、サーバーからディレクトリをダウンロードするときに、ディレクトリ自体だけでなく、その変更のラベル(ディレクトリの「バージョン」(タイムスタンプ)を判別できる番号)も取得する必要があります。

アプリケーションサーバーの起動時に、各ディレクトリのこの数は1に等しくなります(ゼロは予約されていますが、約以下です)。 サーバー上のディレクトリを変更するたびに、番号に1が追加されます。 タイムスタンプが整数であるという事実により、非常に単純なスレッド保護機能であるInterlockedIncrementを使用できます。

クライアントがディレクトリを取得する必要がある場合、クライアントが認識している最後のタイムスタンプをサーバーに送信します。 一致する場合、サーバーはクライアントに0を渡し、それ以上は渡しません。 つまり クライアントでは、参照が関連しています。 クライアントとサーバーのラベルが異なる場合、サーバーはラベルとディレクトリの新しいバージョンをクライアントに渡します。

この場合のレーキは2つあります。 最初のレーキは、サーバーがクライアントにタイムスタンプ値を送信する必要があることです。これは、データベースからのディレクトリの要求の前でした。 そうしないと、サーバーが古いデータと新しいタイムスタンプをクライアントに転送するときに状況が発生する可能性があります。 タイムスタンプ更新を伴う2番目のレーキ:サーバーは、トランザクションが正常に完了した後にのみディレクトリのタイムスタンプを更新する必要があります。 最初のトランザクションが終了するまでデータは別のクライアントに転送されないため、これは重要です(複数のクライアントがあり、誰でも同時にデータを変更および要求できます)。



第三段階


上記の方法は、別の待ち伏せが発見されるまで長い間機能しました-高遅延接続(LANではなくインターネット経由で接続されている場合)、ほとんどの場合、各ディレクトリのサーバーからの応答を待機し、ディレクトリ自体を転送しませんでした。 ほとんどのアクションの前に約12個のディレクトリがあり、ほとんどの場合、更新を要求すると、サーバーは更新が不要であることを返します。 状況は判明しました-ディレクトリーの更新にはマイクロ秒かかり、サーバーからの応答を待つのは10-100ミリ秒です。 その結果、一般的な更新には約1秒(それぞれ100ミリ秒の10個のディレクトリ)かかります。

解決策は最も簡単です。 1つのパッケージ内のすべての必要なディレクトリに関する要求をすぐにサーバーに送信します。 つまり ペアの配列<ディレクトリID> + <クライアントが認識している最後のディレクトリラベル> サーバーは、配列<ディレクトリID> + <現在のラベル> +(必要な場合)<ディレクトリ自体>の形式で応答を送信します。

これにより、ユーザーはリモートデスクトップを使用せずに通常どおりリモートで作業できました。



第4ステップ


ユーザーの数が増え、データがより頻繁に更新されました。 1つのエントリで変更されたとしても、10万のエントリで構成されるディレクトリを完全に更新しなければならなかったことは残念でした。

ソリューションは頼みます。 ディレクトリ変更のログをアプリケーションサーバーに保存します-各タイムスタンプの追加および削除されたレコードのID。 ディレクトリが変更されるたびに、ログに変更を記録します(ディレクトリ要素のIDと変更の種類のみがログに保存されます)。 また、クライアントがラベルを送信すると、サーバーは送信されたラベルからログを見て、追加されたレコードをクライアントに渡し(データベースから取得)、削除されたレコードのIDを渡します。 クライアントとサーバー間のトラフィック、およびデータベースの負荷が何百回も削減されます。

UPD:変更ログは、データベースではなくアプリケーションサーバーのRAMに保存されます。 これにより、データベースの負荷が軽減されますが、操作がより複雑になります(詳細については後述)。

ここでの主な落とし穴は、ディレクトリが変更されたときにログへの書き込みが直接発生し、トランザクションのロールバックのイベントでは、変更がログに書き込まれたが実際には行われなかったことです。

解決策は、トランザクションが確認されるまで予備スレッドを各スレッドに保存することでした。 トランザクションの処理が完了した後にのみ、すべてのフローからアクセス可能なメインログに予備ログを書き込むことができます。



habrasocietyが最適化のための次のステップを促す場合、それは素晴らしいことです。



All Articles