
์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๋ณด์ฌ์ฃผ์ด์ผ ํ๋ ์ํฉ์ ์์ฃผ ๋ง์ฃผ์น๊ฒ ๋ฉ๋๋ค. ์ด๋ ๊ฐ์ฅ ๋จผ์ ๋ ์ค๋ฅด๋ ํด๊ฒฐ์ฑ ์ด ๋ฐ๋ก ํ์ด์ง๋ค์ด์ ์ด์ฃ . ํ์ง๋ง ์ ํต์ ์ธ ํ์ด์ง ๋ฒํธ ๊ธฐ๋ฐ์ ํ์ด์ง๋ค์ด์ ์ ๋ช ๊ฐ์ง ํ๊ณ์ ์ ์๊ณ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฐ์ดํฐ๊ฐ ์ค๊ฐ์ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋๋ฉด ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์๋ ํ์ด์ง์ ๋ด์ฉ์ด ๋ค์ฃฝ๋ฐ์ฃฝ ๊ผฌ์ฌ๋ฒ๋ฆด ์ ์์ฃ ๐ตโ๐ซ๐ซ
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด ๋ฐ๋ก ์ปค์(Cursor) ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ ๋๋ค. ๋ง์น ์ฑ ๊ฐํผ์ฒ๋ผ ํน์ ์์น๋ฅผ ํ์ํด๋๊ณ , ๊ทธ ์ง์ ๋ถํฐ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ธ๋ฐ์, ์ด๋ฒ ๊ธ์์๋ ์ปค์ ํ์ด์ง๋ค์ด์ ์ ๊ฐ๋ ๊ณผ ํ๋ก ํธ์๋์์ TanStack Query์ ๊ฒฐํฉํ์ฌ ์ด๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.

์ปค์ ํ์ด์ง๋ค์ด์ ์ ๋ฐ์ดํฐ์ ํน์ ์ง์ ์ ๊ธฐ์ค์ผ๋ก ๊ทธ ์ดํ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ฐฉ์์ ๋๋ค. ๊ธฐ์กด์ offset/limit ๋ฐฉ์์ด "5ํ์ด์ง์ ๋ฐ์ดํฐ ์ฃผ์ธ์"๋ผ๊ณ ์์ฒญํ๋ค๋ฉด, ์ปค์ ๋ฐฉ์์ "์ด ํญ๋ชฉ ์ดํ์ ๋ฐ์ดํฐ 20๊ฐ๋ฅผ ์ฃผ์ธ์"๋ผ๊ณ ์์ฒญํ๋ ์ ์ด์ฃ . ๋ง์น ๊ธด ๋ฌธ์๋ฅผ ์ฝ๋ค๊ฐ ์ฑ ๊ฐํผ๋ฅผ ๊ฝ์๋๊ณ , ๋์ค์ ๊ทธ ์ง์ ๋ถํฐ ๋ค์ ์ฝ๊ธฐ ์์ํ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค ๐
๊ฐ๋ น, SNS ํผ๋๋ ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํํ ๋ ์ปค์ ํ์ด์ง๋ค์ด์ ์ด ํนํ ์ ์ฉํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์คํฌ๋กค์ ๋ด๋ฆด ๋๋ง๋ค ๋ง์ง๋ง์ผ๋ก ๋ณธ ๊ฒ์๋ฌผ์ ID๋ฅผ ์ปค์๋ก ์ฌ์ฉํ์ฌ ๊ทธ ์ดํ์ ๊ฒ์๋ฌผ์ ์์ฒญํ๋ ์์ด์ฃ .
// ๊ธฐ์กด์ offset/limit ๋ฐฉ์
const fetchUsers = (page, limit) => {
const offset = (page - 1) * limit;
return db.users.findMany({
skip: offset,
take: limit
});
};
// ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
const fetchUsers = (cursor, limit) => {
return db.users.findMany({
take: limit,
cursor: {
id: cursor
},
orderBy: {
id: 'desc'
}
});
};
์ปค์ ๋ฐฉ์์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์ ์งํ ์ ์๋ค๋ ์ ์ ๋๋ค. offset ๋ฐฉ์์ ์ค๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋๋ฉด ํ์ด์ง์ ๋ด์ฉ์ด ๋ฐ๋ฆฌ๊ฑฐ๋ ์ค๋ณต๋ ์ ์๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ํ์ง๋ง ์ปค์ ๋ฐฉ์์ ๊ฐ ๋ฐ์ดํฐ ํญ๋ชฉ์ ๋ถ์ฌ๋ ๊ณ ์ ์๋ณ์(๊ณ ์ ID๋ ํ์์คํฌํ ๋ฑ)๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ์ ๋ฌผ๋ฆฌ์ ์ธ ์์น๋ ์์๊ฐ ๋ฐ๋์ด๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
๋ํ ์ปค์ ๋ฐฉ์์ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ๋ ํจ์จ์ฑ์ด ๊ทน๋ํ ๋ฉ๋๋ค. PostgreSQL ํ๊ฒฝ์์ ์งํํ ๋ฒค์น๋งํฌ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ณด๋ฉด ๊ทธ ์ฐจ์ด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์ ๊ทธ๋ํ๋ 100๋ง ๊ฑด์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋์์ผ๋ก, ํ์ด์ง๋น 20๊ฐ ํญ๋ชฉ์ ๊ธฐ์ค์ผ๋ก ๋ค์ํ ์์น์์ ๋ฐ๋ณต ์ธก์ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ํ๋ ๋๋ค.
๊ทธ๋ํ์์ ๋ณผ ์ ์๋ฏ์ด, offset ๋ฐฉ์์ ๋ ์ฝ๋ ์๊ฐ ์ฆ๊ฐํ ์๋ก ์๋ต ์๊ฐ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ์ฆ๊ฐํฉ๋๋ค. ํนํ 50๋ง ๊ฑด์ ๋์ด๊ฐ๋ ์ง์ ๋ถํฐ ์ฑ๋ฅ ์ ํ๊ฐ ๋๋ ทํด์ง๋ฉฐ, 100๋ง ๊ฑด์ ๊ฐ๊น์์ง ๋๋ ํ๊ท 2์ด ์ด์์ด ์์๋ฉ๋๋ค. ๋ฐ๋ฉด ์ปค์ ๋ฐฉ์์ ๋ฐ์ดํฐ ํฌ๊ธฐ์ ๊ด๊ณ์์ด ์ผ๊ด๋๊ฒ 50ms ์ด๋ด์ ์๋ต ์๊ฐ์ ์ ์งํฉ๋๋ค.
์ด๋ฌํ ์ฑ๋ฅ ์ฐจ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ ๋ฐฉ์์์ ๊ธฐ์ธํฉ๋๋ค. offset ๋ฐฉ์์์๋ ์ง์ ๋ offset๋งํผ์ ๋ ์ฝ๋๋ฅผ ์์ฐจ์ ์ผ๋ก ์ค์บํ ํ ๊ฑด๋๋ฐ์ด์ผ ํ์ง๋ง, ์ปค์ ๋ฐฉ์์์๋ ์ธ๋ฑ์ค๋ฅผ ํตํด ํน์ ์์น์ ์ง์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ฒ๋ค๊ฐ offset ๋ฐฉ์์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๋ offset ํฌ๊ธฐ์ ๋น๋กํ์ฌ ์ฆ๊ฐํ๋๋ฐ, 100๋ง ๊ฑด ๊ธฐ์ค์ผ๋ก ์ต๋ 128MB๊น์ง ์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ผ๋ก ์ธก์ ๋์์ต๋๋ค.
์ด๋ฌํ ์ปค์ ํ์ด์ง๋ค์ด์ ์ ์ฅ์ ์ ์ต๋ํ์ผ๋ก ํ์ฉํ๋ ค๋ฉด ํจ๊ณผ์ ์ธ ๋ฐ์ดํฐ ๊ด๋ฆฌ๊ฐ ํ์์ ์ ๋๋ค. ์ฌ๊ธฐ์ TanStack Query๊ฐ ๋ฑ์ฅํ๋๋ฐ์, TanStack Query๋ฅผ ํ์ฉํ๋ฉด ์ปค์ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ํจ์ฌ ๋ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃฐ ์ ์์ต๋๋ค.
TanStack Query๋ ์ฟผ๋ฆฌ ํค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํฉ๋๋ค. ๊ฒ์์ด, ํํฐ, ํ์ด์ง ๋ฒํธ, ๊ทธ๋ฆฌ๊ณ ์ปค์ ์ ๋ณด๊น์ง ํฌํจ๋ ๋ฐฐ์ด์ ์ฟผ๋ฆฌ ํค๋ก ์ฌ์ฉํ๋ฉด ๊ฐ๊ฐ์ ๊ณ ์ ํ ๊ฒ์ ์ํ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์ ํํ๊ฒ ์บ์ํ ์ ์์ฃ .
const queryKey = [
"getSearch",// ๊ฒ์ ๋๋ฉ์ธ
debouncedQuery,// ๊ฒ์์ด
filter,// ํํฐ ์กฐ๊ฑด
page,// ํ์ฌ ํ์ด์ง
pageSize,// ํ์ด์ง๋น ํญ๋ชฉ ์
cursor// ์ปค์ ์ ๋ณด
];
์ด๋ ๊ฒ ์ค๊ณ๋ ์ฟผ๋ฆฌ ํค๋ ๋ง์น ๋ฐ์ดํฐ์ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ์ฒ๋ผ ์๋ํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ๊ฒ์์ด๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋, ํํฐ๋ฅผ ๋ฐ๊พธ๊ฑฐ๋, ํ์ด์ง๋ฅผ ์ด๋ํ ๋๋ง๋ค TanStack Query๋ ํด๋นํ๋ ์บ์๋ฅผ ์ ํํ ์ฐพ์๋ด์ ์ฌ์ฌ์ฉํ ์ ์์ฃ . ์ด๋ ๋ถํ์ํ API ํธ์ถ์ ์ค์ด๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
์ปค์ ๋ฐ์ดํฐ๋ Zustand์ ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋๊ตฌ์ ๋ง๋๋ฉด ๋์ฑ ๋น์ ๋ฐํฉ๋๋ค. ์๋ฒ์์ ๋ฐ์์จ ์ปค์ ์ ๋ณด๋ฅผ ์ ์ญ ์ํ๋ก ๊ด๋ฆฌํ๋ฉด์๋, TanStack Query์ ์บ์ฑ ๋ฉ์ปค๋์ฆ์ ๋์์ ํ์ฉํ ์ ์์ฃ .
const useSearchStore = create<SearchState>((set) => ({
cursor: { preData: null, nextIndex: null },
setCursor: (cursor) => set({
preData: cursor?.preData || null,
nextIndex: cursor?.nextIndex || null,
}),
}));
// ์ปดํฌ๋ํธ์์์ ํ์ฉ
const { data, isLoading } = useQuery({
queryKey: ["getSearch", debouncedQuery, filter, page, pageSize, cursor],
queryFn: () => getSearch({
query: debouncedQuery,
filter,
page,
pageSize,
cursor,
}),
staleTime: 5 * ONE_MINUTE,
});
์ด ์ฝ๋์์๋ Zustand์ TanStack Query๋ฅผ ๊ฒฐํฉํ์ฌ ์ปค์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ์์ต๋๋ค. Zustand๋ ์๋ฒ์์ ๋ฐํ๋ ์ปค์ ์ ๋ณด๋ฅผ ์ ์ญ์ ์ผ๋ก ๊ด๋ฆฌํ์ฌ ์ฌ๋ฌ ์ปดํฌ๋ํธ๊ฐ ๋์ผํ ์ํ๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ๋์์ค๋๋ค. TanStack Query๋ ์ฟผ๋ฆฌ ํค์ ์ปค์ ์ ๋ณด๋ฅผ ํฌํจ์์ผ, ๊ฐ ์ํ๋ฅผ ๊ณ ์ ํ๊ฒ ๊ตฌ๋ถํ๊ณ ์บ์ฑ ๋ฉ์ปค๋์ฆ์ ํตํด ๋ถํ์ํ ๋คํธ์ํฌ ์์ฒญ์ ์ค์ ๋๋ค. ์ด์ฒ๋ผ ์ํ ๊ด๋ฆฌ์ ์บ์ฑ ์ ๋ต์ ๊ฒฐํฉ์ ๋ฐ์ดํฐ ์์ฒญ์ ์ต์ ํํ๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค.
์ปค์ ๊ธฐ๋ฐ์ ํ์ด์ง ์ ํ์ ํน์ ์ง์ ์ ๊ธฐ์ค์ผ๋ก ์ด์ ๊ณผ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ํ์ํ ์ ์๊ฒ ํด์ค๋๋ค. ์ด๋ TanStack Query์ invalidateQueries๋ฅผ ํ์ฉํ๋ฉด ํ์ํ ๋ฐ์ดํฐ๋ง ์ ํ์ ์ผ๋ก ๊ฐฑ์ ํ ์ ์์ต๋๋ค.
const handlePage = (mode: "prev" | "next") => {
const cursor = {
curPage: page,
preData,
nextIndex,
};
if (mode === "next" && nextIndex) {
setPage(page + 1);
queryClient.invalidateQueries({
queryKey: ["getSearch", searchParam, currentFilter, page + 1, 10,
{ ...cursor, preData: null }],
});
}
};
invalidateQueries๋ ํน์ ์ฟผ๋ฆฌ ํค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ํ ๋ฐ์ดํฐ๋ง ๊ฐฑ์ ํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด๋ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๊ฐ์ ธ์ค๋ ๋์ , ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ์
๋ฐ์ดํธํ์ฌ ๋คํธ์ํฌ ๋ถํ๋ฅผ ์ค์ด๊ณ ์ฑ๋ฅ์ ์ต์ ํํฉ๋๋ค. ์ด๋ ๊ฒ ๊ตฌ์ฑ๋ ํ์ด์ง ์ ํ ๋ก์ง์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ด ๋น๋ฒํ ํ๊ฒฝ์์๋ ์์ ์ ์ผ๋ก ์๋ํ๋ฉฐ, ์ฌ์ฉ์๊ฐ ๋ถ๋๋ฝ๊ณ ๋น ๋ฅด๊ฒ ํ์ด์ง๋ฅผ ํ์ํ ์ ์๋๋ก ์ง์ํฉ๋๋ค. TanStack Query์ ์ปค์ ๊ธฐ๋ฐ ์ค๊ณ๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ ๊ด๋ฆฌ์์๋ ์ด๋ฌํ ์ฅ์ ์ ๊ทน๋ํํฉ๋๋ค.

์ปค์ ํ์ด์ง๋ค์ด์ ๊ณผ TanStack Query ์ฝค๋น๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ํจ๊ณผ์ ์ธ ํด๊ฒฐ์ฑ ์ ์ ์ํฉ๋๋ค. ๊ธฐ์กด์ offset ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ด ๊ฐ์ง ์ฑ๋ฅ๊ณผ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ฉด์๋, TanStack Query์ ์บ์ฑ ์์คํ ์ ํ์ฉํด ์๋ฒ ๋ถํ๊น์ง ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ๋์์ฃ . ํนํ ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋๊ณ ๋ณ๊ฒฝ๋๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌดํ ์คํฌ๋กค์ด๋ ์ค์๊ฐ ํผ๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋๋, ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์์ฐ์ค๋ฝ๊ฒ ๋ณด์ฅํ๋ฉด์๋ ๋ถ๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค. ํ์ด์ง๋ค์ด์ ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ๊ณ์๋ค๋ฉด, ์ปค์ ๋ฐฉ์์ ๋์ ํด๋ณด๋ ๊ฑด ์ด๋ ์ ๊ฐ์? ํ์ด์ง ์ด๋๋ง๋ค ๊น๋นก์ด๊ณ ๋ฉ์ถ์ง ์๋ ๋ก๋ฉ ์คํผ๋์์ ๋ฒ์ด๋์ ๋๊ท๋ชจ ๋ฐ์ดํฐ ๊ด๋ฆฌ์ ๊ฐํผ๋ฅผ ์ก์ ์ ์์๊ฑฐ์์ ๐ฆพ
์ ์ญ์ญ ์ฝํ๋ค์ ์ ์ง๊ธ๊น์ง ๊ธ์์ฐ์๊ตฌ... ๋ง์ด ๋ฐฐ์๊ฐ๋๋ค :)