-
Notifications
You must be signed in to change notification settings - Fork 0
[Book][Refactor] Search -> BookSave 흐름 가독성 향상 및 크롤러 포함된 동시성 로직 개선 #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 커넥션 재사용을 위함
- try-cath -> insertIgnore로 - 의사결정 주석으로 표
- native 쿼리이므로 book 속성 수정 시 조기에 에러 감지하기 위해서
- 그리고 삭제 repository들 jpql로 진행, 최적화를 위해 - 책, 챕터에서 퀴즈 초기화를 book이 아닌 quizService에서 가지도록 함
- find함수와 save함수 분리
- find함수와 save함수 분리 - 베스트 셀러는 책 저장 임시 큐 엔티티이므
Summary of ChangesHello @sunwon12, 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! 이 Pull Request는 검색 및 책 저장 흐름의 전반적인 성능과 안정성을 대폭 개선하는 데 중점을 둡니다. 기존 시스템에서 발생하던 동시성 문제, 외부 API 요청 제한, 그리고 대량 데이터 처리의 비효율성을 해결하기 위해 다양한 최적화 기법을 적용했습니다. 이를 통해 사용자 경험을 향상시키고 시스템 리소스 사용을 효율화하는 것을 목표로 합니다. 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. 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
|
Test Results102 files 102 suites 18s ⏱️ Results for commit a9692fd. ♻️ This comment has been updated with latest results. |
🌻 테스트 커버리지 리포트
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
검색에서 책 저장으로 이어지는 흐름을 리팩토링하고 성능을 최적화한 훌륭한 PR입니다. PR 설명에 작성해주신 상세한 배경, 문제점 분석, 그리고 해결책 선택 과정이 매우 명확하고 논리적입니다. 동시성 제어를 위한 INSERT IGNORE 도입, 챕터 관리를 위한 JDBC Bulk Insert, 논블로킹 I/O를 위한 WebClient 사용, 그리고 외부 API 호출 제어를 위한 Semaphore 활용 등 적용된 변경 사항들이 모두 적절하며 잘 구현되었습니다. 코드가 전반적으로 더 안정적이고 효율적이며 유지보수하기 좋아졌습니다. 몇 가지 추가 개선을 위한 제안 사항을 남깁니다. 고생 많으셨습니다!
| // .retryWhen(Retry.backoff(2, Duration.ofMillis(500)) | ||
| // .filter(t -> t instanceof TimeoutException || t instanceof java.io.IOException)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchTocApiHtml 메서드에서 재시도 로직(retryWhen)이 주석 처리되어 있습니다. fetchBookHtml 메서드에는 재시도 로직이 적용되어 있어 일관성을 맞추고, 외부 API 호출의 안정성을 높이기 위해 이 부분의 주석을 해제하는 것을 고려해 보세요. 특별히 주석 처리한 이유가 있으신가요?
| // .retryWhen(Retry.backoff(2, Duration.ofMillis(500)) | |
| // .filter(t -> t instanceof TimeoutException || t instanceof java.io.IOException)) | |
| .retryWhen(Retry.backoff(2, Duration.ofMillis(500)) | |
| .filter(t -> t instanceof TimeoutException || t instanceof java.io.IOException)) |
| } | ||
|
|
||
| BookCategory effectiveParent = parent; | ||
| if (effectiveParent == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
effectiveParent 를 찾는 과정이 find 메서드와 create 메서드 두군데에서 중복되는데, 메서드 분리해서 중복코드를 제거할 수 있을거같습니다
| "INSERT IGNORE INTO book (alading_book_id, title, author, isbn, isbn13, category_id, description, publisher, published_date, image_url, aladin_url, aladin_star_rating, chapter_count, diary_count, book_size, weight, generated_quiz_count, created_date, updated_date, page) " | ||
| + | ||
| "VALUES (:#{#book.aladingBookId}, :#{#book.title}, :#{#book.author}, :#{#book.isbn}, :#{#book.isbn13}, :#{#book.category.id}, :#{#book.description}, :#{#book.publisher}, :#{#book.publishedDate}, :#{#book.imageUrl}, :#{#book.aladinUrl}, :#{#book.aladinStarRating}, :#{#book.chapterCount}, 0, :#{#book.bookSize}, :#{#book.weight}, 0, NOW(), NOW(), :#{#book.page})", nativeQuery = true) | ||
| int insertIgnoreBook(Book book); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
saveIfAbsent 이름 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정하겠습니다!
| } | ||
| return book; | ||
| public Book saveBook(AladinSearchResponse.SearchItem item) { | ||
| Optional<AladinBookDetail> detail = crawlBookDetail(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DB삽입은 세마포어로 감싸지않은 이유가 궁금합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앞단 크롤러에서 세마포어로 속도 조절을 하기 때문에 디비 커넥션 경쟁 상태는 발생하지 않습니다!
🔍 Search -> BookSave 흐름 리팩토링 및 성능 최적화
배경 및 문제점 (Problem)
기존 로직은 검색 결과를 DB에 저장하는 과정에서 동시성 이슈, 외부 크롤러의 제한, 그리고 성능 병목이 발생했습니다.
save()호출 시 불필요한 조회 및 영속성 컨텍스트 관리 비용해결책 및 트레이드오프 (Solutions & Trade-offs)
2. 트랜잭션 분리 (
REQUIRES_NEW)3. Native Query (INSERT IGNORE)
itemId마다 락을 걸면 충돌 빈도에 비해 성능 저하가 큼.2. REQUIRES_NEW: DB 커넥션을 2배로 점유하므로 리소스 낭비 심함.
3. Try-Catch: 일반
save()후 예외 발생 시 영속성 컨텍스트가 오염되어 후속 복구(조회)가 불가능함.-> INSERT IGNORE: 위 문제들을 모두 해결하며 가장 효율적.
2. 순차 실행
3. Semaphore
Trade-off: 전체 처리량은 제한되지만 안정성 확보.
saveAll(Batch Size 설정)2. JDBC Template Bulk
Trade-off: 구현 복잡도 증가(SQL 직접 작성), type-safety 감소하나 성능 압도적.
2. WebClient
2. OSIV Off
Trade-off: View Layer에서 지연 로딩 불가, Service 계층에서 DTO 변환이나 초기화 완료 필요.
주요 변경 사항 (Changes)
BookSaveService:createBookWithDetails메서드에서INSERT IGNORE쿼리 실행 후findByAladinBookId로 조회하는 Find-Get 패턴 적용.BookRepository: Native Query로insertIgnoreBook구현.AladinCrawlerBatchProcessor:Semaphore(10)도입하여 동시 크롤링 요청 수 제한.AladinCrawlerService: Jsoup을 WebClient로 교체, 타임아웃 10초 및 재시도 로직 설정.ChapterRepository: JDBC Template을 이용한 Bulk Insert/Update/Delete 구현.open-in-view) 설정 False로 변경 추정(코드상 반영).기타 변경사항 (Other Changes)