Skip to content

[Fix] 컬렉션 목록/자식 조회 — 권한 필터링을 앱단이 아니라 SQL에서 처리하도록 수정 #240

Description

@kangcheolung

📌 Description

이슈 #229(컬렉션 트리) 구현 완료 후 코드래빗 리뷰 대응 과정에서 발견하고, 팀 내부
논의를 거쳐 실제 수정하기로 한 성능 이슈 2건. 둘 다 "권한 필터링을 DB 쿼리 한 번에
끝낼 수 있는데 애플리케이션 코드가 대신 반복/재조회를 하고 있다"는 같은 성격의
설계 실수지만, 구체적인 원인과 심각도는 서로 다르다.

문제 상황 1 — GET /collections (컬렉션 목록)

예시: 어떤 사용자가 읽을 수 있는 컬렉션이 총 5,000개이고, 화면엔 "20개씩"
보여준다고 하자. 지금 서버는 이 요청 하나를 처리하려고:

  1. "이 사용자가 읽을 수 있는 컬렉션"을 페이지네이션 없이 5,000개 전부 찾는다
  2. 그 5,000개 중에서 20개만 다시 골라내서 화면에 보여준다

사용자는 "20개만" 원했는데, 서버는 "5,000개를 다 찾아놓고" 나서야 20개로 자르는
셈이다. 편의점에서 삼각김밥 하나 달라고 했더니 점원이 매대의 삼각김밥을 전부
계산대에 꺼내놓고 그중 하나만 건네는 것과 같다.

이 때문에 사용자가 읽을 수 있는 컬렉션 수(N)가 늘어날수록, 페이지 크기와 무관하게
DB↔서버 간 전송량과 서버 메모리 사용량이 N에 비례해서 커진다. "2페이지 보여줘"만
바뀐 요청인데도 서버는 매번 5,000개를 처음부터 다시 찾는다.

지금 컬렉션 규모(수십수백 개 추정)에선 체감이 안 되지만, 컬렉션이 수천수만 개
규모로 늘어나면 목록 화면을 열 때마다 이 낭비가 누적된다.

문제 상황 2 — GET /collections/{id}/children (직계 자식 조회)

예시: 어떤 폴더를 열었더니 그 안에 자식 폴더가 10개 있다고 하자. 지금 서버는:

  1. 자식 폴더 10개를 조건 없이 전부 가져온다
  2. 자식 하나하나마다 "이 사용자가 이걸 볼 수 있는지"를 따로따로 확인한다 (직접
    권한 여부, 역할 권한 여부, 부서 권한 여부, 조상 컬렉션까지 거슬러 올라가서
    상속받은 권한이 있는지까지 — 확인 하나당 최대 여섯 단계)

자식이 10개면 이 여섯 단계짜리 확인이 통째로 10번 반복돼서, 최악의 경우 요청
하나에서 확인 작업이 60번 일어난다. 학생 10명 출석부를 명단으로 한 번에 보는 게
아니라, 학생 한 명씩 불러서 매번 서류를 다시 뒤져 "우리 학교 학생 맞아?"를
확인하는 것과 같다.

이건 문제 1과 달리 "권한 판단에 필요한 최소한의 계산"이 아니라, SQL 조건 하나로
대체 가능한 걸 코드에서 반복하고 있는 순수한 낭비다. 그래서 고치면 손해 보는
부분 없이 딱 줄어들기만 한다.

🎯 설계 / 해결 방법

문제 1

지금처럼 "전체를 찾는 쿼리"와 "그중 일부를 다시 찾는 쿼리"로 나누는 대신, 하나의
쿼리 안에서 "이 사용자가 읽을 수 있는 것 중 이 페이지에 해당하는 항목"과 "전체
개수"를 동시에 계산하도록 바꾼다. 이렇게 하면 페이지 하나를 보여주기 위해 전체를
다 찾아서 앱으로 넘겨주는 과정 자체가 없어지고, DB가 처음부터 필요한 만큼만
돌려준다.

단, "내용 조회용 쿼리"와 "전체 개수 조회용 쿼리"를 완전히 따로 두는 흔한 방식은
쓰지 않는다 — 그렇게 하면 권한 판단(부모 상속 확인 포함)이 각 쿼리마다 처음부터
다시 계산되어 오히려 지금보다 계산 횟수가 늘어난다. 그래서 "내용"과 "전체 개수"를
같은 쿼리 실행 안에서 동시에 뽑아내는 방식으로 설계해서, 권한 판단 계산이
지금과 똑같이 딱 한 번만 일어나도록 한다.

문제 2

같은 부모 밑의 자식들은 "부모(와 그 위 조상들)로부터 상속받는 권한이 있는지"를
전부 똑같이 공유한다 — 자식마다 새로 확인할 필요가 없는 정보다. 이 부분을 자식
개수와 무관하게 딱 한 번만 계산해서 모든 자식에게 공통으로 적용하고, 자식마다
달라질 수 있는 부분(자기 자신의 소유자·공개 여부·직접 부여받은 권한)만 자식별
조건으로 붙인다. 이렇게 하면 "자식을 가져온다"와 "각 자식이 볼 수 있는지
확인한다"가 하나의 조회로 합쳐지고, 자식 개수만큼 반복하던 확인 과정이 사라진다.

📊 성능적으로 어떻게 좋아지는지

지금 고친 후
문제 1 (예시: 읽을 수 있는 컬렉션 5,000개, 20개씩 보기) 매 페이지 요청마다 5,000개를 다 찾은 뒤 20개로
자름 처음부터 20개 + 전체 개수 숫자 하나만 계산
문제 2 (예시: 자식 폴더 10개) 확인 작업 최대 60번(자식마다 최대 6번) 확인 작업 1번으로 끝

문제 1은 사용자가 읽을 수 있는 컬렉션 수가 늘어날수록 부하가 같이 늘어나던
구조가, 컬렉션이 몇 개든 부하가 "페이지 크기 + 숫자 하나"로 고정되는 구조로
바뀐다. 다만 권한 판단 자체(특히 부모 상속 확인)에 드는 계산 비용은 이 기능이
존재하는 한 없앨 수 없는 부분이라 그대로 남는다 — 대신 그 계산이 지금처럼 딱
한 번만 일어나도록 유지된다는 점이 개선이다.

문제 2는 자식 개수만큼 반복되던 확인 작업이, 자식이 몇 개든 조회 한 번으로
끝나는 구조로 바뀐다. 이쪽은 지금 하던 반복 자체가 불필요한 낭비였기 때문에,

지금 컬렉션 규모에서는 두 문제 다 체감 차이가 거의 없다. 이번 작업의 목적은
"지금 느린 걸 빠르게" 만드는 게 아니라, 앞으로 컬렉션 수가 늘어나도 이 두 API의
부하가 늘어나지 않도록 미리 구조를 바꿔두는 것이다.

✅ To-do

  • 컬렉션 목록 조회를 "전체를 찾은 뒤 페이지를 자르는" 2단계 구조에서, 페이지
    내용과 전체 개수를 한 번의 조회로 함께 얻는 구조로 변경
  • 직계 자식 조회를 "전체를 가져온 뒤 자식마다 권한을 확인하는" 구조에서,
    권한 조건을 조회 시점에 함께 적용해 필요한 자식만 한 번에 가져오는 구조로
    변경 (부모 상속 여부는 자식 전체가 공유하도록 한 번만 계산)
  • 더 이상 쓰이지 않게 되는 기존 조회 로직 정리
  • 기존 단위/통합 테스트 회귀 확인 + 위 두 변경에 대한 신규 테스트 추가
    (owner/PUBLIC/USER/ROLE/DEPARTMENT 및 부모 상속 케이스 포함)

✅ 완료 기준

  • 컬렉션 목록 조회가 "전체 조회 후 재조회" 없이 한 번의 조회로 동작한다
  • 직계 자식 조회가 자식 개수와 무관하게 한 번의 조회로 동작한다
  • 기존 권한 판정 결과(owner/PUBLIC/USER/ROLE/DEPARTMENT, 부모 상속 포함)에
    회귀가 없다
  • 기존 테스트 전부 통과 + 신규 테스트 추가

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions