자바스크립트를 사용한 GraphQL Subscriptions를 위한 캐싱 전략

GraphQL Subscriptions은 웹 애플리케이션에서 실시간 데이터 업데이트를 제공하는 기능입니다. 이를 통해 실시간 채팅, 알림, 주문 추적 등 다양한 기능을 구현할 수 있습니다. 그러나 많은 사용자가 동시에 구독을 하게 되면 서버에 부하가 발생할 수 있습니다. 이 문제를 해결하고자 GraphQL Subscriptions를 위한 캐싱 전략을 사용할 수 있습니다.

1. Apollo Client와 Redis를 활용한 캐싱

Apollo Client와 Redis를 함께 사용하여 GraphQL Subscriptions의 캐싱을 구현할 수 있습니다. Apollo Client는 GraphQL 쿼리 결과를 클라이언트 측에서 캐시할 수 있는 기능을 제공하며, Redis는 데이터베이스의 캐시 역할을 수행합니다. 이를 활용하여 서버의 부하를 줄이고 캐시된 데이터를 활용할 수 있습니다.

import { PubSub } from 'graphql-subscriptions';
import { ApolloClient, gql, InMemoryCache } from '@apollo/client';
import Redis from 'ioredis';

// Redis와 연결
const redis = new Redis();

// Apollo Client 초기화
const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  uri: 'http://localhost:8080/graphql',
});

// GraphQL Subscriptions에 대한 PubSub 인스턴스 생성
const pubsub = new PubSub();

// 캐싱된 결과를 가져오는 함수 정의
async function getCachedResult(key) {
  const cachedResult = await redis.get(key);
  if (cachedResult) {
    return JSON.parse(cachedResult);
  }
  return null;
}

// GraphQL Subscriptions 처리 함수 정의
async function resolveSubscription(payload, variables) {
  const cacheKey = JSON.stringify({ payload, variables });

  // 캐시된 결과 확인
  const cachedResult = await getCachedResult(cacheKey);

  if (cachedResult) {
    // 캐시된 결과 반환
    return cachedResult;
  }

  // 캐시된 결과 없을 경우 Apollo Client로 쿼리 실행
  const { data } = await apolloClient.query({
    query: gql`YOUR_SUBSCRIPTION_QUERY`,
    variables,
  });

  // 결과를 Redis에 캐시
  await redis.set(cacheKey, JSON.stringify(data));

  return data;
}

// GraphQL Subscriptions를 위한 설정
const subscriptionOptions = {
  pubsub,
  resolve: resolveSubscription,
};

// GraphQL Subscriptions 서버 시작
startGraphQLServerWithSubscriptions(subscriptionOptions);

위의 예시 코드에서는 Apollo Client로부터 캐싱된 결과를 가져오며, Redis를 통해 결과를 캐시합니다. 데이터를 캐시하고 있는지 확인한 후, 캐시된 결과가 있으면 해당 결과를 반환합니다. 그렇지 않은 경우 Apollo Client를 사용하여 쿼리를 실행하고 결과를 Redis에 캐시합니다. 이를 통해 중복된 쿼리를 최소화하고 처리 성능을 향상시킬 수 있습니다.

2. Prisma와 Dataloader를 활용한 데이터 캐싱

또 다른 캐싱 전략으로는 Prisma와 Dataloader를 사용하는 방법이 있습니다. Prisma는 데이터베이스를 다루기 위한 ORM(Object-Relational Mapping) 도구이며, Dataloader는 데이터 액세스 성능을 최적화하기 위한 유틸리티입니다. 이 두 가지를 함께 사용하여 GraphQL Subscriptions의 데이터 캐싱을 구현할 수 있습니다.

import { PubSub } from 'graphql-subscriptions';
import { PrismaClient } from '@prisma/client';
import DataLoader from 'dataloader';

// PrismaClient 초기화
const prisma = new PrismaClient();

// DataLoader 인스턴스 생성
const dataLoader = new DataLoader(async (keys) => {
  const results = await prisma.post.findMany({ where: { id: { in: keys } } });
  const resultsByKey = results.reduce((acc, post) => {
    acc[post.id] = post;
    return acc;
  }, {});
  
  // DataLoader가 요청한 키에 해당하는 결과 반환
  return keys.map((key) => resultsByKey[key]);
});

// GraphQL Subscriptions에 대한 PubSub 인스턴스 생성
const pubsub = new PubSub();

// GraphQL Subscriptions 처리 함수 정의
async function resolveSubscription(payload, variables) {
  const { postId } = variables;

  // DataLoader를 통해 캐시된 데이터 가져오기
  const cachedResult = await dataLoader.load(postId);

  if (cachedResult) {
    // 캐시된 결과 반환
    return cachedResult;
  }

  // 캐시된 결과 없을 경우 Prisma로 데이터 조회
  const result = await prisma.post.findUnique({ where: { id: postId } });

  // DataLoader에 결과 캐시
  dataLoader.prime(postId, result);

  return result;
}

// GraphQL Subscriptions를 위한 설정
const subscriptionOptions = {
  pubsub,
  resolve: resolveSubscription,
};

// GraphQL Subscriptions 서버 시작
startGraphQLServerWithSubscriptions(subscriptionOptions);

위의 예시 코드에서는 Prisma를 사용하여 데이터베이스에 액세스하고, Dataloader를 통해 캐싱된 결과를 가져옵니다. DataLoader를 사용하면 중복된 요청이 들어왔을 때 해당 요청에 대해 한 번만 데이터를 조회하게 됩니다. 그리고 조회한 결과를 Dataloader에 캐시해 놓아서 다음에 동일한 요청이 들어오면 캐시된 결과를 반환합니다. 이를 통해 데이터베이스 액세스 횟수를 최소화하고 처리 성능을 향상시킬 수 있습니다.

결론

GraphQL Subscriptions를 위한 캐싱 전략을 사용하면 동시에 많은 사용자가 구독을 하더라도 서버의 부하를 줄일 수 있습니다. 위에 제시된 예시 코드를 참고하여 자바스크립트를 사용한 GraphQL Subscriptions의 캐싱을 구현해보세요. 이를 통해 더 나은 성능과 사용자 경험을 제공할 수 있을 것입니다.

참고 자료: