diff options
| author | joonhoekim <26rote@gmail.com> | 2025-07-31 09:34:29 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-07-31 09:34:29 +0000 |
| commit | 0728ce2e0c085b8f1e8699bcdbe3d2000208bc74 (patch) | |
| tree | 3b6299d082314d55065e16a1cf09a0abf2118088 /lib | |
| parent | 10f90dc68dec42e9a64e081cc0dce6a484447290 (diff) | |
(김준회) MDG 쿼리 배치처리 도입 (네트워크 왕복 오버헤드 해결 목적), 마이그레이션간 DELETE 제거, 환경변수 정리
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/soap/batch-utils.ts | 86 | ||||
| -rw-r--r-- | lib/soap/utils.ts (renamed from lib/soap/mdg/utils.ts) | 0 |
2 files changed, 86 insertions, 0 deletions
diff --git a/lib/soap/batch-utils.ts b/lib/soap/batch-utils.ts new file mode 100644 index 00000000..785c85eb --- /dev/null +++ b/lib/soap/batch-utils.ts @@ -0,0 +1,86 @@ +import { inArray, sql } from "drizzle-orm"; +import db from "@/db/db"; + +/** + * 대용량 INSERT/UPSERT 및 하위 테이블 교체 처리를 위한 공통 유틸 함수 모음. + * + * - `bulkUpsert` : 단일 PK(혹은 UNIQUE) 컬럼을 기준으로 다건 UPSERT 수행 + * - `bulkReplaceSubTableData` : FK 컬럼(IN ... ) 조건으로 기존 데이터 삭제 후 신규 데이터를 배치 삽입 + * + * NOTE: Drizzle ORM 의 onConflictDoUpdate 구문은 한 번의 INSERT 문으로도 여러 레코드의 + * UPSERT 를 처리할 수 있으므로, 네트워크 왕복 횟수를 크게 줄일 수 있다. + */ + +/** + * Primary/Unique Key 하나를 기준으로 여러 레코드를 UPSERT 한다. + * @param tx Active transaction object from `db.transaction` + * @param table Drizzle table schema object + * @param data Rows to upsert + * @param uniqueCol Column name that has UNIQUE constraint (e.g. 'MATNR') + * @param chunkSize Split size to avoid exceeding Postgres parameter limit (default 1000) + */ +export async function bulkUpsert<T extends Record<string, unknown>>( + tx: Parameters<Parameters<typeof db.transaction>[0]>[0], + table: any, // Generic Drizzle table type – use `any` to stay flexible + data: T[], + uniqueCol: string, + chunkSize: number = 500, +) { + if (!data.length) return; + + // Build SET clause once, using excluded.* reference for every column except PK / createdAt / id + const buildSetClause = (sample: T) => { + const setObj: Record<string, unknown> = { updatedAt: new Date() }; + for (const col of Object.keys(sample)) { + if (col === uniqueCol || col === "id" || col === "createdAt" || col === "updatedAt") continue; + setObj[col] = sql.raw(`excluded."${col}"`); + } + return setObj; + }; + + const setClause = buildSetClause(data[0]); + + for (let i = 0; i < data.length; i += chunkSize) { + const chunk = data.slice(i, i + chunkSize); + await tx.insert(table) + .values(chunk as any) + .onConflictDoUpdate({ + target: table[uniqueCol], + set: setClause, + }); + } +} + +/** + * 여러 부모 레코드에 매핑된 하위 테이블(자식 테이블) 데이터를 한 번에 교체한다. + * 1) parentIds 에 해당하는 기존 데이터를 삭제한 뒤 + * 2) 새 데이터 배열을 배치 삽입한다. + * + * @param tx Active transaction object + * @param table Drizzle table schema object (child table) + * @param data Rows to insert after deletion + * @param parentField Column object that references the FK to parent (e.g. MATERIAL_MASTER_PART_MATL_DESC.MATNR) + * @param parentIds Parent id list to match IN (...) + * @param chunkSize Batch insert split size (default 1000) + */ +export async function bulkReplaceSubTableData<T extends Record<string, unknown>>( + tx: Parameters<Parameters<typeof db.transaction>[0]>[0], + table: any, + data: T[], + parentField: any, + parentIds: string[], + chunkSize: number = 1000, +) { + if (!parentIds.length) return; + + // 1. 기존 데이터 일괄 삭제 + await tx.delete(table).where(inArray(parentField, parentIds)); + + // 2. 새 데이터 일괄 삽입 (chunking) + if (!data.length) return; + + for (let i = 0; i < data.length; i += chunkSize) { + const chunk = data.slice(i, i + chunkSize); + await tx.insert(table).values(chunk as any); + } +} diff --git a/lib/soap/mdg/utils.ts b/lib/soap/utils.ts index 52c82d47..52c82d47 100644 --- a/lib/soap/mdg/utils.ts +++ b/lib/soap/utils.ts |
