Skip to content

아이템 등록 한도를 백오피스에서 확인·조절 - #946

Merged
m-a-king merged 1 commit into
devfrom
feat/934-quota-backoffice
Aug 15, 2026
Merged

아이템 등록 한도를 백오피스에서 확인·조절#946
m-a-king merged 1 commit into
devfrom
feat/934-quota-backoffice

Conversation

@m-a-king

Copy link
Copy Markdown
Collaborator

Situation

  • 아이템 등록 전역 가용량 상한과 계정 한도 통합 #935 · 전역 가용량 경고 로그를 알림 룰 규약에 맞춤 #943 으로 아이템 등록 한도가 자리를 잡았지만, 값은 여전히 application.yml 과 환경변수에만 있다. 바꾸려면 배포나 재시작이 필요하다.
  • 그게 문제가 되는 상황이 둘이다. 비용이 튀어 급히 조여야 할 때 즉시 못 내리고, 한도가 낮아 정상 사용자가 막힐 때 즉시 못 푼다. 둘 다 분 단위가 아까운 상황이다.
  • 현재 사용량을 볼 방법도 없었다. 누가 얼마나 썼는지, 전역이 얼마나 찼는지 알려면 Redis 키를 직접 조회해야 했다.

Task

  • 한도를 배포 없이 조절하고, 지금 얼마나 쓰고 있는지 화면에서 본다.
  • 값이 DB 로 내려가면 "메모리에 든 값이 실제와 어긋나는" 문제가 따라온다. 그 어긋남을 이미 같은 문제를 푼 선례와 같은 방식으로 막는다.

Action

선례를 그대로 따랐다

추출 모델 지정(#884)이 같은 문제를 이미 풀어 뒀다. 그 구현의 주석에 남은 이유들을 그대로 가져왔다.

장치 막는 것
값 한 벌을 담은 불변 객체를 통째로 교체 (@Volatile) 갱신 도중 들어온 요청이 옛 계정 한도와 새 전역 상한을 섞어 보는
재적재에 @Synchronized 느리게 시작한 주기 재적재가 뒤늦게 끝나며 방금 저장한 값을 옛 값으로 덮는 역전
화면은 캐시가 아니라 저장소를 직접 조회 캐시가 커밋 후 갱신이라 저장 직후 화면이 옛 값을 보여주는
5분 주기 재적재 저장 요청을 받지 않은 다른 서버가 새 값을 영영 모르는 것 (그래서 서버 간 차이는 최대 5분)

여기에 하나 더했다. 재적재가 실패하면 기존 값을 유지한다. env 로 되돌리면 방금 조인 한도가 조용히 풀려 비용 방어가 사라진다.

판정이 읽는 자리를 바꿨다

가드가 값을 하나씩 꺼내 쓰지 않고 한 벌을 한 번에 받는다(ItemQuotaSettings.current()). 하나씩 읽으면 판정 도중 저장이 끼어들 때 한 요청이 섞인 값으로 판단한다.

env 값(ItemQuotaProperties)은 이제 기본값이고, 실효값은 그 위에 DB 오버라이드를 얹은 결과(ItemQuotaSnapshot)다.

저장 구조: 단일 행, 각 컬럼 nullable

null 은 "그 노브만 env 기본값" 이라는 뜻이다. 부분 오버라이드를 허용해, 전역 상한 하나만 급히 내릴 때 나머지 값까지 화면에서 다시 적어 넣지 않게 했다. 행이 없으면 전부 기본값이다(extraction_models 와 같은 "행 없음 = 기본" 규약).

행이 서비스에 하나뿐이라 PK 를 상수로 못박고 DDL 의 CHECK 로 두 행이 생기는 것을 막는다.

범위를 좁힌 둘

뺀 것 이유
창 길이(window) 조절 바꾸면 이미 돌고 있는 카운터는 옛 TTL 로 만료되고 새 카운터만 새 창을 쓴다. 사용자마다 창 시작 시점이 달라 중간 상태를 설명할 수 없다. 창 변경은 드문 일이라 배포로 남긴다
상위 사용자 목록 활성 사용자 키를 전부 훑어야 하는데, 같은 Redis 를 refresh 토큰 저장소가 함께 쓴다. 화면 한 번이 로그인 지연으로 번진다. 특정 계정 조회(키 하나)로 충분하다

조절 노브는 넷이다: 사용 여부 · 계정 한도 · 전역 상한 · 경고선(%).

화면

목록 하나로 끝난다. 설정이 한 벌뿐이라 상세로 들어갈 것이 없고, 계정 조회도 같은 화면에 둬 "현황을 보다가 바로 조인다" 는 흐름이 끊기지 않게 했다.

  • 각 노브에 "지금 값 / 기본값"을 나란히 보인다. 값이 왜 이런지(기본인가, 누가 바꾼 것인가)를 화면에서 바로 안다.
  • 사용 여부는 체크박스가 아니라 select 다. 빈 값이 "기본값 사용" 이라 3상태인데 체크박스는 2상태뿐이다.
  • 잔액이 음수면 그대로 보인다. 마지막 한 번이 한도를 넘길 수 있는 구조라(잔액 방식) 운영자에게는 "얼마나 넘겼나" 가 곧 신호다. 0 으로 접으면 그 초과가 화면에서 사라진다.
  • 누가 언제 무엇을 바꿨는지는 기존 감사 로그에 얹었다. 바뀐 노브만 남긴다 - 매번 네 값을 다 적으면 로그에서 무엇이 달라졌는지 사람이 다시 비교해야 한다.

Result

  • negative control 로 확인했다. 오버라이드를 무시하도록 임시로 되돌리니 "한도를 내리면 그 다음 등록부터 막힌다" 와 "되돌리기" 두 테스트만 정확히 실패했다. DB 에 저장되는 것까지가 아니라 실제 등록 요청의 판정까지 검증한다는 뜻이다.
  • enabled=false 를 "값 없음" 으로 흘려보내는 실수(Boolean 을 Elvis 로 풀 때 흔하다)를 별도 테스트로 고정했다. 그러면 "한도를 끈다" 는 조작이 조용히 무시돼, 정상 사용자를 막고 있는 상태를 되돌릴 수 없게 된다.
  • 백오피스 통합 테스트는 자동 롤백을 쓰지 않는다. 저장이 커밋 후 캐시 재적재를 걸고(롤백되면 타지 않는다), 캐시는 롤백으로 되돌아가지 않아 다른 테스트로 누수된다. 설정 행이 서비스에 하나뿐이라 UUID 로 격리할 수도 없어 각 테스트가 자기 뒷정리를 한다.
  • 서버 간 값이 최대 5분 다를 수 있다. 저장 요청을 받은 서버만 즉시 반영되고 나머지는 주기 재적재로 따라잡는다. 화면 안내에도 적었다.
  • 한도를 내리면 이미 쓴 사용량은 그대로 두고 판정만 새 값으로 한다. 이미 넘긴 사용자는 즉시 막힌다. "비용이 튀어 급히 조인다" 가 첫 동기라 즉시가 맞다.

연관 이슈

- 한도가 env 에만 있어 비용이 튀어 급히 조여야 할 때도, 한도가 낮아 정상 사용자가 막힐 때도 배포나 재시작을 기다려야 했다. 둘 다 분 단위가 아까운 상황이라 배포 없이 바꿀 경로를 만든다
- 판정에 쓰이는 값을 ItemQuotaProperties(env)에서 ItemQuotaSnapshot(env + DB 오버라이드)으로 옮겼다. 소비자는 ItemQuotaSettings.current() 로 값 한 벌을 받는다 — 값을 하나씩 꺼내 쓰면 판정 도중 백오피스 저장이 끼어들어 한 요청이 옛 계정 한도와 새 전역 상한을 섞어 보게 된다
- 저장 구조는 단일 행에 각 컬럼 nullable 이고 null 은 "그 노브만 env 기본값" 이다. 부분 오버라이드를 허용해, 전역 상한 하나만 급히 내릴 때 나머지 값까지 화면에서 다시 적어 넣지 않게 했다. 행이 없으면 전부 기본값이다("행 없음 = 기본" 규약 — extraction_models 와 같다)
- 캐시·재적재·afterCommit reload 는 DbExtractionModelSettings 를 그대로 따랐다. @volatile 통째 교체(reader 가 반쪽 상태를 안 봄), load 에 @synchronized(주기 재적재가 방금 저장한 값을 덮어쓰는 역전 방지), 화면은 캐시가 아니라 저장소를 직접 조회(캐시는 afterCommit 갱신이라 방금 저장한 값이 아직 안 보임)
- 재적재 실패 시 기존 스냅샷을 유지한다. env 로 되돌리면 방금 조인 값이 조용히 풀려 비용 방어가 사라진다
- 창 길이(window)는 조절 대상에서 뺐다. 바꾸면 이미 돌고 있는 카운터는 옛 TTL 로 만료되고 새 카운터만 새 창을 쓰는데, 사용자마다 창 시작 시점이 달라 중간 상태를 설명할 수 없다. 조절 노브는 사용 여부·계정 한도·전역 상한·경고선 넷이다
- 사용량 화면은 전역 현황과 특정 계정 조회까지만 둔다. 상위 사용자 목록은 활성 사용자 키를 전부 훑어야 하는데 같은 Redis 를 refresh 토큰 저장소가 함께 쓰고 있어, 화면 한 번이 로그인 지연으로 번진다
- 잔액은 음수를 0 으로 접지 않고 그대로 보인다. 마지막 한 번이 한도를 넘길 수 있는 구조라(잔액 방식) 운영자에게는 "얼마나 넘겼나" 가 곧 신호다
- 검증: 오버라이드를 무시하도록 임시로 되돌리니 "한도를 내리면 그 다음 등록부터 막힌다" 와 "되돌리기" 두 테스트만 정확히 실패했다. DB 저장이 아니라 실제 등록 요청의 판정까지 확인한다는 뜻이다
@m-a-king m-a-king added the feat 외부 가시적 새 기능 label Aug 15, 2026
@m-a-king m-a-king self-assigned this Aug 15, 2026
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 441d1d57-3437-4131-9545-36b6e51080b8


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다.

@m-a-king
m-a-king merged commit 1f42134 into dev Aug 15, 2026
8 checks passed
@m-a-king
m-a-king deleted the feat/934-quota-backoffice branch August 15, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 외부 가시적 새 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

아이템 등록 한도를 백오피스에서 확인·조절

1 participant