케이스 스터디
CASE STUDY — 운영

조용히 멈춘 워크플로 잡아내기 — n8n 실행은 성공인데 결과가 0건일 때

도입 자동화 운영 / 전 부서구축 4일
n8nPostgresSlackn8n Schedule

알림이 안 온 게 문제가 아니었습니다. 알림이 안 오는 게 정상처럼 보였다는 게 문제였습니다. 실행 기록은 3주 내내 초록색이었는데, 그동안 아무 일도 일어나지 않았습니다.

자동화를 만들고 나면 관심이 자연스럽게 다음 워크플로로 넘어갑니다. 그리고 조용한 상태를 '잘 돌아가는 중'으로 읽게 됩니다. 그런데 자동화가 죽는 방식은 대부분 요란하지 않습니다. API 응답 형식이 바뀌어 필터가 아무것도 못 걸러내거나, 시트 열 이름이 한 글자 바뀌거나, 크론이 조용히 비활성화됩니다. 전부 '성공'으로 기록됩니다.

workflow-watchdog.workflowLIVE
CR매시 정각PG실행 기록 조회!무실행·0건 판정SLSlack 알림

에러 알림만으로는 왜 부족한가

n8n의 Error Trigger는 훌륭하지만 실패한 실행만 잡습니다. 실무에서 자동화가 죽는 방식은 세 가지고, 그중 둘은 Error Trigger에 걸리지 않습니다.

  • 실패 — 실행이 에러로 끝남. Error Trigger가 잡아줍니다. 이건 이미 해결된 문제입니다.
  • 결과 0건 — 실행은 '성공'인데 처리 건수가 0입니다. 필터 조건이 안 맞거나, 원본 데이터 구조가 바뀌었거나, 조회 쿼리가 빈 결과를 냅니다. n8n 입장에서는 아무 문제 없이 끝난 실행입니다.
  • 무실행 — 워크플로가 아예 안 돌았습니다. 크론이 꺼졌거나, 인스턴스가 재시작 후 비활성 상태거나, 트리거가 걸리지 않았습니다. 알릴 실행 자체가 없으므로 어떤 에러 알림도 발생할 수 없습니다. 셋 중 가장 오래 방치됩니다.
이 세 번째가 핵심입니다. 실패는 '무언가 일어났는데 잘못됐다'이고, 무실행은 '아무 일도 일어나지 않았다'입니다. 후자는 워크플로 안에서는 절대 감지할 수 없습니다 — 실행되지 않은 코드는 자기가 실행되지 않았다는 걸 알릴 방법이 없기 때문입니다. 그래서 바깥에서 지켜보는 워크플로가 하나 더 필요합니다.

어떻게 동작하나

  1. 감시 대상 워크플로들이 매 실행마다 자기 이름·시각·상태·처리 건수를 `workflow_runs` 테이블에 한 줄씩 기록합니다. 워크플로 끝에 Postgres 노드 하나를 붙이는 게 전부입니다.
  2. Schedule Trigger 노드가 매시 정각에 감시 워크플로를 깨웁니다.
  3. Postgres 노드가 워크플로별로 가장 최근 실행 1건최근 3회 성공 실행 중 결과가 0건이었던 횟수를 한 번의 쿼리로 가져옵니다.
  4. Code 노드가 세 가지를 판정합니다 — 마지막 실행이 에러인가(실패) / 기대 주기의 2배가 넘도록 실행 기록이 없는가(무실행) / 최근 3회 성공이 모두 0건인가(결과 0건).
  5. 이상이 하나도 없으면 Code 노드가 빈 배열을 반환해 워크플로가 조용히 끝납니다. Slack 노드는 실행되지 않습니다. 정상일 때 알림이 오지 않는 것이 이 설계의 핵심입니다.
  6. 이상이 있으면 종류별 아이콘과 함께 한 건의 Slack 메시지로 묶어 #automation-alerts 채널에 보고합니다.

필요한 것

  • n8n — 자체 호스팅 또는 Cloud. 특별한 커뮤니티 노드는 쓰지 않습니다. 기본 노드 네 개(Schedule Trigger, Postgres, Code, Slack)만 사용합니다.
  • Postgres 데이터베이스 하나 — 새로 만들 필요 없습니다. 이미 쓰고 계신 DB에 테이블 하나만 추가하면 됩니다. 기록되는 데이터는 워크플로 이름·시각·건수뿐이라 용량 부담이 거의 없습니다.
  • Postgres 자격증명 — n8n → Credentials → Postgres. 호스트, 포트(기본 5432), 데이터베이스명, 사용자, 비밀번호를 넣습니다. n8n이 Docker 안에 있고 DB가 호스트 머신에 있다면 호스트에 localhost가 아니라 host.docker.internal을 넣어야 합니다. 초보자가 가장 많이 막히는 지점입니다.
  • Slack Bot User OAuth Token — Slack 앱을 만들고 chat:write, channels:read 스코프를 부여한 뒤 워크스페이스에 설치. xoxb-로 시작하는 토큰을 n8n → Credentials → Slack API에 등록합니다.
  • 알림 받을 Slack 채널 — 예: #automation-alerts. 채널에서 /invite @봇이름으로 봇을 초대해 두어야 게시됩니다. 초대를 안 하면 not_in_channel 에러가 납니다.
  • 감시할 워크플로 목록과 각각의 기대 실행 주기(분) — 매시간 도는 워크플로면 60, 매일 한 번이면 1440입니다. 이 숫자가 무실행 판정의 기준이 됩니다.

1단계 — 기록 테이블 만들기

감시 대상 워크플로들이 실행 기록을 남길 테이블입니다. psql이나 DBeaver 같은 도구에서 아래를 그대로 실행하면 됩니다. 컬럼은 여섯 개뿐입니다.

CREATE TABLE IF NOT EXISTS workflow_runs (
  id                    bigserial   PRIMARY KEY,
  workflow_name         text        NOT NULL,  -- 워크플로 이름 (n8n에 표시되는 이름과 동일하게)
  run_at                timestamptz NOT NULL DEFAULT now(),
  status                text        NOT NULL,  -- 'success' 또는 'error'
  result_count          integer     NOT NULL DEFAULT 0,  -- 이번 실행이 실제로 처리한 건수
  expected_interval_min integer     NOT NULL   -- 기대 실행 주기(분). 매시간이면 60
);

-- 최근 실행을 빠르게 찾기 위한 인덱스. 행이 쌓여도 조회가 느려지지 않습니다.
CREATE INDEX IF NOT EXISTS workflow_runs_name_time_idx
  ON workflow_runs (workflow_name, run_at DESC);
result_count가 이 케이스 전체에서 가장 중요한 컬럼입니다. '성공했다'가 아니라 '몇 건을 처리했다'를 기록하기 때문에 결과 0건을 잡아낼 수 있습니다. 메일을 3통 읽었으면 3, 한 통도 없었으면 0을 넣습니다.

2단계 — 감시할 워크플로에 기록 노드 붙이기

감시하고 싶은 워크플로마다 맨 끝에 `Postgres` 노드를 하나씩 추가합니다. Operation은 Execute Query로 두고 아래 쿼리를 넣습니다. daily-sales-report60은 해당 워크플로에 맞게 바꾸세요.

INSERT INTO workflow_runs
  (workflow_name, status, result_count, expected_interval_min)
VALUES
  ('daily-sales-report', 'success', {{ $items().length }}, 60);

{{ $items().length }}가 이 노드로 넘어온 아이템 개수, 즉 이번 실행이 실제로 처리한 건수입니다. 처리 건수를 다른 방식으로 세고 있다면 그 값을 넣으셔도 됩니다. 실패까지 기록하고 싶다면 워크플로에 Error Trigger를 두고 같은 테이블에 status'error'로 INSERT하는 노드를 하나 더 붙이면 됩니다.

3단계 — 판정 쿼리

감시 워크플로의 Read Run History 노드에 들어가는 쿼리입니다. 다운로드한 JSON에 이미 들어 있으니 그대로 두셔도 되고, 어떻게 판정하는지 확인하고 싶으면 아래를 보세요. 워크플로 하나당 한 행이 나옵니다.

WITH latest AS (
  -- 워크플로별 가장 최근 실행 1건
  SELECT DISTINCT ON (workflow_name)
         workflow_name, run_at, status, result_count, expected_interval_min
  FROM workflow_runs
  ORDER BY workflow_name, run_at DESC
),
last_three AS (
  -- 최근 3회 '성공' 실행 중 결과가 0건이었던 횟수
  SELECT workflow_name,
         count(*) FILTER (WHERE result_count = 0) AS zero_runs,
         count(*)                                 AS sampled_runs
  FROM (
    SELECT workflow_name, result_count,
           row_number() OVER (PARTITION BY workflow_name ORDER BY run_at DESC) AS rn
    FROM workflow_runs
    WHERE status = 'success'
  ) s
  WHERE rn <= 3
  GROUP BY workflow_name
)
SELECT l.workflow_name,
       l.run_at,
       l.status,
       l.result_count,
       l.expected_interval_min,
       round(EXTRACT(EPOCH FROM (now() - l.run_at)) / 60)::int AS minutes_since_last_run,
       COALESCE(t.zero_runs, 0)    AS zero_runs_in_last_3,
       COALESCE(t.sampled_runs, 0) AS sampled_runs
FROM latest l
LEFT JOIN last_three t ON t.workflow_name = l.workflow_name
ORDER BY l.workflow_name;
⬇︎ 워크플로우 다운로드 (workflow-watchdog.json)
Postgres 자격증명 하나, Slack 자격증명 하나, 채널 이름만 넣으면 바로 돌아갑니다. 워크플로우 상단 Sticky Note에 테이블 생성 SQL까지 포함돼 있습니다.
검증 범위를 정확히 밝힙니다. 판정 쿼리는 PostgreSQL 17에서 네 가지 상태(정상 / 무실행 / 최근 3회 0건 / 마지막 실행 실패)를 모두 담은 픽스처로 실행해 결과를 확인했고, Code 노드 로직은 그 쿼리의 실제 출력을 입력으로 Node.js에서 실행해 세 가지 이상을 모두 정확히 판정하고 정상일 때 빈 배열을 반환하는 것까지 확인했습니다. 워크플로우 JSON의 노드 타입·버전은 기존에 배포 중인 워크플로우와 동일한 조합(scheduleTrigger 1.2 / postgres 2.5 / code 2 / slack 2.3)을 사용했습니다. 다만 실제 Slack 워크스페이스로 메시지가 게시되는 것까지는 이 문서 작성 시점에 확인하지 않았습니다. 마지막 검증: 2026-08-15.

설정 순서 (25분)

  1. 테이블 생성 — 위 1단계 SQL을 DB에서 실행합니다. 결과로 CREATE TABLE, CREATE INDEX가 두 번 출력되면 성공입니다.
  2. Postgres 자격증명 등록 — n8n 좌측 메뉴 → Credentials → Add credential → Postgres 검색 → 호스트/포트/DB명/사용자/비밀번호 입력 → 우측 상단 Test 버튼으로 연결 확인. 초록색 체크가 떠야 다음으로 넘어갑니다. 실패하면 십중팔구 호스트 값 문제입니다(위 '필요한 것' 참고).
  3. Slack 앱 만들기 — api.slack.com/apps → Create New App → From scratch → 앱 이름과 워크스페이스 선택 → 좌측 OAuth & Permissions → Scopes의 Bot Token Scopes에 chat:writechannels:read 추가 → 페이지 상단 Install to Workspace → 발급된 xoxb-로 시작하는 토큰 복사.
  4. Slack 자격증명 등록 — n8n → Credentials → Add credential → Slack API → 방금 복사한 토큰 붙여넣기 → Test로 확인.
  5. 봇 초대 — Slack에서 알림 받을 채널을 열고 /invite @봇이름을 입력합니다. 이 단계를 빠뜨리면 나중에 not_in_channel 에러가 납니다.
  6. 워크플로우 import — 위 다운로드 버튼으로 JSON을 저장한 뒤 n8n → Workflows → 우측 상단 ...Import from File.
  7. 채널 이름 교체Post to #automation-alerts 노드를 열고 REPLACE_WITH_YOUR_CHANNEL_NAME_OR_ID를 실제 채널 이름(예: automation-alerts, # 없이)으로 바꿉니다.
  8. 자격증명 연결Read Run History 노드와 Post to #automation-alerts 노드를 각각 열어 Credential 드롭다운에서 방금 만든 것을 선택합니다.
  9. 기록 노드 붙이기 — 감시하려는 워크플로들을 하나씩 열어 맨 끝에 2단계의 Postgres 노드를 추가합니다. 워크플로 이름과 주기(분)를 각각 맞게 넣으세요.
  10. 테스트 — 감시 워크플로 편집 화면에서 Execute Workflow를 누릅니다. 아직 기록이 없다면 아무 일도 일어나지 않는 게 정상입니다. 일부러 이상을 만들어 확인하려면 다음 단계로.
  11. 일부러 알림 띄워보기 — DB에서 INSERT INTO workflow_runs (workflow_name, run_at, status, result_count, expected_interval_min) VALUES ('test-wf', now() - interval '500 minutes', 'success', 3, 60);을 실행한 뒤 다시 Execute Workflow를 누르면 무실행 알림이 Slack에 떠야 합니다. 확인 후 해당 행은 지우세요.
  12. 활성화 — 우측 상단 Activate 토글을 켭니다. 이제 매시 정각에 자동으로 점검합니다.

임계값 조정

Detect Silence + Zero Runs 노드 맨 위에 값 두 개가 있습니다. 이 케이스에서 실제로 손보게 되는 건 이 둘뿐입니다.

  • `SILENCE_TOLERANCE` (기본 2) — 기대 주기의 몇 배까지 기다렸다 무실행으로 판정할지. 2면 매시간 워크플로가 약 2시간 무실행일 때 알립니다. 너무 낮추면 실행이 조금 밀릴 때마다 알림이 오고, 너무 높이면 발견이 늦어집니다. 처음엔 2로 시작해 알림이 잦으면 3으로 올리세요.
  • `ZERO_RUN_STREAK` (기본 3) — 연속 몇 회가 0건이어야 이상으로 볼지. 원래 0건인 날이 흔한 워크플로(예: 주말에 주문이 없는 곳)라면 5 이상으로 올리는 게 낫습니다.

예외 상황이 있으면 어떻게 되나

  • 모든 워크플로가 정상Code 노드가 빈 배열을 반환해 Slack 노드가 실행되지 않습니다. 알림 노이즈 0건. 이게 기본 상태여야 합니다.
  • 감시 워크플로 자체가 죽으면? — 이 설계의 명백한 한계입니다. 감시자를 감시할 사람은 없습니다. 실무에서는 외부 무료 헬스체크 서비스(healthchecks.io 등)에 감시 워크플로가 매시간 핑을 보내게 하고, 핑이 끊기면 메일이 오도록 이중으로 겁니다. 이 한 줄을 추가하지 않으면 나머지가 전부 무의미해질 수 있습니다.
  • 원래 0건이 정상인 날 — 주말·공휴일에 처리 건수가 0인 게 정상인 워크플로가 있습니다. ZERO_RUN_STREAK를 올리거나, Code 노드에서 해당 워크플로 이름을 판정에서 제외하세요.
  • 점검 주기(1시간)보다 짧은 주기로 도는 워크플로 — 5분마다 도는 워크플로는 무실행을 최대 1시간 늦게 발견합니다. 더 빨리 잡아야 한다면 Schedule Every Hour 노드의 크론을 0 */10 * * * *(10분마다)로 바꾸세요.
  • Slack이 다운된 경우 — 알림 자체가 유실됩니다. 중요한 운영 환경이라면 Slack 노드 뒤에 메일 노드를 하나 더 붙여 이중화하는 것을 권합니다.
  • 기록 테이블이 계속 커지는 것 — 하루 수천 건이 쌓여도 인덱스 덕에 조회는 빠르지만, 오래된 행은 정리하는 게 좋습니다. DELETE FROM workflow_runs WHERE run_at < now() - interval '90 days';를 월 1회 도는 워크플로에 넣어두면 됩니다.
3가지
감지하는 실패 유형
1시간
점검 주기
0건
정상일 때 알림
25분
셋업 시간
워크플로는 아무 쓸모 있는 결과를 내지 못하면서도 '성공'으로 표시될 수 있습니다.n8n 커뮤니티 포럼

당신의 업무도
여기 들어갈 수 있어요.

가장 반복적인 업무 하나만 알려주세요. 자동화 시나리오를 그려드립니다.