본문 바로가기
Clone Coding

N8N & Zapier - Background Jobs

by zzuny-code 2026. 1. 4.
반응형

https://www.youtube.com/watch?v=ED2H_y6dmC8

02:55:05 

 

개발을 하다보면 시간이 오래 걸리는 작업을 처리해야 할 때가 있다.

inngest를 활용해 백그라운드 작업을 구현하는 방법을 다뤄본다.

 

  • Setup Inngest - Inngest를 설정한다 (이벤트 기반 백그라운드 작업/워크플로우 도구 설정)
  • Create a background job - 백그라운드에서 실행될 작업(job)을 생성한다
  • Add mprocs - mprocs를 추가해 여러 프로세스를 동시에 관리/실행한다

 

작업 시작전 백그라운드 작업이 필요한 이유 보기. 

 

* 동기식 처리 방식

- 일반적인 동기 처리 방식에서는 사용자가 요청을 보내면 서버에서 모든 작업을 완료한 후 응답을 반환한다.

// 동기적(synchronous) 작업 처리 방식

┌────────────────────────────┐
│  사용자가 '로그인' 클릭         │
└────────────────────────────┘
              │
              ▼
┌────────────────────────────┐
│      요청 전송               │
└────────────────────────────┘
              │
              ▼
┌────────────────────────────┐
│ 즉각적인 응답 (성공 / 실패)      │
└────────────────────────────┘

 

  • User clicks "Login" → 사용자가 “로그인” 버튼을 클릭함
  • Send request → 서버로 요청을 보냄
  • Instant response (success/fail) → 즉각적인 응답 반환 (성공 / 실패)

 

백그라운드 작업이 없을 시

 

  • 장시간 작업을 동기 요청으로 처리하면 UX와 안정성이 크게 저하됨
  • 사용자는 기다리다 이탈할 수 있고, 결과를 영영 못 받을 수도 있음
  • 이런 문제를 해결하기 위해 백그라운드 작업(Background Jobs) 이 필요

 

┌────────────────────────────────────────┐
│ 사용자가 '요약 생성' 클릭                   │
└────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────┐
│ 네트워크 요청 전송                         │
└────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────┐
│ 백엔드에서 요약 생성                        │
│ (약 30초 소요)                           │
└────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────┐
│ 사용자는 계속 대기함                        │
└────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────┐
│ 위험 요소 발생 가능                        │
│ - 타임아웃                               │
│ - 브라우저 탭 종료                         │
│ - 네트워크 연결 끊김                       │
└────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────┐
│ 사용자가 결과를                            │
│ 받지 못할 수도 있음                        │
└────────────────────────────────────────┘

 

 

  • User clicks "Generate Summary" → 사용자가 “요약 생성” 버튼을 클릭함
  • Send network request → 네트워크 요청을 전송함
  • Backend generates summary (30 seconds) → 백엔드에서 요약을 생성함 (약 30초 소요)
  • User waits… → 사용자가 계속 대기함
  • Risk: timeout / tab closed / connection lost → 위험 요소: 타임아웃 / 탭 종료 / 네트워크 연결 끊김
  • User might never get result → 사용자가 결과를 아예 받지 못할 수도 있음

 

백그라운드 작업을 사용하는 경우

 

  • UX 개선: 사용자가 기다리지 않아도 됨
  • 안정성 향상: 타임아웃·연결 끊김 문제 제거
  • 확장성 확보: 무거운 작업을 안전하게 처리 가능
  • 현대적인 아키텍처: 이벤트/큐 기반 백그라운드 처리

 

┌────────────────────────────────────────────┐
│ 사용자가 '요약 생성' 클릭                        │
└────────────────────────────────────────────┘
                    │
                    ▼
┌────────────────────────────────────────────┐
│ 네트워크 요청 전송                             │
└────────────────────────────────────────────┘
                    │
                    ▼
┌────────────────────────────────────────────┐
│ 백그라운드 작업 큐에 등록                        │
└────────────────────────────────────────────┘
          │                          │
          │                          ▼
          │              ┌──────────────────────────┐
          │              │ 백그라운드 작업 실행          │
          │              └──────────────────────────┘
          │                          │
          │                          ▼
          │              ┌──────────────────────────┐
          │              │ 작업 완료 시 사용자 알림       │
          │              └──────────────────────────┘
          ▼
┌────────────────────────────────────────────┐
│ 즉시 응답 반환                                │
│ “요약을 생성 중입니다”                          │
└────────────────────────────────────────────┘
                    │
                    ▼
┌────────────────────────────────────────────┐
│ 사용자는 탭을 닫거나                            │
│ 다른 작업을 계속해도 됨                          │
└────────────────────────────────────────────┘

 

  • Long-running task example (with background jobs) → 오래 걸리는 작업 예시 (백그라운드 작업 사용)
  • User clicks "Generate Summary" → 사용자가 “요약 생성” 버튼을 클릭함
  • Send network request → 네트워크 요청을 전송함
  • Queue background job → 백그라운드 작업을 큐에 등록함
  • Respond: "Summary is being generated" → “요약을 생성 중입니다”라는 즉시 응답 반환
  • User free to close tab or move on → 사용자는 탭을 닫거나 다른 작업을 자유롭게 진행 가능
  • Background job runs → 백그라운드 작업이 실제로 실행됨
  • User notified when done → 작업 완료 시 사용자에게 알림 전달

 

 

프로젝트 설정

 

Prisma 스키마 작성

prisma/schema.prisma - Workflow 데이터를 저장할 테이블을 정의

model Workflow{
  id String @id @default(cuid())
  name String
}

 

데이터베이스 마이그레이션

 

중요: npx prisma generate 명령어를 실행해야 Prisma Client에서 새로운 모델을 인식할 수 있다. 이 단계를 빠뜨리면 TypeScript에서 prisma.workflow를 찾을 수 없다는 오류가 발생한다.

# 기본 마이그레이션
npx prisma migrate dev

# 마이그레이션 이름을 지정하는 방법 (권장)
npx prisma migrate dev --name workflows-table

# ⭐ 마이그레이션 후 Prisma Client 강제 재생성
npx prisma generate

 

 

 

tRPC 라우터 설정

src/trpc/routers/_app.ts

Workflow를 조회하고 생성하는 엔드포인트 생성.

기존의 getUsers를 getWorkflows로 변경하고, createWorkflow 뮤테이션을 추가

// 변경전 

import { baseProcedure, createTRPCRouter, protectedProcedure } from '../init';
import prisma from '@/lib/db';
export const appRouter = createTRPCRouter({
  getUsers: protectedProcedure.query(({ctx}) => {
      return prisma.user.findMany({
        where:{
          id: ctx.auth.user.id
        }
      })
  }),
});
// export type definition of API
export type AppRouter = typeof appRouter;
// 변경 후 
import { createTRPCRouter, protectedProcedure } from '../init';
import prisma from '@/lib/db';
export const appRouter = createTRPCRouter({
  getWorkflows: protectedProcedure.query(({ctx}) => {    
      return prisma.workflow.findMany()
  }),
  createWorkflow : protectedProcedure.mutation(()=>{
    return prisma.workflow.create({
      data: {
        name : "test-workflow"
      }
    })
  })
});
// export type definition of API
export type AppRouter = typeof appRouter;

 


프론트엔드 구현

src/app/page.tsx

서버 컴포넌트에서 클라이언트 컴포넌트로 변경하고, React Query를 사용해 데이터를 조회

 

 

// 변경전 

import { requireAuth } from "@/lib/auth-utils"
import {caller} from "@/trpc/server"
import { LogoutButton } from "./logout";

const Page = async() =>{
 
  await requireAuth();

  const data = await caller.getUsers();
  
  return(
    <div className="min-h-screen min-w-screen flex items-center justify-center">
      로그인한 사용자만 접근 가능
      <div>
        {JSON.stringify(data, null, 2)}
      </div>
      <LogoutButton/>
    </div>
  )
}

export default Page

 

// 변경 후 
"use client"

import { Button } from "@/components/ui/button";
import { LogoutButton } from "./logout";
import { useTRPC } from "@/trpc/client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const Page = () => {
  const trpc = useTRPC();
  const queryClient = useQueryClient()
  const {data} = useQuery(trpc.getWorkflows.queryOptions())

  const create = useMutation(trpc.createWorkflow.mutationOptions({
    onSuccess: () => {
      // 생성 성공 시 목록을 다시 불러옴
      queryClient.invalidateQueries(trpc.getWorkflows.queryOptions())
    }
  }))
  
  return(
    <div className="min-h-screen min-w-screen flex items-center justify-center">
      로그인한 사용자만 접근 가능
      <div>
        {JSON.stringify(data, null, 2)}
      </div>
      <Button 
        disabled={create.isPending} 
        onClick={() => create.mutate()}
      >
        워크플로우 생성
      </Button>
      <LogoutButton/>
    </div>
  )
}

export default Page

 

 

 

Inngest 설정

백그라운드 작업 / 이벤트 기반 워크플로우를 만들기 위한 라이브러리를 설치

 

https://www.inngest.com/

 

Inngest - AI and backend workflows, orchestrated at any scale

Inngest's durable functions replace queues, state management, and scheduling to enable any developer to write reliable, multi-step code faster without touching infrastructure.

www.inngest.com

npm install inngest

npm install inngest@3.44.1

 

npx --ignore-scripts=false inngest-cli@latest dev -u http://localhost:3000/api/inngest

 

http://localhost:8288/runs

이제 여기서 백그라운드에서 일어나고 있는 일들을 확인할 수 있다.

 

Inngest 클라이언트 생성

src/inngest/client.ts

import { Inngest } from "inngest";

export const inngest = new Inngest({ id: "nodebase" });

 

API 라우트 설정

src/app/api/inngest/route.ts

import { serve } from "inngest/next";
import { inngest } from "@/inngest/client"

export const {GET, POST, PUT} = serve({
  client: inngest,
  functions:[

  ]
})

 

백그라운드 함수 작성

 

src/inngest/functions.ts

import { inngest } from "./client";

export const helloWorld = inngest.createFunction(
  { id: "hello-world" },
  { event: "test/hello.world" },
  async ({ event, step }) => {
    await step.sleep("wait-a-moment", "1s");
    return { message: `Hello ${event.data.email}!` };
  },
);

 

함수 등록
src/app/api/inngest/route.ts  function에 helloworld 추가

import { serve } from "inngest/next";
import { inngest } from "@/inngest/client"
import { helloWorld } from "@/inngest/functions";

export const {GET, POST, PUT} = serve({
  client: inngest,
  functions:[
    helloWorld
  ]
})

 

실행 후 http://localhost:8288/runs 에서 백그라운드 작업을 모니터링할 수 있다.

 

 

 

백그라운드 워크플로우 구현

tRPC에서 이벤트 발행

src/trpc/routers/_app.ts

import { inngest } from '@/inngest/client';
import { createTRPCRouter, protectedProcedure } from '../init';
import prisma from '@/lib/db';
import { resolve } from 'path';

export const appRouter = createTRPCRouter({
  getWorkflows: protectedProcedure.query(({ctx}) => {    
      return prisma.workflow.findMany()
  }),
  createWorkflow : protectedProcedure.mutation(async()=>{

    // 일반적인 백그라운드 워크플로우 예시
    // // 1️⃣ 비디오 가져오기 (외부 API / 스토리지)
    // await new Promise((resolve) => setTimeout(resolve, 5_000))

    // // 2️⃣ 비디오 → 텍스트 변환 (무거운 연산)
    // await new Promise((resolve) => setTimeout(resolve, 5_000))

    // // 3️⃣ OpenAI로 전송해서 요약/분석
    // await new Promise((resolve) => setTimeout(resolve, 5_000))


    await inngest.send({
      name: "test/hello.world",
      data: {
        email:"zzuny@zzuny.com"
      }
    }); 

    return prisma.workflow.create({
      data: {
        name : "test-workflow"
      }
    })
  })
});
// export type definition of API
export type AppRouter = typeof appRouter;

 

 

 

백그라운드 함수 개선

src/inngest/functions.ts

- retries 재시도 획수 제어(없어도 댐)

import prisma from "@/lib/db";
import { inngest } from "./client";

export const helloWorld = inngest.createFunction(
  { id: "hello-world", retries: 5 },
  { event: "test/hello.world" },
  async ({ event, step }) => {

    // 비디오 가져오기 (외부 API / 스토리지)
    await step.sleep("비디오 가져오기", "1s");
    // 비디오 → 텍스트 변환 (무거운 연산)
    await step.sleep("비디오 → 텍스트 변환", "1s");
    // OpenAI로 전송해서 요약/분석
    await step.sleep("OpenAI로 전송해서 요약/분석", "1s");

    // return { message: `Hello ${event.data.email}!` };
    await step.run("create-workflow", () => {
        return prisma.workflow.create({
            data: {
                name: "workflow-from-inngest"
            }
        })
    })
  },
);

 

 

프론트엔드 UX 개선

src/app/page.tsx

프론트에는 바로 toast.success("작업 성공") 메시지를 던져. 

사용자는 이게 작업성공인걸 알았으니까. 작업이 완성되면 화면이 고쳐지겟지? 

"use client"

import { Button } from "@/components/ui/button";
import { LogoutButton } from "./logout";
import { useTRPC } from "@/trpc/client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";

const Page = () => {
  const trpc = useTRPC();
  const queryClient = useQueryClient();
  const { data } = useQuery(trpc.getWorkflows.queryOptions());

  const create = useMutation(trpc.createWorkflow.mutationOptions({
    onSuccess: () => {
      // queryClient.invalidateQueries(trpc.getWorkflows.queryOptions())
      // 즉시 성공 메시지 표시
      toast.success("작업 성공");
      // 백그라운드에서 작업이 완료되면 자동으로 화면이 업데이트됩니다
    }
  }));
  
  return (
    <div className="min-h-screen min-w-screen flex items-center justify-center">
      <div>로그인한 사용자만 접근 가능</div>
      <div>
        {JSON.stringify(data, null, 2)}
      </div>
      <Button 
        disabled={create.isPending} 
        onClick={() => create.mutate()}
      >
        워크플로우 생성
      </Button>
      <LogoutButton />
    </div>
  );
}

export default Page;

 

 

 

보너스: mprocs로 개발 환경 개선

- 여러 프로세스를 동시에 실행하고 관리할 수 있는 도구

 

https://github.com/pvolok/mprocs

 

GitHub - pvolok/mprocs: Run multiple commands in parallel

Run multiple commands in parallel. Contribute to pvolok/mprocs development by creating an account on GitHub.

github.com

mprocs 설치

npm install -g mprocs
npm i -D inngest-cli

 

mprocs.yaml 설정 파일 작성

procs:
  inngest:
    cmd: ["npm", "run", "inngest:dev"]

  next:
    cmd: ["npm", "run", "dev"]

 

package.json package.json 스크립트 추가

"scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start",
    "lint": "biome check",
    "format": "biome format --write",
    "inngest:dev": "inngest-cli dev",
    "dev:all": "mprocs"
  },

 

실행

npm run dev:all

 

 

이제 한 번의 명령으로 Next.js 개발 서버와 Inngest Dev Server를 동시에 실행할 수 있다.

 

반응형

'Clone Coding' 카테고리의 다른 글

N8N & Zapier - Error Tracking  (0) 2026.01.06
N8N & Zapier - AI providers  (1) 2026.01.05
N8N & Zapier - Theme & styling  (0) 2025.12.31
N8N & Zapier - Authentication  (0) 2025.12.31
N8N & Zapier - tRPC 설정  (1) 2025.12.29