Merged PR 636: ライセンス発行処理が遅い問題の解決およびトランザクションが効いてなければ効くよう修正する

## 概要
[Task3243: ライセンス発行処理が遅い問題の解決およびトランザクションが効いてなければ効くよう修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3243)

ライセンス発行処理が重複して実行できてしまう不具合を修正しました。
■修正内容
・ライセンス注文テーブルの情報を取得する際に、行ロックを取得するよう修正
・ライセンス注文テーブルに、「注文元アカウントID、POナンバー」の2カラムを対象とするインデックスを作成
・以前に外部キー制約をつけた際、自動で作成されていたインデックスを削除

■ロックについて
共有ロックと排他ロックがある
・共有ロック:共有ロック取得中でも、他のトランザクションが共有ロックを取得できる
       排他ロックは取得できない
・排他ロック:排他ロック取得中は、他のトランザクションは共有ロック・排他ロック共に取得できない
今回の修正では、デフォルト設定で共有ロックを取得していた箇所を、明示的に排他ロックを取得するようにした。

■行ロックについて
・インデックス行に対してロックをかけている
 →インデックスが作成されていない、検索条件にヒットしないなどでうまく動かない
  例)インデックスが作成されていないと、テーブル全体のロックとなってしまう
・上記の都合で検索条件が範囲指定のものにロックをかける際は注意が必要。(今回は一意指定なので問題なし)

■SQLiteを使ったユニットテストが`pessimistic_write`に対応していない件について
`process.env.NODE_ENV`の値を参照(テスト実行中は`test`、ビルドした環境で動かすと`undifind`)し、
テスト実行の場合`pessimistic_write`を付与しないようクエリを修正した。

## レビューポイント
インデックスについて懸念点があるか?

## UIの変更
なし

## 動作確認状況
ローカルで以下を確認
■発行処理について
・同じ注文に対し複数タブで発行処理を実行し、後発の処理が「ライセンス発行済みエラー」となることを確認
・同一アカウントからの異なるPOナンバーの注文を同時に発行し、行ロックによる待ちが発生せず並列に処理されることを確認
・別アカウントからの同一POナンバーの注文を同時に発行し、行ロックによる待ちが発生せず並列に処理されることを確認
■インデックスについて
同一アカウントからの異なるPOナンバーの注文を同時に発行
・インデックスを作成している状態で、行ロックによる待ちが発生せず並列に処理されることを確認
・インデックスを削除した状態で、行ロックによる待ちが発生することを確認
・migrate:up/downが正しく動作することを確認

## 補足
以前のアカウント削除PBIで一時的に設定した外部キー制約の作成時に、自動でインデックスも作成されていたようです。
必要ないインデックスはどこかで削除する必要があるかと思っています。
This commit is contained in:
oura.a 2023-12-27 02:01:24 +00:00
parent 6f62a016d4
commit 9852004a36
3 changed files with 32 additions and 2 deletions

View File

@ -0,0 +1,7 @@
-- +migrate Up
ALTER TABLE `license_orders` ADD INDEX `idx_from_account_id_and_po_number` (from_account_id,po_number);
ALTER TABLE `license_orders` DROP INDEX `license_orders_fk_from_account_id`;
-- +migrate Down
ALTER TABLE `license_orders` DROP INDEX `idx_from_account_id_and_po_number`;
ALTER TABLE `license_orders` ADD INDEX `license_orders_fk_from_account_id` (from_account_id);

View File

@ -295,3 +295,16 @@ export const TERM_TYPE = {
* @const {string}
*/
export const USER_AUDIO_FORMAT = 'DS2(QP)';
/**
* NODE_ENVの値
* @const {string[]}
*/
export const NODE_ENV_TEST = 'test';
/**
* SQLのlockOptionの種類
*/
export const DB_LOCK_MODE = {
PESSIMISTIC_WRITE: 'pessimistic_write',
};

View File

@ -1,5 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, EntityManager, In, Not } from 'typeorm';
import { DataSource, In } from 'typeorm';
import {
LicenseOrder,
License,
@ -9,9 +9,11 @@ import {
} from './entity/license.entity';
import {
CARD_LICENSE_LENGTH,
DB_LOCK_MODE,
LICENSE_ALLOCATED_STATUS,
LICENSE_ISSUE_STATUS,
LICENSE_TYPE,
NODE_ENV_TEST,
SWITCH_FROM_TYPE,
TIERS,
} from '../../constants';
@ -415,15 +417,23 @@ export class LicensesRepositoryService {
const licenseOrderRepo = entityManager.getRepository(LicenseOrder);
const licenseRepo = entityManager.getRepository(License);
let lockOption = {};
// テスト環境の場合は悲観的ロックを行わない
if (process.env.NODE_ENV !== NODE_ENV_TEST) {
lockOption = { mode: DB_LOCK_MODE.PESSIMISTIC_WRITE };
}
const issuingOrder = await licenseOrderRepo.findOne({
where: {
from_account_id: orderedAccountId,
po_number: poNumber,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
...lockOption,
});
// 注文が存在しない場合、エラー
if (!issuingOrder) {
// 注文が存在しない場合、エラー
throw new OrderNotFoundError(`No order found for PONumber:${poNumber}`);
}
// 既に発行済みの注文の場合、エラー