티스토리 뷰

반응형

GitHub Webhook 이벤트 처리 최적화 및 로깅 시스템 구축

이전 글에서는 GitHub Webhook을 활용하여 GitHub Project 보드 상태를 자동으로 변경하는 방법을 다뤘습니다.
이번 글에서는 Webhook 이벤트 처리를 최적화하고, 로깅 및 모니터링 시스템을 구축하여 안정적인 운영을 지원하는 방법을 설명합니다.


1. Webhook 이벤트 최적화의 필요성

GitHub Webhook은 다양한 이벤트를 발생시키며, 요청이 많아질수록 부하 관리 및 이벤트 중복 처리 방지가 중요해집니다.
Webhook 이벤트 처리 시 고려해야 할 사항은 다음과 같습니다.

✅ Webhook 이벤트 최적화 전략

✔️ 이벤트 큐를 활용한 비동기 처리 – Redis와 Bull을 활용하여 이벤트를 큐에 저장 후 순차 실행
✔️ 이벤트 중복 처리 방지 – 동일한 이벤트가 여러 번 처리되지 않도록 ID 기반 중복 필터링
✔️ 에러 핸들링 및 재시도 로직 적용 – 네트워크 오류 발생 시 자동 재시도


2. Webhook 이벤트 처리 아키텍처 개선

🔹 기존 방식 vs 개선된 방식 비교

기존 방식 개선된 방식

Webhook 요청을 즉시 처리 요청을 이벤트 큐에 저장 후 비동기 처리
이벤트 중복 방지 없음 이벤트 ID를 저장하여 중복 요청 차단
네트워크 오류 시 실패 실패한 요청은 일정 시간 후 재시도

3. Redis + Bull을 활용한 Webhook 이벤트 큐 적용

🔹 3-1. Redis 및 Bull 패키지 설치

npm install bull ioredis @nestjs/bull

🔹 3-2. Webhook 이벤트를 큐에 저장하여 비동기 처리

📌 src/webhook/webhook.processor.ts

import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
import axios from 'axios';

@Processor('webhookQueue')
export class WebhookProcessor {
  @Process()
  async handleWebhook(job: Job) {
    const { event, payload } = job.data;
    console.log(`🔄 Processing Webhook Event: ${event}`);

    try {
      if (event === 'issues' && payload.action === 'opened') {
        console.log(`📌 Issue Created: #${payload.issue.number}`);
        await this.updateIssueStatus(payload.issue.number);
      }

      if (event === 'pull_request' && payload.action === 'opened') {
        console.log(`🚀 PR Created: #${payload.pull_request.number}`);
        await this.assignReviewers(payload.pull_request.number);
      }
    } catch (error) {
      console.error(`❌ Webhook Processing Failed: ${error.message}`);
      throw error; // 자동 재시도 가능하도록 예외 발생
    }
  }

  private async updateIssueStatus(issueNumber: number) {
    // GitHub API 호출하여 Issue 상태 업데이트
    console.log(`✅ Issue #${issueNumber} updated successfully`);
  }

  private async assignReviewers(prNumber: number) {
    // GitHub API 호출하여 PR 리뷰어 자동 할당
    console.log(`✅ Reviewers assigned to PR #${prNumber}`);
  }
}

💡 handleWebhook()에서 Webhook 이벤트를 처리하며, 실패 시 자동으로 재시도됩니다.


🔹 3-3. Webhook 컨트롤러에서 Bull Queue 적용

📌 src/webhook/webhook.controller.ts

import { Controller, Post, Headers, Body } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Controller('webhook')
export class WebhookController {
  constructor(@InjectQueue('webhookQueue') private webhookQueue: Queue) {}

  @Post()
  async handleWebhook(@Headers('x-github-event') event: string, @Body() payload: any) {
    console.log(`📥 Received Webhook Event: ${event}`);

    // 이벤트 큐에 추가하여 비동기 처리
    await this.webhookQueue.add({ event, payload });

    return { message: `Webhook event queued: ${event}` };
  }
}

💡 Webhook 요청을 즉시 처리하는 것이 아니라, 큐에 저장한 후 WebhookProcessor에서 비동기 처리됩니다.


4. Webhook 이벤트 중복 처리 방지

반응형

GitHub에서 동일한 이벤트가 여러 번 발생할 경우 중복 요청을 방지해야 합니다.

🔹 4-1. 이벤트 ID를 기반으로 중복 요청 방지

📌 src/webhook/webhook.processor.ts

import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
import * as Redis from 'ioredis';

const redis = new Redis();

@Processor('webhookQueue')
export class WebhookProcessor {
  @Process()
  async handleWebhook(job: Job) {
    const { event, payload } = job.data;
    const eventId = payload.issue?.id || payload.pull_request?.id;

    // Redis에서 기존 이벤트 ID 확인
    const exists = await redis.get(`event:${eventId}`);
    if (exists) {
      console.log(`⚠️ Duplicate Event Ignored: ${eventId}`);
      return;
    }

    // 이벤트 ID 저장 (10분 동안 유지)
    await redis.setex(`event:${eventId}`, 600, 'processed');

    console.log(`🔄 Processing Webhook Event: ${event}`);
  }
}

💡 이벤트 ID를 Redis에 저장하여 중복 요청을 방지할 수 있습니다.


5. Webhook 요청 로그 저장 및 대시보드 구축

Webhook 요청 로그를 저장하면 이벤트 처리 상태를 추적할 수 있습니다.

🔹 5-1. PostgreSQL에 Webhook 로그 저장

📌 prisma/schema.prisma

model WebhookLog {
  id        String @id @default(uuid())
  eventType String
  payload   Json
  createdAt DateTime @default(now())
}

📌 src/webhook/webhook.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class WebhookService {
  constructor(private readonly prisma: PrismaService) {}

  async saveWebhookLog(eventType: string, payload: any) {
    return this.prisma.webhookLog.create({
      data: { eventType, payload },
    });
  }
}

🔹 5-2. Webhook 요청 대시보드 구현 (Next.js)

📌 Webhook 요청 목록 조회 API (src/webhook/webhook.controller.ts)

import { Get, Controller } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Controller('webhook')
export class WebhookController {
  constructor(private readonly prisma: PrismaService) {}

  @Get()
  async getWebhookLogs() {
    return this.prisma.webhookLog.findMany({ orderBy: { createdAt: 'desc' } });
  }
}

💡 Next.js를 활용하여 Webhook 요청 로그를 시각화하면 운영이 더욱 편리해집니다.


6. 마무리 및 다음 글 예고

이번 글에서는 Webhook 이벤트 최적화, 큐 기반 비동기 처리, 중복 요청 방지, 로깅 시스템 구축을 다뤘습니다.
다음 글에서는 Webhook 이벤트 보안 강화 및 배포 전략을 다룰 예정입니다! 🚀

 

 

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함
반응형