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