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.
Inside the project folder create a node project with the command:
npm init
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
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:
serverless deploy
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) {
// the below submits the records to the API then empties the records array.
submitToAPI(records);
}
}
Top comments (0)