いくつかのテーブルからすべてのデータを取得し、何かを再計算して保存するクライアント/サーバー アプリケーションがあります。
例:
各アイテムには「部品表」があります。これは、それを構成する他のアイテムのリストと数量です。したがって、アイテムのコストは、BOM 内のアイテムのコスト * 数量の合計です。最終的に、一部の「ベース」アイテムには BOM がなく、コストが個別に設定されているだけです。(すなわち: 原材料)
例: A の BOM には、2xB と 3xC で構成されていると記載されています。
私が今していることは、なぜこのようにするのか覚えていませんが、DB からすべてのアイテムとすべての BOM を取得し、一度に各アイテムのコストを再帰的に計算することです。1 つのアイテムを計算したら、フラグを立てて、コストを再度やり直さないようにします。(無限再帰も防ぎます)
問題は、これはちょっとばかげているということです。まず、その速度は遅く、変更されていないものを再計算します。さらに悪いことに、十分な大きさの DB を与えると、メモリが不足します。
代わりに、必要に応じてアイテムを再計算できます。アイテムの BOM が変更された場合、その BOM を再計算し、この更新されたアイテムを含むすべての BOM を選択し、それらも再計算します。変更されたアイテムに依存する DB 内の BOM がない最上部に到達するまで、すすぎ、再帰的に繰り返します。
これが実際に意味すること: 一部のアイテムは原材料であり、そのコストは頻繁に更新される可能性があり、一部のアイテムは BOM がほとんど変更されない「エンドユーザー」のものであるとします。ユーザーがこれらの材料の 1 つのコストを変更すると、何千ものアイテムを調べて再計算することになる場合があります。1 つのアイテム/BOM の SELECT に 15 ミリ秒かかるとします (私は Postgresql を使用しています)。1000 のアイテム/BOM を選択するだけで 15 秒かかるため、再計算されたコストを DB のアイテムに更新する必要があります...ああ親愛なる、遅延は数分に変わる可能性があります。
私が働いている会社が使用しているERPソフトウェアは、最初のアプローチを採用しています。つまり、DB全体を一度にバッチ再計算します。これには文字通り何時間もかかり、10年以上の使用でこのアプローチでは問題が蓄積されているようです. バッチ再計算は毎週行われます。
実際に「これを大声で書いた」ので、数分かかることはあまり問題ではないと思います。問題は、私がデータベースをよく理解していないことと、同時実行性について心配していることです。アイテム A の更新には時間がかかるため、アイテム A が更新されている間に誰かが 2 番目のアイテム B を更新する可能性があります。更新しました。
アイテム D は上記の A と B から作られているとします。ユーザー 1 が A を更新すると、サーバー ソフトウェアは DB で数分間マスターベーションを開始し、最終的に D を更新します。しかし、その間にユーザー 2 が B を更新するため、サーバーは最終的に D を再度更新します。
Postgresql のトランザクションを使用すると問題は解決しますか? トランザクションはその時点での DB の状態で開始されるため、トランザクション 1 は D が A1 と B1 から作成され、A1 から A2 に更新されていることを確認しますが、トランザクションが終了してコミットする前に、トランザクション 2 が開始され、A1 も確認されます。そしてB1。T1 は、D = A2 + B1 を再計算してコミットします。しかし、T2 はすでに始まっており、新しい A、A2 は表示されません。そのため、最終的に D = A1 + B2 という DB にコミットしますが、これは正しくありません。D = A2 + B2 である必要があります。
また、一部の処理が重複し、サーバー時間が無駄になります。
T1 と T2 を並列ではなく順番に実行すると、答えは正しいのですが、ユーザー 2 はさらに長く待たなければなりません。また、トランザクションのグループが互いに関係がない場合 (完全に独立した... 依存関係ツリー。つまり、A=X+Y および B=N+M)、並列計算により正しい答えが得られ、さらに高速になります。ユーザー。
重要な注意: 順番に処理する場合でもトランザクションを使用するので、コストを再計算する関数を除いて、ソフトウェアの残りの部分はそのデータを並行して処理できます。
さて、この「プロセス・イン・シーケンス」全体は、もし....DBレイテンシがそれほど「ひどい」ものではないなら、それほど悪くはありません。たとえば、データ全体が RAM に保持される場合、1000 個のオブジェクトを通過するのは簡単です。ああ、でも、データのチャンクをディスク/RAM との間ですばやく移動し、キャッシング (DB を置き換える) を行うシステムを構築したとしても、それはうまくいきません。並行して作業できます。(上記の「重要な注意」)したがって、別のDBを構築することになります。少し速いかもしれませんが、それはばかげている/時間の無駄です。
各アイテムのコストを「キャッシュ」する理由は、それを使用するたびに再計算しないようにするためです。これは、限られたリソースを浪費するだけでなく、DB レイテンシが大きすぎ、同時実行の問題がさらに悪化するためです。
なぜ「彼ら」が大量にそれを行ったのか不思議ではありません...これは私の頭を悩ませています.
Q1: 「最適な」方法でこれをどのように解決しますか?
私の現在の理解から (つまり、以前は黙って無視していた同時実行性の問題に直面した後)、その関数にトランザクションを順番に使用させ、アプリの残りの部分はデータを並行して使用できると考えています。ユーザーに最適です。それが目標です。ユーザーにとっては最善ですが、システムの正確性は保証されています。
後でハードウェアを投入して、ソフトウェアのブラック マジックを使ってレイテンシを短縮できるかもしれませんが、今は自分に嘘をつき始めています。
また、過去 2 か月間、私はいくつかの明らかなこと (プログラミングに関係のないものもありました) に完全に目をつぶっていました。 | |