n8n 교재 · 고급 18편

n8n Postgres · Supabase · MySQL 연동

Upsert·트랜잭션·연결 풀까지 — 워크플로가 DB 를 안전하게 두드리는 4가지 패턴.

워크플로 노드들이 데이터베이스 실린더 아이콘으로 연결선을 뻗는 일러스트 — n8n DB 연동 컨셉

n8n 으로 매일 100건씩 데이터를 쌓다 보면 어느 시점에 Sheets 의 한계가 온다 — 1000행 넘어가면 느려지고, 동시 쓰기에서 충돌이 나고, 쿼리가 안 된다. 답은 명확. Postgres (또는 Supabase·MySQL) 로 옮기는 것.

이번 18편은 DB 입문이 아니라 n8n 안에서 DB 노드를 안전하게 쓰는 패턴. Upsert 가 왜 update 를 안 할 때가 있는지, 연결 풀은 어떻게 잡는지, 1만 건 batch 처리 시 deadlock 을 어떻게 피하는지 — 운영에서 진짜 마주치는 문제들을 짚는다.

1. 어떤 DB 를 고를까 — 3가지 비교

n8n 은 세 DB 전부 native 노드를 제공한다. 기능은 비슷하지만 운영 비용·복잡도·확장성이 다르다. 한 줄 정리.

옵션강점약점
Supabase (Postgres 호스팅)SQL+REST+Realtime+Auth 묶음. 무료 티어 500MB. n8n 에선 Postgres 노드로 그대로 접속5분 idle 후 sleep (무료) → 첫 쿼리 cold start 2-3초
Self-host Postgres완전 제어·무제한 용량·트랜잭션 보장백업·모니터링 직접 관리. 박준성님 docker compose 안에서 docker-postgres 추가 권장
MySQL/MariaDBWordPress 가 이미 쓰고 있으면 같은 인스턴스 재활용 가능. n8n MySQL 노드 안정UPSERT 문법이 Postgres 보다 떨어짐 (ON DUPLICATE KEY UPDATE 만, RETURNING 미지원)
왜 Postgres 우위 — n8n 의 Workflow Static Data 도 Postgres 백엔드를 정식 지원. JSON 컬럼 타입이 우월하고 RETURNING 절로 Upsert 검증을 단계 1번에 끝낼 수 있다. WordPress MySQL 과 분리해서 별도 Postgres 두는 게 디폴트.

2. Upsert — "insert or update" 가 update 안 할 때 함정

n8n Postgres 노드의 Operation: Insert or Update 가 가장 자주 어긋난다. 증상은 단순 — "row 가 생성은 되는데 같은 키 다시 보내면 update 가 안 됨". 원인은 거의 항상 같다.

원인 1 — UNIQUE constraint 부재

Postgres 의 ON CONFLICTUNIQUE 인덱스가 충돌 대상 컬럼에 걸려있어야 작동한다. n8n 노드의 "Update Key" 필드에 컬럼명만 적었다고 자동으로 UNIQUE 가 만들어지진 않음.

먼저 한 번:

CREATE UNIQUE INDEX user_email_idx ON users (email);

이걸 해두고 n8n 노드의 Update Key 를 email 로 설정해야 정상 Upsert.

원인 2 — Operation 결과를 검증 안 함

n8n 은 "쿼리 실행 성공""row 가 실제 바뀜" 을 구분 안 한다. ON CONFLICT 가 매치 안 돼서 0행 영향이어도 노드는 success. 운영에선 RETURNING 절을 추가 + 다음 IF 노드로 검증이 정공법.

Execute Query 모드로 전환 후:

INSERT INTO users (email, name) VALUES ($1, $2)
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name
RETURNING (xmax = 0) AS inserted, *;

inserted=true 면 신규 insert, false 면 update. 이걸 IF 노드로 분기하면 "신규 가입 환영 메일" 과 "정보 갱신 알림" 을 자동 라우팅 가능.

3. 트랜잭션 — n8n 노드의 한계와 우회

n8n Postgres 노드는 Settings → Execute Queries in Transaction 옵션을 켜면 노드 한 개 안에서 여러 row 가 트랜잭션으로 묶인다. 한 row 라도 실패하면 전체 rollback.

한계는 노드 경계를 넘는 트랜잭션이 불가능하다는 것. 워크플로가 "Postgres insert → API 호출 → Postgres update" 인데 API 가 실패해도 첫 insert 는 commit 된 상태. 진짜 atomicity 가 필요하면 두 가지 우회.

방법 A — 모든 DB 작업을 한 Postgres 노드 (Execute Query) 안에서

BEGIN/COMMIT 명시:

BEGIN;
INSERT INTO orders ...;
UPDATE inventory SET qty = qty - 1 WHERE id = $1;
COMMIT;

중간에 API 호출이 끼면 이 패턴은 못 씀. 그땐 방법 B.

방법 B — Saga 패턴 (보상 트랜잭션)

각 단계마다 "실패 시 되돌릴 action" 을 미리 정의. 예: API 실패 → Error Trigger → 이전에 insert 한 orders.id 를 받아서 DELETE. 17편 (Error Workflow) 의 Rollback 단계가 이걸 구현하는 자리.

4. 연결 풀 + Deadlock — 1만 건 batch 의 함정

"매일 새벽 1만 건 동기화" 같은 워크플로를 14편 Split In Batches 로 짜놓으면 100개씩 끊겨서 도는데, 동시성이 높아지면 두 가지 사고가 난다.

실제 사례 — n8n 커뮤니티 사례: 분당 10k+ row UPSERT 워크플로가 deadlock 으로 멈춤. 원인은 동일 row 를 여러 batch 가 동시 잠금. 같은 키로 ORDER BY 후 batch 분할로 해결.

연결 풀 사이징

n8n Postgres 노드는 호출마다 새 연결을 잡는다. Supabase 무료 티어는 동시 60 연결 한도라 batch 100개가 병렬로 가면 다 막힘. 두 가지 처리:

  • n8n 측 — Split In Batches 의 Batch Size 를 줄임 (100 → 25) + Wait 노드 1-2초 끼움
  • Supabase 측 — Supabase 의 PgBouncer (Transaction mode) 사용. 노드 연결 문자열을 6543 포트로 (5432 직접 연결 대신)

Deadlock 회피

같은 row 를 두 batch 가 동시 잠그면 Postgres 가 한쪽을 죽인다. 패턴:

  1. batch 분할 전에 데이터를 UPSERT 키 (PK 또는 UNIQUE) 로 ORDER BY
  2. 같은 키 범위는 같은 batch 안에 가도록
  3. 그래도 deadlock 발생 시 17편 Retry on Fail + 지수 백오프 (5s·10s·20s)

이 패턴이 자리 잡으면 새벽 동기화가 안정적으로 돌아간다. 다음 15편 Merge 노드 + 16편 Sub-Workflow 로 가면 DB 작업을 재사용 가능한 모듈로 분리하는 법까지 완성.

다음 글

n8n 교재 15편 — Merge 노드 + 조인 패턴. Append · Keep Key · SQL Join 으로 두 데이터 스트림 합치기.

© 2026 주나이테크(주) @JUNAITECH