Skip to content

Commit

Permalink
add enableAutoPipelining parameter (#1062)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
CahidArda authored May 15, 2024
1 parent d4bb005 commit 427ace4
Show file tree
Hide file tree
Showing 27 changed files with 474 additions and 76 deletions.
3 changes: 3 additions & 0 deletions examples/auto-pipeline/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions examples/auto-pipeline/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
77 changes: 77 additions & 0 deletions examples/auto-pipeline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Auto Pipeline Example

This nextjs example showcases how Auto Pipelining works.

In the `app/data/redis.ts` file, we define a redis client with `enableAutoPipelining: true`:

```tsx
import { Redis } from '@upstash/redis'

export const LATENCY_LOGGING = true
export const ENABLE_AUTO_PIPELINING = true

const client = Redis.fromEnv({
latencyLogging: LATENCY_LOGGING,
enableAutoPipelining: ENABLE_AUTO_PIPELINING
});

export default client;
```

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):

```tsx
// app/data/getUsers.ts

import client from "./redis"

export async function getUsers() {
const keys = await client.scan(0, { match: 'user:*' });

if (keys[1].length === 0) {
// If no keys found, insert sample data
client.hmset('user:1', {'username': 'Adam', 'birthday': '1990-01-01'});
client.hmset('user:2', {'username': 'Eve', 'birthday': '1980-01-05'});
// Add more sample users as needed
}

const users = await Promise.all(keys[1].map(async key => {
return client.hgetall(key) ?? {username: "default", birthday: "2000-01-01"};
}));
return users as {username: string, birthday: string}[]
}
```

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.

We import the `getUsers` and `getEvents` methods in our page `app/components/page.tsx`:

```tsx
"use server"
import client from "../data/redis"
import { getEvents } from "../data/getEvents";
import { getUsers } from "../data/getUsers";

const DataComponent = async () => {


const [ users, events ] = await Promise.all([
getUsers(),
getEvents()
])

// @ts-ignore pipelineCounter is accessible but not available in the type
const counter = client.pipelineCounter

return (
<div>
... skipped to keep the README short ...
</div>
);
};

export default DataComponent;

```

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.
49 changes: 49 additions & 0 deletions examples/auto-pipeline/app/components/DataComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use server"
import client from "../data/redis"
import { getEvents } from "../data/getEvents";
import { getUsers } from "../data/getUsers";

const DataComponent = async () => {


const [ users, events ] = await Promise.all([
getUsers(),
getEvents()
])

// @ts-ignore pipelineCounter is accessible but not available in the type
const counter = client.pipelineCounter

return (
<div>

<div>
<h2>Users</h2>
<ul>
{users.map(user =>
<li key={user.username}>
<strong>{user.username}</strong> - {user.birthday}
</li>
)}
</ul>
</div>

<div>
<h2>Events</h2>
<ul>
{events.map(event =>
<li key={event.name}>
<strong>{event.name}</strong> - {event.date}
</li>
)}
</ul>
</div>

<div>
<h2>Number of Pipelines Called:</h2> {counter}
</div>
</div>
);
};

export default DataComponent;
18 changes: 18 additions & 0 deletions examples/auto-pipeline/app/data/getEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

import client from "./redis"

export async function getEvents() {
const keys = await client.scan(0, { match: 'event:*' });

if (keys[1].length === 0) {
// If no keys found, insert sample data
client.hmset('event:1', {'name': 'Sample Event 1', 'date': '2024-05-13'});
client.hmset('event:2', {'name': 'Sample Event 2', 'date': '2024-05-14'});
// Add more sample events as needed
}

const events = await Promise.all(keys[1].map(async key => {
return client.hgetall(key) ?? {name: "default", date: "2000-01-01"};
}));
return events as {name: string, date: string}[]
};
18 changes: 18 additions & 0 deletions examples/auto-pipeline/app/data/getUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

import client from "./redis"

export async function getUsers() {
const keys = await client.scan(0, { match: 'user:*' });

if (keys[1].length === 0) {
// If no keys found, insert sample data
client.hmset('user:1', {'username': 'Adam', 'birthday': '1990-01-01'});
client.hmset('user:2', {'username': 'Eve', 'birthday': '1980-01-05'});
// Add more sample users as needed
}

const users = await Promise.all(keys[1].map(async key => {
return client.hgetall(key) ?? {username: "default", birthday: "2000-01-01"};
}));
return users as {username: string, birthday: string}[]
}
12 changes: 12 additions & 0 deletions examples/auto-pipeline/app/data/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

import { Redis } from '@upstash/redis'

export const LATENCY_LOGGING = true
export const ENABLE_AUTO_PIPELINING = true

const client = Redis.fromEnv({
latencyLogging: LATENCY_LOGGING,
enableAutoPipelining: ENABLE_AUTO_PIPELINING
});

export default client;
Binary file added examples/auto-pipeline/app/favicon.ico
Binary file not shown.
Empty file.
22 changes: 22 additions & 0 deletions examples/auto-pipeline/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
15 changes: 15 additions & 0 deletions examples/auto-pipeline/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use server"

import React from 'react';
import DataComponent from './components/DataComponent'

const HomePage = () => {
return (
<div>
<h1>Home Page</h1>
<DataComponent />
</div>
);
};

export default HomePage;
4 changes: 4 additions & 0 deletions examples/auto-pipeline/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
27 changes: 27 additions & 0 deletions examples/auto-pipeline/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "auto-pipeline",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@upstash/redis": "^1.30.1",
"next": "14.2.3",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
8 changes: 8 additions & 0 deletions examples/auto-pipeline/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
1 change: 1 addition & 0 deletions examples/auto-pipeline/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/auto-pipeline/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions examples/auto-pipeline/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Config } from "tailwindcss";

const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
26 changes: 26 additions & 0 deletions examples/auto-pipeline/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@upstash/redis",
"version": "0.0.0-canary.2",
"version": "1.30.2",
"main": "./nodejs.js",
"module": "./nodejs.mjs",
"exports": {
Expand Down
Loading

0 comments on commit 427ace4

Please sign in to comment.