본문 바로가기
Storify

STORIFY (0220) - 소켓 시간 설정, 메모리 관리

by Peter.JH 2024. 2. 20.
728x90

문제:

소켓 연결은 되었지만 어디선가 계속 handleDisconnect를 호출해 연결이 끊겼다. 이런 현상이 발생하는 이유는 여러 가지가 있을 수 있다.

 

1. 클라이언트의 네트워크 문제 : 사용자의 인터넷 연결이 불안정하거나 일시적으로 끊어졌을 때, 웹소켓 연결도 함께 끊어질 수 있다.

 

2. 서버의 네트워크 문제 : 서버 측의 네트워크에 문제가 생겼을 때도 웹소켓 연결이 끊어질 수 있다. 

 

3. 서버나 클라이언트의 자원 부족 : 서버나 클라이언트의 메모리 등의 자원이 부족하면, 웹소켓 연결을 유지하는 데 필요한 자원을 확보하지 못해 연결이 끊어질 수 있다.

 

4. 서버의 설정 문제 : 서버에서 웹소켓 연결을 일정 시간동안 유지한 후 자동으로 끊는 설정이 있을 수 있다. 

 

5. 클라이언트의 동작 : 클라이언트 측에서 웹소켓 연결을 직접 끊은 경우에도 `handleDisconnect` 함수가 호출된다.

 

 

과정:

 위 이유 중에 클라이언트, 서버 네트워크에는 문제가 없었다. 자원문제도 메모리를 확인해본 결과 메모리 사용량이 많지 않아 문제가 되지 않았다. AWS EC2 에서 소켓 시간 설정하는게 맞는지 찾아봤을 때   AWS EC2 서버 자체에서 웹소켓의 연결 시간을 직접 설정하는 것은 힘들다고 판단했다. 웹소켓의 연결 시간이나 유휴 시간은 주로 웹소켓 서버의 구현이나 사용하는 라이브러리/프레임워크에서 결정된다.

 

하지만, AWS의 로드 밸런서나 보안 그룹 설정 등에서 타임아웃을 설정하는 것은 가능하다. 이 설정은 네트워크 레벨에서 연결이 일정 시간동안 활동이 없을 경우 연결을 끊는 역할을 한다. 이러한 설정은 웹소켓 연결의 유휴 시간을 강제로 제한하는 효과가 있지만, 웹소켓 연결이 활성 상태를 유지하도록 하는 '핑-퐁' 메커니즘을 방해할 수 있으므로 주의가 필요하다. 따라서, 웹소켓의 연결 시간을 설정하려면 주로 웹소켓 서버의 구현에서 처리해야 한다.

 

결과:

NestJS에서는 웹소켓 연결을 직접 관리하는 기능을 제공하지 않기 때문에, 연결 유지 시간을 설정하는 것은 어렵다. 하지만, 직접 구현해서 사용하는 것은 가능하다. 각 클라이언트 소켓에 대해 타이머를 설정하고, 일정 시간동안 활동이 없으면 연결을 끊는 방식으로 구현할 수 있다. 

 

interface ExtendedSocket extends Socket {
  timer?: NodeJS.Timeout;
}

async handleConnection(client: ExtendedSocket) {
  // ... 기존 코드 ...

  client.timer = setTimeout(() => {
    client.disconnect();
  }, 10 * 60 * 1000);
}

async handleDisconnect(client: ExtendedSocket) {
  // ... 기존 코드 ...
}

@SubscribeMessage('readNotification')
async handleReadNotification(
  client: ExtendedSocket,
  @MessageBody() data: { notiId: string },
): Promise<void> {
  // ... 기존 코드 ...

  client.timer = setTimeout(() => {
    client.disconnect();
  }, 10 * 60 * 1000);
}

 

 

코드는 모든 클라이언트 소켓에 대해 별도의 타이머를 설정하고, 메시지를 받을 때마다 타이머를 리셋합니다. 이렇게 하면, 일정 시간동안 활동이 없는 클라이언트의 연결을 자동으로 끊을 수 있다. 단, 이 방식은 메모리를 추가로 사용하므로 많은 수의 클라이언트를 처리해야 하는 경우에는 적합하지 않을 수 있다. 

 

 

1. 서버에서 소켓 연결을 계속 끊어 좋아요를 누르기 전에 소켓에서 나감

- 이 문제는 서버가 disconnect를 요청하며 유저가 웹소켓에서 나가게 되면서 발생했다.

- 해결 방안으로는, 사용자가 웹소켓에 더 오랫동안 연결되도록 서버 측에서 타임아웃 설정을 조정하였다.

 

2. 메모리 관리

- 메모리에 사용자 정보를 저장하면서 메모리 누수 문제가 발생할 가능성이 있다.

- 이를 방지하기 위해 `handleDisconnect` 메서드에서 연결이 끊긴 사용자의 정보를 메모리에서 삭제했다.

 


====

웹소켓을 통해 접속 정보를 서버의 메모리 상에서 직접 관리하는 경우, 특히 연결이 끊어진 클라이언트의 정보를 적시에 제거하지 않을 경우 메모리 누수(Memory Leak) 문제가 발생할 가능성이 있다. 이는 시스템의 안정성과 성능에 부정적인 영향을 미칠 수 있다. 따라서, 클라이언트 연결이 종료될 때(handleDisconnect 이벤트 호출 시) 해당 클라이언트와 관련된 모든 정보를 메모리에서 즉시 삭제하여 메모리 누수를 방지하는 로직을 구현하는 것이 중요하다. 

  async handleDisconnect(client: ExtendedSocket) {
    if (client.timer) {
      clearTimeout(client.timer);
    }
    const clientId = this.users.get(client.userId);
    if (clientId === client.id) {
      this.users.delete(client.userId);
    }
    console.log('Client disconnected: ' + client.id);
  }



this.users.delete(client.userId); 부분은 JavaScript의 Map 객체에서 특정 키(client.userId)와 그에 해당하는 값을 삭제하여, 해당 클라이언트와 관련된 정보를 메모리에서 제거하는 과정이다. Map 객체에서 키와 값을 삭제하는 delete 메서드는 해당 키가 존재하면 그 키와 값을 삭제하고 true를 반환하며, 키가 존재하지 않으면 아무 것도 변경하지 않고 false를 반환한다. 이 과정을 통해 메모리에서 클라이언트 정보를 해제하여 메모리 누수를 방지할 수 있다.

그러나 단순히 Map에서 삭제하는 것만으로는 자동으로 메모리가 해제되는 것은 아니며, JavaScript 엔진의 가비지 컬렉션(Garbage Collection) 프로세스에 의해 메모리에서 실제로 해제된다. 삭제된 객체에 대한 모든 참조가 제거되면, JavaScript 엔진의 가비지 컬렉터가 나중에 메모리를 회수할 수 있다. 따라서 delete를 호출하는 것은 메모리 관리의 첫 단계이며, 이후 가비지 컬렉션이 수행되어야 실제 메모리 해제가 일어난다.

728x90