LLM 호출 로그 DB 설계

AI 가이드 검색(LLM) 호출 요청/응답을 MySQL에 기록하는 테이블 설계 및 구현 가이드

마지막 수정: 2026-05

LLM 호출 로그 DB 설계

AI 가이드 검색 요청마다 질문·응답·토큰 사용량·접속 정보를 MySQL에 저장한다.
로컬 개발 시에는 DB 없이도 동작하며, EC2 배포 시 SPRING_PROFILES_ACTIVE=db 활성화로 자동 연결된다.


1. 테이블 DDL (MySQL)

CREATE TABLE T_LLM_CALL_LOG (
    LLM_CALL_LOG_SN   BIGINT          NOT NULL AUTO_INCREMENT COMMENT '로그 일련번호 (PK)',
    QUESTION          TEXT                     COMMENT '사용자 질문',
    ANSWER            LONGTEXT                 COMMENT 'AI 응답 본문',
    MODEL             VARCHAR(100)             COMMENT '사용 모델명 (예: claude-sonnet-4-20250514)',
    INPUT_TOKENS      INT                      COMMENT '입력 토큰 수',
    OUTPUT_TOKENS     INT                      COMMENT '출력 토큰 수',
    ELAPSED_MS        BIGINT                   COMMENT '응답 소요 시간 (ms)',
    SUCCESS_YN        CHAR(1)     DEFAULT 'Y'  COMMENT '성공 여부 (Y/N)',
    ERROR_MSG         VARCHAR(1000)            COMMENT '오류 메시지 (실패 시)',
    CLIENT_IP         VARCHAR(50)              COMMENT '요청자 IP',
    USER_AGENT        VARCHAR(500)             COMMENT '브라우저 User-Agent',
    REGIST_DT         DATETIME(3)  DEFAULT NOW(3) COMMENT '등록 일시',
    PRIMARY KEY (LLM_CALL_LOG_SN)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='LLM 호출 로그';

CREATE INDEX IDX_T_LLM_CALL_LOG_DT      ON T_LLM_CALL_LOG (REGIST_DT);
CREATE INDEX IDX_T_LLM_CALL_LOG_SUCCESS ON T_LLM_CALL_LOG (SUCCESS_YN, REGIST_DT);

2. 프로파일 기반 DB 활성화

로컬 기본 프로파일에는 DataSource 설정이 없다.
EC2 배포 시 환경변수로 db 프로파일을 활성화하면 MySQL이 연결된다.

# EC2 실행 시
export SPRING_PROFILES_ACTIVE=db
export DB_HOST=your-mysql-host
export DB_NAME=guide
export DB_USER=guide
export DB_PASSWORD=your-password
java -jar proj.jar

application-db.yaml:

spring:
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:guide}?characterEncoding=UTF-8&serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true
    username: ${DB_USER:guide}
    password: ${DB_PASSWORD:}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 5
      connection-timeout: 5000

3. VO

public class LlmCallLogVo {
    private String question;
    private String answer;
    private String model;
    private Integer inputTokens;
    private Integer outputTokens;
    private Long elapsedMs;
    private String successYn;
    private String errorMsg;
    private String clientIp;
    private String userAgent;
    // getter / setter
}

4. Service — @ConditionalOnBean

DB 프로파일이 꺼져 있으면 DataSource 빈이 없어 이 서비스도 로드되지 않는다.
Controller에서 @Autowired(required = false)로 주입하면 null-safe하게 처리된다.

@Service
@ConditionalOnBean(DataSource.class)
public class LlmCallLogService {

    private final JdbcTemplate jdbcTemplate;

    public LlmCallLogService(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void save(LlmCallLogVo vo) {
        try {
            jdbcTemplate.update(
                "INSERT INTO T_LLM_CALL_LOG " +
                "(QUESTION, ANSWER, MODEL, INPUT_TOKENS, OUTPUT_TOKENS, ELAPSED_MS, " +
                " SUCCESS_YN, ERROR_MSG, CLIENT_IP, USER_AGENT, REGIST_DT) " +
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(3))",
                vo.getQuestion(), vo.getAnswer(), vo.getModel(),
                vo.getInputTokens(), vo.getOutputTokens(), vo.getElapsedMs(),
                vo.getSuccessYn(), vo.getErrorMsg(),
                vo.getClientIp(), vo.getUserAgent()
            );
        } catch (Exception e) {
            log.error("LLM 호출 로그 저장 실패: {}", e.getMessage(), e);
        }
    }
}

5. Controller 연동 요점

@Autowired(required = false)
private LlmCallLogService llmCallLogService;

// 검색 후 로그 저장
if (llmCallLogService != null) {
    llmCallLogService.save(vo);
}
  • AnthropicService.ask()AnthropicResult를 반환 (answer + inputTokens + outputTokens + successYn)
  • 클라이언트 IP는 X-Forwarded-For 헤더 우선, 없으면 request.getRemoteAddr()

6. 조회 쿼리 예시

-- 최근 실패 호출
SELECT LLM_CALL_LOG_SN, QUESTION, MODEL, INPUT_TOKENS, OUTPUT_TOKENS,
       ELAPSED_MS, ERROR_MSG, CLIENT_IP, REGIST_DT
FROM T_LLM_CALL_LOG
WHERE SUCCESS_YN = 'N'
ORDER BY REGIST_DT DESC
LIMIT 20;

-- 일별 호출 통계
SELECT DATE(REGIST_DT)        AS DT,
       COUNT(*)               AS CALL_CNT,
       SUM(INPUT_TOKENS)      AS TOTAL_INPUT,
       SUM(OUTPUT_TOKENS)     AS TOTAL_OUTPUT,
       AVG(ELAPSED_MS)        AS AVG_MS
FROM T_LLM_CALL_LOG
GROUP BY DATE(REGIST_DT)
ORDER BY DT DESC;

7. 주의사항

  • QUESTION / ANSWER에 개인정보가 포함될 수 있다 — 운영 환경에서 접근 권한 제한 필요
  • 로그 저장 실패가 본 검색 기능에 영향을 주면 안 된다 — Service에서 예외를 삼킴
  • 장기 운영 시 파티셔닝 또는 주기적 아카이빙 전략 필요 (REGIST_DT 기준)