GQL의 Client caching 관련
- GraphQL 데이터를 클라이언트에서는
쿼리
기반으로 캐싱할텐데, 그렇다면 다른 쿼리들이 같은 데이터를 가지고 있을 때 캐시된 데이터의 일관성이 지켜지지 않은 문제는 어떻게 해결될까? - 예를들면 아래의 두 쿼리는 쿼리는 다르나, 내부 데이터는 동일한데?
query { stories { id, text, likeCount } } // 1번
query { story(id: "123") { id, text, likeCount } } // 2번
데이터 정규화
- 여러 계층의 레코드 응답을 단일 계층의 컬렉션으로 정규화한다.
- 고유 식별자(id필드)로 구분되고, 중첩된 객체 구조를 플랫하게 펼쳐서 저장한다.
- 또한, 객체간의 관계는 참조를 통해 유지된다.
query AllStories {
stories {
id,
text,
likeCount
}
}
query SingleStory {
story(id: "123") {
id,
text,
likeCount
}
}
위의 두 쿼리의 결과는 아래와 같이 정규화 되어 저장된다.
{
"ROOT_QUERY": {
"stories": [
{ "__ref": "Story:123" },
{ "__ref": "Story:456" },
// ...
],
"story({"id":"123"})": { "__ref": "Story:123" }
},
"Story:123": {
"id": "123",
"text": "Hello World",
"likeCount": 10
},
"Story:456": {
"id": "456",
"text": "Another story",
"likeCount": 5
}
// ...
}
- 데이터 정합성을 지킨다.
Story:123
이라는 데이터는 두 개의 쿼리에서 사용되지만, 데이터는 한 곳에만 저장되므로 정합성을 유지한다.
- 효율적인 캐시 업데이트
- 예를들어 캐시가 업데이트 되어
Story:123
결과가 업데이트 되어야한다면,Story:123
을 사용하는 모든 쿼리의 캐시가 아닌, 하나의 필드만 업데이트하면 된다.
- 예를들어 캐시가 업데이트 되어
- 참조를 통한 중복 제거 (메모리 효율)
- 동일 객체의 여러 복사본을 저장하지 않아도 되어 메모리 사용이 효율적임
주의해야할 케이스 🧐 - 부분객체업데이트
예를들어 아래의 초기 쿼리 결과는 두번째와 같은 결과로 캐시에 데이터가 저장된다.
query GetUserProfile {
user(id: "123") {
id
name
email
age
bio
}
}
{
"User:123": {
"id": "123",
"name": "Moonhee Kim",
"email": "Mooneee@example.com",
"age": 30,
"bio": "Software developer"
}
}
그런데 만약, 사용자가 자신의 '이름'만 변경하는 뮤테이션을 실행한다고 했을 때 해당 뮤테이션의 결과는 아래와 같다.
mutation UpdateUserName {
updateUser(id: "123", name: "Boogie") {
id
name
}
}
{
"data": {
"updateUser": {
"id": "123",
"name": "Boogie"
}
}
}
이 시점에서 클라이언트는 캐시 업데이트를 주의깊게 해야한다. 객체 부분을 업데이트하는게 아니라 전체 업데이트 해버릴 수 있기 때문.
{
"User:123": {
"id": "123",
"name": "Boogie"
// email, age, bio가 사라짐!
}
}
이러한 동작의 해결방븝으로 Apollo Client 는 기본 병합 동작을 제공한다.
즉, 기본으로 반환된 필드만 업데이트하고 나머지만 유지하는 것.
만약 뮤테이션 결과를 수동으로 캐시에 적용할 때는 아래와 같이 updateQuery
를 사용 하면 된다.
client.mutate({
mutation: UPDATE_USER_NAME,
variables: { id: "123", name: ""," },
update: (cache, { data: { updateUser } }) => {
const existingUser = cache.readQuery({ query: GET_USER_PROFILE, variables: { id: "123" } });
cache.writeQuery({
query: GET_USER_PROFILE,
data: { user: { ...existingUser.user, ...updateUser } },
});
},
});
그냥 캐시에 넣지 않고 refetch 하는 방법도 물론 있긴 하겠다.
Apollo Client 의 UpdateQuery vs WriteFragment
UpdateQuery
- 주로 쿼리 결과 전체를 업데이트 할 때 사용한다.
- 특정 쿼리에 대한 캐시 데이터를 수정한다.
- 전체 쿼리 결과를 다루므로, 여러 필드나 복잡한 데이터 구조를 한번에 업데이트 할 수 있다.
WriteFragment
- 특정 객체의 일부 필드만 업데이트 할 때 사용한다.
- 캐시의 특정 부분만을 직접 수정한다.
- 쿼리 전체가 아닌, 특정 객체나 필드에 대해 더 세밀한 제어가 가능하다.