{"id":17870,"date":"2026-02-12T07:32:16","date_gmt":"2026-02-12T15:32:16","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=17870"},"modified":"2026-02-24T11:16:03","modified_gmt":"2026-02-24T19:16:03","slug":"build-a-rate-limiter-with-couchbase-eventing","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/","title":{"rendered":"Build a Rate Limiter With Couchbase Eventing"},"content":{"rendered":"<h1>Introduction<\/h1>\n<p>Couchbase Server 8.0 introduces a new Eventing function handler called OnDeploy that allows customers to run business logic during Eventing function deployment or resumption without requiring any external mutation to trigger it.<\/p>\n<p>Earlier, customers with use cases that needed to run any logic before an Eventing function deployed or resumed were left with a few choices, such as:<\/p>\n<ol>\n<li>Manually perform the required setup on their own.<\/li>\n<li>Automate the setup via an external script before triggering the Eventing function deployment or resumption.<\/li>\n<\/ol>\n<p>Both these methods are cumbersome and depend on external or manual intervention.<\/p>\n<p>The \u201cdeployment\u201d and \u201cresumption\u201d events in the Eventing function lifecycle mark points where it is about to start processing mutations. This makes the <code style=\"color: #83c694\">OnDeploy<\/code> handler suitable for injecting logic that requires any of the following activities to be performed:<\/p>\n<ol>\n<li>Perform pre-flight checks to ensure that the environment is configured correctly.<\/li>\n<li>Set up caches (e.g., look-up tables) to improve efficiency.<\/li>\n<li>Send, gather, and process data from different Couchbase and external services.<\/li>\n<li>\u201cSelf-trigger\u201d the Eventing function after it deploys\/resumes by modifying at least one document, in its source keyspace.\n<ol type=\"a\">\n<li>This mutation will trigger its <code style=\"color: #83c694\">OnUpdate<\/code> and\/or <code style=\"color: #83c694\">OnDelete<\/code> handler.<\/li>\n<li>This is an advanced use case of <code style=\"color: #83c694\">OnDeploy<\/code> because, traditionally, Eventing function execution had been restricted to trigger only when changes occur in its source keyspace by entities other than the Eventing function itself or by timer expiry.<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<h1>Rate Limiter<\/h1>\n<p>In this post, we will build a robust rate limiter using the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Token_bucket\">token-bucket algorithm<\/a> and Couchbase&#8217;s Eventing service. Along the way, you&#8217;ll get hands-on experience with the new OnDeploy handler and discover how Eventing simplifies integration with other Couchbase services.<\/p>\n<h2>High-Level Design<\/h2>\n<h3>Multi-Dimensional Scaling<\/h3>\n<p><span style=\"font-weight: 400\">The 6-node cluster must have the following services to node mappings:<\/span><\/p>\n<table>\n<thead>\n<tr style=\"background-color: #fbce90\">\n<th>S.No.<\/th>\n<th>Node Number<\/th>\n<th>Service(s)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"background-color: #f3f3f3\">\n<td>1.<\/td>\n<td>0<\/td>\n<td>Data<\/td>\n<\/tr>\n<tr>\n<td>2.<\/td>\n<td>1<\/td>\n<td>Data<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>3.<\/td>\n<td>2<\/td>\n<td>Data<\/td>\n<\/tr>\n<tr>\n<td>4.<\/td>\n<td>3<\/td>\n<td>Eventing, Query<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>5.<\/td>\n<td>4<\/td>\n<td>Eventing, Query<\/td>\n<\/tr>\n<tr>\n<td>6.<\/td>\n<td>5<\/td>\n<td>Indexing<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"padding-top: 15px\">A few points to note about the cluster setup:<\/p>\n<ul style=\"list-style-type: disc\">\n<li>We use 3 Data service nodes to ensure redundancy via data replication.<\/li>\n<li>We run the Eventing service on 2 nodes to increase the Eventing function parallelism.\n<ul style=\"list-style-type: circle;padding: 0px !important\">\n<li>This is done in addition to having multiple workers for our Eventing function.<\/li>\n<\/ul>\n<\/li>\n<li>CPU-intensive services like Data and Eventing must be kept on separate cluster nodes.<\/li>\n<li>We need the Query service because certain operations, such as deleting all documents in a keyspace, can be conveniently performed via the Query service.<\/li>\n<li>We need the Indexing service to create primary indices for the Ephemeral bucket.<\/li>\n<\/ul>\n<h3>Keyspaces<\/h3>\n<p>Our cluster must have the following keyspaces:<\/p>\n<table>\n<tbody>\n<tr>\n<th>S.No.<\/th>\n<th>Bucket Name<\/th>\n<th>Bucket Type<\/th>\n<th>Scope<\/th>\n<th>Collection<\/th>\n<th>Description<\/th>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>1<\/td>\n<td>default<\/td>\n<td>Couchbase<\/td>\n<td>_default<\/td>\n<td>_default<\/td>\n<td>\n<ol>\n<li style=\"font-size: 14px\">Used as the Eventing Function\u2019s \u201cFunction Scope\u201d.<\/li>\n<li style=\"font-size: 14px\">Used for storing the Eventing Functions\u2019 metadata.<\/li>\n<\/ol>\n<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_mobile<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_query<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>rate-limiter<\/td>\n<td>Ephemeral<\/td>\n<td>_default<\/td>\n<td>_default<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_mobile<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_query<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>my-llm<\/td>\n<td>limits<\/td>\n<td>Store the document containing the tier-to-rate limit mapping.<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>my-llm<\/td>\n<td>tracker<\/td>\n<td>Store counter documents to keep track of individual users\u2019 usage.<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>3<\/td>\n<td>my-llm<\/td>\n<td>Couchbase<\/td>\n<td>_default<\/td>\n<td>_default<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_mobile<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>_system<\/td>\n<td>_query<\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>users<\/td>\n<td>accounts<\/td>\n<td>Store the user account details, including their \u201ctier\u201d.<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>users<\/td>\n<td>events<\/td>\n<td>Store the user events that must be rate-limited based on the user\u2019s \u201ctier\u201d.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"padding-bottom: 0px !important\">Note:<\/p>\n<ul>\n<li>The <code style=\"color: #83c694\">rate-limiter<\/code> bucket is <code style=\"color: #83c694\">Ephemeral<\/code> because we don\u2019t need to persist that data. We use the data in that bucket to track per-user rate-limit usage and to cache the tier-to-rate-limit mapping.<\/li>\n<\/ul>\n<h3>External REST API Endpoints<\/h3>\n<p>The Eventing function interacts with external API endpoints that provide the following functionalities:<\/p>\n<ol>\n<li>Provide the latest tier-to-rate-limit mapping.<\/li>\n<li>Accept modifications to the tier-to-rate-limit mapping.<\/li>\n<li>Accept incoming requests that are within the user\u2019s rate limit by our Eventing function.\n<ol type=\"a\">\n<li>In this project, our endpoint will maintain a count of these incoming requests.<br \/>\nThis count will help us verify whether our rate limiter application works as expected.<\/li>\n<\/ol>\n<\/li>\n<li>Provide the number of incoming requests that our Eventing function has deemed to be within the user\u2019s rate limit.<\/li>\n<\/ol>\n<p>The link to the OpenAPI specification of the above API endpoints can be found <a href=\"https:\/\/github.com\/couchbaselabs\/eventing-rate-limiter\/blob\/master\/openapi\/server-api-spec.yaml\">here<\/a>.<\/p>\n<p>Note: The Go program that hosts these REST API endpoints is linked in the Appendix.<\/p>\n<h2>Low-Level Design<\/h2>\n<h3>Eventing Function Setup<\/h3>\n<p>Below is a list of all the changes we need to make to the Eventing function&#8217;s default settings.<\/p>\n<p><strong>Keyspaces<\/strong><\/p>\n<table>\n<thead>\n<tr style=\"background-color: #fbce90\">\n<th>S.No.<\/th>\n<th>Field<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"background-color: #f3f3f3\">\n<td>1.<\/td>\n<td>Function scope<\/td>\n<td>default._default<\/td>\n<\/tr>\n<tr>\n<td>2.<\/td>\n<td>Source Keyspace<\/td>\n<td>my-llm.users.events<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>3.<\/td>\n<td>Eventing Storage Keyspace<\/td>\n<td>default._default._default<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>Settings<\/strong><\/p>\n<table>\n<thead>\n<tr style=\"background-color: #fbce90\">\n<th>S.No.<\/th>\n<th>Field<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"background-color: #f3f3f3\">\n<td>1.<\/td>\n<td>Name<\/td>\n<td>my-llm-rate-limiter<\/td>\n<\/tr>\n<tr>\n<td>2.<\/td>\n<td>Deployment Feed Boundary<\/td>\n<td>From now<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>3.<\/td>\n<td>Description<\/td>\n<td>This Eventing function acts as a rate limiter.<\/td>\n<\/tr>\n<tr>\n<td>4.<\/td>\n<td>Workers<\/td>\n<td>10<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>Bucket Bindings<\/strong><\/p>\n<table>\n<thead>\n<tr>\n<th rowspan=\"2\">S.No.<\/th>\n<th rowspan=\"2\">Bucket<br \/>\nAlias<\/th>\n<th colspan=\"3\">Keyspace<\/th>\n<th rowspan=\"2\">Access<\/th>\n<\/tr>\n<tr>\n<th>Bucket<\/th>\n<th>Scope<\/th>\n<th>Collection<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"background-color: #f3f3f3\">\n<td>1.<\/td>\n<td class=\"left\">UserAccounts<\/td>\n<td>my-llm<\/td>\n<td>users<\/td>\n<td>accounts<\/td>\n<td>Read-only<\/td>\n<\/tr>\n<tr>\n<td>2.<\/td>\n<td class=\"left\">rateLimiter<\/td>\n<td>rate-limiter<\/td>\n<td>my-llm<\/td>\n<td>tracker<\/td>\n<td>Read &amp; Write<\/td>\n<\/tr>\n<tr style=\"background-color: #f3f3f3\">\n<td>3.<\/td>\n<td class=\"left\">tierLimits<\/td>\n<td>rate-limiter<\/td>\n<td>my-llm<\/td>\n<td>limits<\/td>\n<td>Read &amp; Write<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>URL Bindings<\/strong><\/p>\n<table>\n<thead>\n<tr>\n<th class=\"center\">S.No.<\/th>\n<th class=\"left\">URL Alias<\/th>\n<th class=\"left\">URL<\/th>\n<th class=\"center\">Auth<\/th>\n<th class=\"left\">Username<\/th>\n<th class=\"left\">Password<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"background-color: #f3f3f3\">\n<td class=\"center\">1.<\/td>\n<td class=\"left\">llmEndpoint<\/td>\n<td class=\"left nowrap\">http:\/\/localhost:3054\/my-llm<\/td>\n<td class=\"center\" rowspan=\"2\">Basic<\/td>\n<td class=\"left\" rowspan=\"2\">Eventing<\/td>\n<td class=\"left\" rowspan=\"2\">Eventing123<\/td>\n<\/tr>\n<tr>\n<td class=\"center\">2.<\/td>\n<td class=\"left\">tiersEndpoint<\/td>\n<td class=\"left nowrap\">http:\/\/localhost:3054\/tiers<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>Note:<\/strong> Both \u201callow cookies\u201d and \u201cvalidate SSL certificate\u201d options are disabled.<\/p>\n<h3>Complete Application Flow Diagram<\/h3>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Complete-Application-Flow-Diagram.png\" alt=\"Complete Application Flow Diagram\" \/><\/p>\n<p>This diagram shows the interactions among the Eventing function handlers, external REST API endpoints, and keyspaces to behave as a rate limiter based on the token bucket algorithm.<br \/>\nIn the following sections, we will implement the rate limiter step by step.<\/p>\n<h3><code style=\"color: #83c694\">OnDeploy<\/code> Setup<\/h3>\n<h4>Get and Store the Tiers From the External REST API Endpoint<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Get-and-Store-the-Tiers-From-the-External-REST-API-Endpoint.png\" alt=\"Get and Store the Tiers From the External REST API Endpoint\" \/><\/p>\n<p>When the <code style=\"color: #83c694\">OnDeploy<\/code> handler starts executing, it must first get the tiers-to-rate-limit mapping from the external REST API endpoint represented by the <code style=\"color: #83c694\">tiersEndpoint<\/code> URL binding.<br \/>\nThe response from the <code style=\"color: #83c694\">\/tiers<\/code> external REST API will be a JSON value that contains the mapping from tier name (of type <code style=\"color: #83c694\">String<\/code>) to a per-hour rate limit represented by the total count of requests allowed per hour (i.e., total_request_count) (of type <code style=\"color: #83c694\">number<\/code>).<br \/>\nWe store the tiers-to-rate-limit mapping in the <code style=\"color: #83c694\">rate-limit.my-llm.limits<\/code> keyspace.<\/p>\n<pre class=\"lang:default decode:true \">function OnDeploy(action) {\r\n    \/\/ ...\r\n\r\n    \/\/ GET the tiers from the `tiersEndpoint`\r\n    const response = curl('GET', tiersEndpoint);\r\n    if (response.status != 200) {\r\n        throw new Error(\"Error(Cannot get tiers): \" + JSON.stringify(response));\r\n    }\r\n    const tiers = response.body;\r\n    log(\"Successfully retrieved the tiers: \" + JSON.stringify(tiers));\r\n\r\n    \/\/ Write the tiers to the `tierLimits` keyspace, in the document with ID `limits`\r\n    tierLimits[\"limits\"] = tiers;\r\n\r\n    \/\/ ...\r\n\r\n    \/\/ Create a timer to run every 24 hours to refresh the tiers\r\n    let timeAfter24hours = new Date();\r\n    timeAfter24hours.setDate(timeAfter24hours.getDate() + 1);\r\n    log(\"Time after 24 hours is: \" + timeAfter24hours);\r\n\r\n    createTimer(updateTierCallback, timeAfter24hours, \"tier-updater\", {});\r\n\r\n   \/\/ ...\r\n}\r\n\r\n\/\/ Function to update the user tiers every 24 hours\r\nfunction updateTierCallback(context) {\r\n    log('From updateTierCallback: timer fired');\r\n\r\n    \/\/ GET the tiers from the `tiersEndpoint`\r\n    const response = curl('GET', tiersEndpoint);\r\n    if (response.status != 200) {\r\n        log(\"Error(Cannot get tiers): \" + JSON.stringify(response));\r\n    } else {\r\n        const tiers = response.body;\r\n        log(\"Successfully retrieved the tiers: \" + JSON.stringify(tiers));\r\n\r\n        \/\/ Write the tiers to the `tierLimits` keyspace, in the document with ID `limits`\r\n        tierLimits[\"limits\"] = tiers;\r\n    }\r\n\r\n    \/\/ Create a timer to run every 24 hours to refresh the tiers\r\n    let timeAfter24hours = new Date();\r\n    timeAfter24hours.setDate(timeAfter24hours.getDate() + 1);\r\n    log(\"Time after 24 hours is: \" + timeAfter24hours);\r\n\r\n    createTimer(updateTierCallback, timeAfter24hours, \"tier-updater\", {});\r\n}\r\n<\/pre>\n<h4>Reset All Rate Limit Trackers When Deploying<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Reset-All-Rate-Limit-Trackers-When-Deploying.png\" alt=\"Reset All Rate Limit Trackers When Deploying\" \/><\/p>\n<p>In our application, we model Eventing function undeployment as a hard shutdown; hence, during its deployment, we delete all documents that track users\u2019 rate limit usages. We model pause as a temporary suspension of rate-limiting activities; hence, we do not clear those documents in case of Eventing function resumption.<\/p>\n<p>Notice how we treat deployment and resumption operations separately? <code style=\"color: #83c694\">OnDeploy<\/code> makes such use cases possible because Eventing also passes a <code style=\"color: #83c694\">reason<\/code> field in the <code style=\"color: #83c694\">action<\/code> object to the <code style=\"color: #83c694\">OnDeploy<\/code> handler to specify whether the Eventing function is deploying or resuming.<\/p>\n<pre class=\"lang:default decode:true \">function OnDeploy(action) {\r\n    \/\/ ...\r\n \r\n    \/\/ If we are deploying, then we should delete all the existing document in the keyspace `rateLimiter`\r\n    if (action.reason === \"deploy\") {\r\n        let results = N1QL(\"DELETE FROM `rate-limiter`.`my-llm`.tracker\");\r\n        results.close();\r\n        log(\"Deleted all the documents in the `rate-limiter`.`my-llm`.tracker keyspace as we are deploying!\");\r\n    }\r\n\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<h4>Reset the Users\u2019 Rate Limits Every Hour<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Reset-the-Users-Rate-Limits-Every-Hour.png\" alt=\"Reset the Users\u2019 Rate Limits Every Hour\" \/><\/p>\n<p>Given that we are implementing a token-bucket algorithm, we reset the users\u2019 rate limits every hour using timers \u2013 an Eventing functionality that is critical for our use case. We create the first timer in the <code style=\"color: #83c694\">OnDeploy<\/code> handler to fire after one hour. Once the timer-callback fires, it will create a new timer to fire after one hour, and so on, creating a self-recurring timer that fires every hour as long as the Eventing function is deployed.<\/p>\n<p>Observe that this timer did not require any external mutation to trigger the Eventing function to create it. This was all done during deployment\/resumption in the <code style=\"color: #83c694\">OnDeploy<\/code> handler.<\/p>\n<pre class=\"lang:default decode:true \">function OnDeploy(action) {\r\n    \/\/ ...\r\n\r\n    \/\/ Create a timer to run every 1 hour to reset user rate limits\r\n    let timeAfter1Hour = new Date();\r\n    timeAfter1Hour.setHours(timeAfter1Hour.getHours() + 1);\r\n    log(\"Time after 1 hour is: \" + timeAfter1Hour);\r\n\r\n    createTimer(resetRateLimiter, timeAfter1Hour, \"rate-limit-resetter\", {});\r\n\r\n    \/\/ ...\r\n}\r\n\r\n\/\/ Function to reset the rate limits for all users every 1 hour\r\nfunction resetRateLimiter(context) {\r\n    log('From resetRateLimiter: timer fired');\r\n\r\n    let results = N1QL(\"DELETE FROM `rate-limiter`.`my-llm`.tracker\");\r\n    results.close();\r\n\r\n    \/\/ Create a timer to run every 1 hour to reset user rate limits\r\n    let timeAfter1Hour = new Date();\r\n    timeAfter1Hour.setHours(timeAfter1Hour.getHours() + 1);\r\n    log(\"Time after 1 hour is: \" + timeAfter1Hour);\r\n\r\n    createTimer(resetRateLimiter, timeAfter1Hour, \"rate-limit-resetter\", {});\r\n}\r\n<\/pre>\n<h4>Refresh Tier Rate Limits Daily<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Refresh-Tier-Rate-Limits-Daily.png\" alt=\"Refresh Tier Rate Limits Daily\" \/><\/p>\n<p>We model our application to allow rate limit changes every 24 hours; hence, our Eventing function must pull the latest tier-to-rate-limit mapping from the external REST API endpoint every 24 hours to ensure the correct rate limits are applied to our users.<\/p>\n<p>Again, we use self-recurring timers to get the latest tier-to-rate-limit mapping every 24 hours.<\/p>\n<pre class=\"lang:default decode:true \">function OnDeploy(action) {\r\n    \/\/ ...\r\n\r\n    \/\/ Create a timer to run every 24 hours to refresh the tiers\r\n    let timeAfter24hours = new Date();\r\n    timeAfter24hours.setDate(timeAfter24hours.getDate() + 1);\r\n    log(\"Time after 24 hours is: \" + timeAfter24hours);\r\n\r\n    createTimer(updateTierCallback, timeAfter24hours, \"tier-updater\", {});\r\n\r\n    \/\/ ...\r\n}\r\n\r\n\/\/ Function to update the user tiers every 24 hours\r\nfunction updateTierCallback(context) {\r\n    log('From updateTierCallback: timer fired');\r\n\r\n    \/\/ GET the tiers from the `tiersEndpoint`\r\n    const response = curl('GET', tiersEndpoint);\r\n    if (response.status != 200) {\r\n        log(\"Error(Cannot get tiers): \" + JSON.stringify(response));\r\n    } else {\r\n        const tiers = response.body;\r\n        log(\"Successfully retrieved the tiers: \" + JSON.stringify(tiers));\r\n\r\n        \/\/ Write the tiers to the `tierLimits` keyspace, in the document with ID `limits`\r\n        tierLimits[\"limits\"] = tiers;\r\n    }\r\n\r\n    \/\/ Create a timer to run every 24 hours to refresh the tiers\r\n    let timeAfter24hours = new Date();\r\n    timeAfter24hours.setDate(timeAfter24hours.getDate() + 1);\r\n    log(\"Time after 24 hours is: \" + timeAfter24hours);\r\n\r\n    createTimer(updateTierCallback, timeAfter24hours, \"tier-updater\", {});\r\n}\r\n<\/pre>\n<h3><code style=\"color: #83c694\">OnUpdate<\/code> Setup<\/h3>\n<h4>Handling User Events<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Handling-User-Events.png\" alt=\"Handling User Events\" \/><\/p>\n<p>Our application will listen to incoming request documents from the <code style=\"color: #83c694\">my-llm.users.events<\/code> keyspace. These documents have a unique ID and contain data in the format:<\/p>\n<pre class=\"lang:default decode:true \">{\r\n    \"user_id\": String,\r\n    \"respond_to\": String,\r\n    \"payload\": String,\r\n    \"header\": String\r\n}\r\n<\/pre>\n<p>If the user\u2019s request is within their rate limit, all the data in the document except the user_id will be sent to the endpoint protected by our rate limiter.<\/p>\n<h4>Reading the User\u2019s Tier<\/h4>\n<p><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Reading-the-Users-Tier.png\" alt=\"Reading the User\u2019s Tier\" \/><br \/>\nWhen the <code style=\"color: #83c694\">OnUpdate<\/code> handler is triggered by an incoming user event document from the previous step, we must extract the <code style=\"color: #83c694\">user_id<\/code> field from it.<br \/>\nUsing the <code style=\"color: #83c694\">user_id<\/code> field, we\u2019ll retrieve the user\u2019s account details document from the <code style=\"color: #83c694\">my-llm.users.accounts<\/code> keyspace. From this document, we\u2019ll extract the value of the <code style=\"color: #83c694\">tier<\/code> field.<\/p>\n<pre class=\"lang:default decode:true \">function OnUpdate(doc, meta, xattrs) {\r\n    \/\/ ...\r\n\r\n    const user_id = doc.user_id;\r\n\r\n    let done = false;\r\n    while (!done) {\r\n        \/\/ Get the tier of the `user_id`\r\n        let userAccountsMeta = {\r\n            \"id\": user_id\r\n        };\r\n        let userAccountsResult = couchbase.get(userAccounts, userAccountsMeta, {\r\n            \"cache\": true\r\n        });\r\n        if (!userAccountsResult.success) {\r\n            throw new Error(\"Error(Unable to get the user's details): \" + JSON.stringify(userAccountsResult));\r\n        }\r\n        const tier = userAccountsResult.doc.tier;\r\n\r\n        \/\/ ...\r\n    }\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<h4>Reading the Tier\u2019s Rate Limits<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Reading-the-Tiers-Rate-Limits.png\" alt=\"Reading the Tier\u2019s Rate Limits\" \/><\/p>\n<p>We get the rate limits for the user\u2019s tier from the document that contains the tiers-to-rate-limits mapping, located in the <code style=\"color: #83c694\">rate-limit.my-llm.limits<\/code> keyspace.<\/p>\n<pre class=\"lang:default decode:true \">function OnUpdate(doc, meta, xattrs) {\r\n    \/\/ ...\r\n\r\n    while (!done) {\r\n        \/\/ ...\r\n\r\n        \/\/ Get the rate limit for the tier\r\n        let tierLimitsMeta = {\r\n            \"id\": \"limits\"\r\n        };\r\n        let tierLimitsResult = couchbase.get(tierLimits, tierLimitsMeta, {\r\n            \"cache\": true\r\n        });\r\n        if (!tierLimitsResult.success) {\r\n            throw new Error(\"Error(Unable to get the tier limits): \" + JSON.stringify(tierLimitsResult));\r\n        }\r\n        const rateLimit = tierLimitsResult.doc[tier];\r\n\r\n        \/\/ ...\r\n    }\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<h4>Decide Whether to Rate Limit the Request &amp; Update the User\u2019s Rate Limit Usage<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Decide-Whether-to-Rate-Limit-the-Request-Update-the-Users-Rate-Limit-Usage.png\" alt=\"Decide Whether to Rate Limit the Request &amp; Update the User\u2019s Rate Limit Usage\" \/><\/p>\n<p>Given the user&#8217;s rate limit, we now check their current usage to decide if they can make a request. The rate limiter tracks each user&#8217;s usage with a counter document in the <code style=\"color: #83c694\">rate-limit.my-llm.tracker<\/code> keyspace. We create this counter document on demand for each <code style=\"color: #83c694\">user_id<\/code> to store that user&#8217;s request count for the current window, before the token bucket limit is refreshed. If a user&#8217;s usage meets or exceeds their tier&#8217;s limit, we block their request. Otherwise, we forward it to the protected endpoint. Finally, we update the user\u2019s rate-limit usage in their corresponding counter document in the <code style=\"color: #83c694\">rate-limit.my-llm.tracker<\/code> keyspace.<\/p>\n<pre class=\"lang:default decode:true \">function OnUpdate(doc, meta, xattrs) {\r\n    \/\/ ...\r\n\r\n    while (!done) {\r\n        \/\/ ...\r\n\r\n        \/\/ Try to get the rate limit count for the `user_id`\r\n        const userIDMeta = {\r\n            \"id\": user_id\r\n        };\r\n        const result = couchbase.get(rateLimiter, userIDMeta);\r\n\r\n        \/\/ If the rate limit count for the `user_id` does not exist. Try to create it.\r\n        while (!result.success) {\r\n            couchbase.insert(rateLimiter, userIDMeta, {\r\n                \"count\": 0\r\n            });\r\n            result = couchbase.get(rateLimiter, userIDMeta);\r\n        }\r\n\r\n        \/\/ Assign the counter document's `count` and `meta` to local variables for convenience\r\n        const counterDocCount = result.doc.count;\r\n        const counterDocMeta = result.meta;\r\n\r\n        \/\/ Check if the counter has hit the rate limit\r\n        \/\/ We use &gt;= instead of == to handle the edge case where the tier limits have reduced\r\n        \/\/ but the tier tracker documents have not yet been deleted.\r\n        if (counterDocCount &gt;= rateLimit) {\r\n            log(\"User with ID '\" + user_id + \"' hit their rate limit of \" + rateLimit + \"!\");\r\n            done = true;\r\n            continue;\r\n        }\r\n\r\n        \/\/ Update the count in the document\r\n        let res = couchbase.mutateIn(rateLimiter, counterDocMeta, [\r\n            couchbase.MutateInSpec.replace(\"count\", counterDocCount + 1),\r\n        ]);\r\n        \/\/ ...\r\n    }\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<h4>Send the \u201cwithin the limit\u201d Request to the Desired Endpoint<\/h4>\n<p><img decoding=\"async\" style=\"border: 1px solid\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Send-the-within-the-limit-Request-to-the-Desired-Endpoint.png\" alt=\"Send the \u201cwithin the limit\u201d Request to the Desired Endpoint\" \/><\/p>\n<p>User requests, within their corresponding tier\u2019s rate limits, are sent to the REST API endpoint protected by our rate limiter.<\/p>\n<pre class=\"lang:default decode:true \">function OnUpdate(doc, meta, xattrs) {\r\n    \/\/ ...\r\n\r\n    while (!done) {\r\n        \/\/ ...\r\n\r\n        done = res.success;\r\n        if (done) {\r\n            \/\/ POST the request to the `llmEndpoint`\r\n            delete doc.user_id;\r\n            const response = curl('POST', llmEndpoint, doc);\r\n            if (response.status != 200) {\r\n                throw new Error(\"Error(MyLLM endpoint is not working): \" + response.status);\r\n            }\r\n        }\r\n    }\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<h1>Testing Our Application<\/h1>\n<p>Now that we have implemented our rate limiter, we can create the environment to run and test it:<\/p>\n<ol>\n<li>Run the Go program to load a sample set of 100 users.<\/li>\n<li>Run the Go program to start the HTTP server that provides the external REST APIs that our Eventing function interacts with.<\/li>\n<li>Deploy the Eventing function.<\/li>\n<li>To trigger the Eventing function, we must run the Go program to load user event documents into its source keyspace, i.e., <code style=\"color: #83c694\">my-llm.users.events<\/code>.<\/li>\n<li>To get the number of user requests that reach the external REST API endpoint protected by our rate limiter, you must send a <code style=\"color: #83c694\">GET<\/code> request to the <code style=\"color: #83c694\">\/my-llm<\/code> endpoint.<\/li>\n<\/ol>\n<h1>Conclusion<\/h1>\n<p>This post showed how to use the new Couchbase Eventing handler, <code style=\"color: #83c694\">OnDeploy<\/code>, to build a token-bucket rate limiter \u2013 highlighting the power and flexibility of Couchbase Eventing for developing integrated, standalone solutions.<br \/>\nMore broadly, it demonstrates a shift in application development: building applications from the database itself. This enables tailored solutions to diverse requirements \u2013 all within the Couchbase platform.<\/p>\n<h1>Appendix<\/h1>\n<p>Complete Eventing code: <a href=\"https:\/\/github.com\/couchbaselabs\/eventing-rate-limiter\/blob\/master\/eventing\/eventing-function.js\">Click Here<\/a><br \/>\nServer Go Code: <a href=\"https:\/\/github.com\/couchbaselabs\/eventing-rate-limiter\/blob\/master\/server\/server.go\">Click Here<\/a><br \/>\nClient Go Code: <a href=\"https:\/\/github.com\/couchbaselabs\/eventing-rate-limiter\/blob\/master\/event-generator\/event-generator.go\">Click Here<\/a><br \/>\nUser Loader Go Code: <a href=\"https:\/\/github.com\/couchbaselabs\/eventing-rate-limiter\/blob\/master\/user-loader\/user-loader.go\">Click Here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Couchbase Server 8.0 introduces a new Eventing function handler called OnDeploy that allows customers to run business logic during Eventing function deployment or resumption without requiring any external mutation to trigger it. Earlier, customers with use cases that needed [&hellip;]<\/p>\n","protected":false},"author":85695,"featured_media":17881,"comment_status":"open","ping_status":"open","sticky":true,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1816],"tags":[],"ppma_author":[10171],"class_list":["post-17870","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-couchbase-server"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.8 (Yoast SEO v26.8) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build a Rate Limiter With Couchbase Eventing - The Couchbase Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a Rate Limiter With Couchbase Eventing\" \/>\n<meta property=\"og:description\" content=\"Introduction Couchbase Server 8.0 introduces a new Eventing function handler called OnDeploy that allows customers to run business logic during Eventing function deployment or resumption without requiring any external mutation to trigger it. Earlier, customers with use cases that needed [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-12T15:32:16+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-24T19:16:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2400\" \/>\n\t<meta property=\"og:image:height\" content=\"1256\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Rishit Chaudhary, Software Engineer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Rishit Chaudhary, Software Engineer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\"},\"author\":{\"name\":\"Rishit Chaudhary, Software Engineer\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/a63068d4f974ee8c1df6808fd7ad0d7d\"},\"headline\":\"Build a Rate Limiter With Couchbase Eventing\",\"datePublished\":\"2026-02-12T15:32:16+00:00\",\"dateModified\":\"2026-02-24T19:16:03+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\"},\"wordCount\":1657,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png\",\"articleSection\":[\"Couchbase Server\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\",\"name\":\"Build a Rate Limiter With Couchbase Eventing - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png\",\"datePublished\":\"2026-02-12T15:32:16+00:00\",\"dateModified\":\"2026-02-24T19:16:03+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png\",\"width\":2400,\"height\":1256},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build a Rate Limiter With Couchbase Eventing\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/a63068d4f974ee8c1df6808fd7ad0d7d\",\"name\":\"Rishit Chaudhary, Software Engineer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/af670362d7fe103aec32309e743e0794\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg\",\"caption\":\"Rishit Chaudhary, Software Engineer\"},\"url\":\"https:\/\/www.couchbase.com\/blog\/author\/rishitchaudhary\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build a Rate Limiter With Couchbase Eventing - The Couchbase Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/","og_locale":"en_US","og_type":"article","og_title":"Build a Rate Limiter With Couchbase Eventing","og_description":"Introduction Couchbase Server 8.0 introduces a new Eventing function handler called OnDeploy that allows customers to run business logic during Eventing function deployment or resumption without requiring any external mutation to trigger it. Earlier, customers with use cases that needed [&hellip;]","og_url":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/","og_site_name":"The Couchbase Blog","article_published_time":"2026-02-12T15:32:16+00:00","article_modified_time":"2026-02-24T19:16:03+00:00","og_image":[{"width":2400,"height":1256,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png","type":"image\/png"}],"author":"Rishit Chaudhary, Software Engineer","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Rishit Chaudhary, Software Engineer","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/"},"author":{"name":"Rishit Chaudhary, Software Engineer","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/a63068d4f974ee8c1df6808fd7ad0d7d"},"headline":"Build a Rate Limiter With Couchbase Eventing","datePublished":"2026-02-12T15:32:16+00:00","dateModified":"2026-02-24T19:16:03+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/"},"wordCount":1657,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png","articleSection":["Couchbase Server"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/","url":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/","name":"Build a Rate Limiter With Couchbase Eventing - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png","datePublished":"2026-02-12T15:32:16+00:00","dateModified":"2026-02-24T19:16:03+00:00","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/02\/Build-a-Rate-Limiter-With-Couchbase-Eventing.png","width":2400,"height":1256},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/build-a-rate-limiter-with-couchbase-eventing\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Build a Rate Limiter With Couchbase Eventing"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/a63068d4f974ee8c1df6808fd7ad0d7d","name":"Rishit Chaudhary, Software Engineer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/af670362d7fe103aec32309e743e0794","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg","caption":"Rishit Chaudhary, Software Engineer"},"url":"https:\/\/www.couchbase.com\/blog\/author\/rishitchaudhary\/"}]}},"authors":[{"term_id":10171,"user_id":85695,"is_guest":0,"slug":"rishitchaudhary","display_name":"Rishit Chaudhary, Software Engineer","avatar_url":{"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg","url2x":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2026\/01\/Rishit-Chaudhary.jpeg"},"0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/17870","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/85695"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=17870"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/17870\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/17881"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=17870"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=17870"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=17870"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=17870"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}