#how might I implement a high-performance view counter in Convex
6 messages · Page 1 of 1 (latest)
Hi @fair seal. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
For implementing a high-performance view counter in Convex with debouncing and uniqueness filtering, I recommend using the Sharded Counter component, which is specifically designed for high-throughput counting scenarios.
Using Sharded Counter Component
The Sharded Counter component in Convex is perfect for your use case as it:
- Distributes counts across multiple "shards" to handle high throughput
- Provides atomic increment/decrement operations
- Maintains consistency while scaling
Here's how to implement it:
1. Install the component
npm install @convex-dev/sharded-counter
2. Configure in your Convex project
Create a convex.config.ts file in your convex/ folder:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import shardedCounter from "@convex-dev/sharded-counter/convex.config";
const app = defineApp();
app.use(shardedCounter);
export default app;
3. Create a ShardedCounter instance
import { components } from "./_generated/api";
import { ShardedCounter } from "@convex-dev/sharded-counter";
const viewCounter = new ShardedCounter(components.shardedCounter);
4. Implement debouncing and uniqueness filtering
To handle the debouncing and uniqueness filtering (similar to what you'd do with Redis), you can create a table to track recent views and implement a mutation that checks this table before incrementing the counter:
// Track the last time a user viewed a product
export const recordView = mutation({
args: {
productId: v.string(),
userId: v.string(),
// You could also use IP or fingerprint
},
handler: async (ctx, args) => {
const { productId, userId } = args;
// Check if this user has viewed this product recently
const recentView = await ctx.db
.query("productViews")
.withIndex("by_user_product", (q) =>
q.eq("userId", userId).eq("productId", productId)
)
.order("desc")
.first();
const now = Date.now();
const cooldownPeriod = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
// If no recent view or cooldown period has passed
if (!recentView || (now - recentView.timestamp > cooldownPeriod)) {
// Record this view
await ctx.db.insert("productViews", {
userId,
productId,
timestamp: now
});
// Increment the view counter for this product
await viewCounter.inc(ctx, productId);
return true; // View counted
}
return false; // View not counted (within cooldown)
}
});
// Query to get the view count for a product
export const getViewCount = query({
args: { productId: v.string() },
handler: async (ctx, args) => {
return await viewCounter.count(ctx, args.productId);
}
});
5. Define your schema
Don't forget to define your schema for the productViews table:
// convex/schema.ts
export default defineSchema({
productViews: defineTable({
userId: v.string(),
productId: v.string(),
timestamp: v.number(),
}).index("by_user_product", ["userId", "productId"]),
// ... other tables
});
Performance Considerations
The Sharded Counter component is designed for high throughput by distributing counts across multiple shards. You can configure the number of shards based on your needs:
const viewCounter = new ShardedCounter(components.shardedCounter, {
shards: {
// For products with very high view rates
"popular-product-id": 100
},
defaultShards: 16, // Default for other products
});
More shards increase throughput for incrementing, while fewer shards improve latency when querying the count Sharding the counter.
Additional Rate Limiting
If you need more sophisticated rate limiting, Convex also offers a Rate Limiter component that you could use alongside the Sharded Counter for additional protection against abuse Common application needs.
This approach gives you a high-performance view counter with debouncing and uniqueness filtering, similar to what you might implement with Redis, but fully integrated with Convex's reactive data model.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@split cloud Kapa.ai is still learning and improving, please let me know how I did by reacting below.