데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Claude Code와 함께한 웹 성능 개선 이야기

      꼬마집사 26.04.30
      5,272 8 6
      DEVOTEE 요약
      오래된 웹 시스템의 성능 개선 작업은 보통 몇 주가 걸리지만, 필자는 Claude Code의 AI 에이전트 팀을 활용해 단 하루 만에 이를 해결했습니다. AI는 380개 자바 클래스와 49개 SQL 쿼리 파일을 단 5분 만에 분석하여 N+1 문제, SQL Injection 취약점 등 총 56가지의 심각한 개선점을 찾아냈습니다. 이처럼 AI는 기본적인 성능 튜닝을 압도적인 속도와 범위로 처리하지만, 서비스 장애 방지를 위한 인간의 비즈니스 맥락 판단과 검증이 여전히 중요합니다.
      DEVOTEE 추천 블로그

      Claude Code와 함께한 웹 성능 개선 이야기

      조직 내에 Claude Team Plan을 도입했다. (HR에서도 잘 활용할 수 있을까? ㅠㅠ)

      난 무엇을 할 수 있을 까? AI 도구를 처음 도입했는데 그래도 좀 성과물을 보여줘야하지 않을까 해피프라이데이 아침에 생각하다

      무심코 그냥 내가 운영하고 있는 사이트 성능 개선이나 좀 해볼까하는 아이디어가 떠올라 3시간만에 실행한 결과물입니다.

      이 블로그 조차 작업이 끝난 후 기술블로그 Agent 로 생성하였습니다. (이미지는 최근 GPT 5.5가 좋아졌다고 해서 내용을 파악 후 중간중간에 넣어달라고 했고요^^)


      한 번 쭈욱 읽어보시고, 앞으로 이런 AI 시대에 개발자/비개발자 모두가 본인의 업무에 AI를 적용한 사례를 블로그로 쉽게 올릴 수 있다는 생각을 했으면 좋겠네요^^


      image.png


      옛날 옛적에, 성능 튜닝이란

      "사이트가 느려요."


      개발자라면 이 한마디에 등골이 서늘해진 경험이 있을 겁니다. 특히 그 대상이 10년 넘게 운영되어 온 레거시 시스템이라면요.

      JSP, Spring MVC, MyBatis, jQuery... 그 시절의 기술 스택이 켜켜이 쌓여 있는 시스템 앞에서 "느린 부분 찾아서 고쳐주세요"라는 요청은 마치 실타래 뭉치에서 매듭을 찾아달라는 것과 비슷합니다.


      불과 몇 년 전까지만 해도, 이런 작업은 전문 컨설팅의 영역이었습니다.

      DB 튜닝 전문가가 투입되고, APM 도구를 설치하고, 슬로우 쿼리 로그를 며칠간 수집하고, 실행 계획을 하나하나 분석하고... 수백에서 수천만 원의 비용과 몇 주의 시간이 필요한 프로젝트였죠.


      그런데 오늘, 저는 이 작업을 AI와 함께 하루 만에 해냈습니다.


      무대 소개: 380개 클래스의 레거시 시스템

      제가 다루는 시스템은 DevOcean의 THub 플랫폼입니다. Spring 4.0 기반의 eGovframework 위에 구축된, 전형적인 엔터프라이즈 웹 애플리케이션이죠.

      • Java 클래스 380개

      • MyBatis Mapper XML 49개

      • JSP 파일 200개

      • 내부 관리 시스템 + 외부 포털 이중 구조

      이 정도 규모의 시스템에서 성능 병목을 찾으려면, 우선 코드를 읽어야 합니다.

      Controller가 어떤 Service를 호출하고, Service가 어떤 DAO를 거치고, DAO가 어떤 Mapper XML의 어떤 쿼리를 실행하는지.

      한 화면을 렌더링하는 데 관여하는 코드가 4~5개 레이어에 걸쳐 있습니다.

      [Browser] → Controller (.java)
                     → Service (.java)
                          → DAO (.java)
                               → Mapper XML (.xml)
                                    → MySQL DB

      THub의 레이어 구조. 한 화면의 성능을 분석하려면 이 5개 레이어를 모두 추적해야 한다.

      image.png

      사람이 이 작업을 하면? 한 화면당 반나절입니다.


      AI 에이전트 팀 투입

      여기서 Claude Code의 에이전트 팀 기능이 빛을 발했습니다. 저는 3명의 전문가 에이전트를 구성했습니다.

      에이전트

      역할

      Tracer

      Controller에서 Mapper XML까지 호출 경로 추적

      Query Analyst

      SQL 쿼리의 성능 문제 패턴 탐지

      Optimizer

      두 결과를 종합하여 우선순위별 개선안 제시

      그리고 메인 페이지와 블로그 페이지를 대상으로, 4개 에이전트를 동시에 실행했습니다.

      [Tracer-Main]    → 메인 페이지 호출 경로 추적
      [Tracer-Blog]    → 블로그 페이지 호출 경로 추적
      [Query-Main]     → Main_SQL.xml 쿼리 분석
      [Query-Blog]     → Blog_SQL.xml 쿼리 분석

      사람 4명이 동시에 작업하는 것과 같은 효과입니다. 다만, 이 4명은 380개 클래스를 읽는 데 지치지 않고, 49개 Mapper XML의 모든 쿼리를 빠짐없이 검사합니다.

      [리더 (나)]
          ├── Tracer-Main ──→ 메인 Controller→Service→DAO→XML 추적
          ├── Tracer-Blog ──→ 블로그 Controller→Service→DAO→XML 추적
          ├── Query-Main ───→ Main_SQL.xml 전체 쿼리 성능 분석
          └── Query-Blog ───→ Blog_SQL.xml 전체 쿼리 성능 분석
                                    ↓ (약 5분)
                           [종합 보고서 생성]

      Claude Code에서 4개 에이전트가 병렬로 분석하는 구조. 사람 4명이 동시에 코드 리뷰하는 것과 같은 효과다.

      image.png

      약 5분 후, 결과가 도착했습니다.


      충격적인 분석 결과

      심각도

      건수

      HIGH

      26건

      MEDIUM

      19건

      LOW

      11건

      총 56건. 솔직히 놀랐습니다. 이미 운영 중인 서비스에서 이렇게 많은 개선 포인트가 나올 줄은 몰랐거든요.


      그중에서도 특히 눈에 띈 것들을 소개합니다.

      발견 1: SQL Injection 취약점 14곳

      성능을 보러 갔다가 보안 구멍을 발견했습니다. MyBatis에서 ${loginId} 형태로 문자열을 직접 치환하는 코드가 7개 파일, 14곳에 존재했습니다.

      <!-- 위험: SQL Injection 가능 -->
      WHERE USER_ID = '${loginId}'
      
      <!-- 안전: PreparedStatement 바인딩 -->
      WHERE USER_ID = #{loginId}

      ${}는 사용자 입력을 SQL에 그대로 삽입합니다. 공격자가 ' OR '1'='1 같은 값을 넣으면? 상상에 맡기겠습니다. 이건 성능과 관계없이 즉시 수정이 필요한 항목이었습니다.

      발견 2: 블로그 작성자 목록의 N+1 지옥

      블로그 작성자별 목록 페이지(/blog/writer/index.do)를 추적했더니, 10건의 게시글을 보여주는 데 DB를 46회 호출하고 있었습니다.


      원인은 이랬습니다:

      // 게시글 10건을 가져온 뒤...
      for (int i = 0; i < BlogList.size(); i++) {
          // 각 게시글마다 프로필 사진을 3번의 DB 호출로 조회
          filePath = externalBlogService.getUserPhoto(writer);
          // getUserPhoto 내부: checkTid → checkExtUserInfo → getFilePath
      }

      10건 × 3회 = 30회의 추가 DB 호출. 전형적인 N+1 문제입니다.


      수정은 간단했습니다. 목록 쿼리에 LEFT JOIN 3개를 추가해서 프로필 이미지를 한 번에 가져오도록 변경.

      개선 전: 6회(기본) + 30회(프로필) + 10회(썸네일) = 46회
      개선 후: 6회(기본, JOIN으로 프로필 포함) = 6회

      image.png

      발견 3: 블로그 상세의 서브쿼리 폭탄

      블로그 상세 페이지를 렌더링하는 getTechBoadData 쿼리를 열어봤더니, SELECT 절에 상관 서브쿼리가 10개나 들어있었습니다.

      댓글 수, 좋아요 수, 북마크 여부, 팔로우 수, 커뮤니티 목록, 닉네임, 프로필 이미지... 각각이 독립된 서브쿼리로 실행되고 있었죠.


      Before - 서브쿼리 10개가 각각 독립 실행:

      -- 댓글 수: 서브쿼리 1회
      ,FORMAT((SELECT COUNT(*) FROM TB_EXT_BOARD_REPLY 
          WHERE BOARD_ID = A.ID AND BOARD_TYPE = 'BLOG'),0) REPLY_CNT
      -- 좋아요 수: 서브쿼리 1회
      ,FORMAT((SELECT COUNT(*) FROM TB_EXT_LIKE 
          WHERE LIKE_TARGET_ID = A.ID AND LIKE_YN = 'Y'),0) LIKE_CNT
      -- 닉네임: 서브쿼리 1회
      ,(SELECT nickname FROM tb_ext_user_info WHERE tid=c.tid) DEVOCEAN_NICKNAME
      -- INX: 같은 테이블 또 조회!
      ,(SELECT inx FROM tb_ext_user_info WHERE tid=c.tid) DEVOCEAN_INX
      -- 프로필 이미지: 같은 테이블 또또 조회 + JOIN까지!
      ,(SELECT T2.file_path FROM tb_ext_user_info T1 
          INNER JOIN tb_file T2 ON T1.profile_img_idx = T2.idx 
          WHERE T1.tid = C.tid) AS extUserFileLocation
      -- ... 이런 게 10개

      After - LEFT JOIN으로 한 방에:

      -- 댓글 수: 파생 테이블 1번 JOIN
      ,FORMAT(IFNULL(RPLY.REPLY_CNT, 0),0)  REPLY_CNT
      -- 좋아요 수: 파생 테이블 1번 JOIN
      ,FORMAT(IFNULL(EL.LIKE_CNT, 0),0)     LIKE_CNT
      -- 닉네임 + INX + 프로필: LEFT JOIN 1번으로 통합!
      ,EU.NICKNAME  DEVOCEAN_NICKNAME
      ,EU.INX       DEVOCEAN_INX
      ,EUF.FILE_PATH AS extUserFileLocation

      같은 테이블(tb_ext_user_info)을 3번 따로 조회하던 것을 1번의 JOIN으로 통합한 것이 핵심이었습니다.

      image.png


      실수에서 배운 교훈

      성능 최적화가 항상 순탄하지만은 않았습니다.

      블로그 목록 쿼리를 최적화하면서 content 필드를 LEFT(A.CONTENT, 500)으로 잘랐는데, SK AI SUMMIT 카테고리의 게시물 미리보기가 공백으로 표시되는 문제가 발생했습니다.


      원인을 추적해보니, 해당 게시물들은 슬라이드 임베드 등 HTML 마크업이 매우 많아서 처음 500자가 전부 HTML 태그였습니다.

      프론트엔드의 Jsoup.parse().text()가 태그를 제거하면 텍스트가 하나도 안 남는 거죠.

      HTML 500자 → Jsoup 태그 제거 → "" (빈 문자열)

      image.png

      LEFT(A.CONTENT, 4000)으로 늘려서 해결했지만, 이 경험은 중요한 교훈을 남겼습니다:

      성능 최적화는 좋지만, 서비스에 장애를 주면 안 된다.

      AI가 제안하는 최적화든 사람이 하는 최적화든, 반드시 다양한 데이터 유형으로 검증해야 합니다.

      "평균적인 게시물"에서는 잘 작동하지만, HTML이 많은 프레젠테이션이나 임베드 콘텐츠에서 깨질 수 있으니까요.


      하루의 성과

      정리하면, 오늘 하루 동안 이런 작업을 완료했습니다:

      커밋

      내용

      효과

      SQL Injection 전체 제거

      7파일 14곳 ${}#{}

      보안 취약점 제거

      N+1 getUserPhoto 제거

      JOIN 통합

      DB 호출 30회 → 0회

      getTechBoadData 서브쿼리→JOIN

      서브쿼리 10개 변환

      블로그 상세 쿼리 효율화

      getExternalBoardList 서브쿼리→JOIN

      3벌 쿼리 동시 변환

      앱 메인 서브쿼리 36회 → 7회

      Content 잘림 수정

      LEFT(500) → LEFT(4000)

      SK AI SUMMIT 표시 정상화

      그리고 나머지 17건의 개선 항목은 우선순위와 구현 계획이 포함된 결과 보고서로 문서화해두었습니다. 다음에 시간이 날 때 하나씩 진행하면 됩니다.


      이건 마법이 아니라, 도구의 진화다

      솔직히 말하면, 오늘 한 작업 중 AI가 "새로운 기술"을 사용한 건 하나도 없습니다.

      N+1 문제를 JOIN으로 해결하고, 상관 서브쿼리를 파생 테이블로 변환하고, ${}#{}로 바꾸는 건 모두 교과서에 나오는 기본기입니다.


      달라진 건 속도와 범위입니다.


      과거에 성능 컨설턴트가 했던 일을 떠올려보면:

      1. 시스템 구조 파악 → 며칠

      2. 슬로우 쿼리 수집 및 분석 → 며칠

      3. 개선안 수립 → 며칠

      4. 보고서 작성 → 며칠

      5. 실제 수정 및 검증 → 며칠~몇 주

      AI와 함께하면:

      1. 에이전트 팀이 코드 전체를 읽고 분석 → 5분

      2. 56건의 이슈를 심각도별로 분류 → 자동

      3. 개선안과 코드 수정을 함께 제시 → 즉시

      4. 실제 코드 수정 및 검증 → 수 시간

      image.png

      이게 가능한 이유는, AI가 380개 Java 클래스와 49개 Mapper XML을 전부 읽을 수 있기 때문입니다.

      사람은 하루에 이 양을 다 읽을 수 없지만, AI는 할 수 있죠. 그리고 읽은 내용을 바탕으로 패턴을 찾아내는 것도 AI가 잘하는 영역입니다.


      그래서, 컨설턴트는 이제 필요 없는 건가요?

      아닙니다. 오히려 더 중요해질 수도 있습니다.

      AI가 "이 쿼리에 상관 서브쿼리가 10개 있으니 JOIN으로 바꾸세요"라고 말해줄 수는 있지만, "이 최적화가 우리 서비스의 트래픽 패턴에서 실제로 의미가 있는가?"는 사람이 판단해야 합니다.


      실제로 오늘 작업 중에도 이런 판단이 필요했습니다.

      분석에서 getImageBasic의 N+1을 최우선 과제로 올려놓았지만, 실제로는 "모든 게시글에 이미 썸네일이 있어서 해당 분기가 실행되지 않는다"는 도메인 지식이 있었기에 건너뛸 수 있었습니다.


      AI는 코드를 읽지만, 비즈니스 맥락은 사람이 알고 있습니다. 둘의 협업이 최고의 결과를 만들어냅니다.


      마무리

      10년 된 레거시 시스템의 성능을 개선하는 일은, 더 이상 몇 주짜리 프로젝트가 아닙니다.

      올바른 도구와 함께라면, 커피 몇 잔 마시는 동안 보안 취약점을 잡고, 쿼리를 최적화하고, 결과 보고서까지 만들 수 있습니다.


      물론 AI의 제안을 무조건 따르면 안 됩니다.

      SK AI SUMMIT 콘텐츠 공백 사건처럼, 최적화에도 사이드이펙트가 있을 수 있으니까요.

      하지만 그런 실수조차 빠르게 발견하고 수정할 수 있다는 것, 그것이 AI와 함께 일하는 진짜 장점이 아닐까요.


      다음 편에서는 남은 17건의 개선 항목을 하나씩 해치우는 이야기를 들려드리겠습니다. 아마 그것도 하루면 될 겁니다.

      이 글에서 다룬 성능 최적화 작업은 Claude Code의 에이전트 팀 기능을 활용하여 수행되었습니다.


      첨부 문서

      첨부 1. 성능 최적화 종합 보고서 (03_optimization_report.md)

      AI 에이전트 팀이 메인 페이지와 블로그 페이지를 분석하여 생성한 종합 보고서입니다.

      4개 에이전트(tracer-main, tracer-blog, query-main, query-blog)가 병렬로 분석한 결과를 optimizer가 우선순위별로 정리했습니다.

      • 분석 범위: 메인 페이지(ExternalMainController), 블로그 페이지(ExternalBlogController), 공통 모듈(Common_SQL, Search_SQL)

      • 발견 이슈 요약:

      우선순위

      건수

      주요 내용

      P0 보안

      1건

      SQL Injection ${} 바인딩 (7파일 14곳)

      P1 핵심 병목

      5건

      N+1 쿼리, LIMIT 없는 전체 조회, GROUP_CONCAT+LIKE 패턴, 전체 GROUP BY

      P2 서브쿼리

      7건

      상관 서브쿼리(행마다 실행), SELECT *, 페이징 없음, LIKE '%keyword%'

      P3 코드 품질

      7건

      중복 서브쿼리, HashMap 과다, @rownum, <sql> fragment 미활용

      P4 캐싱

      5건

      배너/메뉴/태그 등 마스터 데이터 매 요청 조회

      페이지별 DB 호출 분석:

      • 메인 페이지: 1회 로딩 시 7~21회 DB 호출 (getImageBasic N+1로 최대 12회 추가)

      • 블로그 상세: 25+회 (서브쿼리 10개 + 관련글/추천글 루프)

      • 작성자별 목록: 46회/10건 (getUserPhoto 3회 x N)

      첨부 2. 최적화 결과서 (04_optimization_result.md)

      종합 보고서의 25건 항목 대비 실제 수정 현황을 추적한 결과서입니다.

      • 진행 현황: 전체 25건 중 7건 완료, 1건 보류, 17건 미적용

      • 완료 커밋 이력:

      커밋

      내용

      핵심 효과

      7c3c9f08

      메인 페이지 쿼리 최적화

      GROUP_CONCAT+LIKE 제거, 서브쿼리→JOIN

      9d1a9812

      블로그 목록 쿼리 최적화

      서브쿼리 7개→LEFT JOIN, ${}#{}

      dacc9bde

      미사용 ID 목록 쿼리 제거

      수천건 전체 조회 제거

      e5953eb4

      Blog content 잘림 수정

      SK AI SUMMIT 콘텐츠 표시 정상화

      a189aeba

      SQL Injection 전체 제거

      7파일 14곳 보안 취약점 제거

      17c3b2bc

      N+1 getUserPhoto 제거

      DB 호출 30회→0회

      3dae9df9

      블로그 상세 서브쿼리→JOIN

      서브쿼리 10개→JOIN 9개

      57bc189d

      앱 메인 서브쿼리→JOIN

      3벌 쿼리 서브쿼리 36회→JOIN 7회

      개선 효과 (실측 전 추정):

      페이지

      개선 전

      개선 후

      메인 (앱)

      7~21회

      7~9회

      블로그 목록

      9+N회

      9회

      블로그 상세

      25+회

      10~12회

      작성자별 목록

      46회

      6회

      다음 단계 추천: 페이징/LIMIT 추가(#11~13) → 댓글 쿼리 개선(#9) → 캐싱 적용(P4) → FULLTEXT 검색(#10)


      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      꼬마집사 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기