2025 . ์˜ค์€

repo

polling ์ ์šฉ๊ธฐ

๐Ÿงฉ ๋ฌธ์ œ ์ •์˜

ํ”„๋กœ์ ํŠธ์—์„œ OpenAI์˜ image edit ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ๊ตฌํ˜„ํ•˜๋˜ ์ค‘,
Amplify ํ™˜๊ฒฝ์—์„œ API ์š”์ฒญ์ด 504 Gateway Timeout ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฌธ์ œ์— ์ง๋ฉดํ–ˆ์Šต๋‹ˆ๋‹ค.

  • Vercel ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์š”์ฒญ์ด ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์ง€๋งŒ,
  • Amplify์—์„œ๋Š” Lambda@Edge์˜ timeout ์ œํ•œ์œผ๋กœ ์ธํ•ด SSR ํ•จ์ˆ˜๊ฐ€ 29์ดˆ๋ฅผ ๋„˜๊ธฐ๋ฉด ๊ฐ•์ œ ์ข…๋ฃŒ
  • ํŠนํžˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์€ ํ‰๊ท  40์ดˆ ์ด์ƒ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์ด๋ผ, Amplify ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ๋ถˆ๊ฐ€๋Šฅ

๐Ÿ›  ํ•ด๊ฒฐ ๊ณผ์ •

1. ๊ตฌ์กฐ ๋ถ„๋ฆฌ (๋ฐฑ์—”๋“œ ์—ญํ•  Next.js ์•ฑ ๋„์ž…)

  • ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋กœ์ง์„ ๋ณ„๋„์˜ Next.js ์•ฑ์— ๋ถ„๋ฆฌํ•ด โ€˜๋ฐฑ์—”๋“œ ์—ญํ• โ€™๋กœ ์‚ฌ์šฉ
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ์•ฑ์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ์‹ค์ œ ์ž‘์—…์€ ํ•ด๋‹น ์•ฑ์—์„œ ์ˆ˜ํ–‰

2. Polling ๋ฐฉ์‹ ์ ์šฉ

  • ์ฒซ ๋ฒˆ์งธ API๋Š” ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ๋ฐ›์•„ ์ฆ‰์‹œ 202 ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ณ ,
    ๋‚ด๋ถ€์ ์œผ๋กœ ๋‘ ๋ฒˆ์งธ API๋ฅผ fire-and-forget ๋ฐฉ์‹์œผ๋กœ ํ˜ธ์ถœ
  • ๋‘ ๋ฒˆ์งธ API๋Š” ์‹ค์ œ๋กœ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” OpenAI image edit ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  DB์— ๊ฒฐ๊ณผ ์ €์žฅ

3. ํด๋ผ์ด์–ธํŠธ์—์„œ Polling ์ฒ˜๋ฆฌ

  • ํด๋ผ์ด์–ธํŠธ๋Š” 202 ์‘๋‹ต์„ ๋ฐ›์€ ํ›„ 15์ดˆ ๋Œ€๊ธฐ, ์ดํ›„ 5์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ์ตœ๋Œ€ 20ํšŒ status ํ™•์ธ
  • ๊ฒฐ๊ณผ๊ฐ€ completed ์ƒํƒœ๊ฐ€ ๋˜๋ฉด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋กœ ์ „ํ™˜

4. ๊ธฐํƒ€ ๊ฐœ์„  ์‚ฌํ•ญ

  • Amplify์—์„œ SSR์„ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•ด middleware.ts ๋ฐ app/api/.../route.ts ์™ธ์—
    pages/api/ping.ts๋„ ์ถ”๊ฐ€ํ•˜์—ฌ SSR Lambda๊ฐ€ ํ™•์‹คํžˆ ์ƒ์„ฑ๋˜๋„๋ก ์ฒ˜๋ฆฌ

  • fire-and-forget ํ˜ธ์ถœ ์‹œ .catch()๋กœ ์˜ˆ์™ธ ๋ˆ„๋ฝ ๋ฐฉ์ง€

  • ์ด๊ฑด try catch ๋“  .catch ๋“  ์“ฐ๋ฉด fire-and-forget ์ด ์•ˆ๋˜๋Š” ๊ฒƒ ํ™•์ธ!!!

  • ์•„๋ž˜์ฒ˜๋Ÿผ ๊ผผ์ˆ˜๋กœ ์ฒ˜๋ฆฌํ–ˆ์Œ

import api from "@/apis/axios";
import type { GenResponse } from "@/types/iffy.types";
import { type NextRequest, NextResponse } from "next/server";

function awaitTime(ms: number) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function GET(
	request: NextRequest,
): Promise<NextResponse<GenResponse>> {

	const searchParams = request.nextUrl.searchParams;
	const query = searchParams.get("id");
	
	  
	if (!query) {
		return NextResponse.json(
			{ status: "error", message: "No id provided" },
			{ status: 400 },
		);
	}

	// ์›๋ณธ ์š”์ฒญ์—์„œ ์ฟ ํ‚ค ํ—ค๋” ๊ฐ€์ ธ์˜ค๊ธฐ
	const cookieHeader = request.headers.get("cookie");
	
	// Fire-and-Forget ์š”์ฒญ ์‹œ ์ฟ ํ‚ค ํ—ค๋” ์ „๋‹ฌ
	api.get(`/api/backtask?id=${query}`, {
		headers: {
			...(cookieHeader && { Cookie: cookieHeader }),
		},
	});

	// ์—ฌ๊ธฐ๊ฐ€ ๊ผผ์ˆ˜๋ถ€๋ถ„.. 
	// await ์„ ํ•˜์ง€ ์•Š์œผ๋‹ˆ api.get ์ด ๋˜๊ธฐ๋„ ์ „์— 
	// ๋ฆฌ์Šคํฐ์Šค๋กœ ๋„˜์–ด๊ฐ€๋ฒ„๋ ค ์‹คํ–‰์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ ์ž„์‹œ ํ•ด๊ฒฐ์šฉ
	// upstash ๋“ฑ์„ ์“ฐ๋ผ๊ณ  ํ•˜์ง€๋งŒ.. ๊ฐ„๋‹จํžˆ ์ฒ˜๋ฆฌํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๊ธฐ ๋•Œ๋ฌธ...
	await awaitTime(1000);

	return NextResponse.json(
		{ status: "success", message: "IFFY ์ƒ์„ฑ ์š”์ฒญ ์ „๋‹ฌ" },
		{ status: 202 },
	);
}

๐Ÿ’ก ๋ฐฐ์šด ์  & ์ธ์‚ฌ์ดํŠธ

  • Amplify์˜ SSR ๊ตฌ์กฐ๋Š” Lambda@Edge + CloudFront ๊ธฐ๋ฐ˜์ด๋ฉฐ, timeout์€ ์ตœ๋Œ€29์ดˆ๋กœ ๊ณ ์ •๋˜์–ด ์žˆ์Œ
    โ†’ ์žฅ์‹œ๊ฐ„ ์ž‘์—…์€ ๊ตฌ์กฐ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์šฐํšŒ ์ „๋žต ํ•„์š”
  • Polling ๊ตฌ์กฐ๋Š” Amplify์™€ ๊ฐ™์€ ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์—์„œ ์œ ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ
  • OpenAI image edit API๋Š” ์•„์ง ์‘๋‹ต ํ›„ ์™„๋ฃŒ ํ†ต์ง€๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•„,
    webhook์ด๋‚˜ status ํ™•์ธ API๊ฐ€ ์ƒ๊ธฐ๋ฉด ๊ฐœ์„  ์—ฌ์ง€๊ฐ€ ํผ
  • Amplify๋Š” cold start๊ฐ€ Vercel๋ณด๋‹ค ๋‹ค์†Œ ๋А๋ฆฌ๊ฒŒ ์ฒด๊ฐ๋จ โ†’ ์‹ค์‹œ๊ฐ„์„ฑ์ด ์ค‘์š”ํ•œ ์•ฑ์—์„œ๋Š” ๊ณ ๋ ค ํ•„์š”
  • ์‚ฌ์‹ค ์ด ๋ชจ๋“ ๊ฒŒ ํ”„๋ก ํŠธ๋งŒ ๊ฐ€์ง€๊ณ  ์–ด์จŒ๋“  ํ•ด๊ฒฐ ํ•ด๋ณด๋ ค๋Š” ์ผ์ข…์˜ ํŠธ๋ฆญ์ด๋ฏ€๋กœ ์ตœ์ข…์ ์ธ ์ „๋žต์€ ์•„๋‹˜!
  • poliing ์ž์ฒด๋„ ๊ณ„์† DB๋ฅผ ํ™•์ธํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ๋Š”๋ฐ, supabase ์‚ฌ์šฉ์ค‘์ด๋ฏ€๋กœ ๊ทธ๋ƒฅ realtime ์“ฐ๋Š” ๊ฒƒ๋„ ์ข‹์„ ๋“ฏ!