1. 의문이 든건 아래의 코드에서부터.
router.get('/', async (req, res) => {
const datas = await books.find({})
res.json(datas)
})
books.find({})를 통해 books에서 모든 데이터를 가져오는걸 "기다렸다가" json으로 보내라는 코드이다
기다렸다가...?
node.js는 싱글스레드 기반이니까 blocking인가보다.
2. 근데 js는 싱글 스레드 + 논블락킹이라며?
별 생각 없이 넘어가려했는데.... 흔들리지 않는 개념이 있었다. "js는 싱글스레드 + 논블락킹"이라는 것.
근데 저건 blocking아닌가?
찾다보니 하나의 글을 읽을 수 있었다.
https://medium.com/geekculture/does-async-await-block-javascript-main-thread-c07db9c48c3e
중요한 부분만 가져오자면
요약하자면
"Hello world"가 먼저 출력되고 "After all promise is executed"가 나중에 출력된다는 것.
이는 await이 메인 스레드를 차단하지 않음을 보여준다는 것.
내 공부가 부족한게 뻔하다. 본질로 돌아가자.
3. 결국 async/await는 Promise를 쉽게 쓰기 위함이 아니던가?
바로 내 문제점이 나왔다.
나는 Promise를 잘 모른다....
4. Promise 복습
- 프로미스란?
- 자바스크립트 비동기 처리에 사용되는 객체
- 콜백지옥을 해결하기 위해 도입
- 비동기 작업이 완료되면, 콜백으로 해결하던 다음 작업을 then으로 처리
- 프로미스 객체
- resolve 인자 : 요청이 성공할 경우 호출할 메서드
- reject : 요청이 실패할 경우 호출할 메서드
- 프로미스 처리
- .then() : 프로미스 객체 내부에서 resolve를 호출할 경우, 이 때 호출된 resolve의 매개변수가 then의 인자로 들어가서 사용
- .catch() : 실패해서 reject가 불릴 경우, catch로 인자를 받아 에러처리
- 프로미스 상태
- pending : 처리 중
- fulfilled : 처리 완료
- ejected : 실패
- 프로미스 체이닝
- .then을 연속적으로 사용
- 이전 then 에서 return된 값을 그 다음 then의 인자로 사용
- 여러개의 비동기 작업을 순차적으로 수행할 수 있음
5. 그렇다면 내가 async/await을 blocking으로 착각했던 이유는 뭘까?
결국 await은 프로미스 체이닝을 구현한 것이고, 맨 위에 있는 코드의 res.json(datas)의 경우 실행이 "block" 된게 아니라 await의 리턴을 "기다리는"것이다. 즉 프로미스에서의 then으로 해석하는 것이 맞다. 하지만 프로미스와 달리 then이 써있지 않기 때문에 오해했던 것.
만약 그 뒤에 오는 코드가 await과 관련이 없었다면 바로 진행이 됐을것이다.
6. 남아있는 의문, 그러면 싱글스레드인데 find({})의 처리는 누가하나?
내가 알고있는 이벤트루프의 아키텍쳐가 딱 위의 그림이다.
근데 내가 모르는 것이 있었다.
7. 백그라운드 스레드 풀의 존재
https://snyk.io/blog/node-js-multithreading-worker-threads-pros-cons/
위 글을 참고했다.
나에게 필요한 부분을 정리하면 이렇다.
- 백 그라운드 스레드 풀에서는 워커 스레드가 동작한다.
- 주 프로세스는 싱글 스레드이며 멀티 스레드라면 "동시에 동일 한 상태 (프로세스의 메모리 등) "를 공유해야하지만 워커 스레드의 경우 node의 런타임과 격리된 인스턴스를 생성해서 동작하며, 메인 프로세스와는 메시지 패싱을 통해 데이터를 주고 받는다.
8. 추가적인 궁금증이 생겼다. 왜 굳이 이렇게 하나?
그냥 멀티 스레드를 쓰지. 굳이 왜?
이에 대해서는 멘토(코치)님께서 답이 될만한 좋은 글을 하나 보여주셨다.
"10,000개의 요청이 들어왔을때 어떻게 처리할 것인가?" 에 대한 글이다. (해석해보려다가 너무 길어서 중간부터 gpt 돌렸다 ㅠㅠ)
짧게 요약하자면 이렇다
- 트래픽이 늘어감에 따라 많은 오버헤드 발생 및 처리 능력 필요성 대두
- 웹 서버의 처리능력에는 다양한 요소 개입 (하드웨어 사양, 서버 아키텍쳐 종류 등)
- 여러가지 해결책 존재
- 이벤트 기반 프로그래밍
- IO 작업 다중화
- 스레드 풀링
- 네트워크, OS 개선
- 분산처리 (현대의 방식)
내 질문에 대한 답변은 "이벤트 기반 프로그래밍"에서 찾을 수 있었다.
이벤트 기반 프로그래밍에서는 각 연결에 대해 스레드를 유지하는 대신, 이벤트가 발생할 때 까지 "대기"상태에 있으며 이벤트가 발생할 경우 적절히 대응한 다는 것.
그리고 그러한 방식을 사용하는 "싱글 스레드"로 다루는 Nginx의 경우 더 효율적인 자원 사용을 가져온다는 것.
또한 스레드 풀은 스레드를 생성과 소멸하는 대신 재사용 해서 오버헤드를 감소시킨다는 것.
즉, 위의 내용을 잘 버무리면 "여러 개의 스레드를 운영하는 것은 오버헤드를 초래하며, 이를 잘 관리하는 것이 곧 성능을 향상시키는 것" 이라는 결론을 얻을 수 있다. 또한 이는 트래픽이 과부하될 때 더 두드러지는 장점으로 볼 수있다.
물론 현재의 서버컴퓨터는 분명 많은 코어로 높은 멀티 스레딩 성능을 가질 것이기 때문에, 꼭 어떤것이 정답이라고 볼 수는 없지만. 싱글 스레드 기반으로 이벤트 처리를 잘 하는것은 분명히 메리트가 있음은 확실하다.
node.js와 nginx의 점유율이 그 근거일테고.
9. 마치며
어렵다. 갈길이 멀다.
쩝...