이 글은 미디엄 게시글 <Good practices for high-performance and scalable Node.js applications Part 2/3 >(https://medium.com/iquii/good-practices-for-high-performance-and-scalable-node-js-applications-part-2-3-2a68f875ce79)를 번역한 게시글입니다.
Node.js 어플리케이션을 스케일링 위해선 Node.js가 스케일링할 준비가 되어있어야 한다. 프로세스를 스케일업하기 전에 미리 고민하면 좋은 부분을 이야기해보자.
Decouple application instances from DB
인프라에 대해 먼저 이야기해보자.
여러 호스트에서 어플리케이션을 스케일링하고 싶다면, 당연하게도 DB는 다른 머신에 둬야 한다. 그래야 어플리케이션 머신들을 자유롭게 복제할 수 있다.
어플리케이션과 DB를 같은 머신에 두는 것은 비용이 저렴해서 개발 환경에서는 할만하지만, 프로덕션 환경에서는 부적절한 방식이다. DB와 어플리케이션은 따로 둬야 한다. Redis와 같은 인메모리 데이터베이스도 동일하다.
Be stateless
stateless한 상태를 유지할 것.
어플리케이션의 인스턴스들을 복제할 경우, 각 프로세스는 개별 메모리 공간을 가진다. 이는 만약 단일 머신을 사용하는데 우리가 메모리에 글로벌 변수나 또는 세션에 값을 할당할 경우, 찾기가 힘들어질 수 있다. 왜냐하면 다음 요청에서 밸런스가 다른 프로세스로 연결해주기 때문이다.
런타임 동안 바뀔 가능성이 있는 세팅이나 config들의 경우, 외부 DB에 저장해야 한다. 그래야 다른 모든 프로세스들도 접근할 수 있다.
Stateless authentication with JWT
JWT 쓸 때 stateless하게 쓸 것
stateless한 어플리케이션을 개발할 때 인증은 고려되는 주 토픽이다. 만약 메모리에 세션들을 저장할 경우, 싱글 프로세스에 scoped되기 때문에 문제가 생긴다. 확장 지향을 가진 어플리케이션은 세션 저장 방식을 쓰면 안된다.
만약 이걸 구현하고 싶다면, 네트워크 로드밸런서에게 같은 유저는 항상 같은 머신으로 리다이렉트하도록 설정해주면 된다. 불편하다.
이를 해결하고 싶다면 RAM 대신 DB에 영구저장하면 된다. 그러나 매번 요청에 대해 세션 데이터를 DB에서 체크한다면, 매 API 호출에 대해 disk I/O가 발생한다는 것이고, 이는 성능 관점에서 전혀 좋은 상황이 아니다.
(만약 당신의 프레임워크가 지원한다면) 더 좋고 더 빠른 해결책은 바로 Redis와 같은 인메모리 DB에 저장하는 것이다. Redis 인스턴스는 대개 DB와 같이 어플리케이션 인스턴스의 외부에 있지만, 인메모리 방식이기 때문에 훨씬 빨리 작동한다. 따라서 RAM 내 세션을 저장하는 방식은 메모리르 더 많이 소모할 것이다.
만약 stateless한 인증에 더 효율적인 방식을 원한다면 JWT(Json Web Tokens) 써라. JWT가 작동되는 방식은 심플하다. 유저가 로그인할 때, 서버가 base64 인코딩 된 JSON 객체에 각종 정보를 담아 토큰으로 발행해준다. 이 토큰에는 payload, signature 등이 들어있다. payload는 유저를 authenticate하거나 authorize하는데 사용되는 데이터가 담겨있다. 예를 들어 userId나 ACL role 등 말이다. 그렇게 생성된 토큰은 유저에게 다시 보내지고, 이후 유저가 매번 API 요청할 때마다 인증 목적으로 사용된다.
서버가 들어온 요청을 처리할 때, 토큰의 payload를 가져온 뒤, 자신들이 가지고 있던 비밀키를 사용해서 signature을 다시 만든다. 만약 서버가 새로 만든 signature와 유저가 제출한 signature, 2개가 매칭된다면, payload는 인정되고 유저 인증이 완료된다.
JWT는 어떤 형태로 암호화된 것이 아니다. payload는 단지 base64로 인코딩된 단순한 텍스트에 불과하기에 중요한 정보를 넣으면 안된다. 따라서 만약 어떤 정보를 숨겨서 보내고 싶다면, SSL을 사용해야 한다.
인증 프로세스가 진행되는 동안, 서버는 세션 데이터를 찾기 위해 리소스를 쓰지 않아도 된다. 그래서 다른 프로세스 또는 머신에서 발급받은 토큰이라도 상관없이 사용할 수 있다. RAM에 데이터가 저장되지 않고, 리소스 소모가 큰 Storage I/O도 수행하지 않는다는 점에서 JWT는 스케일 업에 매우 적절한 기술이다.
Storage on S3
여러 머신들을 사용하는 경우, 유저가 생성한 asset들을 파일 시스템에 바로 저장할 수 없다. 왜냐하면 그 서버의 로컬인 특정 프로세스에 파일이 종속되기 때문이다.
이에 대한 해결책은 모든 컨텐츠를 외부 서비스에 저장하는 방법인데, 그 예시로 Amazon S3가 있다. 당신이 DB에 저장할건 컨텐츠가 저장된 URL 절대경로 뿐이다.
S3를 사용하면 각 머신/프로세스들은 같은 방식으로 리소스에 접근할 수 있게 된다.
보통 이를 구현할 때 공식 AWS sdk을 쓴다. Node.js 어플리케이션 내에서 바로 위 서비스를 적용할 수 있게 된다. S3는 저렴하고 멀티 프로세스가 아닌 어플리케이션의 경우 이 방식이 꽤 유용하다.
Properly configure WebSockets
만약 실시간 소통을 위한 웹소켓을 사용한다면, 메시지들을 다른 노드들에게 정확히 브로드캐스팅하기 위해서 백엔드 인스턴스들을 연결할 필요가 있다.
Socket.io 라이브러리는 이러한 문제를 해결하기 위해 socket.io-redis라는 어댑터를 제공한다. 이를 사용하면 Redis pub-sub 기능을 활용하여 서버 인스턴스들을 연결해준다.
멀티 노드 socket.io 환경을 사용하기 위해선 프로토콜을 websockets으로 바꿔야 한다. Because long-polling needs sticky-sessions to work.
'Research > Node.js' 카테고리의 다른 글
Node.js 성능 향상 전략에 대한 고민 (0) | 2023.03.01 |
---|---|
nodejs_번역_고성능 및 확장 가능한 Node.js 어플리케이션에 대해 - 3 (0) | 2023.03.01 |
nodejs_번역_고성능 및 확장 가능한 Node.js 어플리케이션에 대해 - 1 (0) | 2023.02.28 |
Node.js stress test tool (0) | 2023.02.06 |
Node.js API 서버 성능 개선기 요약 (0) | 2023.02.06 |
댓글