import { upstashCache } from "drizzle-orm/cache/upstash";import { drizzle } from "drizzle-orm/...";const db = drizzle(process.env.DB_URL!, { cache: upstashCache(),});
您还可以显式定义您的 Upstash 凭据,默认启用所有查询的全局缓存,或传递自定义缓存选项
import { upstashCache } from "drizzle-orm/cache/upstash";import { drizzle } from "drizzle-orm/...";const db = drizzle(process.env.DB_URL!, { cache: upstashCache({ // 👇 Redis credentials (optional — can also be pulled from env vars) url: '<UPSTASH_URL>', token: '<UPSTASH_TOKEN>', // 👇 Enable caching for all queries by default (optional) global: true, // 👇 Default cache behavior (optional) config: { ex: 60 } })});
缓存配置参考
Drizzle 支持 Upstash 的以下缓存配置选项
export type CacheConfig = { /** * Expiration in seconds (positive integer) */ ex?: number; /** * Set an expiration (TTL or time to live) on one or more fields of a given hash key. * Used for HEXPIRE command */ hexOptions?: "NX" | "nx" | "XX" | "xx" | "GT" | "gt" | "LT" | "lt";};
缓存使用示例
配置缓存后,缓存的行为如下
情况 1:Drizzle 使用 `global: false`(默认,选择性缓存)
import { upstashCache } from "drizzle-orm/cache/upstash";import { drizzle } from "drizzle-orm/...";const db = drizzle(process.env.DB_URL!, { // 👇 `global: true` is not passed, false by default cache: upstashCache({ url: "", token: "" }),});
在这种情况下,以下查询不会从缓存中读取
const res = await db.select().from(users);// Any mutate operation will still trigger the cache's onMutate handler// and attempt to invalidate any cached queries that involved the affected tablesawait db.insert(users).value({ email: "[email protected]" });
要使此查询从缓存中读取,请调用 `.$withCache()`
const res = await db.select().from(users).$withCache();
`.$withCache` 有一组选项,您可以用来管理和配置此特定查询策略
// rewrite the config for this specific query.$withCache({ config: {} })// give this query a custom cache key (instead of hashing query+params under the hood).$withCache({ tag: 'custom_key' })// turn off auto-invalidation for this query// note: this leads to eventual consistency (explained below).$withCache({ autoInvalidate: false })
import { upstashCache } from "drizzle-orm/cache/upstash";import { drizzle } from "drizzle-orm/...";const db = drizzle(process.env.DB_URL!, { cache: upstashCache({ url: "", token: "", global: true }),});
在这种情况下,以下查询将从缓存中读取
const res = await db.select().from(users);
如果要为此特定查询禁用缓存,请调用 `.$withCache(false)`
// disable cache for this queryconst res = await db.select().from(users).$withCache(false);
您还可以使用来自 `db` 的缓存实例来使特定表或标签失效
// Invalidate all queries that use the `users` table. You can do this with the Drizzle instance.await db.$cache.invalidate({ tables: users });// orawait db.$cache.invalidate({ tables: [users, posts] });// Invalidate all queries that use the `usersTable`. You can do this by using just the table name.await db.$cache.invalidate({ tables: "usersTable" });// orawait db.$cache.invalidate({ tables: ["usersTable", "postsTable"] });// You can also invalidate custom tags defined in any previously executed select queries.await db.$cache.invalidate({ tags: "custom_key" });// orawait db.$cache.invalidate({ tags: ["custom_key", "custom_key1"] });
export type CacheConfig = { /** expire time, in seconds */ ex?: number; /** expire time, in milliseconds */ px?: number; /** Unix time (sec) at which the key will expire */ exat?: number; /** Unix time (ms) at which the key will expire */ pxat?: number; /** retain existing TTL when updating a key */ keepTtl?: boolean; /** options for HEXPIRE (hash-field TTL) */ hexOptions?: 'NX' | 'XX' | 'GT' | 'LT' | 'nx' | 'xx' | 'gt' | 'lt';};
const db = drizzle(process.env.DB_URL!, { cache: new TestGlobalCache() });
import Keyv from "keyv";export class TestGlobalCache extends Cache { private globalTtl: number = 1000; // This object will be used to store which query keys were used // for a specific table, so we can later use it for invalidation. private usedTablesPerKey: Record<string, string[]> = {}; constructor(private kv: Keyv = new Keyv()) { super(); } // For the strategy, we have two options: // - 'explicit': The cache is used only when .$withCache() is added to a query. // - 'all': All queries are cached globally. // The default behavior is 'explicit'. override strategy(): "explicit" | "all" { return "all"; } // This function accepts query and parameters that cached into key param, // allowing you to retrieve response values for this query from the cache. override async get(key: string): Promise<any[] | undefined> { const res = (await this.kv.get(key)) ?? undefined; return res; } // This function accepts several options to define how cached data will be stored: // - 'key': A hashed query and parameters. // - 'response': An array of values returned by Drizzle from the database. // - 'tables': An array of tables involved in the select queries. This information is needed for cache invalidation. // // For example, if a query uses the "users" and "posts" tables, you can store this information. Later, when the app executes // any mutation statements on these tables, you can remove the corresponding key from the cache. // If you're okay with eventual consistency for your queries, you can skip this option. override async put( key: string, response: any, tables: string[], config?: CacheConfig, ): Promise<void> { await this.kv.set(key, response, config ? config.ex : this.globalTtl); for (const table of tables) { const keys = this.usedTablesPerKey[table]; if (keys === undefined) { this.usedTablesPerKey[table] = [key]; } else { keys.push(key); } } } // This function is called when insert, update, or delete statements are executed. // You can either skip this step or invalidate queries that used the affected tables. // // The function receives an object with two keys: // - 'tags': Used for queries labeled with a specific tag, allowing you to invalidate by that tag. // - 'tables': The actual tables affected by the insert, update, or delete statements, // helping you track which tables have changed since the last cache update. override async onMutate(params: { tags: string | string[]; tables: string | string[] | Table<any> | Table<any>[]; }): Promise<void> { const tagsArray = params.tags ? Array.isArray(params.tags) ? params.tags : [params.tags] : []; const tablesArray = params.tables ? Array.isArray(params.tables) ? params.tables : [params.tables] : []; const keysToDelete = new Set<string>(); for (const table of tablesArray) { const tableName = is(table, Table) ? getTableName(table) : (table as string); const keys = this.usedTablesPerKey[tableName] ?? []; for (const key of keys) keysToDelete.add(key); } if (keysToDelete.size > 0 || tagsArray.length > 0) { for (const tag of tagsArray) { await this.kv.delete(tag); } for (const key of keysToDelete) { await this.kv.delete(key); for (const table of tablesArray) { const tableName = is(table, Table) ? getTableName(table) : (table as string); this.usedTablesPerKey[tableName] = []; } } } }}