n8n Postgres · Supabase · MySQL 연동
Upsert·트랜잭션·연결 풀까지 — 워크플로가 DB 를 안전하게 두드리는 4가지 패턴.
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/MariaDB | WordPress 가 이미 쓰고 있으면 같은 인스턴스 재활용 가능. n8n MySQL 노드 안정 | UPSERT 문법이 Postgres 보다 떨어짐 (ON DUPLICATE KEY UPDATE 만, RETURNING 미지원) |
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 CONFLICT 는 UNIQUE 인덱스가 충돌 대상 컬럼에 걸려있어야 작동한다. 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 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 가 한쪽을 죽인다. 패턴:
- batch 분할 전에 데이터를 UPSERT 키 (PK 또는 UNIQUE) 로 ORDER BY
- 같은 키 범위는 같은 batch 안에 가도록
- 그래도 deadlock 발생 시 17편 Retry on Fail + 지수 백오프 (5s·10s·20s)
이 패턴이 자리 잡으면 새벽 동기화가 안정적으로 돌아간다. 다음 15편 Merge 노드 + 16편 Sub-Workflow 로 가면 DB 작업을 재사용 가능한 모듈로 분리하는 법까지 완성.
다음 글
n8n 교재 15편 — Merge 노드 + 조인 패턴. Append · Keep Key · SQL Join 으로 두 데이터 스트림 합치기.