Skip to content

UPLUS-16 데이터 사용량 관측 시스템 구현#4

Merged
starboxxxx merged 33 commits into
developfrom
feat/UPLUS-16
Jan 19, 2026
Merged

UPLUS-16 데이터 사용량 관측 시스템 구현#4
starboxxxx merged 33 commits into
developfrom
feat/UPLUS-16

Conversation

@starboxxxx
Copy link
Copy Markdown
Contributor

@starboxxxx starboxxxx commented Jan 15, 2026

🎫 지라 티켓

UPLUS-16


✅ 작업 사항

데이터 사용량 관측 시스템 구현
-> 자세한 사항은 Notion에 기록


📋 체크리스트

  • 코드가 정상적으로 빌드됩니다.
  • 관련 테스트 코드를 작성했습니다.
  • 기존 테스트가 모두 통과합니다.
  • 코드 스타일(Spotless, Checkstyle)을 준수합니다.

⌨ 기타

@starboxxxx starboxxxx requested a review from swthewhite January 15, 2026 15:20
@starboxxxx starboxxxx self-assigned this Jan 15, 2026
@starboxxxx starboxxxx added the feature 기능 추가 label Jan 15, 2026
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @starboxxxx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 Kafka와 Redis를 활용하여 데이터 사용량 관측 시스템을 구현합니다. 주요 변경 사항으로는 요금제 변경 이벤트와 사용량 이벤트를 처리하는 Kafka 컨슈머, Redis에 사용량 및 요금제 한도를 관리하는 로직, 그리고 효율적인 데이터 처리를 위한 Redis Lua 스크립트 도입이 있습니다. 또한, 시스템의 초기 데이터 설정 및 부하 테스트를 위한 유틸리티가 추가되었으며, 기존의 예제 코드는 제거되었습니다.

Highlights

  • 새로운 Kafka 컨슈머 구현: 요금제 변경 및 사용량 데이터 처리를 위한 PlanChangeConsumerUsageConsumer가 추가되었습니다.
  • Redis 기반 데이터 관리: 요금제 한도, 사용량, 처리된 이벤트 관리를 위해 Redis를 활용하는 로직이 도입되었습니다.
  • Redis Lua 스크립트 도입: 사용량 데이터의 중복 제거, 누적 및 임계치 알림 로직을 Redis Lua 스크립트로 구현하여 효율적인 배치 처리를 가능하게 했습니다.
  • Kafka 설정 업데이트: 배치 리스너 컨테이너 팩토리 추가 및 수동 ACK, 재시도 로직을 포함한 오류 핸들러가 설정되었습니다.
  • 기존 예제 코드 제거: 불필요한 예제 컨트롤러, 서비스, 엔티티 등 관련 파일이 삭제되었습니다.
  • 새로운 Kafka 프로듀서 및 스키마 정의: NotificationProducer, PlanChangeProducer, UsageProducer와 관련 데이터 스키마 (CalculatedLimitSchema, PlanChangeSchema, UsageEventSchema)가 추가되었습니다.
  • 초기 데이터 및 부하 테스트 러너 추가: 시스템 초기화를 위한 요금제 시딩 및 사용량 데이터 부하 테스트를 위한 CommandLineRunner 구현체가 추가되었습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 데이터 사용량 관측 시스템을 구현하는 변경 사항을 담고 있습니다. Kafka를 사용하여 plan-changeusage-data 이벤트를 처리하고, Redis를 사용하여 사용량과 한도를 관리하는 로직이 추가되었습니다. 전반적으로 시스템의 핵심 기능이 잘 구현되었지만, 몇 가지 개선점을 제안합니다. 주요 피드백은 예외 처리의 명확성, 코드 중복 제거, 그리고 매직 스트링 사용을 개선하여 유지보수성을 높이는 것에 중점을 두었습니다. 특히 Kafka 컨슈머에서의 예외 처리와 프로듀서에서의 일관성 없는 예외 래핑은 시스템 안정성을 위해 개선이 필요합니다.

Comment on lines +48 to +50
} catch (Exception e) {
throw new ApplicationException(GlobalErrorCode.PLAN_CHANGE_EVENT_PRODUCE_INVALID);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

catch (Exception e) 블록에서 너무 포괄적인 Exception을 잡고 있습니다. 이는 예상치 못한 RuntimeException까지 처리하게 되어 디버깅을 어렵게 만들 수 있습니다. objectMapper.readValue에서 발생하는 JsonProcessingException (혹은 상위 타입인 IOException) 등 예측 가능한 예외를 명시적으로 처리하는 것이 좋습니다. 또한, 여기서 던지는 ApplicationException에 사용된 GlobalErrorCode.PLAN_CHANGE_EVENT_PRODUCE_INVALID는 이벤트 '발행' 오류를 의미하므로, '소비' 로직에 사용하기에는 부적절합니다. 소비 단계의 오류임을 명확히 나타내는 새로운 에러 코드를 정의하는 것을 권장합니다.

Comment on lines +59 to +63
} catch (Exception e) {
log.error("usage batch failed", e);
// 여기서 ack 안 하면 같은 배치가 재시도됨 (at-least-once)
throw new ApplicationException(GlobalErrorCode.NOTIFICATION_EVENT_PRODUCE_INVALID);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

PlanChangeConsumer와 마찬가지로 catch (Exception e)는 너무 광범위합니다. 또한 GlobalErrorCode.NOTIFICATION_EVENT_PRODUCE_INVALID는 알림 이벤트 발행 실패 시에만 적합한 에러 코드입니다. 만약 에러가 JSON 역직렬화나 Redis 작업(redisUtil.applyUsageBatch) 중에 발생한다면 이 에러 코드는 상황을 정확히 설명하지 못합니다. 각 실패 지점(역직렬화, Redis, Kafka 발행)에 맞는 구체적인 예외 처리와 에러 코드를 사용하는 것이 시스템의 문제를 파악하는 데 더 도움이 됩니다.

Comment on lines +34 to +36
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

JsonProcessingException을 잡아서 제네릭 RuntimeException으로 다시 던지는 것은 예외의 구체적인 정보가 사라지게 하고, 일관된 예외 처리 정책을 해칩니다. PlanChangeProducer에서처럼 ApplicationException으로 감싸서 던지는 방식으로 수정하는 것이 좋습니다. 또한, GlobalErrorCode.NOTIFICATION_EVENT_PRODUCE_INVALIDusage-data를 발행하는 이 프로듀서의 맥락과 맞지 않아 보입니다. usage-data 이벤트 발행 실패를 명확히 나타내는 새로운 에러 코드를 정의하는 것을 고려해보세요.

Suggested change
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
} catch (JsonProcessingException e) {
throw new ApplicationException(GlobalErrorCode.NOTIFICATION_EVENT_PRODUCE_INVALID, e);
}

Comment on lines +8 to +11
-- ARGV:
-- [1] N 이후 반복 N개:
-- timeKey, subId, eventId, bytes, ts, ttlSec

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ARGV에 대한 주석이 실제 전달되는 인수와 일치하지 않습니다. 주석에는 timeKey, ttlSec 등이 포함되어 있지만, RedisUtil.applyUsageBatch에서는 subId, eventId, bytes, ts 네 가지 인수만 이벤트별로 전달하고 있습니다. 스크립트의 가독성과 유지보수성을 위해 주석을 실제 구현에 맞게 수정해야 합니다.

Suggested change
-- ARGV:
-- [1] N 이후 반복 N개:
-- timeKey, subId, eventId, bytes, ts, ttlSec
-- ARGV:
-- [1] N (이벤트 개수)
-- 이후 이벤트별로 4개씩 인수가 반복됩니다:
-- subId, eventId, bytes, ts

String prevUnit = redisTemplate.opsForValue().get(unitKey);


if ("ULTIMATE".equals(event.unit())) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

"ULTIMATE"와 같은 문자열 리터럴을 직접 사용하는 것은 오타에 취약하며 코드의 유지보수성을 저해합니다. 요금제 단위를 나타내는 Enum을 정의하여 타입 안정성을 높이고 가독성을 개선하는 것을 권장합니다. 이 피드백은 61, 103라인에도 동일하게 적용됩니다.

Collections.emptyList(),
args.toArray()
);
return result == null ? List.of() : (List<String>) result;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

(List<String>) result와 같은 형변환은 컴파일 시점에 타입 체크가 불가능한 'unchecked cast'입니다. Lua 스크립트가 항상 List<String>을 반환한다고 가정하지만, 예기치 않은 타입이 반환될 경우 ClassCastException이 발생할 수 있습니다. instanceof를 사용하여 리스트의 각 원소 타입을 확인하는 등 보다 안전한 방법으로 형변환하는 것을 고려해볼 수 있습니다.

Comment on lines +46 to +47
factory.getContainerProperties()
.setAckMode(ContainerProperties.AckMode.MANUAL);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

setAckMode가 44라인에서 이미 설정되었습니다. 이 부분은 중복이므로 제거해주세요.

private final KafkaTemplate<String, String> kafkaTemplate;
private final ObjectMapper objectMapper;

public void sendUsageEvent(long subscriptionId, long usageBytes, String yyyyMM, long ttlSec) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

sendUsageEvent 메소드의 파라미터 이름 yyyyMMUsageEventSchemaevent 필드로 전달되고 있습니다. 파라미터 이름이 실제 사용되는 필드 이름과 달라 혼동을 줄 수 있습니다. 명확성을 위해 파라미터 이름을 event로 변경하는 것을 권장합니다.

Suggested change
public void sendUsageEvent(long subscriptionId, long usageBytes, String yyyyMM, long ttlSec) {
public void sendUsageEvent(long subscriptionId, long usageBytes, String event, long ttlSec) {

@github-actions github-actions Bot added the size/XL PR size: XL label Jan 15, 2026
@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/UPLUS-16
Compared to: default branch

Issues

  • 🐞 Bugs: 1
  • 🔐 Vulnerabilities: 2
  • 📎 Code Smells: 48

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=api-usage&branch=feat/UPLUS-16

Generated automatically by GitHub Actions.

@github-actions
Copy link
Copy Markdown

👋 PR 리마인드 알림입니다

이 PR이 몇 시간 동안 업데이트되지 않았어요.

  • 🙋 PR 작성자: 추가 작업 중이거나 설명이 필요하면 코멘트 남겨주세요
  • 👀 리뷰어 / 팀원: 이미 봤다면 LGTM 한 줄도 좋아요!

바쁘실 수 있다는 점 이해합니다 🙏
다만 작은 한 줄이 PR 흐름을 살려줘요 🙂

@paul0755
Copy link
Copy Markdown

확인했습니다!

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/UPLUS-16
Compared to: default branch

Issues

  • 🐞 Bugs: 1
  • 🔐 Vulnerabilities: 0
  • 📎 Code Smells: 48

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=api-usage&branch=feat/UPLUS-16

Generated automatically by GitHub Actions.

@github-actions
Copy link
Copy Markdown

👋 PR 리마인드 알림입니다

이 PR이 몇 시간 동안 업데이트되지 않았어요.

  • 🙋 PR 작성자: 추가 작업 중이거나 설명이 필요하면 코멘트 남겨주세요
  • 👀 리뷰어 / 팀원: 이미 봤다면 LGTM 한 줄도 좋아요!

바쁘실 수 있다는 점 이해합니다 🙏
다만 작은 한 줄이 PR 흐름을 살려줘요 🙂

Copy link
Copy Markdown
Contributor

@swthewhite swthewhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/UPLUS-16
Compared to: default branch

Issues

  • 🐞 Bugs: 1
  • 🔐 Vulnerabilities: 0
  • 📎 Code Smells: 50

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=api-usage&branch=feat/UPLUS-16

Generated automatically by GitHub Actions.

@starboxxxx starboxxxx merged commit 9bcf915 into develop Jan 19, 2026
6 of 8 checks passed
@starboxxxx starboxxxx deleted the feat/UPLUS-16 branch January 19, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능 추가 size/XL PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants