diff options
Diffstat (limited to 'lib/shi-api/shi-api-utils.ts')
| -rw-r--r-- | lib/shi-api/shi-api-utils.ts | 53 |
1 files changed, 43 insertions, 10 deletions
diff --git a/lib/shi-api/shi-api-utils.ts b/lib/shi-api/shi-api-utils.ts index 2203f6b2..1e420356 100644 --- a/lib/shi-api/shi-api-utils.ts +++ b/lib/shi-api/shi-api-utils.ts @@ -89,6 +89,11 @@ export const getAllNonsapUser = async () => { let totalSkipped = 0; // 청크 단위로 매핑과 DB 처리를 동시에 수행 + // 전체 데이터 기준으로 "첫 번째 이메일만 반영" 정책을 지키기 위해 + // 전역 Set 으로 이메일/USR_ID 중복을 관리한다. + const globalSeenEmails = new Set<string>(); + const globalSeenNonsapUserIds = new Set<string>(); + for (let i = 0; i < sourceData.length; i += CHUNK_SIZE) { const chunk = sourceData.slice(i, i + CHUNK_SIZE); debugLog(`[CHUNK ${Math.floor(i/CHUNK_SIZE) + 1}] Processing ${chunk.length} users (${i + 1}-${Math.min(i + chunk.length, sourceData.length)}/${sourceData.length})`); @@ -162,6 +167,20 @@ export const getAllNonsapUser = async () => { continue; } + const emailKey = mappedUser.email.toLowerCase(); + + // 데이터셋 전체 기준 중복 이메일/USR_ID 체크 (첫 번째 건만 반영) + if (globalSeenEmails.has(emailKey)) { + totalSkipped++; + debugWarn(`[GLOBAL] 중복 email 스킵: ${mappedUser.email}, nonsapUserId: ${mappedUser.nonsapUserId}`); + continue; + } + if (mappedUser.nonsapUserId && globalSeenNonsapUserIds.has(mappedUser.nonsapUserId)) { + totalSkipped++; + debugWarn(`[GLOBAL] 중복 nonsapUserId 스킵: ${mappedUser.nonsapUserId}, email: ${mappedUser.email}`); + continue; + } + // 청크 내 중복 체크 if (seenNonsapUserIds.has(mappedUser.nonsapUserId)) { totalSkipped++; @@ -169,7 +188,7 @@ export const getAllNonsapUser = async () => { continue; } - if (seenEmails.has(mappedUser.email.toLowerCase())) { + if (seenEmails.has(emailKey)) { totalSkipped++; debugWarn(`[CHUNK ${Math.floor(i/CHUNK_SIZE) + 1}] 중복 email 발견 (청크 내): ${mappedUser.email}, nonsapUserId: ${mappedUser.nonsapUserId}`); continue; @@ -177,20 +196,34 @@ export const getAllNonsapUser = async () => { // 중복 체크 통과 시 추가 seenNonsapUserIds.add(mappedUser.nonsapUserId); - seenEmails.add(mappedUser.email.toLowerCase()); + seenEmails.add(emailKey); + globalSeenEmails.add(emailKey); + if (mappedUser.nonsapUserId) { + globalSeenNonsapUserIds.add(mappedUser.nonsapUserId); + } mappedChunk.push(mappedUser as InsertUser); } // 매핑된 청크가 있을 경우에만 DB 처리 (트랜잭션으로 처리) if (mappedChunk.length > 0) { - await db.transaction(async (tx) => { - // nonsapUserId를 기준으로 UPSERT (진정한 사용자 고유 ID) - // email은 변경될 수 있지만 nonsapUserId는 변경되지 않음 - await bulkUpsert(tx, users, mappedChunk, 'nonsapUserId', 200); // 청크 내에서도 200개씩 세분화 - }); - - totalUpserted += mappedChunk.length; - debugLog(`[CHUNK ${Math.floor(i/CHUNK_SIZE) + 1}] Successfully processed ${mappedChunk.length} users`); + try { + await db.transaction(async (tx) => { + // nonsapUserId를 기준으로 UPSERT (진정한 사용자 고유 ID) + // email은 변경될 수 있지만 nonsapUserId는 변경되지 않음 + await bulkUpsert(tx, users, mappedChunk, 'nonsapUserId', 200); // 청크 내에서도 200개씩 세분화 + }); + + totalUpserted += mappedChunk.length; + debugLog(`[CHUNK ${Math.floor(i/CHUNK_SIZE) + 1}] Successfully processed ${mappedChunk.length} users`); + } catch (chunkTxError: unknown) { + // 이메일 UNIQUE 제약 위반 등은 건너뛰고 다음 청크 진행 (첫 건 반영 정책) + const pgCode = (chunkTxError as any)?.code; + if (pgCode === '23505') { + debugWarn(`[CHUNK ${Math.floor(i/CHUNK_SIZE) + 1}] Unique constraint 충돌로 청크 스킵: ${(chunkTxError as Error).message}`); + continue; + } + throw chunkTxError; + } } // 청크 간 잠시 대기하여 시스템 부하 방지 |
