스프링 부트로 블로그 서비스 개발하기

포스트 CRUD 기능 - 쓰레드풀을 써서 게시글 대량 삽입 테스트

exena 2026. 1. 10. 15:13

데이터 삽입을 해보자.

@SpringBootTest
@Import(SecurityTestConfiguration.class)
@ActiveProfiles("test")
public class DataInitializer {
    @Autowired
    EntityManager entityManager;
    @Autowired
    TransactionTemplate transactionTemplate;
    Snowflake snowflake = new Snowflake();
    CountDownLatch latch = new CountDownLatch(EXECUTE_COUNT);

    @Autowired
    MemberRegister memberRegister;
    Member member;

    static final int BULK_INSERT_SIZE = 2000;
    static final int EXECUTE_COUNT = 6000;

    @BeforeEach
    void setUp() {
        // 게시글 테이블에 멤버 외래키가 있기 때문에 해당 ID를 가진 멤버가 실제로 DB에 있어야 함
        member = memberRegister.register(MemberFixture.createRegisterRequest());
        // entityManager.flush(); No EntityManager with actual transaction available for current thread 에러 발생
        // entityManager.clear();
    }


    @Test
    void initialize() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i = 0; i < EXECUTE_COUNT; i++) {
            executorService.submit(() -> {
                insert();
                latch.countDown();
                System.out.println("latch.getCount() = " + latch.getCount());
            });
        }
        latch.await();
        executorService.shutdown();
    }

    void insert() {
        transactionTemplate.executeWithoutResult(status -> {
            for(int i = 0; i < BULK_INSERT_SIZE; i++) {
                Article article = Article.of(snowflake.nextId(), PostFixture.getPublishBlogpostFormRequest(), member);
                entityManager.persist(article);
            }
        });
    }
}

ExecutorService의 쓰레드풀을 사용하면 병렬 처리가 가능하다.

10개의 쓰레드가 병렬 처리하도록 해주자. 6000번 벌크 생성이 동작하도록 한다. 벌크 한개당 2000개로 한다.

그럼 1200만개의 게시글을 삽입해야 한다. 쓰레드 한개당 대략 120만개를 담당해야겠네.

전부 삽입하는데 3분정도 걸린 것을 볼 수 있다.

테스트용으로 메모리 h2db를 쓰도록 해서인지 테스트가 빠르게 끝났다.

이러면 테스트의 정확성이 낮아지니 mysql을 사용하도록 테스트 설정을 변경하도록 하겠다.

도커는 docker compose -f service/article/compose-test.yaml up -d 명령어로 실행했다.

근데 테스트에서 몇번 DB 접속에 실패하다가 다시 실행해보니까 되던데 어째서인지는 모르겠다.

이건 compose-test.yaml

이후를 생각하면 db 이름을 testdb보다는 article로 정하는 쪽이 나아보인다.

이건 application-test.properties 파일.

create-drop이 아니라 update로 해줘야 데이터가 삭제되지 않고 남는다.

물론 테스트용 mysql 의존성을 추가해줘야 한다.

 

@ActiveProfiles("test") 를 써줘야 테스트용 설정파일을 사용한다.

show-sql을 false로 해줘야 로그가 데이터 삽입마다 나오는 걸 막을 수 있다.

@Import(SecurityTestConfiguration.class)에 대해서는 https://exena.tistory.com/62 이 글의 마지막 부분 참고.

 

대량삽입 테스트 중에 도커가 부하를 감당하지 못하고 꺼졌다.

void insert() {
    transactionTemplate.executeWithoutResult(status -> {
        for(int i = 0; i < BULK_INSERT_SIZE; i++) {
            Article article = Article.of(snowflake.nextId(), PostFixture.getPublishBlogpostFormRequest(), member);
            entityManager.persist(article);
        }
        entityManager.flush();
        entityManager.clear();
    });
}

위 코드에서 배치 단위로 flush와 clear를 해주자. 메모리의 영속성 관리소에서 실제 저장소로 이동하기에 메모리 부하가 줄어든다.
그럼에도 불구하고 메모리 부하도 크고 용량도 많이 잡아먹어서 실패할 뻔 했다.

성공했다. 도커 MySQL을 사용하니 30분정도 걸렸다.

30건의 데이터를 조회하는데 30초나 걸리는 것을 확인할 수 있다.

실제로 게시판 로딩이 이렇게 오래걸린다면 아무도 우리 서비스를 쓰려고 하지 않을 것이다.

다음 포스팅에서 인덱스 생성과 쿼리 최적화로 이 속도를 높여보도록 하겠다.