QueenCards / ProjectAnalysis

플젝뿌셔
1 stars 0 forks source link

[32] ReactQuery의 GC 타임, stale time에 대해서 설명해주세요. 또 prefetchQuery를 사용한 경험이 있나요? #35

Closed hyeyoonS closed 1 month ago

hyeyoonS commented 1 month ago

📎 질문

(1) ReactQuery의 GC 타임, stale time에 대해서 설명해주세요. (2) prefetchQuery를 사용한 경험이 있나요?

✏ 구술 답변 키워드

(1) Stale Time

Garbage Collection Time

(2) prefetchQuery가 뭔가요?


prefetchQuery를 사용하면 좋은 예시?



✏ 서술 답변

(1) ReactQuery의 GC 타임, stale time에 대해서 설명해주세요.

gcTime과 staleTime의 플로우 예시!

staleTime과 gcTime

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  gcTime: 5 * 60 * 1000, // 5분
  staleTime: 1 * 60 * 1000, // 1분
});

1. staleTime: (number | Infinity)

- staleTime은 데이터가 `fresh에서 stale` 상태로 변경되는 데 걸리는 시간입니다.
 만약 staleTime이 `3000`이면 fresh 상태에서 `3초` 뒤에 stale로 변환됩니다.
- `fresh` 상태일 때는 쿼리 인스턴스가 새롭게 mount 되어도 네트워크 요청(fetch)이 일어나지 않습니다.
- staleTime의 기본값은 `0`이기 때문에 일반적으로 fetch 후에 바로 stale이 됩니다.

2. gcTime: (number | Infinity)

- 데이터가 사용하지 않거나, `inactive` 상태일 때 `캐싱 된 상태로` 남아있는 시간(밀리초)입니다.
- 쿼리 인스턴스가 unmount 되면 데이터는 `inactive 상태로 변경`되며, 캐시는 `gcTime`만큼 유지됩니다.
- gcTime이 지나면 `가비지 콜렉터`로 수집됩니다.
- gcTime이 지나기 전에 쿼리 인스턴스가 다시 mount 되면, 데이터를 fetch 하는 동안 캐시 데이터를 보여줍니다.
- gcTime은 staleTime과 관계없이, 무조건 `inactive` 된 시점을 기준으로 캐시 데이터 삭제를 결정합니다.
- gcTime의 기본값은 `5분`이고, SSR 환경에서는 `Infinity`이입니다.
- 

(2) prefetchQuery를 사용한 경험에 대해서 알려주세요.

1. prefetchQuery가 뭐지?

function Article({ id }) {
  const { data: articleData, isPending } = useQuery({
    queryKey: ['article', id],
    queryFn: getArticleById,
  })

  // Prefetch
  useQuery({
    queryKey: ['article-comments', id],
    queryFn: getArticleCommentsById,
    // Optional optimization to avoid rerenders when this query changes:
    notifyOnChangeProps: [],
  })

  if (isPending) {
    return 'Loading article...'
  }

  return (
    <>
      <ArticleHeader articleData={articleData} />
      <ArticleBody articleData={articleData} />
      <Comments id={id} />
    </>
  )
}

function Comments({ id }) {
  const { data, isPending } = useQuery({
    queryKey: ['article-comments', id],
    queryFn: getArticleCommentsById,
  })

  ...
}

2. prefetchQuery의 동작 방식

  1. 데이터 페칭:
    • prefetchQuery를 호출하면 지정된 쿼리 키와 쿼리 함수를 사용하여 데이터를 가져옵니다.
  2. React Query 캐시에 저장:
    • 가져온 데이터는 React Query의 클라이언트 캐시에 저장됩니다. 이 캐시는 브라우저의 메모리에 저장되며, 동일한 쿼리 키를 사용하는 컴포넌트가 데이터에 접근할 때 재사용됩니다.
  3. 서버 사이드에서 데이터 준비 (SSR/SSG):
    • prefetchQuery는 페이지를 렌더링하기 전에 데이터를 미리 가져와서 캐시에 저장해둡니다.
  4. 캐시된 상태 직렬화:
    • 서버 사이드 렌더링(SSR) 또는 정적 사이트 생성(SSG) 이전에, dehydrate 로 캐시된 상태를 JSON으로 직렬화하여 HTML과 함께 클라이언트로 전달합니다.
    • 클라이언트 측에서는 HydrationBoundary를 사용하여 직렬화된 상태를 복원(hydrate)하고, 동일한 캐시를 사용합니다.

3. 마이펫로그 예시코드

1) 차단한 유저 목록에 접근할 때

//쉬운 예시 
"use server";

import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";
import { getMyInvitations } from "@/app/_api/invitation";
import { getPet, getCode } from "@/app/_api/pets";
import { cookies } from "next/headers";
import PetSubscriberList from "@/app/settings/_components/Pet-Subscriber-List";

const Page = async () => {
  const queryClient = new QueryClient();
  const petId = Number(cookies().get("petId")?.value);
  await queryClient.prefetchQuery({ queryKey: ["my-invitations", petId], queryFn: () => getMyInvitations() });
  await queryClient.prefetchQuery({ queryKey: ["petInfo", petId], queryFn: () => getPet(petId) });
  await queryClient.prefetchQuery({ queryKey: ["inviteCode", petId], queryFn: () => getCode() });

  const dehydratedState = dehydrate(queryClient);

  return (
    <HydrationBoundary state={dehydratedState}>
      <PetSubscriberList petId={petId} />
    </HydrationBoundary>
  );
};

export default Page;

2) 다이어리 목록에서 다이어리 상세 내용에 접근할 때

(cf. 마이펫일기 옆 친구의일기 에 접근할때도 동일한 flow입니다~ 다만 무한스크롤로 구현되어있어 prefetchInfiniteQuery를 사용합니다)


//다이어리 상세페이지: 서버사이드에서 diary라는 이름으로 petId, diaryId 프리페치

import { getDiary } from "@/app/_api/diary";
import DiaryDetail from "@/app/diary/_components/DiaryDetail";
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";
import { cookies } from "next/headers";

const DiaryDetailPage = async ({ params: { id } }: { params: { id: string } }) => {
  const diaryId = Number(id);
  const queryClient = new QueryClient();
  const petId = Number(cookies().get("petId")?.value);
  **await queryClient.prefetchQuery({ queryKey: ["diary", { petId, diaryId }], queryFn: () => getDiary({ diaryId }) });**
);

  return (
    <div className={root}>
      <HydrationBoundary state={dehydratedState}>
        <BackHeader title="육아일기" />
        <div className={container}>
          <DiaryDetail petId={petId} diaryId={diaryId} />
        </div>
      </HydrationBoundary>
    </div>
  );
};

export default DiaryDetailPage;
//페이지 안에 들어가는 클라이언트 컴포넌트: 가져온 petId와 diaryId를 사용해용

"use client"

const DiaryDetail = ({ petId, diaryId }: { petId: number; diaryId: number }) => {
  const [currentPage, setCurrentPage] = useState(0);
  const [isKebabOpen, setIsKebabOpen] = useState(false);
  const [commentValue, setCommentValue] = useState("");
  const { isModalOpen, openModalFunc, closeModalFunc } = useModal();
  const queryClient = useQueryClient();
  const router = useRouter();

  //일기 상세 조회
  **const { data: diary } = useQuery({ queryKey: ["diary", { petId, diaryId }], queryFn: () => getDiary({ diaryId }) });**

4. 🧐PrefetchQuery 언제 사용하면 좋지?

import { getComments, getDiary } from "@/app/_api/diary";
import { getMe } from "@/app/_api/users";
import DiaryDetail from "@/app/diary/_components/DiaryDetail";
import { COMMENT_PAGE_SIZE } from "@/app/diary/(diary)/constant";
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";
import { cookies } from "next/headers";

const DiaryDetailPage = async ({ params: { id } }: { params: { id: string } }) => {
  const diaryId = Number(id);
  const queryClient = new QueryClient();
  const petId = Number(cookies().get("petId")?.value);
  await queryClient.prefetchQuery({ queryKey: ["diary", { petId, diaryId }], queryFn: () => getDiary({ diaryId }) });
  await queryClient.prefetchInfiniteQuery({
    queryKey: ["comments", { petId, diaryId }],
    queryFn: ({ pageParam }) => getComments({ diaryId, page: pageParam, size: COMMENT_PAGE_SIZE, petId }),
    initialPageParam: 0,
  });

  //댓글 입력창 프로필 이미지
  await queryClient.prefetchQuery({ queryKey: ["me"], queryFn: () => getMe() });
  const dehydratedState = dehydrate(queryClient);

  return (
    <div className={root}>
      <HydrationBoundary state={dehydratedState}>
        <BackHeader title="육아일기" styleTop="0" />
        <div className={container}>
          <DiaryDetail petId={petId} diaryId={diaryId} />
        </div>
      </HydrationBoundary>
    </div>
  );
};

export default DiaryDetailPage;

5. 🧐PrefetchQuery 언제 사용하면 별로지?

Nahyun-Kang commented 1 month ago

cacheTime : 메모리에 얼마만큼 있을 것인지, 해당 시간 이후에는 GC( Garbage collector )에 의해 처리가 되며 default 값은 5분입니다.

staleTime : 얼마의 시간이 흐른 후에 데이터를 stale( 신선하지 않은 -> 최신 상태가 아닌 ) 취급할 것인지에 해당하는 옵션입니다.

Jyophie commented 1 month ago

React Query의 GC 타임과 Stale Time

React Query는 서버 상태 관리 라이브러리로, 데이터 페칭, 캐싱, 동기화 및 서버 상태 업데이트를 간편하게 처리할 수 있게 해줍니다.

Stale Time

Stale Time은 React Query에서 데이터를 '신선한'(fresh) 상태로 간주하는 시간입니다. 즉, 데이터가 서버에서 마지막으로 가져온 이후로 지정된 시간 동안 신선하다고 간주되며, 이 기간 동안에는 재요청을 하지 않습니다.

const { data } = useQuery('todos', fetchTodos, {
  staleTime: 10000, // 10초 동안 데이터가 신선하다고 간주됩니다.
});

GC 타임 (Garbage Collection Time)

GC 타임은 캐시에 저장된 데이터가 메모리에서 제거될 때까지의 시간을 의미합니다. 이 시간 동안 사용되지 않은 데이터는 자동으로 가비지 컬렉션(GC)되어 메모리에서 해제됩니다.

const { data } = useQuery('todos', fetchTodos, {
  cacheTime: 600000, // 10분 동안 캐시에 데이터가 유지됩니다.
});