Vercel KV는 웹 프로젝트를 위한 귀중한 도구이지만 광범위하게 사용하면 HTTP 요청 수가 급격히 증가하여 잠재적으로 성능에 영향을 미칠 수 있습니다. Redis 파이프라인은 명령을 일괄 처리하고 요청을 줄이는 방법을 제공하지만 구현하기 어려울 수 있습니다. 파이프라인의 효율성과 기본 Redis 명령의 단순성을 결합한 솔루션이 있습니까? 자동 파이프라인이 추가되었습니다. – 복잡한 코딩 없이 성능을 향상시킬 수 있는 원활한 방법을 제공합니다.
문제
Redis가 데이터 소스로 광범위하게 사용되는 웹페이지를 구축했다고 상상해 보세요. 자체 Redis 호출을 수행하는 여러 구성 요소 중 일부는 다양한 콘텐츠로 여러 번 렌더링됩니다.
이 설정은 웹 페이지가 열릴 때마다 Redis에 대한 상당한 수의 요청을 생성할 수 있습니다. 엄청난 양의 요청으로 인해 상당한 오버헤드가 발생하여 성능에 영향을 미칩니다. 또한 동시 HTTP 요청 수가 심각하게 제한되는 Cloudflare Workers와 같은 환경에서는 이는 중요한 문제가 됩니다.
일반 파이프라인
이러한 상황에서 일반적인 솔루션은 Redis 파이프라인을 사용하는 것입니다. 요청을 하나씩 보내는 대신 파이프라인을 사용하면 여러 명령을 수집하여 함께 실행할 수 있습니다. 이런 방식으로 단일 HTTP 요청은 여러 Redis 명령을 전달하여 HTTP 요청 수를 크게 줄이고 성능을 향상시킵니다.
import { kv } from '@vercel/kv';
const pipeline = kv.pipeline();
pipeline.set("foo", "bar");
pipeline.get("foo");
const res = await pipeline.exec();
console.log(res); // ["OK", "bar"] 파이프라인의 단점
그러나 파이프라인을 사용하면 프로그래머의 관점에서 상당한 오버헤드가 발생합니다. 파이프라인 API는 표준 Redis API와 다르기 때문에 이를 활성화하거나 비활성화하는 것이 쉽지 않은 작업입니다.
또한 단일 파이프라인 내에서 다양한 구성 요소에 대한 데이터를 요청해야 하는 경우 데이터가 사용되는 위치와 별도로 가져오기 논리를 작성해야 합니다. 이러한 분리로 인해 코드베이스가 단편화되고 유지 관리가 덜 어려워질 수 있습니다.
자동 파이프라이닝
자동 파이프라인은 이러한 문제를 원활하게 해결합니다. 자동 파이프라인을 사용하면 기존 코드를 변경하지 않고도 프로젝트에서 파이프라인 기능을 활성화할 수 있습니다. 평소처럼 Redis를 계속 사용할 수 있으며, Redis 클라이언트는 가능할 때마다 자동으로 명령을 일괄 처리하여 성능을 손쉽게 향상시킬 수 있습니다.
자동 파이프라인이 일반적인 시나리오(여러 키에 대한 값 가져오기)를 어떻게 최적화하는지 살펴보겠습니다.
const keys = ["key1", "key2", "key3"];
const values = await Promise.all(keys.map(key => kv.get(key))); 파이프라인이 없으면 이 코드는 Redis에 3개의 HTTP 요청을 보냅니다. 그러나 자동 파이프라인이 활성화되면 이러한 요청은 단일 HTTP 요청으로 일괄 처리됩니다.
관용적인 React 서버 구성요소를 위한 자동 파이프라인
RSC(React Server Components)는 자체 데이터를 가져올 수 있습니다. 일반적인 예는 다음과 같이 구현될 수 있는 트윗 구성 요소입니다.
async function Tweet({id}) {
const tweet = kv.get(`tweets:${id}`)
return <div>{tweet.text}</div>
} 다음과 같은 루프에서 이 구성 요소를 호출하면
{tweetIds.map(id => <Tweet id={id} />)} 위 예시와 동일한 N개의 백엔드 요청을 트리거합니다. 다시 한번 자동 파이프라인을 활성화하면 관용적인 반응 코드를 유지하면서 N 명령을 단일 파이프라인으로 일괄 처리합니다.
작동 방식
자동 파이프라인은 백그라운드에서 '활성 파이프라인'을 유지함으로써 작동합니다. 명령은 파이프라인에 자신을 추가하고 deferExecution를 호출합니다. :
private async deferExecution() {
await Promise.resolve()
return await Promise.resolve()
}
deferExecution에 전화하면 , 이 명령은 Node.js 메인 스레드를 제어합니다. 다음 GET 그런 다음 시퀀스의 명령은 스레드의 제어권을 얻고 실행을 진행하며 첫 번째 GET와 정확히 동일한 작업을 수행합니다. :활성 파이프라인에 자신을 추가하고 스레드 제어권을 양보합니다.
의사 코드로서의 자동 파이프라인 논리는 다음과 같습니다.
let activePipeline: Pipeline;
let pipelinePromises: new WeakMap<Pipeline, Promise<Array<unknown>>>();
let commandIndex: number;
const executeCommand = (command) => {
activePipeline = activePipeline || createNewPipeline();
activePipeline.addCommand(command);
commandIndex++;
const pipelinePromise = deferExecution().then(() => {
if (!pipelinePromises.has(activePipeline) {
const pipelinePromise = pipeline.exec();
pipelinePromises.set(pipeline, pipelinePromise);
activePipeline = null;
commandIndex = 0
};
return pipelinePromises.get(activePipeline)!;
});
const result = await pipelinePromise;
return result[commandIndex];
};
활성 파이프라인에 자신을 추가한 후 세 번째 GET deferExecution도 호출합니다. . 이 시점에서는 양보할 다른 명령이 없기 때문에 연기된 GET 중 하나입니다. 명령은 제어권을 되찾고 파이프라인을 실행합니다.
이 파이프라인은 예제에서 각각 초기 명령에 해당하는 세 가지 결과를 반환합니다. 각 명령은 해당 인수의 인덱스를 추적하여 일괄 응답에서 올바른 결과를 수신하는지 확인합니다.
자동 파이프라인 논리 뒤에 있는 코드는 여기에서 확인할 수 있습니다.
v0.dev의 자동 파이프라이닝
v0.dev의 성능에 대한 자동 파이프라이닝의 영향은 방문 페이지의 개선에서 분명하게 드러납니다. 랜딩 페이지에서는 과거 쿼리 및 세대의 예를 보여줍니다. 먼저 표시할 항목 목록을 가져온 다음 각 항목에 대해 개별적으로 가져오기 때문에 수많은 Redis 요청이 발생합니다.

자동 파이프라인을 활성화한 후 스위치를 켜면 이 프로세스가 최적화됩니다. 이제 여러 개별 가져오기를 포함했던 두 번째 부분이 단일 파이프라인 작업으로 통합되었습니다.
자동 파이프라인을 구현하기 전에는 이러한 Redis 요청으로 인해 상당한 수의 개별 HTTP 요청이 발생하여 페이지 로드 시간이 느려졌습니다. 그러나 자동 파이프라이닝을 활성화하면 이러한 요청이 효율적으로 파이프라인으로 일괄 처리되어 필요한 HTTP 요청 수가 줄어듭니다. 결과적으로 페이지 로드 시간이 약 450ms에서 약 200ms로 단축되었습니다.
v0 팀은 처음에 자동 파이프라인을 자체적으로 해킹하기 위해 출시했습니다. 이제 Upstash의 Redis 클라이언트 v1.31.3과 Vercel KV v2.0에서 자동 파이프라이닝을 사용할 수 있습니다.