Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Sending Telemetry from GraphQL under NodeJS without Spamming

Posted on: 2019-06-11

I am running a GraphQL server hosted with NodeJS. Under the hood, it is an Express server with Apollo Middleware. I am collecting different information along with errors. The way it works is that I have an Elastic Search server fronted by a simple REST API facade. The endpoint on the REST accepts a single telemetry payload or a collection of them. The latter is recommended. When I use the REST facade for the web, I am collecting all call to the telemetry and batch the request every 5 seconds or when the browser send a beacon (when leaving the page). It reduces the load on the server by limiting the number of HTTP requests. For example, if there is 24 different telemetry within a few seconds, it performs a single HTTP request with 24 entries.

Telemetry Information flow

Under NodeJS, I could do something similar with a timer, but while reading how the DataLoader library handles batching I thought I could code a similar pattern. Instead of leveraging on time, I could batch every telemetry on a single NodeJS event loop. In the NodeJS world, this is called a "tick". There are two ways to accomplish the batching, and I leaned on setImmediate.

NodeJS Event Loop

The idea is that NodeJS runs in an infinite loop. It is possible to mention to the system to prepare the execution later, on the next loop with setImmediate which execute when the "poll phase" is completed. setImmediate is different from the setTimeout because it does not rely on a time threshold. Often, libraries use process.nextTick. it processes the task after the event loop. I avoided using process.nextTime because in some situation it can cause an infinite loop with recursivity. setImmediate is enough to delay the execution. In fact, after using it for more than two weeks, every telemetry collected within a single GraphQL request are batched together which is perfect in my case.

The code in the NodeJS server is short. There is a class that consists of a boolean field that indicates if we have batched information. By default, the value is false until we invoke the first time the function to send the telemetry. When the flag is true, we keep calling the code that will add into an array all the telemetry but we do not call the function performing the HTTP request to the API; we wait that the setImmediate function callback is executed. When this one is executed and returned with a successful HTTP code, we copy the content of the array, flush the data from the list of telemetry, send the information and turn back the flag to false. Ready for the next round of batching. While the code is sending the telemetries, other telemetries can be collected. The data is added to the array to be sent. In case of failure, the data is added back to the next batch.

public send(data: TelemetryPayload): void {
    const dateToSend = { ...data };
    this.listDataToSend.push(dateToSend);
    if (!this.isTransmittingQueuedPayload) {
        this.isTransmittingQueuedPayload = true;
        setImmediate(() => {
            this.send(); // Perform HTTP requests with all the this.listDataToSend
        });
    }
}

Overall, the code is pretty clean and under one hundred lines of code. It reduces drastically the number of HTTP requests while being easy to read once we get the setImmediate detail clarified.

My Other GraphQL Blog Posts