π Spring Thread, Transaction, Connection κ΄κ³
μλ νμΈμ, μ΅κ·Ό ν λΉμ μ€νλ§ λμλ‘ μ€ν°λ μ€ μλμ λΉμ·ν μ§λ¬Έμ΄ μμμ΅λλ€.
"νΈλμμ μ΄ μλ‘ μμ±λ λ μ€λ λκ° μλ‘ μμ±λ κΉμ?"
μ΄μ κ΄λ ¨νμ¬ μλμ κ΄κ³λ€μ λν΄ κ°λ΅ν μ 리ν΄λ³΄κ³ μ ν©λλ€.
(κ΄λ ¨ μ½λλ GitHubμμ νμΈν μ μμ΅λλ€. π )
- Thread per transaction vs One thread for serveral transaction
- Thread per connection vs One thread for all connections
νλ¦° λ΄μ©μ΄ μ‘΄μ¬ν μ μμΌλ μ΄λ¬ν λΆλΆμμλ νΌλλ°± μ£Όμλ©΄ κ°μ¬νκ² μ΅λλ€. π
(β» ν΄λΉ ν¬μ€ν μ https://truehong.tistory.com/140 λΈλ‘κ·Έλ₯Ό μ°Έκ³ νμ¬ μμ±νμμ΅λλ€.)
Thread per transaction vs One thread for serveral transaction
λ¨Όμ νλμ μ€λ λμμ νλμ νΈλμμ μ κ΄λ¦¬νλμ§ νΉμ νλμ μ€λ λμμ μ¬λ¬κ°μ νΈλμμ μ κ΄λ¦¬νλμ§ κ°λ¨ν μμ λ₯Ό ν΅ν΄ μ΄ν΄λ³΄κ² μ΅λλ€.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | @Service @RequiredArgsConstructor public class OuterService { private final InnerService innerService; @Transactional public void outerMethod() { System.out.println("========================================="); System.out.println(Thread.currentThread().getId() + ", " + Thread.currentThread().getName()); System.out.println("========================================="); innerService.innerMethod(); } } @Service public class InnerService { /* μλ‘μ΄ νΈλμμ
μμ± */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { System.out.println("========================================="); System.out.println(Thread.currentThread().getId() + ", " + Thread.currentThread().getName()); System.out.println("========================================="); } } ... @Test void transactionNewTest() { outerService.outerMethod(); } | cs |
κ²°κ³Όλ₯Ό νμΈν΄λ³΄λ©΄ νΈλμμ μ μλ‘ μμ±νμΌλ μ€λ λμ id, nameμ΄ λμΌνκ²μΌλ‘ 보μ νλμ μ€λ λμμ μ¬λ¬ κ°μ νΈλμμ μ κ΄λ¦¬νλ κ²μ νμΈν μ μμ΅λλ€.
νλμ μ€λ λμμ μ¬λ¬ νΈλμμ μ κ΄λ¦¬νκΈ° λλ¬Έμ μ μ½λμμ μλ‘μ΄ νΈλμμ μ΄ μμ±λλ InnerServiceμμ μμΈκ° λ°μνμ¬ λ‘€λ°±μ΄ λλ€λ©΄, μμ νΈλμμ μΈ OuterServiceμμλ λ‘€λ°±μ΄ λλ€λ κ²μ μμΈ‘ν μ μμ΅λλ€.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | @Service @RequiredArgsConstructor public class OuterService { private final InnerService innerService; private final ItemRepository itemRepository; @Transactional public void outerMethod() { System.out.println("========================================="); System.out.println(Thread.currentThread().getId() + ", " + Thread.currentThread().getName()); System.out.println("========================================="); itemRepository.addCount("item", 0); innerService.innerMethod(); // λ‘€λ°± λμ΄μ μ€ν μλ¨ itemRepository.print(); } } @Service @RequiredArgsConstructor public class InnerService { private final ItemRepository itemRepository; @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { System.out.println("========================================="); System.out.println(Thread.currentThread().getId() + ", " + Thread.currentThread().getName()); itemRepository.addCount("item", 5); throw new RuntimeException(); } } @Repository public class ItemRepository { private Map<String, Integer> item = new HashMap<>(); public void addCount(String name, Integer count) { item.put(name, count); } public void print() { System.out.println(item); } } @SpringBootTest class OuterServiceTest { @Autowired private OuterService outerService; @Autowired private InnerService innerService; @Test void transactionNewTest() { outerService.outerMethod(); } } | cs |
InnerServiceμμ κ°μ μ μΌλ‘ μμΈλ₯Ό λ°μμμΌ νμΈν΄λ³΄λ©΄ OuterService μμλ itemRepository.save() λ©μλκ° μ€νλμ§ μκ³ μ’ λ£λλ κ²μ νμΈν μ μμ΅λλ€.
How to manage thread per transaction
κ·Έλ λ€λ©΄ μλ‘μ΄ νΈλμμ μμ μλ‘μ΄ μ€λ λλ‘ κ΄λ¦¬νκ³ μ νλ €λ©΄ μ΄λ»κ² ν΄μΌν κΉμ?
κ°λ¨ν λ°©λ²μΌλ‘λ λΉλκΈ°μΈ @Async μ΄λ Έν μ΄μ μ μΆκ°νλ©΄ λκΈ΄νλ κΆμ₯νλ λ°©λ²μ μλ λ― ν©λλ€.
μμ μμλ νΈλμμ μ μλ‘ μμ±νλ(REQUIRES_NEW)λ₯Ό μ€μ ν InnerService ν΄λμ€μ λ©μλμ μ€μ νλ©΄ λ©λλ€.
1 2 3 4 5 6 7 8 9 | @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { System.out.println("========================================="); System.out.println(Thread.currentThread().getId() + ", " + Thread.currentThread().getName()); itemRepository.addCount("item", 5); throw new RuntimeException(); } | cs |
μμ κ°μ΄ @Async μ΄λ Έν μ΄μ μ μ€μ νκ³ ν μ€νΈν΄λ³΄λ©΄ InnerServiceμμ μμΈκ° λ°μνμ¬ λ‘€λ°±νμ§λ§, OuterServiceμλ μ νκ° λμ§ μκ³ (λ‘€λ°±λμ§μκ³ ) μ€νλλ κ²μ νμΈν μ μμ΅λλ€.
Thread per connection vs one thread for all connections
μ€νλ§μ΄ μ 곡νλ νΈλμμ μΆμν κ³μΈ΅κ΅¬μ‘°λ μλμ κ°μλ°μ, PlatformTransactionManager μΈν°νμ΄μ€λ₯Ό ꡬννλ λ°©μμΌλ‘ JtaTransactionManager, JpaTransactionManager, DataSourceTransactionManager(Jdbc), HibernateTransactionManager λ±μ ꡬν체λ€μ΄ μ‘΄μ¬ν©λλ€.
μ€νλ§μ κ²½μ° λ©ν°μ€λ λ κΈ°λ°μΌλ‘ μ€νλ§ νΈλμμ μ νλμ μ€λ λλΉ μ¬λ¬ 컀λ₯μ μ μ¬μ©νλλ°μ, Jdbc κΈ°λ°μ νΈλμμ μ μ²λ¦¬ν λ μ¬μ©νλ DataSourceTransactionManager ν΄λμ€μ λ¬Έμμμ νμΈν μ μμ΅λλ€.
Binds a JDBC Connection from the specified DataSource to the current thread, potentially allowing for one thread-bound Connection per DataSource.
DataSourceλ‘ λΆν° νμ¬ μ€λ λμ JDBC Connectionμ λ°μΈλ©νμ¬ μ μ¬μ μΌλ‘ DataSource λΉ νλμ μ€λ λ μ°κ²°ν©λλ€.
μ¦, νλμ μ€λ λλΉ μ¬λ¬ 컀λ₯μ (μ¬κΈ°μ μ΅μ΄ 컀λ₯μ νμΈ DataSourceμ μ€μ λ 컀λ₯μ λ§νΌ, μ€νλ§λΆνΈ 2.0 μ΄μμμ HikariCP)μ λ°μΈλ©νμ¬ μ¬μ©νλ λ― ν©λλ€.
μλ μ€μ μ ν΅ν΄ 컀λ₯μ νμ μνλ₯Ό νμΈν μ μμ΅λλ€.
1 2 3 4 5 6 7 8 9 10 11 | -- application.yml logging: level: com.zaxxer.hikari: TRACE com.zaxxer.hikari.HikariConfig: DEBUG -- application.properties logging.level.com.zaxxer.hikari=TRACE logging.level.com.zaxxer.hikari.HikariConfig=DEBUG | cs |
μ μ¬μ§μμ μ΄ 10κ°μ 컀λ₯μ μ νμ μΆκ°λ₯Ό ν΄λλλ°, μ΄λ HikariCPμ default κ° μ λλ€.
μΆκ°μ μΌλ‘ λ€λ₯Έ λν΄νΈ κ°λ€μ΄ κΆκΈνλ©΄ HikariCPλ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ.
why one thread for all connections?
κ·ΈλΌ μ νλμ μ€λ λμ μ¬λ¬ 컀λ₯μ μ μ¬μ©ν κΉμ? μ΄μ°λ³΄λ©΄ λΉμ°ν μ§λ¬ΈμΌ μ μμ§λ§, μ무λλ 컀λ₯μ λΉ μ€λ λλ₯Ό μμ±νλ κ²μ λ§€μ° λ§€μ° λΉν¨μ¨μ μΌν λ°μ, μλ₯Ό λ€μ΄ 컀λ₯μ μ΄ 10,000μ΄λΌλ©΄ μ€λ λλ 10,000κ°κ° λκΈ°λλ¬Έμ λ§€μ° λ§μ μ€λ²ν€λκ° μμ λ― ν©λλ€.
μ΄μ κ΄λ ¨νμ¬ stackoverflowμ λ΅λ³μ΄ λμμλλ°μ, μμ½νμλ©΄ λ€μκ³Ό κ°μ΅λλ€.
- Stack memory μ μ½ - Non-blocking IO saves the stack memory
- λΆνκ° λμ λ Context Switching λ° μ»€λ μ ν κ°μ
νλμ μ€λ λμ μ¬λ¬ 컀λ₯μ μ μ¬μ©νλ μ΄μ λ μ¬μ€μ Stack memoryλ₯Ό μ μ½νκ³ μ νλ μ΄μ κ° 99%μ λλΌκ³ ν©λλ€.
μ΄λ₯Ό νλ‘μΈμ€ λ° μ€λ λμ κ°λ μ λΉλμ΄λ³΄λ©΄.. μ€λ λλ νλ‘μΈμ€ λ΄μμ Stackλ§ λ°λ‘ ν λΉμλ°κ³ κ·Έ μΈμ μμμ 곡μ κ° κ°λ₯ν μμμΈλ° μ΄λ‘ μΈν΄ μ€λ λκ° λμ΄λ μλ‘ κ³΅μ κ° λΆκ°λ₯ν Stack λ©λͺ¨λ¦¬κ° κ³μν΄μ μ¦κ°νκΈ° λλ¬Έμ΄ μλκΉ μκ°μν©λλ€.
References
- https://truehong.tistory.com/140
- https://stackoverflow.com/questions/48019034/thread-per-connection-vs-one-thread-for-all-connections-in-java
- https://stackoverflow.com/questions/39933929/why-thead-per-multiple-connections-model-is-considered-better-than-thread-per-co
- https://stackoverflow.com/questions/9267815/does-the-spring-transaction-manager-bind-a-connection-to-a-thread
- https://stackoverflow.com/questions/63104436/what-is-the-meaning-of-potentially-allowing-for-one-thread-bound-connection-per
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/DataSourceTransactionManager.html
'Spring' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
JUnit5 assertThat vs assertAll vs assertSoftly (3) | 2022.09.17 |
---|---|
Spring Boot profiles μ€μ νκΈ° (1) | 2022.09.05 |
Spring Data MongoDB Array field $elemMatch(MongoRepository Custom) (2) | 2022.08.20 |
Spring Boot SQL μ€μ (hibernate, logging) (4) | 2022.03.12 |
Spring AOP - (2) AOP κ°λ λ° μ€μ΅ (0) | 2021.09.24 |
λκΈ