Firebase와 Algolia 동기화 지연 문제 해결하기
firebase를 BaaS(Backend as a Service)로 사용한 프로젝트에서
관리자 페이지를 개발하고 있었다.
그런데.
1. 문제 발생
어떤 문제냐면
사용자 목록 화면에서 사용자 정보를 수정하고 목록으로 돌아와서 refetch하면
계속 최신 정보 반영이 안 됐다.
그런데 또 몇 얼마 뒤에 새로고침하면 반영이 된다.
2. 원인 찾기
코드 상에서는 아무 문제도 발견할 수 없었고
평소 firebase를 썼을 때 이런 문제가 없었기에 참 기이했다.
그래서 문제 상황에 대한 가설을 세워보았는데
(1) Algolia에서 캐싱 데이터를 반환하나?
공식문서에서 확인해보았다.
역시 캐싱을 하고 있었다.
추가로 찾아보니 클라이언트, 서버 양측에서 따로 캐싱을 한다고 한다.
그런데 새로고침했을 때 적용되는 것을 보니 클라이언트 캐싱을 날리면 될 것 같아서
그렇다면 refresh prop을 추가해줘서 캐시 무효화 조건을 넣어줬다.
typescriptconst searchUsers = async ({ query, options, refresh = false,}: SearchOptions & { refresh?: boolean }): Promise<SearchResponse> => { if (refresh) { await usersSearchClient.clearCache(); } return usersIndex.search(query, options);};
결과는?
여전히 되지 않았다.
공식문서에 나와있는 모든 캐시 날리는 법을 해봐도 똑같았다..
Firebase의 검색 기능의 한계를 마주한 걸까? 싶어서 원망하는 마음으로 Firebase에 들어갔는데
생각해보니 Algolia를 Firebase Function에 연결해놓았었다.
DB 변경시 Firebase Function을 트리거하도록 해놨었는데
그렇다는 건
DB가 수정된 이후에 Algolia 색인 레코드에 반영되고 그 사이에 내가 fetching을 하는 것이었다..
그림으로 보면 이러한 원인이었다.
3. 해결하기
어떻게든 딜레이가 생기기 때문에
즉시 refetch하면 최신 정보 반영이 안 되고. 그렇다고 Algolia에 반영되고
refetch하기엔 사용자가 딜레이를 감당해야 해서
optimistic update하기로 결정했다.
- 우선 캐싱된 유저 목록을 담는 global state를 만든다.
typescriptimport { User } from 'src/types/types';import { create } from 'zustand'; /** * firebase db -> algolia로 동기화되는 딜레이 사이에 fetch하는 문제로 * sync가 맞지 않는 경우가 생겨 * update data는 cache로 관리함. */ interface CachingUserStoreType { cachedUsers: { [k: string]: User }; hasCachedUser: (id: string) => void; clearCachedUser: VoidFunction; updateCachedUser: (id: string, user: User) => void; deleteCachedUser: (id: string) => void;} export const useCachingUserStore = create<CachingUserStoreType>((set, get) => ({ cachedUsers: {}, hasCachedUser: (id: string) => get().cachedUsers[id], clearCachedUser: () => set({ cachedUsers: {} }), updateCachedUser: (id: string, user: User) => { const cachedUsers = { ...get().cachedUsers, [id]: user }; set({ cachedUsers }); }, deleteCachedUser: (id: string) => { const cachedUsers = { ...get().cachedUsers }; delete cachedUsers[id]; set({ cachedUsers }); },}));
- 업데이트 함수에서 캐시도 업데이트해준다.
typescriptconst handleSave = async () => { try { await updateUser({ id, authority, status, stamps }); updateCachedUser(id, { ...user, authority, status, stamps }); successToast('수정이 완료되었습니다.'); onClose(); refetch(); } catch (err) { console.error(err); errorToast('수정에 실패하였습니다.'); }};
- 유저 fetch 훅에서 캐싱 데이터 동기화
typescript// ...const { cachedUsers, clearCachedUser } = useCachingUserStore();// ...const syncCachedUsers = (users: User[]) => { return users.map((user) => cachedUsers[user.objectID!] || user);}; React.useEffect(() => { fetchUsers(searchQuery); return () => { mountRef.current = false; clearCachedUser(); }; // eslint-disable-next-line react-hooks/exhaustive-deps}, []); const cachedData = syncCachedUsers(data); return { data: cachedData, loading, totalPages, fetchUsers, refreshUsers,};
결과는?
짜릿하네요.
Related Articles
- React 모달 라이브러리를 만들기까지의 변천사저는 간단한 모달 컴포넌트조차 매번 넣어야 하는 게 불편했어요. 그래서 모달 컴포넌트를 함수로 간단히 실행하는 발상에서 시작해, react-handle-alert라는 라이브러리를 탄생시키기까지의 여정을 담았습니다.
2024.05.04ryxxn - React Quill에서 이미지를 업로드하기 위한 올바른 방법검색 결과에 나온 코드를 참고했다가 버그가 생겨 한참을 헤맸던 경험이에요. React에서 DOM API를 사용했을 때 발생한 버그를 겨우 찾은 이야기
2024.04.30ryxxn - 설악버스 개발기정류장에 부착돼있는 버스 시간표가 어르신들과 아이들이 보기 힘들어하는 모습을 많이 봤습니다. 그렇게 만들게 된 버스 시간표 웹앱 개발기입니다.
2024.03.22ryxxn - swiper 캘린더 최적화 하기인턴십 중 스와이퍼 캘린더를 구현하면서 lazy loading을 적용하여 성능 최적화를 했다.
2024.02.11ryxxn