[나만무] Socket.IO와 Redis로 만드는 대규모 동시 접속 서비스

맹쥐·2025년 6월 26일
7

소켓이 무엇이고, 왜 필요한가?

일반적인 웹사이트는 보통 HTTP(Hypertext Transfer Protocol)라는 통신 방식을 사용한다.
이건 마치 택배 주문과 같다.

여러분의 브라우저(클라이언트)가 서버에 "이 페이지 주세요!"라고 요청하면, 서버가 페이지를 보내주는 식이다.
요청이 있을 때만 통신하고, 한 번 보내면 끝나는 ..
이를 단발성 통신이라고 한다.

하지만, 실시간으로 여러 명이 동시에 작업하는 경우에는 이런 방식이 적합하지 않다.

한 사이트에 여러명이 접속해서 실시간으로 페이지를 변경하는 경우,
내가 " 이 페이지 주세요 ! " 라고 요청하지 않아도
변경사항이 내 화면에 반영돼야하는 경우가 있다.

이때 필요한 것이 바로 소켓(Socket),
특히 웹소켓(WebSocket)이다.

웹소켓은 브라우저와 서버 사이에 지속적인 양방향 통신 채널을 열어준다.
마치 전화 통화처럼, 한 번 연결되면 서로 끊임없이 대화할 수 있는 길을 만들어주는 거다.


웹소켓을 사용하면?

(우리 프로젝트에 적용해보자.)

  • 한 사용자가 픽셀을 그리면, 그 정보가 서버로 즉시 전달된다.
  • 서버는 이 정보를 받아 연결된 다른 모든 사용자들에게 즉시 "누가 어디에 무슨 색 픽셀을 그렸어!"라고 알려줄 수 있다.
  • 이 모든 과정이 거의 실시간으로 이루어져서, 모든 사용자가 동일한 캔버스를 동시에 그리고 있는 것처럼 보이게 된다.

Socket.IO란 ?

웹소켓은 표준 기술이지만, 실제로 구현하려면 다소 복잡한 부분이 있다.
연결이 끊겼을 때 다시 연결하거나, 오래된 브라우저에서 웹소켓을 지원하지 않을 경우를 대비하는 등 고려할 점이 많다.

Socket.IO는 이런 복잡한 웹소켓 통신을 아주 쉽게 만들어주는 라이브러리이다.

Socket.IO는 다음과 같은 장점들을 제공한다.

  • 안정적인 연결: 네트워크 상황이 좋지 않거나 일시적으로 연결이 끊어져도 자동으로 재연결을 시도해준다.
  • 이벤트 기반 통신: '픽셀 그리기'와 같은 특정 동작을 '이벤트'로 정의하고 데이터를 함께 주고받을 수 있어서 코드를 깔끔하게 관리할 수 있다.
  • 다양한 환경 지원: 웹소켓이 지원되지 않는 환경에서도 유사한 실시간 통신 방식을 자동으로 사용해준다 (폴백 메커니즘).

흐름을 살펴보자.

전체적인 흐름은 다음과 같다.

  1. 서버(백엔드)는 Socket.IO 서버를 실행하고, 클라이언트 연결을 기다린다.

  1. 클라이언트(프론트엔드)는 Socket.IO 클라이언트로 서버에 연결한다.
  1. 특정 이벤트가 발생하면 데이터를 주고받는다.
  1. 서버는 받은 이벤트를 다른 클라이언트에게 브로드캐스트 한다.

그런데, 서버가 여러 대가 되면 어떻게 될까?

우리 프로젝트처럼 다수의 사용자가 동시에 접속하는 서비스는 단일 서버로는 감당하기 어렵기 때문에,
운영 환경에서는 보통 여러 대의 서버 인스턴스를 띄우고,
그 위에 로드 밸런서를 두어 트래픽을 분산시킨다.

하지만 이런 구조에서는 문제가 생긴다.
예를 들어 A 사용자가 서버 1에 연결되어 있고,
B 사용자가 서버 2에 연결되어 있다고 하자.

A가 픽셀을 그렸을 때, 서버 1은 이 정보를 받지만,
서버 2에 연결된 B는 그 소식을 들을 방법이 없다!
서버끼리는 기본적으로 서로 대화를 하지 않기 때문이다.

이럴 때 필요한 것이 바로 Redis 기반의 Socket.IO 어댑터다.

모든 서버가 공통된 Redis 서버에 연결되면,
서버 간에도 이벤트 메시지를 주고받을 수 있어
마치 하나의 서버처럼 모든 사용자에게 실시간 브로드캐스트가 가능해진다.


어떻게 적용하나요?

Socket.IO는 Redis 어댑터를 아주 간단하게 적용할 수 있게 되어 있다.

"Socket.IO는 Redis 어댑터와의 연동이 매우 쉽다.
그리고 실시간 멀티 서버 환경에서 Socket.IO를 쓰려면 어댑터가 꼭 필요하다.
그래서 Redis를 선택한 결정적인 이유가 ‘Socket.IO와의 궁합’ 때문이기도 하다.

1. 패키지 설치

npm install redis @socket.io/redis-adapter

2. 서버 코드에 어댑터 등록

import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";

const io = new Server(httpServer);

// Redis 연결 (ElastiCache 등 사용 가능)
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();

await Promise.all([pub.connect(), sub.connect()]);
io.adapter(createAdapter(pub, sub));  // 🔗 이 한 줄이 핵심

io.on("connection", (socket) => {
  socket.on("drawPixel", (data) => {
    socket.broadcast.emit("drawPixel", data);  // 기존 로직 그대로 사용 가능!
  });
});

새로운 인스턴스가 Auto Scaling으로 자동 생성되어도
이 Redis 어댑터 설정만 있으면 바로 참여하게 된다.
즉, 자동 확장되더라도 모든 서버가 실시간 통신을 공유할 수 있게 되는 것이다.

profile
이유민

0개의 댓글