Conversation
- GET /posts/recent 엔드포인트 추가 - 7일 이내 발행된 게시물을 커서 기반 페이지네이션으로 반환 - 좋아요/스크랩 여부 배치 조회로 N+1 방지 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Summary of ChangesHello, 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은 최근 게시물을 조회하는 새로운 API 엔드포인트를 도입하여 사용자 경험을 향상시킵니다. 효율적인 데이터 조회를 위해 N+1 문제를 해결하고, 기존 스토리 게시물 조회 로직의 버그를 수정하여 시스템 안정성을 높였습니다. 로그인 여부와 관계없이 모든 사용자가 최신 게시물을 원활하게 탐색할 수 있도록 지원합니다. Highlights
🧠 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
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This PR introduces an API to retrieve recent posts and addresses the N+1 problem in the existing story post retrieval logic. While the overall code is well-written with good test coverage, the getRecentPostsWithPaging method introduces a significant performance flaw that could lead to a Denial of Service (DoS) vulnerability. This method retrieves all matching records into memory before applying pagination, which can exhaust server resources under high load or an intentional attack. It is strongly recommended to refactor this to perform paging at the database level. Additionally, there's an opportunity to improve duplicate sorting logic using a Comparator.
| public List<Post> getRecentPostsWithPaging(PageRequestDto pageRequest) { | ||
| Instant sevenDaysAgo = Instant.now().minus(7, ChronoUnit.DAYS); | ||
|
|
||
| List<FreePost> freePosts = queryFactory | ||
| .selectFrom(freePost) | ||
| .leftJoin(freePost._super.user, user).fetchJoin() | ||
| .where( | ||
| freePost._super.isBlocked.isFalse() | ||
| .and(freePost._super.postStatus.eq(PostStatus.PUBLISHED)) | ||
| .and(freePost._super.createdAt.goe(sevenDaysAgo)) | ||
| ) | ||
| .orderBy(freePost._super.createdAt.desc(), freePost._super.id.desc()) | ||
| .fetch(); | ||
|
|
||
| List<StoryPost> storyPosts = queryFactory | ||
| .selectFrom(storyPost) | ||
| .leftJoin(storyPost._super.user, user).fetchJoin() | ||
| .leftJoin(storyPost.primaryComposer).fetchJoin() | ||
| .where( | ||
| storyPost._super.isBlocked.isFalse() | ||
| .and(storyPost._super.postStatus.eq(PostStatus.PUBLISHED)) | ||
| .and(storyPost._super.createdAt.goe(sevenDaysAgo)) | ||
| ) | ||
| .orderBy(storyPost._super.createdAt.desc(), storyPost._super.id.desc()) | ||
| .fetch(); | ||
|
|
||
| List<CurationPost> curationPosts = queryFactory | ||
| .selectFrom(curationPost) | ||
| .leftJoin(curationPost._super.user, user).fetchJoin() | ||
| .leftJoin(curationPost.primaryComposer).fetchJoin() | ||
| .where( | ||
| curationPost._super.isBlocked.isFalse() | ||
| .and(curationPost._super.postStatus.eq(PostStatus.PUBLISHED)) | ||
| .and(curationPost._super.createdAt.goe(sevenDaysAgo)) | ||
| ) | ||
| .orderBy(curationPost._super.createdAt.desc(), curationPost._super.id.desc()) | ||
| .fetch(); | ||
|
|
||
| List<Post> allPosts = new ArrayList<>(); | ||
| allPosts.addAll(freePosts); | ||
| allPosts.addAll(storyPosts); | ||
| allPosts.addAll(curationPosts); | ||
|
|
||
| allPosts.sort((p1, p2) -> { | ||
| int dateCompare = p2.getCreatedAt().compareTo(p1.getCreatedAt()); | ||
| if (dateCompare != 0) return dateCompare; | ||
| return Long.compare(p2.getId(), p1.getId()); | ||
| }); | ||
|
|
||
| return pagingUtils.applyCursorPaginationToList( | ||
| allPosts, | ||
| pageRequest, | ||
| Post::getCreatedAt, | ||
| Post::getId | ||
| ); | ||
| } |
There was a problem hiding this comment.
The getRecentPostsWithPaging method fetches all posts from the last 7 days for each post type (FreePost, StoryPost, CurationPost) into memory using .fetch() without any database-level limit. This approach, where application-level sorting and paging are applied after loading all data, can lead to excessive memory consumption and CPU usage, potentially causing an OutOfMemoryError and crashing the application under high load. This constitutes a Denial of Service (DoS) vulnerability.
To remediate this, it is strongly recommended to perform pagination at the database level. A more scalable approach involves:
- Querying the base
Postentity with cursor-based paging to retrieve a single page ofPostentities. - To avoid N+1 queries, group the returned
Postentities by their specific type. - For each type group, execute separate queries using
WHERE id IN (...)andfetchJointo retrieve associated relationships. This populates the persistence context, preventing additional queries for related data.
Additionally, the orderBy clauses currently used when fetching data for each post type are unnecessary, as sorting is re-applied in memory. Removing these can reduce database load.
| allPosts.sort((p1, p2) -> { | ||
| int dateCompare = p2.getCreatedAt().compareTo(p1.getCreatedAt()); | ||
| if (dateCompare != 0) return dateCompare; | ||
| return Long.compare(p2.getId(), p1.getId()); | ||
| }); |
There was a problem hiding this comment.
이 정렬 로직은 getPostsByComposerIdWithPaging 메서드(189-195행)에서도 동일하게 사용되고 있습니다. 코드 중복을 피하고 가독성을 높이기 위해 Comparator를 사용하는 것이 좋습니다. Comparator.comparing()과 thenComparing()을 연결하여 사용하면 더 선언적이고 읽기 좋은 코드를 작성할 수 있습니다.
allPosts.sort(java.util.Comparator.comparing(Post::getCreatedAt, java.util.Comparator.reverseOrder())
.thenComparing(Post::getId, java.util.Comparator.reverseOrder()));
Summary
GET /posts/recent엔드포인트 추가 (7일 이내 발행 게시물 최신순 반환)getAllStoryPostsWithPagingprimaryComposer fetchJoin 누락 버그 수정Test plan