summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-07-31 09:34:29 +0000
committerjoonhoekim <26rote@gmail.com>2025-07-31 09:34:29 +0000
commit0728ce2e0c085b8f1e8699bcdbe3d2000208bc74 (patch)
tree3b6299d082314d55065e16a1cf09a0abf2118088 /lib
parent10f90dc68dec42e9a64e081cc0dce6a484447290 (diff)
(김준회) MDG 쿼리 배치처리 도입 (네트워크 왕복 오버헤드 해결 목적), 마이그레이션간 DELETE 제거, 환경변수 정리
Diffstat (limited to 'lib')
-rw-r--r--lib/soap/batch-utils.ts86
-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