Repository navigation
perf: replace audit log OFFSET paging with cursors - #4
Merged
Merged
Conversation
Seongbin-Choo
commented
Sep 8, 2026
| @Query(""" | ||
| select audit from ParsingAuditLog audit | ||
| where audit.createdAt < :createdAt | ||
| or (audit.createdAt = :createdAt and audit.id < :id) |
Owner
Author
There was a problem hiding this comment.
행값 비교 (created_at, id) < (?, ?) 대신 OR 조건을 쓴 이유는 MariaDB 실측에서만 드러났습니다. 동일 인덱스에서도 행값 비교는 깊이만큼 이동했지만 이 형태는 type=range와 index condition으로 모든 깊이에서 19회 이동을 유지했습니다.
Seongbin-Choo
commented
Sep 8, 2026
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE parsing_audit_log MODIFY created_at DATETIME(6) NOT NULL; | |||
Owner
Author
There was a problem hiding this comment.
created_at NOT NULL은 커서 정렬 키에서 NULL 행이 순회 밖으로 빠지는 것을 막지만, 대용량 MariaDB에서는 테이블 재구성이 발생할 수 있습니다. 그래서 PR Upgrade note에 NULL 사전 점검·백업·배포 창 필요성을 명시했고, 100만 행 로컬 적용 시간 2.601초도 운영 예상치로 일반화하지 않았습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
문제
관리자 감사 로그 조회는
Page기반 OFFSET 페이징이었고, 정렬 키(created_at, id)를 지원하는 인덱스도 없었습니다. 로컬 MariaDB 12.3.2에 짧은 마스킹 감사 로그 100만 건을 넣어 측정하니 첫 페이지부터 100만 행을 스캔했고, 별도COUNT(*)도 매 요청마다 실행됐습니다.판단
(created_at, id)를 정렬·커서 키로 사용했습니다. 같은 생성 시각이 여러 건이어도id DESC가 순서를 결정합니다.created_at < ? OR (created_at = ? AND id < ?)형태를 사용했습니다.size + 1건만 읽어hasNext를 판단하므로 전체 개수용 count 쿼리를 제거했습니다.{items, nextCursor, hasNext}로 맞췄고size를 1~100으로 제한했습니다.결과
V6 적용 후 실제 엔티티 조회 칼럼과 같은 원시 SQL을 각 위치에서 7회 실행해 최소값을 기록했습니다(
LIMIT 20).실제 API는
hasNext를 위해size=20일 때 최대 21건을 읽습니다. 위 19회는 원시 SQL이 첫 위치를 찾은 뒤 추가로 이동한 횟수입니다.API 변경
GET /api/v1/audit?page=...&size=...→ SpringPageGET /api/v1/audit?cursor=...&size=...→{items,nextCursor,hasNext}마이그레이션 및 Upgrade note
V6는
parsing_audit_log.created_at을NOT NULL로 바꾸고idx_audit_created_at_id(created_at,id)를 생성합니다.MODIFY ... NOT NULL은 큰 MariaDB 테이블을 재구성해 잠금 또는 쓰기 지연을 만들 수 있습니다. 적용 전에 NULL 존재 여부와 백업을 확인하고 배포 창을 확보해야 합니다. 로컬 100만 건 테이블에는 NULL이 없었고 V6 두 문장 적용에 2.601초가 걸렸지만, 운영 예상치로 그대로 사용하지 않습니다.Docker Compose MariaDB 11.4.13에서도 V6 적용, Hibernate 스키마 검증, 백엔드 헬스체크를 확인했습니다.
버린 선택지
(created_at, id) < (?, ?)행값 비교: 요청 이력 측정에서 MariaDB가 이를 효율적인 범위 조건으로 변환하지 않아 OR 형태를 유지했습니다.검증
./gradlew test bootJar성공ParsingAuditCursorPagingTest4개: 동점 시각 전체 순회, 잘못된 cursor, size 범위, 비숫자 sizegit diff --check성공