有害なメッセージの処理

このトピックでは、Service Broker を使用するアプリケーションで、有害なメッセージの自動検出を利用せずに、有害なメッセージを検出してキューから削除する方法について説明します。

Service Broker には、有害なメッセージを自動的に検出する機能が用意されています。有害なメッセージの自動検出により、キューからメッセージを受信するトランザクションが 5 回ロールバックされると、キューの状態が OFF に設定されます。この機能は、アプリケーションがプログラムで検出できない致命的なエラーに対する保護手段を提供します。ただし、通常の処理では、できるだけこの機能を使用しないでください。有害なメッセージの自動検出によってキューが停止するので、有害なメッセージが削除されるまで、アプリケーションのすべての処理が中断されます。代わりに、アプリケーション ロジックの一環として、有害なメッセージの検出と削除を試みます。

ここで説明する方法では、メッセージが特定の回数失敗した場合にそのメッセージを削除することを想定しています。多くのアプリケーションでは、この想定は有効です。ただし、アプリケーションでこの方法を使用する前に、次の点を考慮してください。

  • アプリケーションのエラー数が信頼できるかどうか。アプリケーションによっては、正常の操作時であってもメッセージが失敗する場合があります。たとえば、受注アプリケーションでは、注文を処理するサービスの処理の方が、新しい顧客レコードを追加するサービスよりも時間がかかりません。このような場合、新しい顧客の注文をすぐに処理できないことは正常です。メッセージが有害なメッセージかどうかを判断する際に遅延を考慮する必要があります。サービスは、いくつかのエラーを考慮してからメッセージを削除する必要があります。

  • メッセージの内容をすばやく確実に確認し、成功の可能性がないことを検出できるかどうか。確認できる場合は、プログラムでメッセージを処理できなかった回数を数える方法よりも優れています。たとえば、従業員名や従業員 ID 番号が指定されていない経費報告書は処理できません。このような場合、処理不可能なメッセージを処理しようとせず、メッセージにすばやく応答した方が効率的なプログラムになります。同様に、他の検証も検討してください。たとえば、ID 番号が指定されていても、その番号が正当な範囲を超えている場合 (負の数値の場合) などは、メッセージ交換をすぐに終了します。

  • エラーが発生した後にメッセージを削除する必要があるかどうか。アプリケーションで大量のメッセージを処理し、各メッセージの有効期間が制限されている場合、操作の失敗の原因となるメッセージをすぐに削除することが最も効率的な場合があります。たとえば、メッセージが発信先のサービスからの進行状況レポートを提供する場合、発信側サービスでは、空の進行状況レポートのメッセージを処理しないで受信をコミットすることで、空の進行状況レポートを破棄することを選択できます。この場合は、メッセージ交換を続行します。

アプリケーションで有害なメッセージを処理する方法を決める場合は、次の点を考慮してください。

  • アプリケーションがエラーおよびメッセージの内容をログに記録する必要があるかどうか。多くの場合、この操作は不要です。ただし、アプリケーションによっては、メッセージの内容を保存しておくことが適切な場合もあります。

  • アプリケーションがエラーに関する他の情報をログに記録する必要があるかどうか。場合によっては、メッセージ交換に関する他の情報を追跡する必要があります。たとえば、sys.conversation_endpoints カタログ ビューを使用して、有害なメッセージを生成したリモート ブローカー インスタンスを識別することができます。

  • アプリケーションがメッセージ交換をエラーで終了する必要があるかどうか。または、サービスのコントラクトを使用して、メッセージ交換を終了せずにエラーを示すことをアプリケーションに許可する必要があるかどうか。多くのサービスでは、有害なメッセージを受信することは、コントラクトに記述されたタスクを完了できないことを意味します。この場合、アプリケーションはメッセージ交換をエラーで終了します。それ以外の場合は、1 つのメッセージが失敗してもメッセージ交換を続行できます。たとえば、倉庫から在庫データを受信するサービスは、不明な部品番号を含むメッセージを受信する場合があります。サービスはメッセージ交換を終了するのではなく、オペレーターが後で調査できるようにメッセージを個別のテーブルに保存できます。

例 : 有害なメッセージの検出

次の Transact-SQL の例では、有害なメッセージを処理するためのロジックを含む、状態を保持しない簡単なサービスを示します。このストアド プロシージャでは、メッセージを受信する前に、トランザクションを保存します。プロシージャでメッセージを処理できない場合は、トランザクションを保存した時点にロールバックされます。部分ロールバックでは、そのメッセージのメッセージ交換グループにロックを保持したまま、メッセージをキューに返します。プログラムはメッセージ交換グループのロックを保持し続けるので、別のキュー リーダーによってメッセージが処理されるリスクもなく、失敗したメッセージの一覧を保持するテーブルを更新できます。

次の例では、アプリケーションのアクティブ化ストアド プロシージャを定義します。

CREATE PROCEDURE ProcessExpenseReport
AS
BEGIN
  WHILE (1 = 1)
    BEGIN
      BEGIN TRANSACTION ;
      DECLARE @conversationHandle UNIQUEIDENTIFIER ;
      DECLARE @messageBody VARBINARY(MAX) ;
      DECLARE @messageTypeName NVARCHAR(256) ;

      SAVE TRANSACTION UndoReceive ;

        WAITFOR ( 
                  RECEIVE TOP(1)
                    @messageTypeName = message_type_name,
                    @messageBody = message_body,
                    @conversationHandle = conversation_handle
                    FROM ExpenseQueue
                 ), TIMEOUT 500 ;

        IF @@ROWCOUNT = 0
        BEGIN
          ROLLBACK TRANSACTION ;
          BREAK ;
        END ;

        -- Typical message processing loop: dispatch to a stored
        -- procedure based on the message type name.  End conversation
        -- with an error for unknown message types.

        -- Process expense report messages. If processing fails,
        -- roll back to the save point and track the failed message.

        IF (@messageTypeName =
              '//Adventure-Works.com/AccountsPayable/ExpenseReport')
          BEGIN
            DECLARE @expenseReport NVARCHAR(MAX) ;
            SET @expenseReport = CAST(@messageBody AS NVARCHAR(MAX)) ;
            EXEC AdventureWorks2008R2.dbo.AddExpenseReport
              @report = @expenseReport ;
            IF @@ERROR <> 0
             BEGIN
               ROLLBACK TRANSACTION UndoReceive ;
               EXEC TrackMessage @conversationHandle ;
             END ;
            ELSE
             BEGIN
               EXEC AdventureWorks2008R2.dbo.ClearMessageTracking
                 @conversationHandle ;
             END ;
           END ;
        ELSE

        -- For error messages and end dialog messages, end the
        -- conversation.

        IF (@messageTypeName =
              'https://schemas.microsoft.com/SQL/ServiceBroker/Error' OR
             @messageTypeName =
              'https://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
          BEGIN
            END CONVERSATION @conversationHandle ;
            EXEC dbo.ClearMessageTracking @conversationHandle ;
          END ;


         COMMIT TRANSACTION ;
    END ;
END ;

TrackMessage ストアド プロシージャは、メッセージが失敗した回数を追跡します。これまでにメッセージが失敗していない場合は、このプロシージャによって、メッセージの新しいカウンターが ExpenseServiceFailedMessages テーブルに挿入されます。メッセージが失敗したことがある場合は、メッセージが失敗した回数についてカウンターをチェックします。このプロシージャは、カウンターがあらかじめ定義された数より小さい場合にそのカウンターを加算します。カウンターがあらかじめ定義された数より大きくなると、メッセージ交換をエラーで終了し、そのメッセージ交換のカウンターをテーブルから削除します。

CREATE PROCEDURE TrackMessage
@conversationHandle uniqueidentifier
AS
BEGIN
  IF @conversationHandle IS NULL
    RETURN ;

  DECLARE @count INT ;
  SET @count = NULL ;
  SET @count = (SELECT count FROM dbo.ExpenseServiceFailedMessages
                  WHERE conversation_handle = @conversationHandle) ;

  IF @count IS NULL
    BEGIN
      INSERT INTO dbo.ExpenseServiceFailedMessages
        (count, conversation_handle)
        VALUES (1, @conversationHandle) ;
    END ;
  IF @count > 3
    BEGIN
      EXEC dbo.ClearMessageTracking @conversationHandle ;
      END CONVERSATION @conversationHandle
        WITH ERROR = 500
        DESCRIPTION = 'Unable to process message.' ;
    END ;
  ELSE
    BEGIN
      UPDATE dbo.ExpenseServiceFailedMessages
        SET count=count+1
        WHERE conversation_handle = @conversationHandle ;
    END ;
END ;
GO

ExpenseServiceFailedMessages テーブルの定義には、次のサンプルに示すように、conversation_handle 列および count 列だけが含まれています。

CREATE TABLE ExpenseServiceFailedMessages (
  conversation_handle uniqueidentifier PRIMARY KEY,
  count smallint
) ;

ClearMessageTracking プロシージャは、次のサンプルに示すように、ExpenseServiceFailedMessages テーブルからメッセージ交換のカウンターを削除します。

CREATE PROCEDURE ClearMessageTracking
  @conversationHandle uniqueidentifier
AS
BEGIN
   DELETE FROM dbo.ExpenseServiceFailedMessages
     WHERE conversation_handle = @conversationHandle ;
END ;
GO

ここで紹介した方法は、意図的に簡単にしてあります。ニーズに適したアプリケーションを構築する場合は、このトピックの考え方を基礎として使用してください。たとえば、アプリケーションが状態を保持する場合は、失敗したメッセージに関する追跡情報をアプリケーションの状態テーブルに含める方が効率的です。

上記のストアド プロシージャでは、トランザクションが失敗する原因となるエラーが処理されません。このサービスはトランザクション全体が失敗する原因となるメッセージを受信すると、トランザクションをロールバックします。このロールバックが 5 回発生すると、有害なメッセージの自動検出によって、キューの状態が OFF に設定されます。この場合、有害なメッセージは、別のアプリケーションまたは管理者が削除する必要があります。

メッセージで実行する処理が原因でトランザクションが失敗したと考えられる場合は、TRY ステートメントと CATCH ステートメントを使用して、エラーを処理できます。エラー処理の詳細については、「データベース エンジン エラーの処理」を参照してください。