NodeJS+Express 동시 리퀘스트가 들어올 시 모듈 사용에 대한 질문입니다.

조회수 1446회

안녕하세요. 현재 AWS 람다를 이용하여 Express 앱을 구동 중입니다. Single Thread 기반인 NodeJS로 하다보니 궁금한 것들이 생겼는데요.

// dateUtil.js
"use strict";
var DateUtil = (function () {
    function DateUtil() {
        console.log('created');
    }
    DateUtil.prototype.dateToTime = function (dateObject) {
        var date = dateObject.getTime();
        return date;
    };
    return DateUtil;
}());
exports.default = new DateUtil();
// app.js
var express = require('express');
var dateUtil = require('./dateUtil')
var app = express();
var server = app.listen(3000, function(){
    console.log("Express server has started on port 3000")
})

실제 앱을 서버로 올리고 유저 A,B 가 접속 했을 시 dateUtil는 서로 공유가 되더군요.(created 한번 찍힘)

다른 언어로 했을 시엔 멀티 스레딩으로 서버가 돌다보니 유저 간의 메모리 공유는 신경 안썼는데말이죠. 여기서 궁금한 점이 생겼습니다.

만약 위 dateUtil 처럼 import 로 모듈 function을 가져올 시 route 내에서 여러 사용자가 해당 모듈을 동시에 사용할 경우 function 내부에 있는 데이터도 공유가 될 가능성이 있을까요? (위 코드는 단순하지만 실제 변수 및 로직이 많을 경우)

아니면 실제 function 호출 시 유저 별로 각 스택 메모리가 할당되는 식이라 그럴 염려는 없는 걸까요.

실제 sleep 걸고 여러 시도를 해봤지만 공유가 되진 않았습니다만, 이론 적으로 확실히 찾질 못해서 질문 남깁니다.

  • (•́ ✖ •̀)
    알 수 없는 사용자

2 답변

  • 미리 양해를 구합니다.

    제가 질문의 요지를 잘 파악한건지는 읽어보고 판단해주세요.

    원하시는 내용이 아닐 수도 있습니다.


    작성 하신 코드에 몇가지 추가해 보죠.

    // 추가
    var count = 0;
    
    var DateUtil = (function () {
        function DateUtil() {
            console.log('created');
        }
        DateUtil.prototype.dateToTime = function (dateObject) {
            var date = dateObject.getTime();
            return date;
        };
        // 추가
        DateUtil.prototype.getCount = function () {
            return count++;
        };
        return DateUtil;
    }());
    
    // 편의를 위해 변경
    exports = new DateUtil();
    

    그리고 app.js 에서 불러봅니다.

    for (var i = 0; i < 100; i++) {
        var dateUtil = require('./dateUtil');
        console.log(dataUtil.getCount());
    }
    

    어떻게 될까요?

    0 ~ 99 까지 찍힙니다. DataUtile 모듈 스코프에 선언된 변수값을 공유하고 있는거죠.

    그래서

    var DateUtil = (function () {
        // 여기로 위치 변경
        var count = 0;
        function DateUtil() {
            console.log('created');
        }
        DateUtil.prototype.dateToTime = function (dateObject) {
            var date = dateObject.getTime();
            return date;
        };
        DateUtil.prototype.getCount = function () {
            return count++;
        };
        return DateUtil;
    }());
    
    exports = new DateUtil();
    

    이렇게 바꾸면 어떻게 될까요?

    그래도 0부터 99까지 찍힐거에요. 이미 위 모듈은 인스턴스를 리턴했고 사용하는 app.js 에서는 정확히 처음 만든 그 인스턴스를 받아옵니다. 매번 require 할때마다 새로운 인스턴스 뽑아내는게 아니구요. 결국 하나의 count를 쓰고 있는거에요.

    결국 사용자 접속마다 다른 인스턴스를 줘야 할 상황이라면

    매 접속마다 인스턴스화를 할 수 있도록 클래스를 exports하고 app.js 와 같은 컨트롤러에서 직접 인스턴스화 하여 사용해 주는게 맞다고 봅니다.

    function DateUtil() {
        console.log('created');
    }
    DateUtil.prototype.dateToTime = function (dateObject) {
        var date = dateObject.getTime();
        return date;
    };
    
    exports = DateUtil;
    
    //app.js
    
    var DateUtil = require('./DateUtil');
    
    function blahblah (user) {
        //...
        var du = new DateUtil();
        user.updateDate = du.dateToTime(obj);
        //...
    }
    

    뭐 대충 이런식으로요?

    위의 내용은 그냥 요약하면 각 사용자는 특정 모듈에서 선언된 동일한 리소스를 바라볼 수 있다는 것을 보인겁니다.

    위와는 별개로 결론을 말씀드리면 멀티쓰레딩과 공유자원으로 인하여 생기는 데이터 손실과 관련된건 걱정할 필요가 없을거 같은데요.

    말씀하신대로 어짜피 싱글스레드라서 콜 스택에 쌓인 순으로 진행되므로 위의 count 같은 자원에 동시 접근하여 r/w 할때 생기는 문제 없을거에요.

  • aws lambda 는 컨테이너로 구동을 합니다.

    어느정도의 기간동안(약5분) 요청이 없으면 해당 컨테이너는 종료가 됩니다.

    재요청이 있을때(cold start) 응답이 늦은 경우는 그 때문입니다. 즉 stateless 하기 때문에 상태등을 저장하는 것은 하면 안됩니다만 여러 요청시에 같은 건테이너에 요청을 했다면 저장된 데이터를 받을겁니다.(즉 공유가 됨)

    된다는 것인지 안된다는 것이지 혼동이 올 수 있을 듯 합니다.

    aws lambda 는 오토스케일링등이 되는 완전히 관리되는 서비스입니다. 질문자가 개발한 lambda 에 트래픽이 몰리면 오토스케일을 진행하고 그 새로 생성된 컨테이너의 데이터는 당연히 초기화가 된 상태입니다. 컨테이너끼리 상태공유가 되지 않습니다.

    정리하면 lambda 를 통해 어떤 데이터나 상태를 공유하면 안됩니다.

    상태나 세션, 변경이 필요한 데이터의 공유는 dynamodb, rds, elasticache, s3 같은 외부 저장서비스를 이용해야 합니다.

    • 답변 감사합니다. 제가 질문의 요지가 좀 어긋났던 것 같습니다. 위와 같은 상태일 경우 function 으로 모듈을 제작했을 시 import 해서 라우트 내에서 쓸 경우 함수 스코프 내부도 공유가 되어 데이터 오염이 일어날 수 있는지 질문이었습니다. 일반적으론 function이 실행 될 시 지역 변수로 메모리가 새로 할당되어 유저별로 function 이 독립적으로 실행되는 것으로 알고 있는데 node는 다른 점이 있는가 해서 올린 질문입니다! 어찌보면 nodejs 라기보단 memory에 관한 자료 구조가 nodejs 에선 다른 점이 있는가? 일 수도 있겠네요 알 수 없는 사용자 2019.5.14 13:05
    • 간단하게 생각하면 됩니다. lambda 는 독립된 컨테이너 구조로 동작합니다. 이 컨테이너는 자동으로 상황에 따라 2~3개도 될 수가 있습니다. 또한 5분동안 요청이 없다면 중지됩니다. 그 후 재 요청이 오면 컨테이너를 다시 부팅하여 서비스 합니다. 질문자는 단순히 공유문제만 언급을 하고 있는데 당연히 컨테이너가 부팅된 상태서 같은 컨테이너에 여러 요청이 오면 공유가 되는 겁니다. 그런데 스케일 아웃이 되어 컨테이너가 하나 더 생성이 되었다면 그 컨테이너에서는 공유가 안됩니다. 공유가 되는지 여부를 떠나 이해를 하고 사용해야 합니다. 간단히 정리하면 lambda 는 stateless 하니 공유를 해서 사용하면 안되고 물론 컨테이너가 warm 상태라면 되지만 5분동안 요청이 없으면 중지이므로 warm 상태 보장이 안됩니다. 정영훈 2019.5.14 14:37

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)