SYDESYDE
SYDESYDE
  • 피드
  • 쇼케이스
  • 블로그
  • 모임
SYDESYDE© 2026 SYDE. All rights reserved.
SYDE 소개문의하기
Kakao
SYDE 오픈채팅
Instagram
Instagram
Threads
Threads
커뮤니티 가이드라인협업문의이용약관개인정보처리방침
커뮤니티 가이드라인협업문의이용약관개인정보처리방침

© 2026 SYDE. All rights reserved.

새로고침해도 네컷만화는 한 번만 만들어져야 하니까

새로고침해도 네컷만화는 한 번만 만들어져야 하니까

h하루들· 7일 전· 기술
목차
  • 같은 요청인지 서버는 어떻게 알까?
  • 하루들의 생성 흐름부터 살펴보자
  • 처음에는 Provider에서 키를 관리하려고 했다
  • 생성 버튼을 누르는 순간 키를 만들자
  • 상황마다 어떤 키를 써야 할까?
  • 멱등성 키는 내용이 아니라 ‘생성 시도’를 따라간다

목차

  • 같은 요청인지 서버는 어떻게 알까?
  • 하루들의 생성 흐름부터 살펴보자
  • 처음에는 Provider에서 키를 관리하려고 했다
  • 생성 버튼을 누르는 순간 키를 만들자
  • 상황마다 어떤 키를 써야 할까?
  • 멱등성 키는 내용이 아니라 ‘생성 시도’를 따라간다

하루들은 사용자가 작성한 내용을 바탕으로 AI 네컷만화를 만들어 주는 서비스예요. 하루에 네컷만화를 만들 수 있는 횟수는 기본 세 번이에요.

그런데 네컷만화 생성에는 시간이 걸려요. 사용자가 생성 버튼을 다시 누르거나, 결과를 기다리다가 페이지를 새로고침할 수도 있죠. 이때 같은 생성 요청이 여러 번 전달됐다는 이유로 네컷만화가 여러 개 만들어진다면 어떨까요? 사용자는 한 번 만들었다고 생각하는데, 하루 세 번의 생성 기회를 모두 써버릴 수도 있어요.

서버에서는 이런 중복 생성을 막기 위해 멱등성 키를 사용하고 있었어요. 처음에는 백엔드에서 처리하는 문제이니 프론트엔드에서 크게 고민할 일은 없다고 생각했습니다. 하지만 생성 화면을 구현하다 보니 서버가 중복 요청을 구분하려면 프론트엔드가 같은 생성 시도에 같은 키를 보내야 한다는 사실을 알게 됐어요.

이번 글에서는 하루들 프론트엔드에서 멱등성 키를 언제 만들고, 어디에 두고, 어떤 상황에서 다시 사용하기로 했는지 이야기해보려고 합니다.

같은 요청인지 서버는 어떻게 알까?

멱등성은 같은 작업을 여러 번 요청해도 결과에 미치는 영향이 한 번 요청했을 때와 같도록 하는 성질이에요.

하루들에 적용하면, 사용자가 시작한 한 번의 네컷만화 생성 시도가 네트워크 문제나 새로고침 때문에 여러 번 서버에 전달되더라도 생성 작업은 하나로 취급되어야 한다는 뜻입니다.

이를 위해 클라이언트는 생성 요청에 멱등성 키를 함께 보냅니다. 서버는 이미 처리 중이거나 처리한 키가 다시 들어오면 새로운 생성 작업을 시작하지 않아요.

여기서 중요한 점은 서버가 작성한 내용만 보고 같은 요청인지 판단하지 않는다는 거예요. 내용이 같더라도 서로 다른 멱등성 키가 전달되면 별개의 생성 시도로 볼 수 있습니다. 반대로 한 번의 생성 시도를 재요청할 때는 같은 키를 전달해야 서버도 이를 재시도로 알아볼 수 있고요.

결국 프론트엔드에서는 이런 질문에 답해야 했어요.

멱등성 키는 언제 새로 만들고, 언제 기존 것을 다시 써야 할까?

하루들의 생성 흐름부터 살펴보자

현재 하루들에서는 내용 작성 페이지와 생성 페이지가 나뉘어 있어요.

사용자가 작성 페이지에서 날짜와 내용을 입력하고 생성 버튼을 누르면, React Router를 이용해 생성 페이지로 이동합니다. 입력한 날짜와 내용은 location.state로 전달하고, 실제 생성 API는 이동한 생성 페이지에서 호출해요.

서비스 화면

평소에는 단순한 흐름이지만, 브라우저의 뒤로가기와 새로고침을 끼워 넣으면 고려할 상황이 늘어납니다.

  • 작성 페이지에서 새로고침한다.

  • 생성 버튼을 눌러 생성 페이지로 이동한다.

  • 생성 페이지에서 기다리다가 새로고침한다.

  • 생성 페이지에서 뒤로가기로 작성 페이지에 돌아온다.

  • 생성이 실패해 작성 페이지로 돌아간 뒤 다시 시도한다.

작성 페이지에서 새로고침했다면 아직 생성 버튼을 누르지 않았으니 새 키를 만들 필요가 없어요. 반면 생성 페이지에서 새로고침했다면 이미 시작한 생성을 다시 요청하는 것이므로 기존 키를 써야 합니다. 같은 새로고침이라도, 생성 버튼을 누르기 전인지 후인지에 따라 키를 다르게 다뤄야 했어요.

처음에는 Provider에서 키를 관리하려고 했다

처음에는 생성 요청에 필요한 값을 관리하는 DiaryGenerateContext의 Provider에서 멱등성 키를 만들려고 했어요. 생성 메서드도 그곳에서 제공하고 있었으니, 키 역시 함께 관리하면 자연스러워 보였습니다.

하지만 Provider가 렌더링될 때 일반 변수로 새로운 키를 만드는 방식에는 문제가 있었어요. 리렌더링될 때마다 키가 바뀔 수 있기 때문입니다. 키를 한 번만 만든다고 해도, 생성 페이지를 새로고침해 React 애플리케이션이 다시 시작되면 이전 값은 사라져요.

생성 페이지에서는 Provider의 메서드를 통해 API를 호출하고 있었으므로, 새로고침 후 새로운 키가 만들어지면 사용자는 같은 생성을 다시 시도했는데 서버에는 새로운 작업처럼 전달되는 상황이 생깁니다.

그렇다면 React 상태로 관리하면 어떨까요?

const [idempotencyKey] = useState(() => crypto.randomUUID());

useState의 초기화 함수를 사용하면 적어도 같은 마운트에서 렌더링할 때마다 키가 새로 만들어지지는 않아요. useRef도 리렌더링 사이에 값을 유지할 수 있습니다.

const idempotencyKey = useRef(crypto.randomUUID());

하지만 두 방법 모두 페이지 새로고침과 컴포넌트 재마운트를 넘어 값을 유지하지는 못해요. 새로고침 후 애플리케이션이 다시 실행되면 상태와 ref도 처음부터 만들어집니다.

우리가 유지해야 하는 것은 "컴포넌트가 살아 있는 동안의 값"이 아니었어요. 사용자가 시작한 한 번의 생성 시도에 속한 값이었습니다. 컴포넌트보다 생성 시도의 수명이 더 길 수 있는 거죠.

생성 버튼을 누르는 순간 키를 만들자

그래서 키를 만드는 시점을 바꿨어요. 작성 페이지에서 미리 키를 만들어 두는 대신, 사용자가 생성 버튼을 누르는 순간 새로운 키를 발급하기로 했습니다.

그 순간부터 하나의 생성 시도가 시작됐다고 볼 수 있기 때문이에요. 날짜와 작성한 내용, 멱등성 키를 함께 location.state에 담아 생성 페이지로 전달합니다. 아래 코드는 흐름을 보여주기 위한 예시예요.

navigate("/generate", {
  state: {
    date,
    content,
    idempotencyKey: crypto.randomUUID(),
  },
});

생성 페이지에서는 전달받은 키로 API를 호출합니다. 이 페이지가 새로고침되어도 같은 히스토리 항목의 location.state에서 키를 다시 읽어 사용할 수 있어요.

const { date, content, idempotencyKey } = location.state;

await generateComic({
  date,
  content,
  idempotencyKey,
});

이렇게 하면 키를 컴포넌트가 마운트될 때마다 새로 만드는 것이 아니라, 사용자가 새 생성을 시작할 때마다 만들게 됩니다.

물론 location.state가 어디서나 존재하는 것은 아니에요. 사용자가 생성 페이지의 URL로 직접 들어오거나, 필요한 상태 없이 해당 페이지에 도착했다면 전달받을 키도 없습니다. 따라서 생성 페이지에서는 필요한 상태가 있는지 확인하고, 없다면 작성 페이지로 안내해야 해요.

상황마다 어떤 키를 써야 할까?

기준을 정하고 나니 각 상황에서의 처리 방식도 명확해졌어요.

상황

멱등성 키 처리

작성 페이지에서 새로고침

아직 생성을 시작하지 않았으므로 키가 없어도 됨

생성 버튼 클릭

새로운 생성 시도로 보고 새 키 발급

생성 페이지에서 새로고침

이미 시작한 생성이므로 기존 키 재사용

생성 중 같은 요청을 다시 보냄

기존 키 재사용

생성 실패 후 작성 페이지로 돌아가 다시 생성 버튼 클릭

새로운 생성 시도로 보고 새 키 발급

특히 생성 페이지의 새로고침이 중요했어요. 화면은 처음부터 다시 표시되지만, 사용자가 새로운 네컷만화를 만들기로 결정한 것은 아닙니다. 따라서 새로운 키를 발급해서는 안 되고, 이전 키로 요청해야 해요.

서버에서 이미 같은 키의 생성 작업을 진행하고 있다면 GENERATION_IN_PROGRESS 상태를 전달합니다. 하루들 프론트엔드에서는 이를 받았을 때 생성이 진행 중이라는 메시지를 보여주고 홈으로 이동하도록 처리했어요. 새로고침이 새로운 생성 작업으로 이어지지 않게 한 거죠.

반면 생성이 실패해 사용자가 작성 페이지로 돌아오고, 내용을 다시 작성한 뒤 생성 버튼을 누른다면 새로운 생성 시도로 취급했습니다. 이 경우에는 이전 키를 재사용하지 않고 새 키를 발급해요.

멱등성 키는 내용이 아니라 ‘생성 시도’를 따라간다

처음에는 멱등성 키를 어디에 저장할지 고민했어요. 일반 변수로 둘지, useState나 useRef를 사용할지, 브라우저 저장소를 사용할지를 먼저 생각했죠. 하지만 저장 방법을 고르려면 그보다 앞서 무엇을 같은 생성 시도로 볼지 정해야 했습니다.

하루들에서는 생성 페이지를 새로고침해 다시 보내는 요청은 같은 시도로, 작성 페이지에서 생성 버튼을 다시 누르는 행동은 새로운 시도로 봤어요. 클라이언트가 이 기준에 따라 키를 만들고 재사용해야 서버도 중복 요청을 올바르게 판단할 수 있습니다. 멱등성 키 관리는 백엔드, 프론트엔드만의 서로 다른 문제가 아니라, 클라이언트와 백엔드가 "같은 작업"의 범위를 합의해야 하는 문제였어요.

그 기준이 정해지자 저장 위치도 결정할 수 있었습니다. 하루들에서는 생성 페이지를 새로고침해도 기존 키를 다시 읽을 수 있어야 했고, 새로운 생성을 시작할 때는 새 키를 만들어야 했어요. 그래서 컴포넌트 상태 대신 location.state를 선택했습니다. 서비스의 생성 흐름과 재시도 방식이 다르다면 적절한 저장 방법도 달라질 거예요.

만약 운영하는 서비스에서 멱등성 키를 관리해야 한다면, 어디에 저장할지부터 고르기보다 우리 서비스에서 무엇을 한 번의 시도로 볼지 먼저 생각해보는 게 어떨까요?

  • #멱등성
  • #멱등성키
  • #state
44
하
하루들우리끼리 통하는 네컷만화

하루들님의 다른 글

  • 기록에서 공유로, 하루들 리브랜딩 이야기5일 전
  • 추석 연휴를 하루 앞두고, 모든 그림일기가 갑자기 사라졌다.8일 전
  • 우리는 왜 가설검증을 할까? - 19일 전

댓글 0

My avatar