본문 바로가기

프로젝트/나만의명언집

리팩토링 히스토리(24.08.21 ~)

반응형

해당 포스트는

- 현재 포스트는 나만의 명언집 프로젝트(NextJS 로 개발)의 코드를 개선하면서 얻은 산출물을 남겨두는 용도로 작성 되었습니다.

 

포스트 추가

server action 적용 전

- 클라이언트로 부터 서버로 요청이 전송되는데 0.21ms 걸림

- 서버로 부터 응답을 기다리는데 1.11초 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 0.61ms 걸림

server action 적용 이후

- 클라이언트로 부터 서버로 요청이 전송되는데 0.37ms 걸림

- 서버로 부터 응답을 기다리는데 257.79ms초 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 0.61ms 걸림

결과적으로

-  결과적으로, 1100ms−257.79ms=842.21ms(약 0.8초 ; 약 75.8%) 단축

 

포스트 수정

Server Action 적용 전

- 클라이언트로 부터 서버로 요청이 전송되는데 0.14ms 걸림

- 서버로 부터 응답을 기다리는데 1.25초 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 0.56ms 걸림

 

Server Action 적용 후

- 클라이언트로 부터 서버로 요청이 전송되는데 0.17ms 걸림

- 서버로 부터 응답을 기다리는데 105.16ms 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 6.77ms 걸림

 

결과적으로

- 1250 ms - 115.27ms  = 1137.73ms(약 1.135초; 약 90.78% ) 단축

 

포스트 삭제

Server action 적용 전

- 클라이언트로 부터 서버로 요청이 전송되는데 0.18ms 걸림

- 서버로 부터 응답을 기다리는데  723.91ms 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 0.43ms 걸림

 

Server Action 적용 후

- 클라이언트로 부터 서버로 요청이 전송되는데 0.18ms 걸림

- 서버로 부터 응답을 기다리는데  209.75ms 걸림

- 서버로 부터 받은 콘텐츠를 다운로드 하는데 18.49ms 걸림

 

결과적으로

- 723.91ms - 209.75ms  = 513.16ms(약 0.513초; 약 28.98% ) 단축

 

 

Home 페이지 

 

각 명언 세부 페이지 조회수 요청

현재 문제

각 명언 카드 마다 view 측정 카운트를 별도로 fetch 요청으로 불러오므로 요청되는 게시글의 수가 증가할 수록 전체 로드 시간이 점점 길어짐

 

요청 개수가 218 건 인 경우

 

 

게시글의 수가 증가하여 요청 수가 545건이 되었을 때 로드 시간도 2.57초로 길어짐 

 

현재 조회수 요청 서버 측 로직

기존 조회수 요청 로직

GET 요청 시 쿼리 스트링으로 받은 명언 별 id 로 조회수를 개별적으로 조회. 명언의 개수가 많을수록 그 개수 만큼 선형적으로 HTTP GET 요청이 발생

// GET | 명언 조회수 가져오기
export async function GET(req: NextRequest, res: { params: { id: string } }) {
  const { id } = res.params || { id: 0 }
  const db = await openDB();

  try {
    const selectQuery = ` SELECT views FROM views WHERE quote_id = $1 `
    const selectResult = await db.query(selectQuery, [id])
    const views = selectResult.rows[0]?.views || 0
    
    return NextResponse.json({ ...HTTP_CODE.OK, views })

  } catch (error) {
    
    console.error('/api/quotes/[id]/views/route.ts', error)
    
    return NextResponse.json(HTTP_CODE.INTERNAL_SERVER_ERROR)

  } finally {
    db.end()
  }
}

 

변경된 로직

- 애초에 명언 테이블 조회시 views 테이블과 inner 조인하여 한 번에 응답 객체에 담아서 보내도록 수정

- 실질적으로 HTTP 요청이 선형적인 증가에서 최초 1회로 감소

export async function GET(
  req: NextRequest,
  res: { params: { category: string } },
) {
  try {
    const { category } = res.params
    const limit = req.nextUrl.searchParams.get('limit') || 30
    const limitNum = Number(limit)
    const db = await openDB()

    /** 명언 목록  */
    if (category !== 'category-all') {
      const page = req.nextUrl.searchParams.get('page') || 0
      const pageNum = Number(page)
      const query = `
        SELECT A.quote_id, B.author AS author, quote, job, birth, intro, C.views AS view
        FROM quotes A
        INNER JOIN authors B ON A.author_id = B.author_id
        INNER JOIN views C ON A.quote_id = C.quote_id
        WHERE B.author = $1
        ORDER BY A.quote_id DESC
        LIMIT $2 OFFSET $3
        `
      const results = await db.query(query, [
        category,
        limit,
        pageNum * limitNum,
      ])


      const items = results.rows
      console.log(items)
      await db.end()
      return NextResponse.json(items)
    }
}

 

조회 시 다음과 같이  view 도 함께 조회되도록 개선

[
  {
    quote_id: 94,
    author: '오손 웰스',
    quote: '자신이 해야 할 일을 결정하는 사람은 세상에서 단 한 사람, 오직 나 자신뿐이다.',
    job: '미국 배우',
    birth: null,
    intro: null,
    view: 5
  }
]

 

각 페이지별 카테고리

예시) 사용자 명언 페이지

 

메타데이터 조회(현재 카테고리 수 / 전체 카테고리 수) ex. 사용자 명언 페이지

 

- 서버가 클라이언트의 요청을 처리하고 응답하는 데 까지 1.85초 걸림(오래 걸림)

- 클라이언트가 서버로 요청하는 데 걸린 시간은 0.13ms 걸림(적당)

- 서버로 부터 받은 콘텐츠를 다운로드 하는 시간은 0.90ms 걸림(적당)

 

카테고리 목록 조회

 

- 서버가 클라이언트의 요청을 처리하고 응답하는 데 까지 1.68초 걸림(오래 걸림)

- 클라이언트가 서버로 요청하는 데 걸린 시간은 0.45ms 걸림(적당)

- 서버로 부터 받은 콘텐츠를 다운로드 하는 시간은 0.42ms 걸림(적당)

 

 

문제 및 개선 방향

- 서버 응답 대기 시간(1.85초): 전체 시간 중 가장 큰 부분을 차지 하며,  렌더링 할 데이터의 개수(명언 리스트의 길이)가 클수록 메타데이터를 요청하는 API 로직이 미치는 영향도 증가하는 것은 불가피

- 해당 데이터의 경우 굳이 클라이언트 측에서 조회할 필요가 없으므로 서버 측 렌더링 통해 미리 데이터를 렌더링하는 방향으로 개선

- API 요청을 제거하고, 즉시 조회할 수 있는 함수 구현 후 재사용

// metadata.ts
import { openDB } from '@/utils/connect'
import { QUOTE_CATEGORY_TOTAL_LIMIT } from '@/constants'


// 메타데이터 요청 타입
export enum Target {
    USER_QUOTE_CATEGORY_ALL = 'USER_QUOTE_CATEGORY', // 명언 카테고리 메타데이터
    USER_QUOTE = 'USER_QUOTE',
    QUOTE = 'USER_QUOTE',
    QUOTE_CATEGORY = 'QUOTE_CATEGORY',

}
export type QuoteMetaDataType = {
    type: Target
    category?: string
};
export async function getQuoteMetadata({ type, category }: QuoteMetaDataType) {

    let query = ''

    // 쿼리 분기처리
    switch (type) {
        case Target.USER_QUOTE_CATEGORY_ALL:
            query = `
            SELECT COUNT(DISTINCT category) AS count
            FROM user_quotes
            `
            break;
        case Target.USER_QUOTE:
            query = `
            SELECT COUNT(*) AS count
            FROM user_quotes
            WHERE category = $1
            `
            break;
        case Target.QUOTE:
            query = '/api/quotes/meta'
            break;
        case Target.QUOTE_CATEGORY:
            query = '/api/quotes/category/meta'
            break;
        default:
            throw new Error('요청 타입이 맞지 않습니다.')
    }

    // 조회
    const db = await openDB()
    try {
        const result = category
            ? await db.query(query, [decodeURIComponent(category)])
            : await db.query(query)
            
        const TOTAL_COUNT = Number(result.rows[0].count) || 0
        const MAX_PAGE = (Math.ceil(TOTAL_COUNT / QUOTE_CATEGORY_TOTAL_LIMIT))

        return { maxPage: MAX_PAGE, totalCount: TOTAL_COUNT }
    } catch (error) {
        console.error('메타데이터 생성 실패:', error)
        return false
    } finally {
        db.end()
    }
}

 

 

반응형