개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Redis 파이프라인 — 네트워크 왕복 줄이기

2026년 2월 3일
RedisPipeline성능 최적화네트워크배치

사용자 1000명의 세션을 만료시켜야 합니다. 하나씩 DEL을 1000번 호출하면 네트워크 왕복이 1000번 발생합니다. 파이프라인을 쓰면 명령 1000개를 한 번에 보내고 응답도 한 번에 받습니다.

네트워크 왕복 비용

Redis 명령 하나의 처리 시간은 마이크로초지만, 네트워크 왕복(RTT)은 밀리초입니다.

일반 방식 (1000개 명령):
  RTT = 1ms라면 → 1000 × 1ms = 1000ms = 1초

파이프라인 (1000개 명령):
  RTT = 1ms라면 → 1 × 1ms = 1ms

명령 자체 처리 시간보다 네트워크 대기 시간이 훨씬 길 때 파이프라인의 효과가 큽니다.

RTT(Round Trip Time): 요청을 보내고 응답을 받기까지 걸리는 네트워크 왕복 시간. 같은 데이터센터 내에서도 0.5~2ms 정도가 걸립니다.

기본 사용법

redis-cli에서

# 여러 명령을 파이프로 전송
(echo -e "SET key1 value1\r\nSET key2 value2\r\nGET key1"; sleep 0.1) | redis-cli --pipe

Node.js (ioredis)

import Redis from "ioredis";
const redis = new Redis();

// 파이프라인 생성 및 실행
const pipeline = redis.pipeline();

pipeline.set("user:1:name", "철수");
pipeline.set("user:2:name", "영희");
pipeline.set("user:3:name", "민준");
pipeline.get("user:1:name");
pipeline.get("user:2:name");

const results = await pipeline.exec();
// [
//   [null, "OK"],
//   [null, "OK"],
//   [null, "OK"],
//   [null, "철수"],
//   [null, "영희"],
// ]
// 각 항목: [에러, 결과]

체이닝 방식

const results = await redis
  .pipeline()
  .set("a", 1)
  .set("b", 2)
  .incr("a")
  .incr("b")
  .exec();

대량 데이터 처리

// 사용자 1000명의 세션 만료 처리
async function expireAllSessions(userIds: string[]) {
  const pipeline = redis.pipeline();

  for (const userId of userIds) {
    pipeline.del(`session:${userId}`);
  }

  await pipeline.exec();
}

// 대량 캐시 웜업
async function warmupCache(products: Product[]) {
  const pipeline = redis.pipeline();

  for (const p of products) {
    pipeline.set(
      `cache:product:${p.id}`,
      JSON.stringify(p),
      "EX",
      3600
    );
  }

  await pipeline.exec();
}

청크 단위 처리

한 번에 너무 많은 명령을 보내면 메모리 부하가 생깁니다. 청크로 나눠 처리합니다.

async function batchProcess(items: string[], chunkSize = 500) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    const pipeline = redis.pipeline();

    for (const item of chunk) {
      pipeline.del(item);
    }

    await pipeline.exec();
  }
}

파이프라인 vs 트랜잭션 vs MSET

목적이 다르다

  • MSET/MGET: 여러 키를 한 번에 읽고 쓰는 전용 명령. 파이프라인보다 단순하고 원자적.
  • 파이프라인: 다양한 명령을 묶어 네트워크 왕복을 줄이는 최적화. 원자성 없음.
  • MULTI/EXEC(트랜잭션): 원자성 보장. 중간에 다른 명령이 끼어들 수 없음.
MSET user:1 "철수" user:2 "영희"  ← 원자적, 단순
vs
pipeline.set("user:1", "철수")    ← 비원자적, 다양한 명령 가능
pipeline.set("user:2", "영희")

파이프라인은 여러 명령이 Redis에 도착하는 순간에 다른 클라이언트 명령이 끼어들 수 있습니다. 순서 보장은 되지만 원자성은 보장되지 않습니다.

파이프라인 주의 사항

이전 결과를 다음 명령에 쓸 수 없다

const pipeline = redis.pipeline();
pipeline.get("key1");
// pipeline.set("key2", ???)  ← get 결과를 여기서 쓸 수 없음
// exec 전에는 결과를 알 수 없기 때문

이런 경우 파이프라인이 아닌 Lua 스크립트나 트랜잭션을 써야 합니다.

에러 처리

const results = await pipeline.exec();
for (const [err, result] of results) {
  if (err) {
    console.error("명령 실패:", err);
  }
}

파이프라인 내 하나의 명령이 실패해도 나머지는 계속 실행됩니다.

← 이전 글Redis Sentinel — 자동 장애 복구와 고가용성
다음 글 →Redis Pub/Sub — 실시간 메시지 브로드캐스트