본문 바로가기
카테고리 없음

[톺아보기 시리즈] 낙관적 업데이트에 대해서 알고계세요?

by klm hyeon woo 2025. 1. 14.

목차

· 낙관적 업데이트란?

· 좋은 사용성을 위해 낙관적 업데이트를 많이 사용해도 되나요?

· 낙관적 업데이트는 어떻게 사용해요?


낙관적 업데이트란?

낙관적 업데이트는 성공적인 상태 업데이트가 이뤄질거라는 가정 하에 서버 응답 이전에 UI를 미리 업데이트하는 방법을 의미해요, 사용자 요청을 서버가 성공적으로 처리할 것이라고 미리 예상하고, 유저 인터페이스를 즉각적으로 변경하여 사용자에게 빠른 반응을 보여줍니다. 나관적 업데이트의 대표적인 예시로 좋아요 기능을 들 수 있어요. 예를 들어 사용자가 좋아요 버튼을 클릭하면 서버 응답을 기다리지 않고, 화면에 바로 좋아요 클릭에 대한 상태를 보여주는거예요. 서버 응답이 성공적으로 돌아오면 그대로 두고, 혹시나 실패한다면 유저 인터페이스에서 해당 좋아요 상태를 다시 해제하거나 오류 메세지를 보여주는 방식인거죠.

 

낙관적 업데이트의 장점은, 서버 응답 속도와 관계 없이 즉각적인 피드백을 제공하여 사용자들이 시스템을 빠르게 사용할 수 있다는 점이에요, 특히 네크워크 상태가 좋지 않거나 응답 시간이 길어도 사용자 경험에는 영향을 덜 미치게 됩니다. 다만 서버에서 오류가 발생하면 잠시동안 화면에 잘못된 정보가 표시될 수 있습니다. 따라서 이 경우를 대비한 오류 핸들링에 관련된 롤백 로직을 같이 설계해야줘야 한다는 주의점을 가지고 있어요. 실제로 투두리스트를 로컬 스토리지에 저장하는 것이 아닌 온라인 플랫폼 형태로 만들었던 적이 있었어요, 투두리스트를 생성하거나, 완료 체크를 하는 일이 사실 프론트엔드 입장에서는 빠르게 처리되어야하는 일이지만, 서버 통신을 통해 완성 여부가 반영되는 유저 인터페이스 형태라서 상당히 느렸던 경험이 있었습니다. 이때 낙관적 업데이트를 통해 프론트엔드에서는 성공 결과에 대한 화면을 먼저 보여주고, 그 뒤를 따라 서버에서 업데이트를 해주는 방식으로 사용자 경험을 개선 시켰던 경험이 있어요.

좋은 사용성을 위해 낙관적 업데이트를 많이 사용해도 되나요?

많은 곳에 낙관적 업데이트를 적용하는게 항상 좋은 것은 아니에요, 낙관적 업데이트는 요청이 성공할 가능성이 높고, 사용자 경험을 즉시 개선하는데 큰 장점이 있을 때 사용하는 것이 적합합니다. 간단한 플로우를 통해 결과 값을 도출하는 (위에서 예시로 든 투두리스트 플랫폼) 데이터의 경우에는 낙관적 업데이트를 사용해도 무방하지만, 결제나 거래 내역과 같이 중요한 비즈니스 데이터를 다루는 경우에는 낙관적 업데이트가 오히려 사용자 경험을 저해할 수 있어요. 낙관적 업데이트를 적용했을 때, 요청에 실패한다면 민감도 높은 정보가 순간적으로 잘못 표시되면서 사용자 경험을 크게 저해할 수 있거든요. 또한 네트워크 환경이 불안정한 경우에는 요청에 대한 실패율이 높아지기 때문에 잦은 로백이 발생할 수 있어요, 이 경우에도 역시나 사용자 경험을 저해할 수 있기에 오히려 서버 응답을 기다리는 것이 더 좋은 판단일 수 있어요.

낙관적 업데이트는 어떻게 사용해요?

낙관적 업데이트의 경우 요즘 많이 사용하는 tanstack-query의 onMutate 라는 메서드를 통해 세부 로직들로 구성하여 사용할 수 있어요.

const useDeleteLike = (idx: string) => {
  const queryClient = useQueryClient();
  return useMutation(['deleteLike'], {
    mutationFn: () => deleteLike(idx),
    onMutate: async () => {
      await queryClient.cancelQueries(['like']); // 덮어쓰기를 하지 않기 위해 쿼리를 미리 취소
      const oldData = queryClient.getQueryData(['like']); // 오래된 데이터를 먼저 저장합니다.
      queryClient.setQueryData(['like'], ''); // 미리 유저 인터페이스에 데이터를 적용
      return () => queryClient.setQueryData(['like'], oldData); // 만약 에러나서 롤백 이전 데이터를 리턴
    },
    onError: (error, variable, rollback) => {
      if (rollback) rollback();
      else console.log(error);
    },
    onSettled: () => {
      queryClient.invalidateQueries(['like']);
    },
  });
};

 

댓글