While developing
the latency benchmark for the serverless databases (DynamoDB, FaunaDB, Upstash),
I wished there was an API where I will record the latency numbers and get the
histogram back. In this tutorial, I will build such an API where you can record
your latency values from any application. It will be a REST API with following
methods:
- record: Records numeric values into the histogram.
- get: Returns the histogram object.
Motivation
I will show how easy to develop a generic API using AWS Lambda and Serverless
Redis.
See code.
1
Create a Redis (Upstash) Database
Create a database as getting started
2
Serverless Project Setup
If you do not have it already install serverless framework via:
npm install -g serverless
In any folder run serverless
as below:
>> serverless
Serverless: No project detected. Do you want to create a new one? Yes
Serverless: What do you want to make? AWS Node.js
Serverless: What do you want to call this project? histogram-api
Project successfully created in 'histogram-api' folder.
You can monitor, troubleshoot, and test your new service with a free Serverless account.
Serverless: Would you like to enable this? No
You can run the “serverless” command again if you change your mind later.
See Using AWS SAM, if you prefer AWS SAM
over Serverless Framework.
Inside the project folder create a node project with the command:
Then install the redis client and histogram library with:
npm install ioredis
npm install hdr-histogram-js
Update the serverless.yml
as below. Copy your Redis URL from console and
replace below:
service: histogram-api
frameworkVersion: "2"
provider:
name: aws
runtime: nodejs12.x
lambdaHashingVersion: 20201221
environment:
REDIS_URL: REPLACE_YOUR_URL_HERE
functions:
record:
handler: handler.record
events:
- httpApi:
path: /record
method: post
cors: true
get:
handler: handler.get
events:
- httpApi:
path: /get
method: get
cors: true
This example uses ioredis, you can copy the connection string from the
Node tab in the console.
3
Code
Edit handler.js as below.
const hdr = require("hdr-histogram-js");
const Redis = require("ioredis");
if (typeof client === "undefined") {
var client = new Redis(fixUrl(process.env.REDIS_URL));
}
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true,
};
const SIZE = 10000;
module.exports.get = async (event) => {
if (!event.queryStringParameters || !event.queryStringParameters.name) {
return {
statusCode: 400,
headers: headers,
body: JSON.stringify({
message: "Invalid parameters. Name is needed.",
}),
};
}
const name = event.queryStringParameters.name;
const data = await client.lrange(name, 0, SIZE);
const histogram = hdr.build();
data.forEach((item) => {
histogram.recordValue(item);
});
return {
statusCode: 200,
body: JSON.stringify({
histogram: histogram,
}),
};
};
module.exports.record = async (event) => {
let body = JSON.parse(event.body);
if (!body || !body.name || !body.values) {
return {
statusCode: 400,
headers: headers,
body: JSON.stringify({
message: "Invalid parameters. Name and values are needed.",
}),
};
}
const name = body.name;
const values = body.values;
await client.lpush(name, values);
return {
statusCode: 200,
body: JSON.stringify({
message: "Success",
name: name,
}),
};
};
function fixUrl(url) {
if (!url) {
return "";
}
if (url.startsWith("redis://") && !url.startsWith("redis://:")) {
return url.replace("redis://", "redis://:");
}
if (url.startsWith("rediss://") && !url.startsWith("rediss://:")) {
return url.replace("rediss://", "rediss://:");
}
return url;
}
We have two serverless functions above. get
takes name
as parameter and
loads a list from Redis. Then builds a histogram using the values in the list.
The record
function takes name
and values
as parameters. It adds the
values
to the Redis List with name name
.
The get
function calculates the histogram over the latest 10000 latency
records. Update the SIZE parameter to change this number.
The fixUrl
is a helper method which corrects the Redis url format.
4
Deploy and Try the API
Deploy your functions with:
The command will deploy two functions and output two endpoints. Try the
endpoints with setting parameters as below:
Record latency numbers to perf-test-1
:
curl --header "Content-Type: application/json" -d "{\"name\":\"perf-test-1\", \"values\": [90,80,34,97,93,45,49,57,99,12]}" https://v7xx4aa2ib.execute-api.us-east-1.amazonaws.com/record
Get the histogram for perf-test-1
:
curl https://v7xx4aa2ib.execute-api.us-east-1.amazonaws.com/get?name=perf-test-1
Batching
It can be costly to call a remote function each time for latency calculation. In
your application, you should keep an array or queue as a buffer for the latency
numbers, then submit them in batches to the API when the array reaches the batch
size. Something like below:
let records = [];
let batchSize = 1000;
function recordLatency(value) {
records.push(value);
if (records.length >= batchSize) {
submitToAPI(records);
}
}