Update guidance around KV storage for rate limiting
This commit is contained in:
parent
73ec9b8f87
commit
7c98c55853
@ -77,7 +77,7 @@ _⚠️ READ BEFORE PROCEEDING_
|
||||
- Generate an API key and store in environment variable `OPENAI_SECRET_KEY`
|
||||
- Setup usage limits to avoid unexpected charges (_recommended_)
|
||||
2. Add rate limiting (_recommended_)
|
||||
- As an additional precaution, create a [Vercel KV](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) store and link it to your project in order to enable rate limiting—no further configuration necessary
|
||||
- As an additional precaution, create an Upstash Redis store from the storage tab of the Vercel dashboard and link it to your project in order to enable rate limiting—no further configuration necessary
|
||||
3. Configure auto-generated fields (optional)
|
||||
- Set which text fields auto-generate when uploading a photo by storing a comma-separated list, e.g., `AI_TEXT_AUTO_GENERATED_FIELDS = title, semantic`
|
||||
- Accepted values:
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@radix-ui/react-visually-hidden": "^1.1.2",
|
||||
"@upstash/ratelimit": "^2.0.5",
|
||||
"@upstash/redis": "^1.34.4",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/blob": "^0.27.1",
|
||||
"@vercel/kv": "^3.0.0",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"ai": "^4.1.34",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@ -31,16 +31,16 @@ importers:
|
||||
version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@upstash/ratelimit':
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5(@upstash/redis@1.34.3)
|
||||
version: 2.0.5(@upstash/redis@1.34.4)
|
||||
'@upstash/redis':
|
||||
specifier: ^1.34.4
|
||||
version: 1.34.4
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))
|
||||
'@vercel/blob':
|
||||
specifier: ^0.27.1
|
||||
version: 0.27.1
|
||||
'@vercel/kv':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))
|
||||
@ -1763,11 +1763,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@upstash/redis': ^1.34.3
|
||||
|
||||
'@upstash/redis@1.31.3':
|
||||
resolution: {integrity: sha512-KtVgWBUEx/LGbR8oRwYexwzHh3s5DNqYW0bjkD+gjFZVOnREJITvK+hC4PjSSD+8D4qJ+Xbkfmy8ANADZ9EUFg==}
|
||||
|
||||
'@upstash/redis@1.34.3':
|
||||
resolution: {integrity: sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==}
|
||||
'@upstash/redis@1.34.4':
|
||||
resolution: {integrity: sha512-AZx2iD5s1Pu/KCrRA7KVCffu3NSoaYnNY7N9YI7aLAYhcJfsriQKTe+8OxQWJqGqFbrvm17Lyr9HFnDLvqNpfA==}
|
||||
|
||||
'@vercel/analytics@1.5.0':
|
||||
resolution: {integrity: sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==}
|
||||
@ -1799,10 +1796,6 @@ packages:
|
||||
resolution: {integrity: sha512-X5EG9W1cZW+Nbt/XdrJJSl5DzCXXn1JRP5nfFwkpFD03nB6uh6BldwX4syElHcciM4Pih8CS7Ri1mtLCJvxSHA==}
|
||||
engines: {node: '>=16.14'}
|
||||
|
||||
'@vercel/kv@3.0.0':
|
||||
resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
|
||||
engines: {node: '>=14.6'}
|
||||
|
||||
'@vercel/speed-insights@1.2.0':
|
||||
resolution: {integrity: sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw==}
|
||||
peerDependencies:
|
||||
@ -6500,18 +6493,14 @@ snapshots:
|
||||
|
||||
'@upstash/core-analytics@0.0.10':
|
||||
dependencies:
|
||||
'@upstash/redis': 1.31.3
|
||||
'@upstash/redis': 1.34.4
|
||||
|
||||
'@upstash/ratelimit@2.0.5(@upstash/redis@1.34.3)':
|
||||
'@upstash/ratelimit@2.0.5(@upstash/redis@1.34.4)':
|
||||
dependencies:
|
||||
'@upstash/core-analytics': 0.0.10
|
||||
'@upstash/redis': 1.34.3
|
||||
'@upstash/redis': 1.34.4
|
||||
|
||||
'@upstash/redis@1.31.3':
|
||||
dependencies:
|
||||
crypto-js: 4.2.0
|
||||
|
||||
'@upstash/redis@1.34.3':
|
||||
'@upstash/redis@1.34.4':
|
||||
dependencies:
|
||||
crypto-js: 4.2.0
|
||||
|
||||
@ -6530,10 +6519,6 @@ snapshots:
|
||||
throttleit: 2.1.0
|
||||
undici: 5.28.4
|
||||
|
||||
'@vercel/kv@3.0.0':
|
||||
dependencies:
|
||||
'@upstash/redis': 1.34.3
|
||||
|
||||
'@vercel/speed-insights@1.2.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))':
|
||||
optionalDependencies:
|
||||
next: 15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
|
||||
@ -33,7 +33,7 @@ export default function AdminAppConfigurationClient({
|
||||
hasDatabase,
|
||||
isPostgresSslEnabled,
|
||||
hasVercelPostgres,
|
||||
hasVercelKv,
|
||||
hasRedisStorage,
|
||||
hasStorageProvider,
|
||||
hasVercelBlobStorage,
|
||||
hasCloudflareR2Storage,
|
||||
@ -95,7 +95,7 @@ export default function AdminAppConfigurationClient({
|
||||
// Connection status
|
||||
databaseError,
|
||||
storageError,
|
||||
kvError,
|
||||
redisError,
|
||||
aiError,
|
||||
// Component props
|
||||
simplifiedView,
|
||||
@ -358,25 +358,19 @@ export default function AdminAppConfigurationClient({
|
||||
{renderEnvVars(['OPENAI_SECRET_KEY'])}
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title={hasVercelKv && isAnalyzingConfiguration
|
||||
? 'Testing KV connection'
|
||||
title={hasRedisStorage && isAnalyzingConfiguration
|
||||
? 'Testing Redis connection'
|
||||
: 'Enable rate limiting'}
|
||||
status={hasVercelKv}
|
||||
isPending={hasVercelKv && isAnalyzingConfiguration}
|
||||
status={hasRedisStorage}
|
||||
isPending={hasRedisStorage && isAnalyzingConfiguration}
|
||||
optional
|
||||
>
|
||||
{kvError && renderError({
|
||||
connection: { provider: 'Vercel KV', error: kvError},
|
||||
{redisError && renderError({
|
||||
connection: { provider: 'Redis', error: redisError},
|
||||
})}
|
||||
<AdminLink
|
||||
// eslint-disable-next-line max-len
|
||||
href="https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database"
|
||||
externalIcon
|
||||
>
|
||||
Create Vercel KV store
|
||||
</AdminLink>
|
||||
{' '}
|
||||
and connect to project in order to enable rate limiting
|
||||
Create Upstash Redis store from storage tab
|
||||
on Vercel dashboard and connect to this project
|
||||
to enable rate limiting
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
// eslint-disable-next-line max-len
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
import { runAuthenticatedAdminServerAction } from '@/auth';
|
||||
import { testKvConnection } from '@/platforms/kv';
|
||||
import { testRedisConnection } from '@/platforms/redis';
|
||||
import { testOpenAiConnection } from '@/platforms/openai';
|
||||
import { testDatabaseConnection } from '@/platforms/postgres';
|
||||
import { testStorageConnection } from '@/platforms/storage';
|
||||
@ -22,26 +22,26 @@ export const testConnectionsAction = async () =>
|
||||
const {
|
||||
hasDatabase,
|
||||
hasStorageProvider,
|
||||
hasVercelKv,
|
||||
hasRedisStorage,
|
||||
isAiTextGenerationEnabled,
|
||||
} = APP_CONFIGURATION;
|
||||
|
||||
const [
|
||||
databaseError,
|
||||
storageError,
|
||||
kvError,
|
||||
redisError,
|
||||
aiError,
|
||||
] = await Promise.all([
|
||||
scanForError(hasDatabase, testDatabaseConnection),
|
||||
scanForError(hasStorageProvider, testStorageConnection),
|
||||
scanForError(hasVercelKv, testKvConnection),
|
||||
scanForError(hasRedisStorage, testRedisConnection),
|
||||
scanForError(isAiTextGenerationEnabled, testOpenAiConnection),
|
||||
]);
|
||||
|
||||
return {
|
||||
databaseError,
|
||||
storageError,
|
||||
kvError,
|
||||
redisError,
|
||||
aiError,
|
||||
};
|
||||
});
|
||||
|
||||
@ -234,8 +234,9 @@ export default function AdminAppInsightsClient({
|
||||
'yellow',
|
||||
)}
|
||||
expandContent={<>
|
||||
Create Vercel KV store and link to this project
|
||||
in order prevent abuse by to enabling rate limiting.
|
||||
Create Upstash Redis store from storage tab on
|
||||
Vercel dashboard and link to this project to
|
||||
prevent abuse by enabling rate limiting.
|
||||
</>}
|
||||
/>}
|
||||
{(noStaticOptimization || debug) && <ScoreCardRow
|
||||
|
||||
@ -111,8 +111,8 @@ export const HAS_DATABASE =
|
||||
export const POSTGRES_SSL_ENABLED =
|
||||
process.env.DISABLE_POSTGRES_SSL === '1' ? false : true;
|
||||
|
||||
// STORAGE: VERCEL KV
|
||||
export const HAS_VERCEL_KV =
|
||||
// STORAGE: REDIS
|
||||
export const HAS_REDIS_STORAGE =
|
||||
Boolean(process.env.KV_URL);
|
||||
|
||||
// STORAGE: VERCEL BLOB
|
||||
@ -261,7 +261,7 @@ export const APP_CONFIGURATION = {
|
||||
/\/verceldb\?/.test(process.env.POSTGRES_URL ?? '') ||
|
||||
/\.vercel-storage\.com\//.test(process.env.POSTGRES_URL ?? '')
|
||||
),
|
||||
hasVercelKv: HAS_VERCEL_KV,
|
||||
hasRedisStorage: HAS_REDIS_STORAGE,
|
||||
hasVercelBlobStorage: HAS_VERCEL_BLOB_STORAGE,
|
||||
hasCloudflareR2Storage: HAS_CLOUDFLARE_R2_STORAGE,
|
||||
hasAwsS3Storage: HAS_AWS_S3_STORAGE,
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
import { kv } from '@vercel/kv';
|
||||
|
||||
export const testKvConnection = () => kv.get('test');
|
||||
@ -1,12 +1,17 @@
|
||||
import { generateText, streamText } from 'ai';
|
||||
import { createStreamableValue } from 'ai/rsc';
|
||||
import { createOpenAI } from '@ai-sdk/openai';
|
||||
import { kv } from '@vercel/kv';
|
||||
import { Redis } from '@upstash/redis';
|
||||
import { Ratelimit } from '@upstash/ratelimit';
|
||||
import { AI_TEXT_GENERATION_ENABLED, HAS_VERCEL_KV } from '@/app-core/config';
|
||||
import {
|
||||
AI_TEXT_GENERATION_ENABLED,
|
||||
HAS_REDIS_STORAGE,
|
||||
} from '@/app-core/config';
|
||||
import { removeBase64Prefix } from '@/utility/image';
|
||||
import { cleanUpAiTextResponse } from '@/photo/ai';
|
||||
|
||||
const redis = Redis.fromEnv();
|
||||
|
||||
const RATE_LIMIT_IDENTIFIER = 'openai-image-query';
|
||||
const RATE_LIMIT_MAX_QUERIES_PER_HOUR = 100;
|
||||
const MODEL = 'gpt-4o';
|
||||
@ -15,9 +20,9 @@ const openai = AI_TEXT_GENERATION_ENABLED
|
||||
? createOpenAI({ apiKey: process.env.OPENAI_SECRET_KEY })
|
||||
: undefined;
|
||||
|
||||
const ratelimit = HAS_VERCEL_KV
|
||||
const ratelimit = HAS_REDIS_STORAGE
|
||||
? new Ratelimit({
|
||||
redis: kv,
|
||||
redis,
|
||||
limiter: Ratelimit.slidingWindow(RATE_LIMIT_MAX_QUERIES_PER_HOUR, '1h'),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
5
src/platforms/redis.ts
Normal file
5
src/platforms/redis.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Redis } from '@upstash/redis';
|
||||
|
||||
const redis = Redis.fromEnv();
|
||||
|
||||
export const testRedisConnection = () => redis.get('test');
|
||||
Loading…
Reference in New Issue
Block a user