Skip to content

Feat/#146: 포즈 조회수#149

Merged
koosco merged 3 commits intostagingfrom
feat/#146
Feb 25, 2026
Merged

Feat/#146: 포즈 조회수#149
koosco merged 3 commits intostagingfrom
feat/#146

Conversation

@koosco
Copy link
Member

@koosco koosco commented Feb 20, 2026

summary

  • 포즈 상세 조회시 조회수가 증가 기능 추가

details

  • 사용자 한명당 하루에 조회수를 1만 증가하도록 redis 검사 로직을 추가
  • 동시성 문제르 고려해 auto update 방식을 사용

@koosco koosco self-assigned this Feb 20, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @koosco, 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은 포즈 상세 조회 시 조회수를 추적하고 표시하는 새로운 기능을 도입합니다. Redis 캐싱을 활용하여 사용자별 일일 고유 조회수 제한을 적용하며, 조회수 업데이트 시 발생할 수 있는 동시성 문제를 처리합니다. 또한, 이 기능에 필요한 데이터베이스 스키마 변경 사항도 포함되어 있습니다.

Highlights

  • 조회수 기능 추가: 포즈 상세 조회 시 조회수가 증가하는 기능을 추가했습니다.
  • 일일 고유 조회수 제한: Redis를 사용하여 사용자 한 명당 하루에 한 번만 조회수가 증가하도록 로직을 구현했습니다.
  • 동시성 처리: 조회수 증가 시 동시성 문제를 고려하여 데이터베이스의 자동 업데이트 방식을 사용했습니다.
  • 데이터베이스 스키마 변경: TB_POSE 테이블에 view_count 컬럼을 추가했습니다.

🧠 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.

Changelog
  • src/main/kotlin/com/neki/pose/application/port/PoseRepositoryPort.kt
    • incrementViewCount 메서드를 추가했습니다.
  • src/main/kotlin/com/neki/pose/application/port/PoseViewCachePort.kt
    • 조회수 캐싱을 위한 새로운 인터페이스 PoseViewCachePort를 추가했습니다.
  • src/main/kotlin/com/neki/pose/application/usecase/GetPoseUseCase.kt
    • 조회수 증가 로직과 캐시 확인을 통합하도록 수정했습니다.
  • src/main/kotlin/com/neki/pose/domain/entity/Pose.kt
    • viewCount 속성을 추가했습니다.
  • src/main/kotlin/com/neki/pose/infra/cache/fake/FakePoseViewCacheAdapter.kt
    • 테스트를 위한 PoseViewCachePort의 가짜 구현체를 추가했습니다.
  • src/main/kotlin/com/neki/pose/infra/cache/redis/PoseViewRedisCacheKey.kt
    • Redis 캐시 키를 위한 유틸리티 객체를 추가했습니다.
  • src/main/kotlin/com/neki/pose/infra/cache/redis/RedisPoseViewCacheAdapter.kt
    • Redis 기반의 PoseViewCachePort 구현체를 추가했습니다.
  • src/main/kotlin/com/neki/pose/infra/persist/PoseRepositoryAdapter.kt
    • PoseRepositoryPort의 incrementViewCount 메서드를 구현했습니다.
  • src/main/kotlin/com/neki/pose/infra/persist/jpa/PosesQueryRepository.kt
    • JPAQueryFactory를 사용하여 incrementViewCount 메서드를 추가했습니다.
  • src/main/resources/db/migration/V12__add_view_count_to_pose.sql
    • view_count 컬럼을 추가하는 SQL 마이그레이션을 추가했습니다.
  • src/test/kotlin/com/neki/e2e/pose/GetPoseE2ETest.kt
    • 조회수 증가 및 일일 고유 조회수를 위한 E2E 테스트를 추가했습니다.
  • src/test/kotlin/com/neki/e2e/pose/PoseE2ETestBase.kt
    • FakePoseViewCacheAdapter를 포함하고 상태를 초기화하도록 기본 테스트 클래스를 업데이트했습니다.
Activity
  • 현재까지 사람의 활동은 없습니다.
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.

@github-actions
Copy link

Code Format Check ✅ PASSED

Spotless Check: success

✨ All code formatting checks passed!


Pushed by: @koosco, Action: pull_request

Copy link

@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

This pull request introduces a new feature to track pose view counts, ensuring that each user's view is counted only once per day using Redis caching. However, the Redis-based viewer tracking has a significant logic flaw and potential resource exhaustion issue. The current implementation uses a single Redis Set per pose and resets its TTL every time a new viewer is added, which can prevent users from ever incrementing the count again for popular poses and lead to unbounded memory growth in Redis. A more granular key structure is recommended to address these issues. The PR also includes necessary database schema changes, updates to the GetPoseUseCase to integrate view count logic, and corresponding test cases, with the implementation correctly handling concurrency for view count updates and providing a fail-open mechanism for Redis failures.

Comment on lines 19 to 31
val key = PoseViewRedisCacheKey.viewKey(poseId)
return try {
val added = redisTemplate.opsForSet().add(key, userId) ?: 0
if (added > 0) {
redisTemplate.expire(key, VIEW_TTL)
true
} else {
false
}
} catch (e: Exception) {
log.warn("[PoseViewCache] Failed to check viewer for pose: $poseId, user: $userId", e)
true // fail-open: Redis 장애 시 신규 조회로 처리
}

Choose a reason for hiding this comment

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

security-medium medium

The current implementation of addViewer uses a single Redis Set per pose (pose:view:{poseId}) to track unique viewers. The TTL of this set is reset to 24 hours every time a new viewer is added. This introduces two significant issues:

  1. Logic Flaw: The requirement is to allow a user to increment the view count once per day. However, because the TTL is reset for the entire set whenever any new user views the pose, an existing user's ID will remain in the set as long as the pose remains popular. This prevents the user from ever incrementing the view count again during that period, even if more than 24 hours have passed since their last view.
  2. Resource Exhaustion (DoS): For popular poses, the set of user IDs can grow indefinitely as long as the TTL keeps being reset, leading to significant memory consumption in Redis and potential Denial of Service.
    Remediation: Change the Redis key structure to be per-user and per-pose, for example: pose:view:{poseId}:{userId}. Set a fixed 24-hour TTL on this key when it is first created, and do not reset it.
        val key = "${PoseViewRedisCacheKey.viewKey(poseId)}:$userId"
        return try {
            redisTemplate.opsForValue().setIfAbsent(key, true, VIEW_TTL) ?: false
        } catch (e: Exception) {
            log.warn("[PoseViewCache] Failed to check viewer for pose: $poseId, user: $userId", e)
            true // fail-open: Redis 장애 시 신규 조회로 처리
        }

?: throw BusinessException(ResultCode.NOT_FOUND)

if (poseViewCache.addViewer(command.poseId, command.userId)) {
transactionRunner.run { poseRepository.incrementViewCount(command.poseId) }

Choose a reason for hiding this comment

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

medium

The transactionRunner.run block is used here to increment the view count. While this ensures the database update is transactional, it's important to consider if incrementViewCount itself needs to be part of a larger business transaction or if it can be an independent operation. Given that addViewer is called outside this transaction, there's a potential for a view to be recorded in Redis but not in the database if the transaction fails later, or vice-versa if addViewer fails after the transaction commits. However, the current implementation's 'fail-open' for Redis suggests a preference for eventual consistency in view counts, so this might be acceptable. If strict consistency is required, the addViewer call should also be part of the transaction or handled with compensating actions.

Comment on lines +29 to +30
log.warn("[PoseViewCache] Failed to check viewer for pose: $poseId, user: $userId", e)
true // fail-open: Redis 장애 시 신규 조회로 처리

Choose a reason for hiding this comment

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

medium

The fail-open strategy (true // fail-open: Redis 장애 시 신규 조회로 처리) means that if Redis is down or an exception occurs, the view count will still be incremented in the database. This prioritizes view count incrementation over preventing duplicate views during Redis outages. This is a valid design choice, but it's important to acknowledge the trade-off: in case of Redis failure, a single user might increment the view count multiple times within 24 hours. Ensure this behavior is acceptable for the business requirements.

@github-actions
Copy link

Test ✅ PASSED

Test Result: success

✨ All tests passed!


Pushed by: @koosco, Action: pull_request

Copy link
Member

@Darren4641 Darren4641 left a comment

Choose a reason for hiding this comment

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

Comment 추가했습니다~!

@github-actions
Copy link

Code Format Check ✅ PASSED

Spotless Check: success

✨ All code formatting checks passed!


Pushed by: @koosco, Action: pull_request

@github-actions
Copy link

Test ✅ PASSED

Test Result: success

✨ All tests passed!


Pushed by: @koosco, Action: pull_request

Copy link
Member

@Darren4641 Darren4641 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! LGTM~

@koosco koosco merged commit d8ca9dd into staging Feb 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants