Back in a previous tech talk, we described how multiple layers of caching enables TBA to scale while keeping costs low. While most of the caching is internal to TBA’s servers, there are a few things applications that use our API can do to ensure that queries are being done efficiently. The TBA Android App uses these techniques to reduce latency and bandwidth usage for our users, and you can do the same for your application!
The TBA API is served via HTTP(S). An application can make an HTTP Request to TBA to which the TBA servers will return an HTTP Response. Both requests and responses can contain headers and a message body. Headers can contain useful information about things like authentication, caching, and body content length. The body contains the actually useful “data,” such as match results.
A basic request to a TBA API endpoint may look like this:
and an empty body.
The server response may have headers like:
Content-Length: 15782 Cache-Control: public, max-age=61 Last-Modified: Mon, 28 Aug 2017 03:33:54 GMT ETag: W/"770444596ae7e1162d031880a707cdf6"
and have the event’s match results in the body.
Typically, HTTP request libraries have support to use these headers to make efficient requests, but they aren’t always enabled by default. Here’s what you can do to make sure you’re making use of them.
Taking advantage of edge cache
The last layer in TBA’s multi-layered caching scheme is edge cache, which makes use of HTTP caching. Edge caches read headers like the Cache-Control header to determine whether a page should be cached. Because all of the data from the TBA API is public (there isn’t any private data, such as information about a particular user), we tell edge caches that they can use the cached data for anyone that requests the same URL within a certain period of time. This is great! TBA’s servers will only need to respond to the first request out of potentially thousands — the rest will be handled by intermediate caching services.
However, only requests to the same URL will be cached. This makes sense; if users A and B both request “/event/2017casj/matches,” you’d want there to be a cache “hit,” whereas if user B requests “/team/frc254” instead, you’d want there to be a cache “miss.” Despite the fact that the TBA API allows either the X-TBA-Auth-Key set as either a header or URL query string for authentication, it is highly recommended that applications set the header and not the query string. This is because unique authentication keys cause the URL to be different among different users, resulting in cache misses even if the data is actually the same. Using the header for authentication not only decreases the load on TBA’s servers, it also results in much faster response latency for the users (<50 ms vs. >500 ms in many cases).
Note: URL query strings also aren’t considered “secure” for protecting your X-TBA-Auth-Key. The only reason we allow them is for certain applications that don’t allow setting a header, such as Excel or Google Sheets.
304 Not Modified
By taking advantage of edge cache, we’ve decreased server load and reduced response latency in general. We can do even better in specific situations. For example, if nothing has changed since the last request, the server shouldn’t need to send the same data again.
TBA API responses contain Last-Modified and ETag headers. Future requests to the same API endpoint can include conditional headers to let the server know what version of the data was last received. When making a request, by setting the headers If-Modified-Since and If-None-Match to the last response’s values of Last-Modified and ETag respectively, the server will know whether anything has changed since the client last made the request. If nothing has changed, it will return a 304 Not Modified response with an empty body, saving on bandwidth and data transmission time!
Like edge cache, this works on a per-URL-basis — make sure you’re keeping track of Last-Modified and ETag headers for each URL separately. HTTP request libraries for your favorite language might not enable conditional headers by default (if they automatically handle them at all), so it’s a good idea to double check.
Further reducing bandwidth
The nature of the JSON data structure of TBA API responses (particularly for lists of teams, events, and matches) means that there are a lot of repeated keys. This makes GZIP compression especially useful for reducing the bandwidth needed for a request — by over 95% in some cases.
If your HTTP request library supports GZIP, make sure that the request’s Accept-Encoding header includes GZIP. To verify that TBA is serving GZIPped content, the response header Content-Encoding should say “gzip.” Adding “gzip” somewhere in the User-Agent header may sometimes be necessary to force GZIP compression.
By ensuring that proper HTTP headers are set when making requests to the TBA API, a response that takes over 500 milliseconds and uses 300+ KB of bandwidth can be optimized to take well under 50 milliseconds and use less than 1 KB of bandwidth. This may not be significant for applications that make only a few requests. However, for heavy consumers of the API (such as the TBA Android App), efficient querying decreases latency, reduces bandwidth, and alleviates load from the TBA servers.
As a reminder, querying the API follows a polling paradigm — a client asks the server if anything has changed. Server load may still grow as more and more people are polling at more frequent intervals since caching is never guaranteed. There is no current rate limit on the TBA API, but they may be imposed if things ever get out of hand. Instead of frequent polling, consider checkout out our webhooks which can push data to you when it is updated.