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 ์ฐ๋ ๊ฒ๋ ์ข์ ๋ฏ!