일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- async
- DB 설계
- @async
- autowired
- restful
- 라이브커머스
- 이커머스 api
- 스프링
- 자바
- 인턴생활
- 네이버
- Spring
- JPA
- api설계
- SpringBoot
- RDB
- AWS
- 멀티쓰레드
- mariaDB
- 서버구축
- db 스키마
- 실검
- 미디어서버
- 이커머스
- Gradle
- API
- 스트리밍서버
- REST API
- wooza
- cmi
- Today
- Total
Polymor!
비동기에 대한 이해( feat. Spring @Async) 본문
지난번 포스팅에서도 한번 작성한 적이 있는 '비동기'라는 주제의 이야기. 아직도 그 끝을 파진 못했지만 공부를 하다 깨달은 점들이 있어서 글을 작성해 봅니다.
*** 결론은 무엇이 비동기,동기 그리고 Blocking, Nonblocking 인지는 어떤 '맥락' 에서인지에 따라 논하는게 맞는 것같다.
그렇지 않으면 상이한 두 개념을 오롯이 이해하기가 만만치않다. 의식의 흐름만 계속되면서 악순환이 마련된다.. 내얘기.....
[비동기 : Asynchronous]
우선 두가지 맥락을 이해하자. 비동기로 '동시성(Concurrency)' 혹은 '병렬성(Parallelism)' 을 구현할 수 있다.
-
동시성(Concurrency) - '논리적인개념' : 쓰레드가 혹은 프로세스가 다수개
동시성은 CPU코어의 시분할 멀티 태스킹으로 이해하면 될 것 같다. 멀티 프로레싱, 멀티 쓰레딩도 포함 시켜도 된다. 단, CPU 싱글코어라고 가정 했을 때 말이다. (CPU코어는 일을하는 주체 이다. CPU는 매 순간 어떤 프로세스이건 딱 1줄의 명령어를 Fetch해서 '처리'하는 점을 기억하자.)이말은 무엇이냐, 아무리 '동시'에 10개의 독립적인 프로세스(메모리 상에 로드 된 프로그램) 이 실행된다 하여도, 그게 '진짜' 동시간대에 실행되는 것이 아니라, 그렇게 보이는 것 뿐이다. 시분할되어 여러 프로세스를 어떤 우선 순위 큐같은 자료구조에 넣어두고, 사실상 '순차적으로 하나씩' 매우 빠르게 실행되어 동시에 실행되는 것 처럼 보이는 것 뿐이다.
자, 그럼 이게 비동기랑 무슨 연관이 있냐?
다시 10개의 프로세스가 실행되고 있다는 이야기로 되돌아가보자. 1번 프로세스가 종료되어야 2번 프로레스를 실행할 수 있고 2번이 끝나야 3번이 시작될 수 있고.. 이러한 맥락의 상황은 '동기'식 방식이자 앞 프로세스가 'Blocking'을 하고있다 라고 표현 할 수 있다. 그렇다면 1번 프로세스가 버퍼링이 걸려 10분동안 끝나지않는다면, 2번~10번 프로세스는 무한 '대기'상태에 있다. 그렇기때문에, 1~10번 프로세스는 서로 완전 독립적으로 ('비동기') 실행되어야만 동시성이 보장된다. Non-blocking과 물론 연관이 있지만, 이 이야기는 다른 특정 상황의 맥락에서 다시 자세히 살펴보자.
-
병렬성(Parallelism) - '물리적인 개념' : CPU 코어 즉 프로세서가 여러개
병렬성은 동시성과 다른 개념으로, '한 시점에 정말 여러개의 프로세스가 실행되는 것'을 생각하면 된다. 즉, 최소 듀얼 코어가 보장되어야 병렬성을 구현할 수 있다.
우리가 통상적으로 다룰 '비동기'는 동시성 맥락이 대부분이라고 생각하면된다. 결국 하나의 자원(CPU)를 어떻게 효율적으로 여러 프로세스 혹은 쓰레드가 사용하면 좋을지에 대한 이야기가 앞으로의 내용들이다. 아니, 사실상 하나의 어플리케이션 내의 멀티 쓰레드를 이야기 하게 될 것이다. 비동기 구현을 위해 Fork 로 프로세스를 새로 생성하면서 발생하는 컨텍스트 스위칭은 하는 것보다 Thread를 생성하는 것이 훨씬 효율적이다. Stack외엔 공유된 메모리 자원을 사용하기 때문에 복사될 데이터의 양이 훨씬 더 적기때문이다.
비동기를 그럼 어떻게 이해하면 좋을까? "A라는 작업과 B라는 작업이 발생 순서에 서로 영향을 주지않는 것"
통상적으로 비동기적 프로그래밍을 구현하기 위해 멀티쓰레드를 기반으로 하게된다고 알고 있다. 그렇다면 '비동기는 무조건 멀티 쓰레드이고 Non-Blocking이네?' 라고 이해하면 될 것인가? 정답은 아니다.
I/O 를 핸들링 하는 대표적인 '비동기' 함수로 알려져 있는 SELECT() 함수를 떠올려보자. (Blocking + Async)
SELECT는 기본적으로 커널에게 다수의 file descriptor들에 대한 감지된 I/O 이벤트 정보를 요청하여 변화를 포착하였을 때 리턴된다. 이 맥락에서 즉 SELECT함수는 Blocking함수라는 것이다. 포착되기 전엔 호출한 곳으로 커널에서 제어권을 넘기지 않는다. 단 여기서 중요한 것은, 일단 감지가 되어 호출한 곳으로 돌아가고 난 후에 다음 로직들이 실행되는 동안에도 파일 디스크립터에 대한 I/O 이벤트 감지 업무는 실행 되고 있다. 여기서 이 맥락이 바로 비동기라는 것이다! 꼭 쓰레드를 생성하지 않아도, 맥락상 비동기가 구현되는 것이다.
참고로 epoll은 select 처럼 계속해서 정보를 넘기지 않기 위해서 관찰 대상인 fd들의 정보를 담은 저장소를 직접 운영체제가 담당한다.
* Spring에서 두가지 맥락의 비동기가 있다. - Controller layer VS Service layer
다음 두 상황을 가정하고 비교해보자.
-
하나의 서버에 10만명이 A라는 CONTROLLER에 request를 보냈을때, 어떻게 처리할 것인가?
먼저 알아야 할 점은, Spring의 Bean은 Singleton-Pattern을 따른다. 싱글톤 패턴은 다수의 요청에도 해당 클래스에 대한 인스턴스는 하나만 생성이 된다. 불필요한 메모리 누수를 방지할 수 있다는 장점이 있다.
Spring bean에 등록된 객체는 싱글톤을 따르기때문에, 하나의 객체만이 Heap영역에 있고 , 클라이언트 Thread 들에서 그 객체를 공유케된다. 즉, 메소드 영역의 메소드만 공유하게 해주는 하나의 인스턴스가 등록되는 것이라 생각하자.
그래서 필연적으로 해당 객체에는 상태(State) field를 가지고 있으면 하나의 공유 자원에 대한 Thread-Safe가 보장이 되지않겠다. 다시 말해, Thread-Safe는 기본적으로 보장되지않는다.
Singleton 패턴을 조금 더 생각해보자. - 어떻게 해야 Thread-Safe하게 구현 할 수 있을 것인가?
Eager Initialization 은 컴파일 타임에 Class Loader 가 정적 바인딩을 통해 인스턴스를 생성하는 것을 의미한다. 이렇게 인스턴스를 생성하지 않고 Runtime에 동적 바인딩으로 객체가 필요할 때, 즉 클라이언트의 요청이 왔을 때 생성하면 synchroized 를 통해 생성해야하는데, 이는 불필요하게 매번 블로킹이 되기때문에 비효율적이다.
public class Singleton {
// Eager Initialization
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
다시 이야기해서 Client들의 요청은 개별 쓰레드로 동시에 처리가 되는 맥락에서 '비동기'적인 처리라고 말할 수 있다.
아~ 그런데 오해하지말자. 100만개의 요청을 위해 100만개를 직접 Thread 생성하고 삭제하는 것은 절대아니다!! Thread는 기본적으로 임계값이 있기 때문에 이것은 Thread Pool 로 JVM이 관리하는 영역으로 문제가 해결이 된다. Thread Pool 을 사용하면 매번 생성, 삭제를 할 필요없이 재사용 가능하다. Thread도 Context-Switching 비용이 부담이 있기때문에 훨씬 효율적이다.
*(번외) Thread Pool 와 DB Connection pool 의 크기, 실행 쓰레드 최대 개수 등은 같을까?
당연히 다르다. 매번 Request 마다 DB를 접속하는 것은 아니기때문에 1:1 매핑일리없다. 참고로 Connection Pool도 싱글톤 패턴이다.
-
A라는 SERVICE 안에서 a라는 작업과 b라는 작업을 서로 순서 무관하게 동시에 진행하고 싶을때 어떻게 할 것인가?
=> 이 맥락에서 비동기 @Async 가 사용되는 것이다.
물론 앞서 이야기했던 기본적인 자바,스프링의 쓰레드 개념과 동일한 개념이지만, 강조하고 싶은것은 '비동기'를 논할 때는 항상 맥락을 우선적으로 파악하는 연습이 중요하다고 생각해서 굳이 분리를 한 것이다.
*본론 : Spring에서의 비동기 @Async 에 대해 이야기해보자
'Web' 카테고리의 다른 글
JPA , hibernate, 그리고 SpringDataApi (0) | 2021.02.24 |
---|---|
[네이버 라이브 쇼핑]스트리밍 서버 분석 1편 (0) | 2021.02.19 |
[AWS] RDB 와 스프링 연동하기 (0) | 2021.02.10 |
[e-commerce] JPA 영속성 전이(feat.상품을 장바구니에 담기) (1) | 2021.02.07 |
[e-commerce] Rest API 설계 디자인 (0) | 2021.02.07 |