MQE 技術仕様(tMQLog ベースの通知キュー)
MQE(Message Queue Engine)は、SQL Server のテーブル tMQLog をキューとして使う GIIP の通知送信エンジンです。本書ではテーブルスキーマ、ストアドプロシージャのシグネチャ、送信パイプライン、そして実装者が必ず把握すべき制約を記述します。
⚠️ はじめに — アーキテクチャの誤解を正す
MQE に 外部メッセージブローカーは存在しません。 専用キュープロトコル、別途の通信ポート、失敗メッセージの隔離キュー、メッセージ有効期限といった概念も GIIP には実装されていません。実際の構造は次のとおりです。
| 構成要素 | 実体 |
|---|---|
| キュー | SQL Server テーブル tMQLog |
| プロデューサー | ストアドプロシージャ pApiMQLogPutbyAk / pApiMQLogPutbySk(HTTP または T-SQL から呼び出し) |
| コンシューマー | 送信ワーカー(execmqe)。pApiMQListbySk で未送信一覧を読み、pApiMQUpdatebySk で状態を更新 |
| 送信先設定 | テーブル tMQENotificationConfig(/ja/mq-config 画面 で管理) |
| 配信チャンネル | メール(SMTP)、Slack API |
🔁 パイプライン
🗄️ tMQLog スキーマ
| # | 列 | 型 | NULL | 説明 |
|---|---|---|---|---|
| 1 | mqSn | bigint IDENTITY | N | 主キー。メッセージ通番。API 応答の mqSn はこの値です。 |
| 2 | mqRegdt | datetime | Y | 登録時刻。SP が GETDATE() を設定します。 |
| 3 | mqSchdt | datetime | Y | 送信予定時刻。SP が GETDATE() を設定します(即時送信)。 |
| 4 | mqSentdt | datetime | Y | 送信完了時刻。NULL なら未送信です。 |
| 5 | ktSn | bigint | Y | KVS トリガー通番(tKVSTrigger)。トリガー由来の通知でのみ使用。 |
| 6 | clSn | bigint | Y | KVS チェックログ通番(tKVSChkLog)。 |
| 7 | cSn | bigint | Y | 顧客番号。SK から導出されます。 |
| 8 | mqType | varchar(16) | Y | 配信チャンネル。既定値 EMAIL。 |
| 9 | mqTo | nvarchar(200) | Y | 送信先。SLACK ならチャンネル ID、EMAIL ならメールアドレス。 |
| 10 | mqSubject | nvarchar(500) | Y | 件名。重複防止ゲートの判定キーの一つ。 |
| 11 | mqBody | nvarchar(max) | Y | 本文。 |
| 12 | mqFromEmail | varchar(255) | Y | 送信元メールアドレス。 |
| 13 | mqFromName | nvarchar(100) | Y | 送信者表示名。 |
| 14 | mqErrMsg | nvarchar(1000) | Y | 直近の送信失敗理由。 |
| 15 | mqRetryCnt | tinyint | Y | 試行回数。初期値 0、上限 3。 |
| 16 | mqPriority | tinyint | Y | 優先度。SP は常に 2 を挿入します。小さいほど先に送信。 |
| 17 | mcSn | int | Y | メールコンテンツ通番(tMQEMailContents)。 |
| 18 | mqAiAnalyzed | bit | Y | MQE AI 分析済みフラグ。初期値 0。 |
📥 プロデューサー SP
pApiMQLogPutbyAk — JSON 単一パラメータ方式(推奨)
pApiMQLogPutbyAk
@ak VARCHAR(200), -- 実際は SK。tSecretKey.skey と突き合わせます。
@jsondata NVARCHAR(MAX) -- メッセージ内容の JSON
@jsondata のキー:
| キー | 必須 | 対象列 | 既定値 |
|---|---|---|---|
mqSubject | 必須 | mqSubject | — |
mqBody | 必須 | mqBody | — |
mqTo | 任意 | mqTo | NULL |
mqType | 任意 | mqType | 'EMAIL' |
mqFromEmail | 任意 | mqFromEmail | NULL |
mqFromName | 任意 | mqFromName | NULL |
SP が固定で書き込む値: mqRegdt = GETDATE()、mqSchdt = GETDATE()、mqSentdt = NULL、mqRetryCnt = 0、mqPriority = 2、mqAiAnalyzed = 0。
戻り値は常に 1 行 3 列です。
RstVal (int) | RstMsg (nvarchar) | mqSn (bigint)
HTTP 経路:
POST https://giipfaw.azurewebsites.net/api/giipApiJson
Content-Type: application/x-www-form-urlencoded; charset=utf-8
usertoken=<SK>&text=MQLogPut&jsondata=<URL エンコードした JSON>
Azure Function はこのリクエストを exec pApiMQLogPutbyAK N'<usertoken>', N'<jsondata>' に変換して実行します。
pApiMQLogPutbySk — 個別パラメータ方式
pApiMQLogPutbySk
@sk NVARCHAR(100),
@mqTo NVARCHAR(200),
@mqSubject NVARCHAR(500),
@mqBody NVARCHAR(MAX),
@mqType VARCHAR(16) = 'EMAIL',
@mqFromEmail VARCHAR(255) = NULL,
@mqFromName NVARCHAR(100) = NULL
動作は pApiMQLogPutbyAk と同じですが、@mqTo も必須 です(NULL なら RstVal=400)。HTTP 経路は giipApiSk2 で、text に SP 名とパラメータ名を並べます。
POST https://giipfaw.azurewebsites.net/api/giipApiSk2
Content-Type: application/x-www-form-urlencoded; charset=utf-8
token=<SK>&text=MQLogPut mqTo mqSubject mqBody mqType&jsondata=<URL エンコードした JSON>
mqTo を省略したい場合は pApiMQLogPutbyAk(giipApiJson)を使ってください。
🔐 認証と cSn の決定規則
どちらの SP も次の 1 文で顧客を決定します。
SELECT @cSn = csn FROM tSecretKey WITH(NOLOCK) WHERE skey = @ak; -- PutbySk は @sk
- パラメータ名は
@ak/usertokenですが、AK ではなく SK を渡します。 SP のコメントにもtreating @ak as sk for this contextと明記されています。 - cSn を直接指定するパラメータはありません。 対象顧客を変えるには、その顧客の SK を使う必要があります。
- 一致する SK が無い場合
@cSnは NULL になり、そのメッセージはどの送信先設定とも結び付かないため送信されません。
🛑 重複防止ゲート
INSERT の直前に次の検査が実行されます。
IF EXISTS (
SELECT 1 FROM tMQLog WITH(NOLOCK)
WHERE mqSentdt IS NULL
AND mqSubject = @mqSubject
AND mqTo = @mqTo
AND cSn = @cSn
)
BEGIN
SELECT 200 AS RstVal, N'Skipped: Identical unsent message already exists' AS RstMsg, 0 AS mqSn;
RETURN;
END
設計上重要な性質:
- 判定キーは
(mqSubject, mqTo, cSn)の 3 つ で、本文(mqBody)は見ません。件名が同じなら本文が違っても破棄されます。 - スキップは失敗ではなく成功(
RstVal=200)として応答 されるため、呼び出し側は必ずmqSnを確認する必要があります。 - 判定対象が 未送信メッセージ であるため、送信先設定の欠落などで送信できなかったメッセージが残っていると その件名は永久に塞がれます。
実装指針:
- 定期送信されるレポートは件名に日付または時刻を含めます(
... - 2026-09-16)。 - 応答の
mqSnが 0 なら送信失敗とみなしてログに残します。
📤 コンシューマー SP
pApiMQListbySk — 送信待ち一覧の取得
pApiMQListbySk @ak VARCHAR(64) -- SK
抽出条件(WHERE 句)は次のとおりです。
WHERE ml.mqSentdt IS NULL
AND ISNULL(ml.mqSchdt, DATEADD(SECOND, -1, GETDATE())) < GETDATE()
AND (ml.mqTo IS NOT NULL OR kt.kMailAct = 1 OR mnc.mncSn IS NOT NULL)
ORDER BY ml.mqPriority ASC, ml.mqSchdt ASC
3 番目の条件が重要です。mqTo が NULL の場合、同じ cSn・同じ mqType の有効な tMQENotificationConfig が存在するときにのみ 一覧に含まれます。そうでなければメッセージは保存されたまま永久に待機します。
この SP は次の条件で設定テーブルを結合し、Slack の資格情報も併せて返します。
LEFT JOIN tMQENotificationConfig mnc WITH(NOLOCK)
ON ml.cSn = mnc.csn AND ml.mqType = mnc.mqType AND mnc.mncIsActive = 1
DB の照合順序が SQL_Latin1_General_CP1_CI_AS(大文字小文字を区別しない)であるため、mqType の SLACK と slack は同一として一致します。
pApiMQUpdatebySk — 送信結果の反映
pApiMQUpdatebySk
@ak VARCHAR(64), -- SK
@mqSn BIGINT, -- 対象メッセージ
@mqErrMsg NVARCHAR(1000) = NULL -- 失敗時の理由
| 呼び出し形態 | 動作 |
|---|---|
@mqErrMsg が NULL | mqSentdt = GETDATE()、mqErrMsg = NULL、mqRetryCnt + 1 → 送信完了 |
@mqErrMsg あり、mqRetryCnt < 3 | mqErrMsg を記録し mqRetryCnt + 1。mqSentdt は NULL のままなので 次のバッチが再取得します。 |
@mqErrMsg あり、mqRetryCnt >= 3 | リトライ上限に到達 |
存在しない @mqSn | RstVal=404 |
リトライ上限は SP 内部に @maxRetries = 3 として固定されています。設定で変更することはできません。
⚙️ 送信先設定テーブル tMQENotificationConfig
| 列 | 型 | NULL | 説明 |
|---|---|---|---|
mncSn | int | N | 主キー |
csn | int | N | 顧客番号 |
mqType | varchar(16) | N | email または slack |
mncEmailTo | nvarchar(1000) | Y | 受信者。カンマ区切りで複数指定可 |
mncEmailCc | nvarchar(1000) | Y | CC |
mncEmailBcc | nvarchar(1000) | Y | BCC |
mncSlackBotToken | varchar(255) | Y | Slack Bot User OAuth Token(xoxb-) |
mncSlackChannelId | varchar(100) | Y | チャンネル ID |
mncSlackChannelName | nvarchar(200) | Y | チャンネル表示名(参考用) |
mncIsActive | bit | N | 有効フラグ。0 なら結合対象から外れます |
mncPriority | tinyint | N | 優先度(1〜10) |
mncDescription | nvarchar(1000) | Y | 説明 |
mncCreatedAt / mncUpdatedAt | datetime | N/Y | 作成・更新時刻 |
mncCreatedBy / mncUpdatedBy | int | Y | 作成者・更新者 uSn |
管理用 SP は pApiMQEConfigListbyAK、pApiMQEConfigPutbyAK、pApiMQEConfigDeletebyAK、pApiMQEConfigTestbyAK の 4 つで、/ja/mq-config 画面 が giipApiSk2 経由でこれらを呼び出します。
🧩 その他の MQE 資産
| オブジェクト | 用途 |
|---|---|
tMQEAdvReport + pApiMQEAdvResultPutbySk | MQE AI 分析結果の保存。@mqSn が必要なため tMQLog の行に紐づく構造であり、単独の登録経路ではありません。 |
pApiMQEHealthCheckReportBySK、pApiMQEHealthCheckReportSingleBySK、tMQEHealthCheckRules | ヘルスチェック結果を tMQLog に投入 |
tMQEMailFormLayout、tMQEMailContents、pApiMQEMailFormLayoutGetbySk | メール HTML テンプレート |
pAutoKVSLtoMQL、pMQETrgUpdateAuto | KVS トリガー由来の自動通知を投入 |
tMQCmdLog、pMQCmdList | コマンドログ |
📊 運用実測値(2026-09-16)
- 登録から送信までの所要時間: 最短 2 秒、最長で約 7 分。送信ワーカーが数分間隔のバッチで動作するためです。
- 未送信のまま長期滞留しているメッセージが存在します(2026-02-05 登録、
mqToNULL、当該mqTypeの有効な送信先設定なし)。上記「重複防止ゲート」3 番の実例です。 - リトライ上限に達したメッセージの例:
mqRetryCnt = 3、mqErrMsg = account_inactive(Slack アカウント無効)。
🌏 非 ASCII 文字の取り扱い
件名・本文に日本語 / 韓国語 / 中国語が含まれる場合:
- T-SQL 直接呼び出し: 文字列リテラルに必ず
Nを付けます(@jsondata = N'{...}')。省くと?で保存されます。 - HTTP 呼び出し: リクエストボディを UTF-8 ファイルに保存し
curl --data-binary @ファイルで送ります。非 ASCII 文字をシェル引数で直接渡すと、Windows 環境ではコードページ変換により?となり、そのまま DB に保存されることを実測しました(2026-09-16)。
送信ワーカーには mojibake ガード があります。本文に ? が連続していると送信を拒否し、mqErrMsg に
MOJIBAKE: body contains a run of N consecutive '?' ... (giip #2462) を記録してリトライ上限を使い切ります。
つまり 壊れたメッセージは送信されずキューの中で死にます。 実測(2026-09-16): シェル引数で送った 2 件は
ガードに阻まれて未送信、ファイル経由で送った 1 件は 36 秒後に正常送信されました。
🔬 動作検証の方法
副作用なしで SP の動作を確認するには、トランザクション内で実行してロールバックします。
BEGIN TRAN;
EXEC dbo.pApiMQLogPutbyAk
@ak = 'YOUR_SECRET_KEY',
@jsondata = N'{"mqTo":"ops@example.com","mqSubject":"[verify] 2026-09-16 12:00","mqBody":"verification","mqType":"EMAIL"}';
-- 期待: RstVal=200, RstMsg=Success, mqSn>0
EXEC dbo.pApiMQLogPutbyAk
@ak = 'YOUR_SECRET_KEY',
@jsondata = N'{"mqTo":"ops@example.com","mqSubject":"[verify] 2026-09-16 12:00","mqBody":"second call","mqType":"EMAIL"}';
-- 期待: RstVal=200, RstMsg=Skipped: Identical unsent message already exists, mqSn=0
ROLLBACK;
🔗 関連文書
- MQE 通知メッセージ登録ガイド — 実行可能な呼び出し例と画面の使い方
- Slack ボット設定ガイド — Bot Token の発行とチャンネル招待
バージョン: 2.0
最終更新: 2026-09-16
ソースファイル: giipv3/public/help/mqe-spec.ja.md