logo

Blog

Firebase와 Algolia 동기화 지연 문제 해결하기

#react#firebase#algolia
ryxxn profileryxxn
2024.03.24
thumbnail

firebaseBaaS(Backend as a Service)로 사용한 프로젝트에서 관리자 페이지를 개발하고 있었다.

그런데.

1. 문제 발생

어떤 문제냐면
사용자 목록 화면에서 사용자 정보를 수정하고 목록으로 돌아와서 refetch하면
계속 최신 정보 반영이 안 됐다.
그런데 또 몇 얼마 뒤에 새로고침하면 반영이 된다.


2. 원인 찾기

코드 상에서는 아무 문제도 발견할 수 없었고
평소 firebase를 썼을 때 이런 문제가 없었기에 참 기이했다.

그래서 문제 상황에 대한 가설을 세워보았는데

(1) Algolia에서 캐싱 데이터를 반환하나?

공식문서에서 확인해보았다.

document-cache-section

역시 캐싱을 하고 있었다.

추가로 찾아보니 클라이언트, 서버 양측에서 따로 캐싱을 한다고 한다.

그런데 새로고침했을 때 적용되는 것을 보니 클라이언트 캐싱을 날리면 될 것 같아서

그렇다면 refresh prop을 추가해줘서 캐시 무효화 조건을 넣어줬다.

typescript
const searchUsers = async ({  query,  options,  refresh = false,}: SearchOptions & { refresh?: boolean }): Promise<SearchResponse> => {  if (refresh) {    await usersSearchClient.clearCache();  }
  return usersIndex.search(query, options);};

결과는?


여전히 되지 않았다.

공식문서에 나와있는 모든 캐시 날리는 법을 해봐도 똑같았다..

Firebase의 검색 기능의 한계를 마주한 걸까? 싶어서 원망하는 마음으로 Firebase에 들어갔는데

firebase-functions

생각해보니 Algolia를 Firebase Function에 연결해놓았었다.

DB 변경Firebase Function을 트리거하도록 해놨었는데

그렇다는 건

DB가 수정된 이후에 Algolia 색인 레코드에 반영되고 그 사이에 내가 fetching을 하는 것이었다..

그림으로 보면 이러한 원인이었다.

cause-of-problem

3. 해결하기

어떻게든 딜레이가 생기기 때문에

즉시 refetch하면 최신 정보 반영이 안 되고. 그렇다고 Algolia에 반영되고

refetch하기엔 사용자가 딜레이를 감당해야 해서

optimistic update하기로 결정했다.

  1. 우선 캐싱된 유저 목록을 담는 global state를 만든다.
typescript
import { 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 });  },}));
  1. 업데이트 함수에서 캐시도 업데이트해준다.
typescript
const handleSave = async () => {  try {    await updateUser({ id, authority, status, stamps });
    updateCachedUser(id, { ...user, authority, status, stamps });
    successToast('수정이 완료되었습니다.');
    onClose();    refetch();  } catch (err) {    console.error(err);    errorToast('수정에 실패하였습니다.');  }};
  1. 유저 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,};

결과는?

result

짜릿하네요.

Related Articles