금요일

Prisma N+1 쿼리 문제 완벽 해결

 🔍 검색 키워드: Prisma N+1 쿼리, include relation, select 최적화, Prisma 성능 튜닝, 데이터베이스 쿼리 최적화


Prisma N+1 쿼리 문제 완벽 해결


증상: 데이터 조회 시 데이터베이스 쿼리가 너무 많다

사용자 목록 조회 후 각 사용자의 프로필을 가져오려니 쿼리가 100개 이상 날아가나요?


1. 사용자 목록 조회 (1개 쿼리)

2. 각 사용자의 프로필 조회 (N개 쿼리)

결과: 1 + N = 101개 쿼리!


이것이 N+1 쿼리 문제입니다. 성능 저하, 데이터베이스 부하 증가를 초래합니다.


원인 분석


원인 1: 관계 데이터를 include/select 없이 조회

const users = await prisma.user.findMany();

for (const user of users) {

  const profile = await prisma.profile.findUnique({

    where: { userId: user.id }

  });

}

결과: 1 + N개 쿼리


원인 2: 중첩 관계에서 include 누락

const posts = await prisma.post.findMany();

for (const post of posts) {

  const author = await prisma.user.findUnique({

    where: { id: post.authorId }

  });

  const comments = await prisma.comment.findMany({

    where: { postId: post.id }

  });

}

결과: 1 + N + N*M개 쿼리


해결 방법


✅ 방법 1: include로 관계 데이터 포함 (기본)

const users = await prisma.user.findMany({

  include: {

    profile: true

  }

});

1개 쿼리로 완료!


✅ 방법 2: select로 필요한 컬럼만 선택 (최적화)

const users = await prisma.user.findMany({

  select: {

    id: true,

    email: true,

    profile: {

      select: {

        bio: true,

        avatar: true

      }

    }

  }

});

데이터양 감소, 성능 향상


✅ 방법 3: where 조건으로 불필요한 데이터 필터링

const post = await prisma.post.findUnique({

  where: { id: 1 },

  include: {

    comments: {

      where: { approved: true },

      orderBy: { createdAt: 'desc' },

      take: 10

    }

  }

});

효율적!


결론

- include로 관계 데이터 포함 — N+1의 기본 해결

- select로 필요한 필드만 — 성능 + 보안

- where로 DB 수준 필터링 — 불필요한 데이터 전송 방지

- 중첩은 2단계까지 — 복잡도와 성능의 균형

- 쿼리 로깅으로 검증 — 개발 중 문제 조기 발견


Prisma는 ORM이지만, SQL 수준의 최적화 의식을 가져야 효율적인 쿼리를 만들 수 있습니다.

TypeScript 제네릭 타입 추론 완벽 정복

🔍 검색 키워드: TypeScript 제네릭 타입 추론, 제네릭 제약 조건, Generic extends, TypeScript 고급 타입, 타입 안전성


TypeScript 제네릭 타입 추론 완벽 정복


증상: 제네릭이 자동 추론되지 않는다

함수에 제네릭을 사용했는데 타입이 unknown이거나 명시적으로 지정해야 하나요?


이것이 제네릭 타입 추론의 복잡성입니다. 동작하지 않으면 수동 타입 지정이 필요하지만, 올바른 제약 조건을 설정하면 자동 추론이 가능합니다.


원인 분석


원인 1: 제약 조건 없는 제네릭

function processArray<T>(arr: T[]): T[] {

  return arr.sort(); // ❌ 에러: T에 sort가 있다는 보장 없음

}


원인 2: 여러 제네릭 간 관계 미정의

function transform<T, U>(input: T): U {

  return input as U; // ❌ 위험한 as 사용

}


해결 방법


✅ 방법 1: 제약 조건으로 구조 정의 (extends)

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {

  return obj[key]; // ✓ key는 T의 키만 가능

}


✅ 방법 2: infer로 중첩 타입 추출

type GetArrayElement<T> = T extends Array<infer U> ? U : never;


type NumArray = GetArrayElement<number[]>; // number ✓


✅ 방법 3: 조건부 타입으로 분기

type IsString<T> = T extends string ? true : false;


type A = IsString<'hello'>; // true ✓

type B = IsString<number>; // false ✓


결론

- 제약 조건(extends)은 필수 — 타입 안전성과 추론 향상

- infer로 타입 추출 — 제네릭 값을 유연하게 활용

- 조건부 타입으로 분기 — 복잡한 타입 관계 표현

- 오버로드로 정확하게 — 여러 입출력 조합 처리


제네릭을 마스터하면 TypeScript의 진정한 강력함을 경험할 수 있습니다.

목요일

React useEffect 무한루프 문제 완벽 해결 가이드

🔍 검색 키워드: React useEffect 무한루프, useEffect 의존성 배열, React Hook 성능 최적화, useEffect 디버깅

React useEffect 무한루프 문제 완벽 해결 가이드

증상: useEffect가 계속 실행된다

개발 중 콘솔을 보니 로그가 무한히 출력되거나 API 요청이 계속 날아가고 있나요?

useEffect: 무한 실행 상태
  console.log("Effect 실행"); // 계속 반복

이것이 useEffect 무한루프입니다. 성능 저하, 메모리 누수, API 서버 부하까지 초래하는 심각한 문제입니다.


원인 분석

원인 1: 의존성 배열 누락

useEffect(() => {
    console.log("매 렌더링마다 실행");
      fetchData();
      }, ); // 의존성 배열 없음 = 모든 렌더링마다 실행

해결 방법

✅ 방법 1: 의존성 배열 명시 (필수!)

올바른 코드:

useEffect(() => {
    fetchData();
    }, []); // 마운트 시 1회만 실행

결론

  • 의존성 배열은 필수 — 생략하면 무한루프
  • 의존성 값은 안정화 — 객체/배열은 useMemo, useCallback으로