Skip to content

Commit 427ace4

Browse files
authored
add enableAutoPipelining parameter (#1062)
* add enableAutoPipelining parameter other changes: - made the proxy return type Redis - handled properties only available in Redis by returning them from redis * add auto pipelining example * rename variables in auto-pipeline * bump version to 1.30.2
1 parent d4bb005 commit 427ace4

27 files changed

+474
-76
lines changed

examples/auto-pipeline/.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

examples/auto-pipeline/.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

examples/auto-pipeline/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
## Auto Pipeline Example
2+
3+
This nextjs example showcases how Auto Pipelining works.
4+
5+
In the `app/data/redis.ts` file, we define a redis client with `enableAutoPipelining: true`:
6+
7+
```tsx
8+
import { Redis } from '@upstash/redis'
9+
10+
export const LATENCY_LOGGING = true
11+
export const ENABLE_AUTO_PIPELINING = true
12+
13+
const client = Redis.fromEnv({
14+
latencyLogging: LATENCY_LOGGING,
15+
enableAutoPipelining: ENABLE_AUTO_PIPELINING
16+
});
17+
18+
export default client;
19+
```
20+
21+
We utilize this client in the `app/data/getUsers.ts` and `app/data/getEvents.ts` files to fetch data from the redis server (if there is no data, we insert data for the purposes of this example):
22+
23+
```tsx
24+
// app/data/getUsers.ts
25+
26+
import client from "./redis"
27+
28+
export async function getUsers() {
29+
const keys = await client.scan(0, { match: 'user:*' });
30+
31+
if (keys[1].length === 0) {
32+
// If no keys found, insert sample data
33+
client.hmset('user:1', {'username': 'Adam', 'birthday': '1990-01-01'});
34+
client.hmset('user:2', {'username': 'Eve', 'birthday': '1980-01-05'});
35+
// Add more sample users as needed
36+
}
37+
38+
const users = await Promise.all(keys[1].map(async key => {
39+
return client.hgetall(key) ?? {username: "default", birthday: "2000-01-01"};
40+
}));
41+
return users as {username: string, birthday: string}[]
42+
}
43+
```
44+
45+
Both `getUsers` and `getEvents` work in a similar way. They first call and await scan to get the keys. Then, they call `HGETALL` with these keys.
46+
47+
We import the `getUsers` and `getEvents` methods in our page `app/components/page.tsx`:
48+
49+
```tsx
50+
"use server"
51+
import client from "../data/redis"
52+
import { getEvents } from "../data/getEvents";
53+
import { getUsers } from "../data/getUsers";
54+
55+
const DataComponent = async () => {
56+
57+
58+
const [ users, events ] = await Promise.all([
59+
getUsers(),
60+
getEvents()
61+
])
62+
63+
// @ts-ignore pipelineCounter is accessible but not available in the type
64+
const counter = client.pipelineCounter
65+
66+
return (
67+
<div>
68+
... skipped to keep the README short ...
69+
</div>
70+
);
71+
};
72+
73+
export default DataComponent;
74+
75+
```
76+
77+
Thanks to auto pipelining, the scan commands from the two methods are sent in a single pipeline call. Then, the 4 `HGETALL` commands are sent in a second pipeline. In the end, 6 commands are sent with only two pipelines, with minimal overhead for the programmer.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use server"
2+
import client from "../data/redis"
3+
import { getEvents } from "../data/getEvents";
4+
import { getUsers } from "../data/getUsers";
5+
6+
const DataComponent = async () => {
7+
8+
9+
const [ users, events ] = await Promise.all([
10+
getUsers(),
11+
getEvents()
12+
])
13+
14+
// @ts-ignore pipelineCounter is accessible but not available in the type
15+
const counter = client.pipelineCounter
16+
17+
return (
18+
<div>
19+
20+
<div>
21+
<h2>Users</h2>
22+
<ul>
23+
{users.map(user =>
24+
<li key={user.username}>
25+
<strong>{user.username}</strong> - {user.birthday}
26+
</li>
27+
)}
28+
</ul>
29+
</div>
30+
31+
<div>
32+
<h2>Events</h2>
33+
<ul>
34+
{events.map(event =>
35+
<li key={event.name}>
36+
<strong>{event.name}</strong> - {event.date}
37+
</li>
38+
)}
39+
</ul>
40+
</div>
41+
42+
<div>
43+
<h2>Number of Pipelines Called:</h2> {counter}
44+
</div>
45+
</div>
46+
);
47+
};
48+
49+
export default DataComponent;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
import client from "./redis"
3+
4+
export async function getEvents() {
5+
const keys = await client.scan(0, { match: 'event:*' });
6+
7+
if (keys[1].length === 0) {
8+
// If no keys found, insert sample data
9+
client.hmset('event:1', {'name': 'Sample Event 1', 'date': '2024-05-13'});
10+
client.hmset('event:2', {'name': 'Sample Event 2', 'date': '2024-05-14'});
11+
// Add more sample events as needed
12+
}
13+
14+
const events = await Promise.all(keys[1].map(async key => {
15+
return client.hgetall(key) ?? {name: "default", date: "2000-01-01"};
16+
}));
17+
return events as {name: string, date: string}[]
18+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
import client from "./redis"
3+
4+
export async function getUsers() {
5+
const keys = await client.scan(0, { match: 'user:*' });
6+
7+
if (keys[1].length === 0) {
8+
// If no keys found, insert sample data
9+
client.hmset('user:1', {'username': 'Adam', 'birthday': '1990-01-01'});
10+
client.hmset('user:2', {'username': 'Eve', 'birthday': '1980-01-05'});
11+
// Add more sample users as needed
12+
}
13+
14+
const users = await Promise.all(keys[1].map(async key => {
15+
return client.hgetall(key) ?? {username: "default", birthday: "2000-01-01"};
16+
}));
17+
return users as {username: string, birthday: string}[]
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
import { Redis } from '@upstash/redis'
3+
4+
export const LATENCY_LOGGING = true
5+
export const ENABLE_AUTO_PIPELINING = true
6+
7+
const client = Redis.fromEnv({
8+
latencyLogging: LATENCY_LOGGING,
9+
enableAutoPipelining: ENABLE_AUTO_PIPELINING
10+
});
11+
12+
export default client;
25.3 KB
Binary file not shown.

examples/auto-pipeline/app/globals.css

Whitespace-only changes.

examples/auto-pipeline/app/layout.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Metadata } from "next";
2+
import { Inter } from "next/font/google";
3+
import "./globals.css";
4+
5+
const inter = Inter({ subsets: ["latin"] });
6+
7+
export const metadata: Metadata = {
8+
title: "Create Next App",
9+
description: "Generated by create next app",
10+
};
11+
12+
export default function RootLayout({
13+
children,
14+
}: Readonly<{
15+
children: React.ReactNode;
16+
}>) {
17+
return (
18+
<html lang="en">
19+
<body className={inter.className}>{children}</body>
20+
</html>
21+
);
22+
}

0 commit comments

Comments
 (0)