Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Giảm 50% Thời Gian Tải v0.dev Với Auto-Pipelining

Vercel KV là công cụ quý giá cho các dự án web, tuy nhiên việc sử dụng nó rộng rãi có thể dẫn đến sự gia tăng nhanh chóng về số lượng yêu cầu HTTP, có thể ảnh hưởng đến hiệu suất. Mặc dù Redis pipelines cung cấp cách gom các lệnh thành batch và giảm yêu cầu, nhưng chúng có thể khó triển khai. Có giải pháp nào kết hợp hiệu suất của pipelines với sự đơn giản của các lệnh Redis cơ bản không? Đó là lúc auto-pipelining xuất hiện – cung cấp cách liền mạch để nâng cao hiệu suất mà không có độ phức tạp về mã hóa.

Vấn Đề

Hãy tưởng tượng bạn đã xây dựng một trang web nơi Redis được sử dụng rộng rãi làm nguồn dữ liệu. Nhiều component tự tạo cuộc gọi Redis của riêng chúng, với một số được render nhiều lần với nội dung khác nhau.

Thiết lập này có thể tạo ra số lượng đáng kể yêu cầu đến Redis mỗi khi trang web được mở. Thể tích yêu cầu lớn này tạo ra overhead đáng kể, ảnh hưởng đến hiệu suất. Ngoài ra, trong môi trường như Cloudflare Workers, nơi số lượng yêu cầu HTTP đồng thời bị giới hạn nghiêm ngặt, điều này trở thành vấn đề cấp bách.

Regular Pipelines (Pipelines Thông Thường)

Giải pháp thông thường trong tình huống này là sử dụng Redis pipelines. Thay vì gửi yêu cầu một per một, pipelines cho phép bạn gom nhiều lệnh và thực thi chúng cùng nhau. Cách này, một yêu cầu HTTP duy nhất mang nhiều lệnh Redis, giảm đáng kể số lượng yêu cầu HTTP và cải thiện hiệu suất.

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"]

Nhược Điểm Của Pipelines

Tuy nhiên, sử dụng pipelines tạo ra overhead đáng kể từ góc độ lập trình viên. API pipeline khác với API Redis tiêu chuẩn, khiến việc bật hoặc tắt nó trở thành nhiệm vụ không tầm thường.

Hơn nữa, nếu bạn cần yêu cầu dữ liệu cho các component khác nhau trong một pipeline đơn lẻ, bạn phải viết logic lấy dữ liệu tách biệt khỏi nơi dữ liệu sẽ được sử dụng. Sự tách biệt này có thể dẫn đến codebase phân mảnh và khó bảo trì hơn.

Auto-Pipelining

Auto-pipelining giải quyết các vấn đề này một cách liền mạch. Với auto-pipelining, bạn có thể bật chức năng pipeline trong dự án mà không thay đổi mã hiện có. Bạn có thể tiếp tục sử dụng Redis như bình thường, trong khi client Redis tự động gom các lệnh vào batch bất cứ khi nào có thể, nâng cao hiệu suất một cách dễ dàng.

Hãy khám phá cách auto-pipelining tối ưu hóa một kịch bản phổ biến: lấy giá trị cho nhiều khóa:

const keys = ["key1", "key2", "key3"]; const values = await Promise.all(keys.map(key => kv.get(key)));

Không có pipelines, mã này sẽ gửi ba yêu cầu HTTP đến Redis. Tuy nhiên, với auto-pipelining được bật, các yêu cầu này sẽ được gom batch thành một yêu cầu HTTP duy nhất.

Auto-Pipelines Cho React Server Components Theo Phong Cách (Idiomatic)

React Server Components (RSC) có thể lấy dữ liệu của riêng chúng. Một ví dụ phổ biến là component tweet có thể được triển khai như sau

async function Tweet({id}) { const tweet = kv.get(`tweets:${id}`) return <div>{tweet.text}</div> }

Nếu bạn gọi component này trong vòng lặp như

{tweetIds.map(id => <Tweet id={id} />)}

bạn kích hoạt cùng N yêu cầu backend như ví dụ trên. Một lần nữa, kích hoạt auto-pipelining gom N lệnh thành một pipeline đơn lẻ trong khi giữ nguyên code react theo phong cách.

Cách Hoạt Động

Auto-pipelining hoạt động bằng cách duy trì một 'active pipeline' (pipeline hoạt động) ở background. Các lệnh thêm chính chúng vào pipeline và gọi deferExecution:

private async deferExecution() { await Promise.resolve() return await Promise.resolve() }

Khi gọi deferExecution, lệnh nhường quyền kiểm soát luồng chính (main thread) của Node.js. Lệnh GET tiếp theo trong chuỗi sau đó giành quyền kiểm soát luồng và tiếp tục thực thi, làm chính xác điều mà lệnh GET đầu tiên làm: Thêm chính nó vào active pipeline và nhường quyền kiểm soát luồng.

Đây là logic auto pipeline dưới dạng pseudo-code:

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]; };

Sau khi thêm chính nó vào active pipeline, lệnh GET thứ ba cũng gọi deferExecution. Lúc này, vì không có lệnh nào khác để nhường, một trong các lệnh GET đã trì hoãn sẽ giành lại quyền kiểm soát và thực thi pipeline.

Pipeline này trả về ba kết quả trong ví dụ của chúng ta, mỗi cái tương ứng với lệnh ban đầu. Mỗi lệnh theo dõi chỉ số của đối số của nó, đảm bảo nó nhận kết quả đúng từ phản hồi batch.

Mã đằng sau logic auto-pipelining có sẵn tại đây.

Auto-Pipelining Trong v0.dev

Tác động của auto-pipelining đến hiệu suất v0.dev rõ ràng từ sự cải thiện trang đích. Trang đích trình diễn các ví dụ từ truy vấn và tạo sinh trước. Đầu tiên, nó lấy danh sách mục để hiển thị và sau đó thực hiện lấy riêng lẻ cho mỗi mục, dẫn đến nhiều yêu cầu Redis.

Giảm 50% Thời Gian Tải v0.dev Với Auto-Pipelining

Sau khi bật auto-pipelining, quy trình này được tối ưu hóa chỉ với việc bật công tắc. Bây giờ, phần thứ hai, vốn trước đây liên quan đến nhiều lần lấy riêng lẻ, được gộp thành một thao tác pipeline đơn lẻ.

Trước khi triển khai auto-pipelining, các yêu cầu Redis này dẫn đến số lượng đáng kể yêu cầu HTTP riêng lẻ, làm chậm thời gian tải trang. Tuy nhiên, với auto-pipelining được bật, các yêu cầu này được gom batch hiệu quả vào pipelines, giảm số lượng yêu cầu HTTP cần thiết. Kết quả là, thời gian tải trang được cải thiện, giảm từ khoảng 450ms xuống khoảng 200ms.

Đội ngũ v0 ban đầu triển khai auto-pipelining như một hack cho chính họ. Auto-pipelining hiện có sẵn trong v1.31.3 của client Redis Upstash, cũng như v2.0 của Vercel KV.