Servidor Couchbase

Build a Rate Limiter With Couchbase Eventing

Introdução

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 to run any logic before an Eventing function deployed or resumed were left with a few choices, such as:

  1. Manually perform the required setup on their own.
  2. Automate the setup via an external script before triggering the Eventing function deployment or resumption.

Both these methods are cumbersome and depend on external or manual intervention.

The “deployment” and “resumption” events in the Eventing function lifecycle mark points where it is about to start processing mutations. This makes the OnDeploy handler suitable for injecting logic that requires any of the following activities to be performed:

  1. Perform pre-flight checks to ensure that the environment is configured correctly.
  2. Set up caches (e.g., look-up tables) to improve efficiency.
  3. Send, gather, and process data from different Couchbase and external services.
  4. “Self-trigger” the Eventing function after it deploys/resumes by modifying at least one document, in its source keyspace.
    1. This mutation will trigger its Sobre a atualização e/ou OnDelete handler.
    2. This is an advanced use case of OnDeploy 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.

Rate Limiter

In this post, we will build a robust rate limiter using the token-bucket algorithm and Couchbase’s Eventing service. Along the way, you’ll get hands-on experience with the new OnDeploy handler and discover how Eventing simplifies integration with other Couchbase services.

High-Level Design

Dimensionamento multidimensional

The 6-node cluster must have the following services to node mappings:

table {
border-collapse: collapse;
width: 60%;
}
th, td {
border: 1px solid #000;
padding: 8px;
text-align: left;
}

S.No. Node Number Service(s)
1. 0 Dados
2. 1 Dados
3. 2 Dados
4. 3 Eventing, Query
5. 4 Eventing, Query
6. 5 Indexação

A few points to note about the cluster setup:

  • We use 3 Data service nodes to ensure redundancy via data replication.
  • We run the Eventing service on 2 nodes to increase the Eventing function parallelism.
    • This is done in addition to having multiple workers for our Eventing function.
  • CPU-intensive services like Data and Eventing must be kept on separate cluster nodes.
  • We need the Query service because certain operations, such as deleting all documents in a keyspace, can be conveniently performed via the Query service.
  • We need the Indexing service to create primary indices for the Ephemeral bucket.

Keyspaces

Our cluster must have the following keyspaces:

table {
border-collapse: collapse;
width: 100%;
margin: 20px 0;
}
th, td {
border: 1px solid #000;
padding: 10px;
text-align: left;
vertical-align: top;
}
th {
background-color: #d9d9d9;
font-weight: 600;
}

.group-header {
background-color: #fbce90;
font-weight: bold;
}

S.No. Nome do balde Tipo de caçamba Escopo Coleção Descrição
1 padrão Couchbase Padrão Padrão
  1. Used as the Eventing Function’s “Function Scope”.
  2. Used for storing the Eventing Functions’ metadata.
_system _mobile
_system _query
2 rate-limiter Efêmera Padrão Padrão
_system _mobile
_system _query
my-llm limites Store the document containing the tier-to-rate limit mapping.
my-llm tracker Store counter documents to keep track of individual users’ usage.
3 my-llm Couchbase Padrão Padrão
_system _mobile
_system _query
usuários contas Store the user account details, including their “tier”.
usuários eventos Store the user events that must be rate-limited based on the user’s “tier”.

Observação:

  • O rate-limiter bucket is Efêmera because we don’t 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.

External REST API Endpoints

The Eventing function interacts with external API endpoints that provide the following functionalities:

  1. Provide the latest tier-to-rate-limit mapping.
  2. Accept modifications to the tier-to-rate-limit mapping.
  3. Accept incoming requests that are within the user’s rate limit by our Eventing function.
    1. In this project, our endpoint will maintain a count of these incoming requests.
      This count will help us verify whether our rate limiter application works as expected.
  4. Provide the number of incoming requests that our Eventing function has deemed to be within the user’s rate limit.

The link to the OpenAPI specification of the above API endpoints can be found aqui.

Note: The Go program that hosts these REST API endpoints is linked in the Appendix.

Low-Level Design

Eventing Function Setup

Below is a list of all the changes we need to make to the Eventing function’s default settings.

Keyspaces

table {
border-collapse: collapse;
width: 60%;
}
th, td {
border: 1px solid #000;
padding: 8px;
text-align: left;
}

S.No. Campo Valor
1. Function scope default._default
2. Source Keyspace my-llm.users.events
3. Eventing Storage Keyspace default._default._default

Configurações

table {
border-collapse: collapse;
width: 60%;
}
th, td {
border: 1px solid #000;
padding: 8px;
text-align: left;
}

S.No. Campo Valor
1. Nome my-llm-rate-limiter
2. Deployment Feed Boundary A partir de agora
3. Descrição This Eventing function acts as a rate limiter.
4. Workers 10

Bucket Bindings

table {
border-collapse:collpase;
width:100%;
max-width:900px;}
th,td {
border: 1px solid #000;
padding: 8px 12px;
text-align:center;
}
thead th {
background-color: #d9d9d9;
font-weight:bold;
}
.left {
text-align:left;
}

S.No. Balde
Apelido
Espaço-chave Acesso
Balde Escopo Coleção
1. UserAccounts my-llm usuários contas Read-only
2. rateLimiter rate-limiter my-llm tracker Read & Write
3. tierLimits rate-limiter my-llm limites Read & Write

URL Bindings

table {
border-collapse:collapse;
width:880px;
}

.left {
text-align:left;
}

.nowrap {
white-space: nowrap;
}

S.No. URL Alias URL Auth Nome de usuário Senha
1. llmEndpoint http://localhost:3054/my-llm Básico Eventos Eventing123
2. tiersEndpoint http://localhost:3054/tiers

Observação: Both “allow cookies” and “validate SSL certificate” options are disabled.

Complete Application Flow Diagram

Complete Application Flow Diagram

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.
In the following sections, we will implement the rate limiter step by step.

OnDeploy Configuração

Get and Store the Tiers From the External REST API Endpoint

Get and Store the Tiers From the External REST API Endpoint

Quando o OnDeploy handler starts executing, it must first get the tiers-to-rate-limit mapping from the external REST API endpoint represented by the tiersEndpoint URL binding.
The response from the /tiers external REST API will be a JSON value that contains the mapping from tier name (of type Cordas) to a per-hour rate limit represented by the total count of requests allowed per hour (i.e., total_request_count) (of type número).
We store the tiers-to-rate-limit mapping in the rate-limit.my-llm.limits keyspace.

Reset All Rate Limit Trackers When Deploying

Reset All Rate Limit Trackers When Deploying

In our application, we model Eventing function undeployment as a hard shutdown; hence, during its deployment, we delete all documents that track users’ 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.

Notice how we treat deployment and resumption operations separately? OnDeploy makes such use cases possible because Eventing also passes a razão no campo ação object to the OnDeploy handler to specify whether the Eventing function is deploying or resuming.

Reset the Users’ Rate Limits Every Hour

Reset the Users’ Rate Limits Every Hour

Given that we are implementing a token-bucket algorithm, we reset the users’ rate limits every hour using timers – an Eventing functionality that is critical for our use case. We create the first timer in the OnDeploy 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.

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 OnDeploy handler.

Refresh Tier Rate Limits Daily

Refresh Tier Rate Limits Daily

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.

Again, we use self-recurring timers to get the latest tier-to-rate-limit mapping every 24 hours.

Sobre a atualização Configuração

Handling User Events

Handling User Events

Our application will listen to incoming request documents from the my-llm.users.events keyspace. These documents have a unique ID and contain data in the format:

If the user’s 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.

Reading the User’s Tier

Reading the User’s Tier
Quando o Sobre a atualização handler is triggered by an incoming user event document from the previous step, we must extract the user_id field from it.
Usando o user_id field, we’ll retrieve the user’s account details document from the my-llm.users.accounts keyspace. From this document, we’ll extract the value of the tier campo.

Reading the Tier’s Rate Limits

Reading the Tier’s Rate Limits

We get the rate limits for the user’s tier from the document that contains the tiers-to-rate-limits mapping, located in the rate-limit.my-llm.limits keyspace.

Decide Whether to Rate Limit the Request & Update the User’s Rate Limit Usage

Decide Whether to Rate Limit the Request & Update the User’s Rate Limit Usage

Given the user’s rate limit, we now check their current usage to decide if they can make a request. The rate limiter tracks each user’s usage with a counter document in the rate-limit.my-llm.tracker keyspace. We create this counter document on demand for each user_id to store that user’s request count for the current window, before the token bucket limit is refreshed. If a user’s usage meets or exceeds their tier’s limit, we block their request. Otherwise, we forward it to the protected endpoint. Finally, we update the user’s rate-limit usage in their corresponding counter document in the rate-limit.my-llm.tracker keyspace.

Send the “within the limit” Request to the Desired Endpoint

Send the “within the limit” Request to the Desired Endpoint

User requests, within their corresponding tier’s rate limits, are sent to the REST API endpoint protected by our rate limiter.

Testing Our Application

Now that we have implemented our rate limiter, we can create the environment to run and test it:

  1. Run the Go program to load a sample set of 100 users.
  2. Run the Go program to start the HTTP server that provides the external REST APIs that our Eventing function interacts with.
  3. Deploy the Eventing function.
  4. To trigger the Eventing function, we must run the Go program to load user event documents into its source keyspace, i.e., my-llm.users.events.
  5. To get the number of user requests that reach the external REST API endpoint protected by our rate limiter, you must send a OBTER request to the /my-llm ponto final.

Conclusão

This post showed how to use the new Couchbase Eventing handler, OnDeploy, to build a token-bucket rate limiter – highlighting the power and flexibility of Couchbase Eventing for developing integrated, standalone solutions.
More broadly, it demonstrates a shift in application development: building applications from the database itself. This enables tailored solutions to diverse requirements – all within the Couchbase platform.

Apêndice

Complete Eventing code: Click Here
Server Go Code: Click Here
Client Go Code: Click Here
User Loader Go Code: Click Here

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

Author

Posted by Rishit Chaudhary

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.