SQLとアプリケーションで計算を実行する場合の長所と短所は何ですか


154

shopkeeper テーブルには次のフィールドがあります。

id (bigint),amount (numeric(19,2)),createddate (timestamp)

たとえば、上の表があるとします。昨日のレコードを取得し、金額をセントに印刷してレポートを生成したいと思います。

1つの方法は、Javaアプリケーションで計算を実行し、簡単なクエリを実行することです

Date previousDate ;// $1 calculate in application

Date todayDate;// $2 calculate in application

select amount where createddate between $1 and $2 

次に、レコードをループしてJavaアプリケーションで金額をセントに変換し、レポートを生成します

別の方法は、SQLクエリ自体で計算を実行するようなものです。

select cast(amount * 100 as int) as "Cents"
from shopkeeper  where createddate  between date_trunc('day', now()) - interval '1 day'  and  date_trunc('day', now())

レコードをループしてレポートを生成します

ある方法では、すべての処理がJavaアプリケーションで行われ、簡単なクエリが実行されます。その他の場合、すべての変換と計算はSQLクエリで行われます。

上記の使用例は単なる例であり、実際のシナリオでは、テーブルには同様の種類の処理を必要とする多くの列が含まれる場合があります。

パフォーマンスなどの面でどちらのアプローチが優れているか、またその理由を教えてください。


2
日付の計算は、ほとんどまたはまったく効果がありません-SQLエンジンが実際に1回だけ日付を計算すると仮定します。それらをアプリケーションで定義することは、レポートのタイトルやその他の目的のために、いずれかの時点で定義されるため、完全に意味があります。この場合、値を100で乗算することは、どの階層でも実行できます。レンダリングのためにこれらの行をループするためです。* 100は、フロントエンド以外のどの階層でも遅くなることはほとんどありません。どちらの場合も、計算は最小限であり、周囲の操作に比べて小さく、パフォーマンスの問題ではありません。
モルグ。

回答:


206

それは多くの要因に依存します-しかし最も重要なことは:

  • 計算の複雑さ(スケールアップするデータベースサーバーではなく、スケールアウトするため、アプリサーバーで複雑なクランチを実行することをお勧めます)
  • データ量(大量のデータにアクセス/集計する必要がある場合、dbサーバーで実行すると帯域幅が節約され、インデックス内で集計を実行できる場合はディスクIO)
  • 利便性(SQLは複雑な作業に最適な言語ではありません-特に手続き型の作業には適していませんが、セットベースの作業には非常に適しています。ただし、エラー処理はお粗末です)

いつものように、データをapp-serverに戻す場合、列と行を最小化することが有利です。クエリが調整され、適切にインデックスが作成されていることを確認すると、どちらのシナリオでも役立ちます。

あなたのメモについて:

レコードをループします

レコードをループすることは、ほとんどの場合、SQLで行うのが間違っていることです。セットベースの操作を記述することをお勧めします。

原則として、データベースのジョブを最小限に抑え、「このデータを保存し、このデータをフェッチする」ことをお勧めします。ただし、サーバーでのエレガントなクエリによって多くの帯域幅を節約できるシナリオの例は常にあります。

また考慮してください:これが計算コストが高い場合、どこかにキャッシュできますか?

正確な「どちらが良い」が必要場合; 両方の方法でコーディングして比較します(どちらか一方の最初のドラフトが100%調整されていない可能性が高いことに注意してください)。しかし、それに対する典型的な使用法を考慮に入れてください。実際には、一度に5回(個別に)呼び出される場合は、それをシミュレートします。


ループは、多かれ少なかれ「一度に1行ずつ」の処理を意味します。つまり、2 *のネットワークレイテンシと4つのコンテキストスイッチラウンドトリップです。はい:それは高価です。「ネイティブの」DBMS操作は、ディスクI / O(システムコール)を最小限に抑えるためにすべてのハードワークを実行しますが、システムコールごとに複数の行をフェッチするように管理します。一度に1行に少なくとも 4つのシステムコールが必要です。
wildplasser

@wildplasserは不要です。サーバーは、行が到着したときに消費する行をストリーミングしている可能性があります。「リーダー」メタファーは珍しくありません。
マークグラベル

1
@マーク・カベル:まあ、それは依存します。アプリケーションプログラムのフットプリントが論理レコードが1つしかない場合は、だいたいOKです。しかし、私が知っている「フレームワーク」のほとんどは、起動時にすべてのレコードを吸い込み、1つずつ実行する傾向があります。ロックは別の落とし穴です。
wildplasser

経験則としては、SQLサーバーから、最終的に必要のないデータの行を取り戻さないでください。たとえば、集計操作を実行する必要がある場合、それらはSQLに属している可能性があります。テーブルまたはサブクエリ間の結合?SQL。これは、バッジで使用するアプローチでもあり、これまでのところ、スケールに対応しています:-)
Sklivvz

1
@zinkingこれはセットベースの操作になります。そのシナリオでは、ループコードを記述しません。これは実装の詳細です。「ループ」とは、カーソル
Marc Gravell

86

比喩を使ってみましょう。パリで金色のネックレスを購入したい場合、金細工師はケープタウンまたはパリに座ることができますが、これはスキルと好みの問題です。しかし、そのために南アフリカからフランスに大量の金鉱石を送ることは決してないでしょう。鉱石は採掘現場(または少なくとも一般的な地域)で処理され、金のみが出荷されます。アプリとデータベースについても同様です。

限り、PostgreSQLの懸念している、あなたは非常に効率的に、サーバー上ではほとんど何もすることができます。RDBMSは複雑なクエリに優れています。手続き上のニーズには、tcl、python、perlなど、さまざまなサーバー側スクリプト言語から選択できます。ほとんどの場合、私はPL / pgSQLを使用します。

最悪のシナリオは、より大きなセットのすべての1行に対してサーバーに繰り返しアクセスすることです。(これは、一度に1トンの鉱石を出荷するようなものです。)

2番目の行では、一連のクエリを送信する場合、それぞれ前のクエリに依存しますが、すべてのクエリはサーバー上の1つのクエリまたはプロシージャで実行できます。(これは、金と各宝石を別々の船で順次発送するのと同じです。)

アプリとサーバーの間を行き来するのはコストがかかります。サーバーおよびクライアント用。それを削減しようとすると、あなたは勝つでしょう-エルゴ:必要に応じてサーバー側の手順や洗練されたSQLを使用してください。

ほぼすべての複雑なクエリをPostgres関数にパックするプロジェクトを完了しました。アプリはパラメーターを渡し、必要なデータセットを取得します。高速、クリーン、シンプル(アプリ開発者にとって)、I / Oは最小限に抑えられます...低カーボンフットプリントの光沢のあるネックレス。


12
このアナロジーを使用して、他の開発者と有意義に設計上の決定を下すように注意します。類推は、論理的なものというよりは修辞的なものです。とりわけ、アプリサーバーにデータを送信する方が、金鉱石を金細工に送信するよりもはるかに安価です。
Doug

3
鉱石を金に変換する技術がない場合、または高価な場合(鉱山労働者がこれらの他の労働者を殺したいため)は、安価なものに応じて、鉱石または金を送ります。金細工師と鉱夫の間、特に金細工師が複数いる場合。
Dainius 2013

1
私が同意するとおり、SQL @a_horse_with_no_nameでループベースの計算を行うのは常に悪いことだとは思いません。とにかくこれを行う必要がある場合があります。Erwinのメタファーが示すようにデータがフェッチされたときに計算されます。または、データがフェッチされたときに、これを犠牲にしてこれを繰り返す必要があります。
2013

-1これは一方的な議論であるため、トレードオフを無視し、反対側の最善のケースを検討して反論する代わりに、反対側にストローマンを設定します。「アプリとサーバーの間を行き来するのは費用がかかる」-絶対に:しかし、それだけが費用がかかるものではなく、さまざまな費用を相互に比較検討する必要があります。「高度なSQL」クエリまたはストアドプロシージャが特定のケースに最適であることがわかる場合があります。ただし、そのような決定を行う際には、通常、ケースの詳細を考慮する必要があります。
yfeldblum 2013

クールなアナロジーですが、残念ながらそれは間違った仮定に基づいています。金鉱石の発送は非常に一般的です。金のストリッピング比は約1:1(金と廃棄物)ですが、オフサイトで処理する方が安上がりで、より良い設備と仕上がりの品質を利用できます。貨物のサイズにもよりますが、処理効率を0.1%上げると、比較的高い収益が得られる可能性があります(出荷価格が2倍になりますが)-最近の金は非常に高価です。例えば鉄のような他の鉱石も通常出荷されます(鉄のストリッピング率は約60%です!)。
Chris Koston 2013年

18

この場合、データベースエンジンはJavaよりも効率的な10進算術ルーチンを備えている可能性が高いので、SQLで計算を行う方が少し良いでしょう

一般に、行レベルの計算ではそれほど大きな違いはありません。

それが違いを生むところは:

  • SUM()、AVG()、MIN()、MAX()などの集計計算では、データベースエンジンはJava実装よりも桁違いに高速です。
  • 行のフィルタリングに計算が使用されるすべての場所。DBでのフィルタリングは、行を読み取ってから破棄するよりもはるかに効率的です。

12

データアクセスロジックのどの部分をSQLで実行し、どの部分をアプリケーションで実行する必要があるかについて、黒と白はありません。Mark Gravellの表現が好きで、

  • 複雑な計算
  • データ集約的な計算

SQLの力と表現力は、かなり過小評価されています。ウィンドウ関数の導入以来、厳密ではないセット指向の計算の多くは、データベースで非常に簡単かつエレガントに実行できます。

アプリケーションアーキテクチャ全体に関係なく、常に3つの経験則に従う必要があります。

  • データベースとアプリケーションの間で転送されるデータ量を最小限に抑える(DBでの計算に有利)
  • データベースによってディスクから読み込まれるデータの量をスリムに保ちます(不必要なデータアクセスを回避するためにデータベースがステートメントを最適化できるようにするため)
  • 複雑な同時計算でデータベースをCPU制限にプッシュしないでください(アプリケーションメモリにデータを取り込み、そこで計算を実行するため)。

私の経験では、きちんとしたDBAときちんとしたデータベースに関するある程度の知識があれば、すぐにDBのCPU制限に遭遇することはありません。

これらの事柄が説明されているいくつかのさらなる読書:


2

一般に、同じまたは他のプロジェクトの他のモジュールまたはコンポーネントもそれらの結果を得る必要がある可能性がある場合は、SQLで物事を行います。サーバー側でのアトミック操作も優れています。それ以上の処理をせずに最終的な値を取得するには、DB管理ツールからストアドプロシージャを呼び出すだけです。

これは当てはまらない場合もありますが、当てはまる場合には意味があります。また、一般的に、dbボックスは最高のハードウェアとパフォーマンスを備えています。


再利用性はどの層にも存在する可能性があり、SQLでより多くの計算を行う理由(パフォーマンスに関して)ではありません。「一般的にはdbボックス」:これは誤りであり、さらにマークグラベルが言ったように、スケーリングは同じようには機能しません。ほとんどのデータベースは適切に実行するためにほとんどハードウェアを必要とせず、パフォーマンスパターンはアプリケーションサーバーのパフォーマンスとはほとんど関係ありません(つまり、SQLサーバーの予算の2/3を神のようなIOに費やしますが、それ以上は使いません。アプリサーバーのストレージスタックの場合は数百よりも多くなります)。
モルグ。

1

ORMの上に書いている場合や、カジュアルな低パフォーマンスのアプリケーションを作成している場合は、アプリケーションを単純化するパターンを使用してください。高性能アプリケーションを作成し、スケールについて慎重に検討している場合は、処理をデータに移すことで成功します。処理をデータに移すことを強く推奨します。

これを2つのステップで考えてみましょう:(1)OLTP(少数のレコード)トランザクション。(2)OLAP(多くのレコードの長時間スキャン)。

OLTPの場合、高速(1秒あたり10,000〜100,000トランザクション)にしたい場合は、データベースからラッチ、ロック、デッドロックの競合を削除する必要があります。これは、トランザクションの長いストールを排除する必要があることを意味します。クライアントからDBへのラウンドトリップでクライアントに処理を移動することは、そのような長いストールの1つです。(読み取り/更新をアトミックにするために)存続期間の長いトランザクションを使用して、非常に高いスループットを実現することはできません。

Re:水平スケーリング。最新のデータベースは水平方向に拡張されます。これらのシステムは、HAとフォールトトレランスを既に実装しています。それを活用して、アプリケーションスペースの簡素化を試みてください。

OLAPを見てみましょう。この場合、テラバイト単位のデータをアプリケーションにドラッグして戻すのは恐ろしいことです。これらのシステムは、圧縮された事前に整理された列データに対して非常に効率的に動作するように特別に構築されています。最新のOLAPシステムも水平方向にスケーリングし、作業を水平方向に分散する高度なクエリプランナーを備えています(内部的に処理をデータに移動します)。


0

フロントエンドで計算を実行するか、バックエンドで計算を実行するかは、ビジネス実装の目標を決定できるかどうかで非常に決まります。同時に、Javaコードは、SQLコードよりも適切に記述されたパフォーマンスを発揮する場合と、その逆の場合があります。しかし、混乱した場合でも、最初に判断することができます-

  1. データベースのsqlを介して単純なものを達成できる場合は、dbの方がパフォーマンスが高く、そこで計算が行われるため、結果フェッチを使用することをお勧めします。ただし、実際の計算であちこちからの計算が多すぎる場合は、アプリケーションコードを使用できます。どうして?ほとんどの場合、ループのようなシナリオはsqlで適切に処理されないため、フロントエンド言語はこれらのために設計されています。
  2. 多くの場所で同様の計算が必要な場合は、計算コードをdbの最後に配置することで、同じ場所に配置することをお勧めします。
  3. 多くの異なるクエリを介して最終結果を達成するために行われる多くの計算がある場合は、同じコードをストアドプロシージャに配置して、バックエンドから結果を取得してフロントで計算するよりもパフォーマンスを向上させることができるため、db endも実行します。終わり。

コードをどこに配置するかを決める前に考えることができる他の多くの側面があります。1つの認識は完全に間違っています-すべてはJava(アプリコード)で最もよく行うことができます。


0

パフォーマンスの観点を形成する:これは非常に単純な算術演算であり、データベースの下にあるディスクから実際にデータをフェッチするよりもはるかに速く実行できます。また、where句の値の計算は、どのランタイムでも非常に高速になる可能性があります。要約すると、ボトルネックは値の計算ではなくディスクIOである必要があります。

読みやすさの観点から、ORMを使用する場合は、アプリサーバー環境で実行する必要があると思います。ORMを使用すると、セットベースの操作を使用して、基になるデータを非常に簡単に操作できます。とにかく生のSQLを書くつもりなら、そこで計算を行うことに何の問題もありません。SQLも適切にフォーマットされていれば、少し見栄えがよく、読みやすくなります。


0

重要なのは、「パフォーマンス」が定義されていないことです。

私にとって最も重要なのは開発者の時間です。

SQLクエリを記述します。遅すぎる場合、またはDBがボトルネックになる場合は、再検討してください。そのときまでに、2つのアプローチのベンチマークを行い、セットアップに関連する実際のデータ(ハードウェアと現在使用しているスタック)に基づいて決定を下すことができます。


0

特定の例やベンチマークがなければ、パフォーマンスの違いを説明できないと思いますが、別の見方があります。

あなたはどちらをより良く維持できますか?たとえば、フロントエンドをJavaからFlash、HTML5、C ++などに切り替えることができます。膨大な数のプログラムがそのような変更を経ているか、複数のデバイスで作業する必要があるため、そもそも複数の言語で存在しています。

適切な中間層がある場合でも(上記の例ではそうではないようです)、その層が変更され、JBossがRuby / Railsになる可能性があります。

一方、SQLバックエンドをリレーショナルDBではないものでSQLに置き換えることはまずありません。そうしたとしても、フロントエンドを最初から書き直す必要があるので、要点はわかりません。

私の考えは、DBで計算を行う場合、すべてを再実装する必要がないため、後で2番目のフロントエンドまたは中間層を作成する方がはるかに簡単になるということです。しかし実際には、「人々が理解できるコードを使用してこれをどこで実行できるか」が最も重要な要素だと思います。


jbossからrubyに変更する場合、dbを変更する可能性が非常に高く(そして、いずれにしてもこれらの計算を採用する必要があります)、nosqlなどの別の何かに変更できる可能性は低くありません。
Dainius

0

これに答える方法を単純化するには、負荷分散を検討することです。負荷が最も大きい容量に配置したい(意味がある場合)。ほとんどのシステムでは、すぐにボトルネックになるのはSQLサーバーなので、おそらく答えは、SQLが必要以上に1オンスの仕事をしたくないということです。

また、ほとんどのアーキテクチャでは、システムのコアを構成するのはSQLサーバーであり、追加されるのは外部システムです。

しかし、上記の計算は非常に簡単なので、システムを限界まで押し込まない限り、配置するのに最適な場所は、配置したい場所です。数学が、たとえば距離の計算のためにsin / cos / tanを計算するような簡単なものではなかった場合、その努力は簡単ではなくなり、注意深い計画とテストが必要になる場合があります。


0

この質問に対する他の回答は興味深いものです。驚いたことに、誰もあなたの質問に答えていません。あなたは不思議に思っています:

  1. クエリでCentにキャストする方が良いですか?centsへのキャストがクエリに何かを追加するとは思いません。
  2. クエリでnow()を使用する方が良いですか?クエリで日付を計算するのではなく、クエリに日付を渡したいと思います。

詳細:質問1では、端数の集計が丸め誤差なしで機能することを確認したいとします。数値19,2はお金に見合うと思います。2番目の場合、整数は問題ありません。お金のためにフロートを使用することはこの理由で間違っています。

質問2については、プログラマーとして「今」と見なされる日付を完全に制御できるようにしたいと思います。now()のような関数を使用すると、自動単体テストを作成するのが難しい場合があります。また、トランザクションスクリプトが長い場合は、変数をnow()に設定し、その変数を使用して、すべてのロジックでまったく同じ値を使用することをお勧めします。


0

この質問に取り組むために実際の例を挙げましょう

ohlcデータの加重移動平均を計算する必要がありました。計算するために、それぞれに記号が付いた約134000のキャンドルがあります。

  1. オプション1 Python / Nodeなどで実行する
  2. オプション2 SQL自体で実行する!

どちらがいいですか?

  • 私がこれをPythonで行う必要がある場合、基本的に、最悪の場合はすべての保存されたレコードをフェッチし、計算を実行してすべてを保存する必要があります。
  • 新しいキャンドルを取得するたびに加重移動平均が変化することは、定期的に大量のIOを実行することを意味しますが、これは私の兆候ではありません
  • SQLでは、おそらくすべてを計算して保存するトリガーを作成するだけでよいので、時々、各ペアの最終WMA値をフェッチするだけで済み、それがはるかに効率的です。

必要条件

  • すべてのキャンドルのWMAを計算して保存する必要がある場合は、Pythonで計算します
  • しかし、最後の値しか必要ないため、SQLはPythonよりもはるかに高速です。

励ましを与えるために、これは加重移動平均を実行するPythonバージョンです

WMAはコードで実行

import psycopg2
import psycopg2.extras
from talib import func
import timeit
import numpy as np
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute('select distinct symbol from ohlc_900 order by symbol')
for symbol in cur.fetchall():
cur.execute('select c from ohlc_900 where symbol = %s order by ts', symbol)
ohlc = np.array(cur.fetchall(), dtype = ([('c', 'f8')]))
wma = func.WMA(ohlc['c'], 10)
# print(*symbol, wma[-1])
print(timeit.default_timer() - t0)
conn.close()

SQLによるWMA

"""
if the period is 10
then we need 9 previous candles or 15 x 9 = 135 mins on the interval department
we also need to start counting at row number - (count in that group - 10)
For example if AAPL had 134 coins and current row number was 125
weight at that row will be weight = 125 - (134 - 10) = 1
10 period WMA calculations
Row no Weight c
125 1
126 2
127 3
128 4
129 5
130 6
131 7
132 8
133 9
134 10
"""
query2 = """
WITH
condition(sym, maxts, cnt) as (
select symbol, max(ts), count(symbol) from ohlc_900 group by symbol
),
cte as (
select symbol, ts,
case when cnt >= 10 and ts >= maxts - interval '135 mins'
then (row_number() over (partition by symbol order by ts) - (cnt - 10)) * c
else null
end as weighted_close
from ohlc_900
INNER JOIN condition
ON symbol = sym
WINDOW
w as (partition by symbol order by ts rows between 9 preceding and current row)
)
select symbol, sum(weighted_close)/55 as wma
from cte
WHERE weighted_close is NOT NULL
GROUP by symbol ORDER BY symbol
"""
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute(query2)
# for i in cur.fetchall():
# print(*i)
print(timeit.default_timer() - t0)
conn.close()

信じられないかもしれませんが、クエリは、重み付け移動平均を実行するPure Pythonバージョンよりも高速に実行されます!!! 私はそのクエリを書くために一歩一歩進んだので、そこにぶらさげればうまくいくでしょう

速度

0.42141127300055814秒Python

0.23801879299935536秒のSQL

データベースに134000の偽のOHLCレコードがあり、1000株に分割されています。これは、SQLがアプリサーバーよりも優れている例です。


1
ただし、これを数百万回できるだけ早く実行する必要がある場合は、dbレプリカよりも並列pythonアプリを生成する方がはるかに簡単です。SQLに依存する特定のスケールまでは確かに高速/安価ですが、最終的には、アプリケーションでこの計算を実行する方がよい転換点があります。
レニー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.