[Apollo] In Memory Cache 에 대해서...

Dev Cho·2020년 7월 21일
2

[Apollo-Client]

목록 보기
1/1

프로젝트를 진행하면서 가장 어려웠고 시간을 많이 할애 했던 부분은 cache 에 관한 작업이었다. 사전에 노마드코더에서 apollo client 관련 강의도 듣고 apollo client 를 사용해서 프로젝트를 진행했던 블로그들을 찾아 글을 읽어보면서 cache 관리만 잘하면 프로젝트를 진행하는데 무리가 없을거라고 생각했다.

하지만 결과적으로 cache 를 제대로 사용하지 못한채 프로젝트는 종료되었고 내가 무었때문에 힘들었고, 어떤 것이 잘 되지 않았나 찾아보기 위해서 InMemoryCache 에 대해 알아보기로 했고 지금까지 알아본 것들을 정리해보려고 한다.

일단 가장 어려웠고 힘든 것이 cacheupdate 가 되면 화면을 다시 그려주는 부분이었는데 내부에서 어떤 움직임이 일어나는지 알아봐야 할 것 같았다.

In memory Cache 란?

apollo 공식 문서를 보면 아래와 같이 쓰여져 있다.

apollo clientgql 의 결과를 InMemoryCache 에 저장하며, 이를 통해 클라이언트는 불필요한 네트워크 요청 없이 동일한 데이터에 대해 요청할 수 있다.

공식 문서를 좀 더 들여다 보면 InMemoryCache 는 쿼리 응답 개체를 내부 데이터 저장소에 저장하기 전에 이를 정규화(?) 하며 아래와 같은 단계를 가진다

  • 캐시는 응답 받은 데이터에 포함된 모든 식별 가능한 객체에 대해 고유한 ID를 생성
  • 캐시는 개체를 ID별로 플랫 룩업 테이블(DB 테이블 같이..?)에 저장한다.
  • 들어오는 객체가 기존 객체와 동일한 ID로 저장될 때마다 해당 객체의 필드가 병합된다.

그리고 InMemoryCache 는 기본적으로 __typenameid 또는 _id 를 결합시켜서 고유한 식별자를 만든다.(이 기본 식별자를 id 또는 _id 이외의 다른 필드로 정의하고 싶을 경우 설정해 줄 수 있다.)

예시로 __typenametask 이며 id14 일 경우에는 task:14 의 식별자가 생성되는 것이다.

Cache Update 는 어떻게 할까?

In Memory Cache 에 데이터가 어떻게 저장되는지 알아보았다. 그렇다면 query 를 실행 후에 cache 를 업데이트하려면 어떻게 해야할까?

이 경우에는 여러 방법을 사용하여 해결할 수 있다.

  • mutation 이 실행 된 후 새로고침(F5) 실행
  • mutation 이 실행 된 후 직접 cache 에 접근하여 데이터 변경(writeQuery)
  • fetchPolicy 사용

위 방식들을 활용하면 cache 를 업데이트 할 수 있으며 변경된 UI 를 볼 수 있다.

그렇다면 나에게는 어떤 문제가 있었을까?

cache 를 업데이트 하는 방법들을 알고 있음에도 불구하고 내가 직접 코드를 작성하면서 겪은 에러와
어려웠던 점들을 알아보자. 아래 코드는 apollo 공식 문서 예시다.

// Query that fetches all existing to-do items
const query = gql`
  query MyTodoAppQuery {
    todos {
      id
      text
      completed
    }
  }
`;

// Get the current to-do list
const data = client.readQuery({ query });

// Create a new to-do item
const myNewTodo = {
  id: '6',
  text: 'Start using Apollo Client.',
  completed: false,
  __typename: 'Todo',
};

// Write back to the to-do list, appending the new item
client.writeQuery({
  query,
  data: {
    todos: [...data.todos, myNewTodo],
  },
});

첫 번째로 cachedata 를 추가 또는 제거할 때는 immutable 규칙을 지켜야 한다. 생각보다 이 규칙은 정말 중요한데 cache 는 업데이트가 끝나기 전까지 변하지 않으므로 이 immutable 규칙을 적용해주어야 한다. 또한 immutable 을 지키지 않아 cache 가 업데이트 되지 않는다는 이슈에 대해서는 stackoverflow 에도 매우 많이 올라와 있다.(주로 immer 라이브러리를 사용하거나 Lodash Clone Deep 방식을 사용하는 것 같다.)

두 번째로 위 코드를 예로 들면 data.tods 의 Object 와 myNewTodo 의 Object 가 서로 같은 타입을 가지고 있어야 한다는 것이다. 위의 코드는 간단한 Object 형태로 id 를 가지고 있어 업데이트가 무난히 이루어지지만 nested Object 의 경우 cache 가 업데이트 되는 과정이 굉장히 복잡한데 아직도 이 부분에 대해서 정확히 이해한 바가 없어 더 공부해야 할 필요가 있다.

정리 및 느낀점

사실 immutable 로 인해 cache 가 업데이트 되지 않았던 것은 크게 문제가 되지 않았지만 writeQuery 를 실행할 때 마다 filed name 에 대해 undfined 에러가 발생할 때 마다 도저히 감을 잡지 못했었다. 그럴 때 마다 위에서 말한대로 업데이트 하려는 데이터와 writeQuery 안에 있는 data 내부의 Object 를 같게 맞춰줘서 에러를 해결 했지만 nested Object 의 경우는 아직도 해결하지 못했다. 이와 관련해서 몇 가지를 더 찾아보았는데 @Client 의 필드가 누락되었거나 여기서 설명하진 않았지만 id 또는 _id 로 구분되지 않는데 dataIdFromObject 를 정의하지 않아 발생할 수 있는 문제인 것 같았다. 그래도 이 기회로 조금이나마 cache 에 대해 알 수 있었지만 내가 제대로 이해하고 사용하지 못하고 있다는 점에서 매우 찝찝한 상황이다. 앞으로 더 많이 사용할 기회가 생기고 공부하다 보면 이해할 수 있을거라고 생각하며 apollo 를 사용해서 프로젝트를 해본 경험에 가치를 두자.

참고 자료
1. How to update the Apollo Client’s cache after a mutation
2. How to Rock the Party with Apollo GraphQL Cache
3. Apollo Docs

1개의 댓글

comment-user-thumbnail
2021년 1월 11일

고민의 흔적이 보이는 멋진 글이네요
많이 배워갑니다 :)

답글 달기