web-dev-qa-db-ja.com

SQLデータベースを使用して時限イベントアーキテクチャを作成する方法

この問題は一般的な性質のものであると思うので、私の質問のタイトルは一般的ですが、ステージを設定するために、具体的な例を提供します。

データベーステーブルによって駆動される独自のワークフローエンジンを使用しています。これらのテーブル内には、ワークフローを表す有向グラフが潜んでいます。グラフにはステージとアクティビティが含まれています。 2つのステージノード間に線が引かれ、結果のアクティビティノードには実行されるコードが含まれます。 CSScriptを使用して、その場でコードをコンパイルおよび実行します。

ワークフロー内で、タスクレコードは実行される作業を表します。各タスクには、関連するメタデータがXML形式で含まれています。タスクレコードは有向グラフを走査し、タスクがアクティビティを通過するときにコードが実行されます。そのため、任意の時点で、各ステージには、アクティビティで実行されるのを待っているx個のタスクが含まれる場合があります。

アクティビティでタスクを実行するには、タスクをスケジュールする必要があります。日時、タスクID、ステージID、アクティビティIDを含むスケジュールレコードは、このタスクが次に実行される時間と場所を決定します。定期的に、期限のあるスケジュールレコードを返すクエリを実行し、返されたレコードごとにアクティビティインスタンスを立ち上げて実行し、タスクレコードをパラメーターとして渡します。

このクエリは、毎秒10回実行されていました。最近、クエリがレコードを返さなかった回数を数えるコードを追加しました。この数が60に達した場合は、クエリ間隔を1秒あたり1回に減らし、再度カウントを開始します。カウントが再び60に達した場合、間隔を1分に1回に減らします。クエリ結果にレコードが表示される場合は、間隔を1秒あたり10回に戻し、カウントプロセスを再開します。正味の効果は、スケジュールテーブルはビジー状態のアクティビティ期間中に急速にポーリングされ、静かな期間中にまばらにポーリングされることです。この1つの簡単な変更だけで、Azureインスタンスごとに1か月あたり数百ドル節約できると予想しています。

だから私の質問です。

これは明らかにポーリングパターンです。それを作成する方法はありますか"event-driven、"定期的にデータベースをポーリングする必要がなく、スケジュールレコードの期限が切れたときにのみデータベースがヒットするようにしますか?

6
Robert Harvey

一般的な解決策は、非同期通知をサポートするデータベースを使用することです。いくつかは行います:

  • Oracle-オブジェクトの変更通知(オブジェクト変更通知またはOCN)および指定されたクエリの結果の変更(クエリ結果変更通知またはQRCN)の登録を許可します。
  • PostgreSQL-スタンドアロンコマンドまたは関数の一部としてNOTIFYステートメントを使用して生成されたタグとオプションのペイロードを含む単純な通知。 (後者はトリガーの一部である可能性があります。)クライアントは、接続ハンドルでLISTENステートメントとselectingを発行することにより、通知をサブスクライブできます(言語バインディングによってどのように変化するか)。
  • SQL Server-クライアントがWAITFORステートメントとRECEIVEステートメントの組み合わせを使用してイベントをリッスンできる組み込みのキューシステム。 OracleのようなOCN/QRCNもある(または持っていた)場合があります。
  • Sybase-クライアントが要求した場合にクライアントでコールバックを呼び出すことができる登録済みのプロシージャがあります。 (これについては肯定的ではありません。)

そうでないもの(MySQL、DB2)で困っている場合は、他の回答で説明されている方法のいずれかを使用して、帯域外で実行する必要があります。

何かが変更されたことをデータベースが通知するメソッドを作成したら、次のイベントが発生するまでの時間を決定するクエリを実行して、通知を待つことができます。通知を受け取った場合は、クエリ/待機サイクルを繰り返します。通知が届かない場合は、計算した時間に達したことを意味し、イベントが要求することをすべて実行する必要があります。これにより、何かが発生することが確実であることがわかっている場合にのみデータベースにクエリを実行するようになります。

4
Blrfl

特に1秒あたり複数回ポーリングする予定のスケジュールに対して、データベースに対してクエリを繰り返し実行すると、Scheduleオブジェクトのメモリ内キャッシュが非常に役立つことがわかります。

アプリケーションサーバーが水平方向にスケーラブルで負荷分散されていると想定すると、クラスター内のノードがオンラインになると、初期化を実行してグローバルスレッドセーフキューを構築できます。各スケジュールタスクをメモリ内のキューに保持することは、並べ替え可能なデータであるため理にかなっています。これは、最初はクラスター内のノードプロセスごとに1つのデータベースクエリを構成します。

キューをポーリングする

ポーリング操作のコストは、基本的に、ソートされたキューの最初の要素のピークです。この操作がメモリ内にあるキューは、ナノ秒単位で測定できます。最初のアイテムが期日である場合は、このプロセスが開始されます。

イベント駆動型のアクティビティ生成

MQのようなものが役に立つ場合があります。キューから次のタスクをポップすると、タスクの詳細を含むメッセージをMQに配置できます。一連のアクティビティインスタンス生成プロセスは、このキューをリッスンできます。つまり、最も利用可能な、または最も速いNodeフェッチすることにより、メッセージを取得し、アクティビティを実行する責任があります。

新しいスケジュールされたタスクはどうですか?!

新しいスケジュールされたタスクをシステムに追加するには、別のリスニングプロセスで別のMQを使用します。これらのプロセスが、データベーステーブルを新しいスケジュールとタスクで更新する責任を負います。ただし、すべてのメモリ内プロセスリストを更新する必要があります。これを実現するにはさまざまな方法がありますが、トピックのようなものはそのようなユースケースの優れたソリューションです。

ここのトピックの詳細情報: http://activemq.Apache.org/how-does-a-queue-compare-to-a-topic.html

各Nodeプロセスは、新しいスケジュールされたタスクをメモリ内キューに適用するために使用するトピックにサブスクライブできます。

なぜこれが優れたアプローチなのですか?

多少複雑ですが、スケーラブルで、復元力があり、効率がよく、迅速に回復できるという点が大きな点です。ノードは削除または追加することができ、データベースは新しいNodeを正しく初期化して貢献を開始できるようにするための元帳としてのみ使用されます。

1
maple_shaft

これはイベント駆動型のソリューションではありませんが、特定の問題に対する代替ソリューションの可能性があると思います。

あなたが遭遇している問題は、揮発性または不揮発性メモリに情報の一部を保存するかどうかを決定しているときに遭遇するリスク/報酬のトレードオフの典型であるように思えます。不揮発性メモリの方が安く安全ですが、データの取得にはかなり時間がかかり、通常、システムの制約によりサイズが制限されます。

各タスクについて説明するこのメタデータは、長期の予定アイテムと同様に、データベースに適切に保存されているように聞こえます。しかし、あなたの投稿では、次に何を実行するかを知るためにデータベースを継続的にスキャンしているプロセスについて説明しています。このimmediateキューはアプリケーションにとって確かに重要ですが、アプリケーションが夜間シャットダウンされた後はpersistが必要であるように聞こえません。アプリは、今何を実行するかを知って、次に進む必要があるだけです。

重要なリファクタリングになる可能性があることを明らかにしているかもしれませんが、スケジューラのその部分をデータベースからデータ構造の形でアプリケーション層まで移動できないかどうか疑問に思います。スケジュールされたすべてのタスクについてデータベースを直接ポーリングするのではなく、これは1時間に1回(または定義した任意の時間セグメント)だけを実行して、次の時間セグメント中に実行してそれらをアプリケーション層のデータ構造。これにより、アプリケーションが実行する必要のあるデータベース呼び出しの量が大幅に削減されます。スケジューラは、ローカルに保存されたこのデータ構造を直接ポーリングして、即時タスクを実行できます。もちろん、これにはschedulingクラスがこの処理時間セグメントを認識し、すぐにスケジュールされたタスクを(データベースではなく)データ構造に直接割り当てる必要があります。

しかし、最終的な結果は、アプリケーションレイヤーをすぐに処理するために活用することでデータベースを本当に長期的なストレージに制限することを除いて、基本的に現在と同じシステムになります。

1
DanK

私が思いつく唯一のことは、何らかのWebサービス呼び出しを使用して、タスクを実行する必要があることをWatcherプロセスに事前に通知することです。

さらに一歩進んで、RabbitMQなどの通知システムを利用して、メッセージをキューに送信し、そのタスクの実行に必要な情報を継続することができます。 Watcherはこのキューをサブスクライブし、処理するものがある場合にのみデータベースにアクセスできます。

そうは言っても、RabbitMQサーバーがダウンした場合に保留中のタスクを実行する方法が必要になります。これにより、システムのフォールトトレラント性が非常に高くなります。

0
Greg Burghardt

私が理解しているように、タスクは実行する必要があります。

  • 実行予定です
  • または、それは別のタスクによって作成されたばかりです
  • または、次のステップに移行して実行の準備ができました

実行予定のタスクが、実行すべき数秒後に実行されることを気にしますか? (私はそうではないと想定しています)

したがって、クエリを実行して、60秒ごと、およびタスクテーブルに追加するたびに新しいタスクを見つけます。これにより、新しく作成された「今すぐ実行」タスクは高速になりますが、ポーリングははるかに少なくなります。

または、SqlDependencyを使用して、タスクがテーブルに追加されたときに通知を受け取ります。次に、次のタスクが期限になるまでの長い間、クエリが低く返されるようにします。

0
Ian

これはポーリングなしで解決できます。

あなたはタイムイベントだけに興味があると思います。

  • 実行されていないすべてのタイムイベントを含むデータベーステーブルがあります。
  • sql-query "getNextEvent"があり、最小のevent-datetimeをまだ実行していない次を返します。
  • getNextEventは、event-datetime-queueが変更されるたび、またはtime-event-actionが終了するたびに実行されます。
  • このevent-datetimeがnowより前の場合(過去)、イベントは期限切れになり、すぐに実行できます。
  • このevent-datetimeが将来の場合、event-time-egineはそのdatetimeまでスリープできます。

バッテリー効率Android目覚まし時計はこのように動作します

0
k3b