Queue & Cache
Persistent FIFO queue and key-value cache with multiple driver backends
Queue
Persistent queue for background processing. Available via ctx.queue.
// Push a single item onto a topic
await ctx.queue.push('email-queue', {
to: 'user@example.com',
subject: 'Hello',
});
// Push multiple items at once
await ctx.queue.pushBulk('email-queue', [
{ to: 'alice@example.com', subject: 'Welcome' },
{ to: 'bob@example.com', subject: 'Welcome' },
]);
// Pop items for processing (FIFO, locked)
const items = await ctx.queue.pop('email-queue', 5);
for (const item of items) {
try {
await sendEmail(item.data);
await ctx.queue.remove([item.id]); // Remove after success
} catch (err) {
// Leave in queue — lock expires, will be retried
}
}Lock Mechanism
When you pop items, they're locked for a cooldown period (default: 5 minutes). During this time, other consumers won't see them. If you don't remove an item, it becomes available again after the lock expires — providing automatic retry behavior.
Namespacing
Create a namespaced queue to isolate topics:
const userQueue = ctx.queue.withNamespace('users');
await userQueue.push('welcome-emails', { userId: 123 });
// Topic becomes 'users:welcome-emails' internallyQueue Drivers
knex(database-backed, default) — uses "available"/"locked" status states in the databaseredis— uses Lua scripts for atomic pop-and-lock operations
Cache
Key-value cache. Available via ctx.cache.
// Set with optional TTL (milliseconds)
await ctx.cache.set('user:123', userData, 3_600_000); // 1 hour
// Get (returns null if missing or expired)
const cached = await ctx.cache.get('user:123');
// Delete
await ctx.cache.delete('user:123');
// Cache-aside helper — get from cache or compute and store
const user = await ctx.cache.wrap(
'user:123',
async () => ctx.db('users').where('id', 123).first(),
'1h',
);Cache-Aside Pattern
wrap() is the recommended way to use caching. It checks the cache first, and if the key is missing (or expired), calls the function, stores the result, and returns it:
export const GET = {
handler: async (ctx) => {
const { cache, db } = ctx;
// Cached for 10 minutes
const stats = await cache.wrap(
'dashboard:stats',
async () => {
const users = await db('users').count('* as count').first();
const posts = await db('posts').count('* as count').first();
return { users: users.count, posts: posts.count };
},
'10m',
);
return { data: stats };
},
};If the cached value is corrupt (invalid JSON), wrap treats it as a cache miss and recomputes.
Namespacing
Create a namespaced cache to avoid key collisions:
const userCache = ctx.cache.withNamespace('users');
await userCache.set('123', userData); // Key becomes 'users:123' internally
await userCache.get('123');Cache Drivers
memory(in-process, default) — fast, single-server only. Usescache-managerinternally.redis(distributed) — shared across multiple servers. Supports indefinite TTL.
// arcway.config.js
export default {
cache: {
driver: 'redis',
redis: {
host: 'localhost',
port: 6379,
},
},
};