Skip to content

Conversation

@sunwon12
Copy link
Contributor

@sunwon12 sunwon12 commented Jan 2, 2026

동시성 처리 전략 보고서 (Concurrency Strategy Report)

이 문서는 BookSaveServiceBookCategoryService에서 병렬 처리 시 발생하는 동시성 문제(중복 생성)를 해결하기 위해 채택한 전략과 기술적 배경을 정리한 문서입니다.

1. 개요 및 배경

기존의 책 검색 및 저장 로직은 20권의 책을 순차적으로 크롤링하고 저장하는 방식으로, 사용자 응답 속도가 매우 느린 문제가 있었습니다. 이를 개선하기 위해 20개의 스레드가 동시에 크롤링 및 저장을 수행하는 병렬 처리 방식으로 전환했습니다.

그러나 성능 향상을 위한 이 병렬 처리는 **동일한 책이나 카테고리에 대한 동시 접근(Race Condition)**을 유발하여 데이터 중복 및 데드락 이슈를 발생시켰습니다. 본 PR은 이러한 동시성 문제를 해결하고 성능과 데이터 무결성을 모두 해결한 PR입니다.


2. 전략 비교 및 선택

A. 책 저장 (BookSaveService)

전략: Try-Catch (Optimistic Locking 관점)

try {
    return bookRepository.save(book);
} catch (DataIntegrityViolationException e) {
    return bookRepository.findByAladingBookId(...) ...
}
  • 이유 1 (Edge Case): 저장 로직 진입 전에 이미 findByAladingBookId로 존재 여부를 검사합니다. 따라서 save() 단계에서 중복이 발생하는 것은 극히 드문 엣지 케이스입니다.
  • 이유 2 (성능 - RTT): Insert Ignore + Select 방식은 항상 2회의 DB 왕복(RTT)이 발생하지만, Try-Catch 방식은 대부분의 경우(성공 시) 1회만 발생하므로 평균 성능이 더 좋습니다.
  • 이유 3 (유지보수): Book 엔티티는 필드와 연관관계가 많아, 이를 Native Query(INSERT IGNORE)로 일일이 매핑하는 것은 복잡도가 높고 유지보수가 어렵습니다.

B. 카테고리 저장 (BookCategoryService)

전략: INSERT IGNORE + READ_COMMITTED

-- 1. Insert Ignore (Native Query)
INSERT IGNORE INTO book_category (name, parent_id) VALUES (?, ?);

-- 2. Select (Repository Method)
SELECT * FROM book_category WHERE name = ? AND parent_id = ?;

이유 1: INSERT IGNORE 사용 이유 (Transaction Safety)

  • 카테고리는 국내도서 > 소설 > 한국소설처럼 계층적으로 연속 생성됩니다.
  • 만약 Try-Catch를 사용하여 DataIntegrityViolationException이 발생하면, 스프링의 트랜잭션은 **'Rollback-Only'**로 마킹됩니다.
  • 이 경우 예외를 잡아도 상위 트랜잭션(책 저장 로직 등)까지 전부 롤백되는 치명적인 문제가 발생합니다.
  • INSERT IGNORE는 DB 차원에서 무시하므로 예외가 터지지 않아 트랜잭션을 안전하게 유지할 수 있습니다.

이유 2: READ_COMMITTED 격리 수준 사용 이유 (Visibility)

  • 문제 상황 (REPEATABLE_READ): MySQL 기본 격리 수준에서는 트랜잭션 시작 시점의 스냅샷을 봅니다. 스레드 A가 생성 후 커밋해도 스레드 B의 SELECT 문에는 해당 데이터가 보이지 않는 Phantom Miss가 발생할 수 있습니다.
  • 해결책 (READ_COMMITTED): 매 쿼리마다 최신 커밋된 데이터를 다시 읽어오므로, 다른 트랜잭션이 커밋한 데이터를 즉시 조회하여 중복 생성을 방지합니다.

이유 3: ROOT 카테고리 별도 처리 (Unique Constraint Issue)

  • 문제 상황: 최상위 ROOT 카테고리는 parent_idNULL입니다. MySQL의 Unique Constraint (parent_id, name)NULL 값에 대해 중복 체크를 하지 않는 특성이 있습니다.
  • 해결책: ROOT 카테고리 생성 시 Primary Key(ID)를 1강제 지정하여 삽입합니다. PK 제약조건을 활용하여 Unique Constraint의 한계를 극복했습니다.

3. 요약 (Summary)

대상 전략 핵심 이유
Book Try-Catch 대부분 성공하는 케이스(RTT 1회 유리), 엔티티 매핑 복잡도 고려
Category INSERT IGNORE 트랜잭션 롤백 방지 (Rollback-Only 회피), 계층 구조 안정성
Isolation READ_COMMITTED INSERT IGNORE 후 최신 데이터 조회 보장 (Phantom Miss 해결)
Transaction REQUIRED (Default) REQUIRES_NEW 사용 시 커넥션 풀 고갈 및 데드락 위험 방지

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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!

이 PR은 기존의 순차적 책 크롤링 및 저장 방식을 병렬 처리로 전환하면서 발생한 동시성 문제를 해결하는 데 중점을 둡니다. 책과 카테고리 저장 로직에 각각 최적화된 동시성 처리 전략을 적용하여 데이터 무결성을 보장하고 성능을 유지합니다. 특히 카테고리 계층 구조의 동시성 문제를 해결하기 위해 INSERT IGNOREREAD_COMMITTED 격리 수준을 활용하며, ROOT 카테고리 처리 방식을 개선했습니다.

Highlights

  • 동시성 이슈 해결 전략 도입: 병렬 처리 시 발생하던 책 및 카테고리 저장의 동시성 문제(중복 생성, 데드락)를 해결하기 위한 전략이 도입되었습니다.
  • 책 저장 동시성 처리: 책 저장(BookSaveService)에는 Try-Catch (낙관적 잠금 관점) 전략이 적용되었습니다. 이는 대부분의 경우 1회 RTT로 성능 이점을 가지며, 엔티티 복잡도를 고려한 선택입니다.
  • 카테고리 저장 동시성 처리: 카테고리 저장(BookCategoryService)에는 INSERT IGNORE 네이티브 쿼리와 READ_COMMITTED 격리 수준이 사용되었습니다. 이는 트랜잭션 롤백 방지 및 최신 데이터 가시성 확보를 위함입니다.
  • ROOT 카테고리 특별 처리: 최상위 ROOT 카테고리의 parent_idNULL일 때 MySQL의 Unique Constraint가 작동하지 않는 문제를 해결하기 위해, ID를 고정하여 PK 제약조건으로 중복 생성을 방지하는 전략이 구현되었습니다.
  • 카테고리 서비스 리팩토링: BookCategoryService가 전체 카테고리 경로를 받아 계층적으로 카테고리를 생성하거나 조회하는 getOrCreateByFullPath 메서드를 도입하여 로직이 개선되었습니다.
  • 동시성 테스트 추가: 새로운 BookCategoryServiceConcurrencyTest가 추가되어 카테고리 동시 생성 시 중복 없이 하나만 생성되는지 검증합니다.

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

@sunwon12 sunwon12 requested a review from AlphaBs January 2, 2026 13:09
@sunwon12 sunwon12 self-assigned this Jan 2, 2026
@sunwon12 sunwon12 added the 🔫fix 잘못된 로직 수정 label Jan 2, 2026
Copy link
Contributor

@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 robust system for managing hierarchical book categories, with a strong focus on handling concurrent creation. The BookCategory entity was updated to correctly process 'ROOT' categories in path generation. A new BookCategoryInitializer ensures a 'ROOT' category with ID 0 is created on application startup. The BookCategoryRepository now includes an insertRoot() method. The BookCategoryService was significantly refactored, introducing a getOrCreateByFullPath() method to handle hierarchical category creation, and implementing a concurrency strategy using INSERT IGNORE and READ_COMMITTED transaction isolation for category creation, with detailed explanations for these choices. The BookSaveService was updated to utilize the new category service and also uses READ_COMMITTED isolation, employing a try-catch block for DataIntegrityViolationException for book saving, again with detailed concurrency strategy comments. New concurrency tests were added for category creation, and existing book saving tests were adjusted. Review comments highlighted an inconsistency in the 'ROOT' category ID (0 vs. 1) across different components, suggested externalizing the 'ROOT' string as a constant for better maintainability, pointed out a redundant find call within the create method in BookCategoryService, and recommended improving the readability and robustness of chained findByNameAndParent().get() calls in the concurrency test.

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

Test Results

101 files  101 suites   18s ⏱️
511 tests 511 ✅ 0 💤 0 ❌
521 runs  521 ✅ 0 💤 0 ❌

Results for commit ba104c3.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

🌻 테스트 커버리지 리포트

Overall Project 52.7% -0.04% 🍏
Files changed 92.77% 🍏

File Coverage
BookCategoryInitializer.java 100% 🍏
BookCategoryService.java 89.19% -7.43% 🍏
BookSaveService.java 88.78% 🍏
BookCategory.java 79.33% -0.67% 🍏

@sunwon12 sunwon12 merged commit c637040 into dev Jan 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔫fix 잘못된 로직 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants