Supabase 기반의 완벽한 서버 사이드 ACID 트랜잭션 및 스키마 정규화 가이드

다국어 번역 관리 시스템(TMS)에서 데이터 정합성은 서비스의 생명선과도 같다. 수만 개의 번역 항목과 용어집 자산 중 단 하나라도 데이터베이스 적재 과정에서 누락되거나 잘못 매핑되면, 제품의 로컬라이징 신뢰도에 치명타를 입기 때문이다.

최근 진행된 ‘Translation Manager’ 프로젝트의 Phase 3~5 스프린트에서는 기존 시스템이 안고 있던 구조적 한계를 극복하고 엔터프라이즈급 안정성을 확보하기 위한 대대적인 백엔드 리팩토링을 단행하였다.

다대다(N:M) 관계 설계를 위한 스키마 정규화부터, API 레이어 병목을 해결한 통합 중복 검사 서비스, 그리고 클라이언트 트랜잭션의 한계를 극복한 서버 사이드 RPC 기반의 ACID 마이그레이션 엔진 구축 과정과 PostgREST 캐시 트러블슈팅 사례를 상세히 공유한다.

1. Schema 정규화 – 단일 컬럼의 한계 극복과 다대다(N:M) 관계 설계

문제 상황: 단일 product_code 컬럼의 한계

기존 데이터베이스 스키마는 translations 테이블 내부에 product_code 텍스트 필드가 직접 하드코딩되어 박혀 있는 단순 구조였다.

단일 제품 제한: 하나의 번역 텍스트나 리소스 항목이 여러 제품군에서 공통 공유될 수 없음.

데이터 중복 발생: 동일한 키와 번역본이 여러 서비스 프로덕션에서 재사용될 때마다 레코드를 복사해서 새로 인서트해야 함.

유지보수 비용 가중: 특정 제품 코드 변경 시 수만 행에 달하는 데이터베이스 테이블 로우를 통째로 업데이트해야 하는 위험 수반.

해결책: Link 테이블 패턴을 이용한 구조적 분리

다대다(N:M) 관계형 매핑을 유연하게 지원하기 위해 코어 테이블에서 product_code 컬럼을 과감히 제거(Drop)하고, 교차 관계를 전담 추적할 translation_productsglossary_products 링크 테이블을 신설하는 정규화를 단행하였음.

SQL 예시문

기존에 축적되어 있던 실데이터의 정합성을 보장하기 위해, 컬럼 유실 전 서브쿼리를 구동하여 기존 1:1 매핑 정보를 신규 링크 테이블로 안전하게 이전(INSERT INTO … SELECT) 시킨 후 레거시 필드를 Drop 처리하였음.

2. Duplicate Check Service – 분산된 로직의 통합 중복 검사

문제 상황: 파편화된 정책과 API 성능 저하

기존 아키텍처에서는 대량 마이그레이션 프리뷰 단계(SELECT COUNT(*))와 개별 UI 저장 단계(SELECT * LIMIT 1)의 중복 체크 소스 코드가 제각각 파편화되어 동작하고 있었음.

이로 인해 마이그레이션은 대소문자를 엄격히 구분하고, 개별 저장은 구분하지 않는 등의 정책 불일치가 발생하였고, 대용량 파일 파싱 시 중복 조회를 위해 수백 번의 원격 데이터베이스 API 호출이 발생하여 인프라 병목을 가중시켰음.

해결책: 5분 TTL 캐싱 기반의 통합 서비스 구축

정책 일원화와 호출 비용 최적화를 위해 대소문자를 무시하는 인덱스 스캔(ilike) 조건절을 탑재하고, 메모리 단에 5분 만료 TTL(Time-To-Live) 캐싱 구조를 장착한 DuplicateCheckService 레이어를 신설하였음.

스크립트

이 고도화된 통합 서비스를 통해 데이터 검증 정책이 완벽하게 일치되었으며, 마이그레이션 일괄 프리뷰 시 Promise.all 비동기 병렬 처리 구문과 맞물려 초고속 중복 판별 성능을 확보하게 되었음.

3. PostgreSQL RPC 기반의 완벽한 서버 사이드 ACID 트랜잭션 가동

문제 상황: 클라이언트 사이드 트랜잭션의 한계

기본적으로 Supabase JavaScript SDK 라이브러리는 브라우저나 Next.js 서버 단에서 구동되는 클라이언트 사이드 트랜잭션 API를 원천적으로 지원하지 않는다.

기존에는 오류 발생 시 이미 성공한 인서트를 역순으로 추적해 지우는 보상 트랜잭션(Compensating Transaction) 코드를 작성해 사용했으나, 로직이 극도로 복잡하고 네트워크 단절 시 데이터베이스에 찌꺼기가 남는 등 완벽한 원자성을 보장하기 어려웠음.

해결책: Stored Procedure(RPC)를 통한 All-or-Nothing 구현

PostgreSQL 엔진 내부에서 트랜잭션 블록을 원자적으로 제어할 수 있도록 데이터베이스 내장 함수 프로시저 체계인 migrate_batch를 PL/pgSQL 언어로 설계하여 배포하였음.

중단 없는 데이터 이주를 위해 대량의 JSONB 어레이 데이터를 통째로 덤프 받아 데이터베이스 코어 단에서 순회 컴파일 연산을 수행하도록 최적화하였음.

SQL 관련

클라이언트 단(rpc-migration.ts)과 데이터 접근 저장소 레이어(SupabaseMigrationRepository) 역시 단일 .rpc('migrate_batch', ...) 호출 구조로 깔끔하게 래핑 개정하였음. 이로써 기하급수적으로 증가하던 API 네트워크 왕복 비용이 단 1회로 유실 없이 고속화되었음.

4. 트러블슈팅: PGRST202 Schema Cache 지옥의 원인과 정밀 해법

문제와 증상 분석

데이터베이스 단에 신규 RPC 함수를 DROP/CREATE 빌드한 직후, Next.js API 백엔드 라우터 단에서 PostgREST 엔진 게이트웨이가 함수 서명 명세를 인지하지 못하는 치명적인 크래시 오류가 관측되었음.

원인 분석 및 해결 파이프라인

PostgREST Schema Cache 병목: Supabase의 고속 REST API 중계 레이어인 PostgREST는 데이터베이스 DDL 구조와 오버로딩 함수 메타데이터를 메모리에 강하게 캐싱함.

파라미터 인덱스 미스매치: 개발 가동 단계에서 배치 고유 키인 p_batch_id 변수를 추가하여 서명을 변경했으나, 메모리 캐시는 과거의 매개변수 순서와 개수를 고집하여 런타임 매칭 실패를 야기함.

동기화 한계: 가상 커넥션 풀링 환경 특성상 NOTIFY pgrst, 'reload schema' 명령어를 전송하더라도 분산 인스턴스 전역의 캐시가 즉각 동기화되지 않는 한계가 포착되었음.

최종 해결 방안:

우선 PostgREST 컴파일러가 유추하기 가장 이상적인 형태로 p_batch_id 식별자 매개변수를 SQL 프로시저 서명과 TypeScript 클라이언트 바인딩 파라미터 양측 모두에서 첫 번째 최선행 최상단 인덱스로 완벽하게 정렬 동기화시켰음.

이후 캐시 고착 현상을 완전히 도려내기 위해 Supabase 원격 관리자 대시보드 인터페이스로 진입하여 Settings ──> General ──> Restart Project 하드 하드웨어 재시작 공정을 단행하여, 전 분산 게이트웨이 인스턴스가 깨끗한 상태의 최신 스키마 캐시 버전을 강제 강제 로드하도록 조치하여 버그를 완전 격퇴하였음.

스크립트

5. 엔터프라이즈 아키텍처 성과 요약

이번 Phase 3~5 아키텍처 대개편 공정을 통해 Translation Manager 프로젝트는 단순한 파일 적재 CRUD 시스템의 한계를 뚫고 정밀한 정합성을 담보하는 무결점 데이터 플랫폼으로 거듭났음.

정규화 과정을 거치며 다대다 링크 관계의 유연성이 무한 확보되었고, 5분 TTL 분산 캐싱을 활용해 인프라 트래픽을 드라마틱하게 청소하였음.

또한, 불완전 유실 찌꺼기 자산을 주기적으로 영구 자동 세척해 줄 가비지 컬렉터 프로시저(cleanup_orphaned_migration_data) 스케줄링 가동을 위해, 서버 환경 변수 단에 CRON_SECRET 마스터 키를 주입 바인딩 마감하였으니 후속 운영팀은 배포 본 가동 전 해당 환경 변수 래핑 상태를 최종 확인바람.

댓글 남기기