Faster ORDS REST Services with ETags

Faster ORDS REST Services with ETags

·

6 min read

Despite reading Kris and Colm's excellent posts on the subject and the Wikipedia definition, I found myself struggling to understand eTags and how they aid performance with Oracle REST Data Services (ORDS). This post is my effort to understand ETags better and hopefully help others.

What is an ETag

When you define an ORDS Template, ORDS will default the value for 'HTTP Entity Tag Type' to Secure Hash (see screenshot below). Screenshot Showing Oracle ORDS Template with eTag Enabled

If we call this REST service, ORDS will automatically generate a hash of the returned payload and include it in the Response Header (see screenshot below). Screenshot Showing ETag in Response Header If the response payload does not change, you will get the same ETag returned every time you call the REST service. If the payload changes, then the ETag changes. 💡This was my first lightbulb moment.

We could now store the ETag value in a local table, and the next time we call the REST service, check if the returned ETag matches the stored value. If there is a match, then we know the response did not change, and we don't need to process the response. This may in itself improve performance, especially if processing the response is expensive. However, the real payback for ETags is in the If-None-Match header.

Note: ORDS only generates an ETag for GET requests, but the GET request can be based on SQL or PL/SQL.

The If-None-Match Header

Screenshot of Postman showing If-None-Match Header The above screenshot passes the If-None-Match header with the ETag value we stored from a previous call. With 'HTTP Entity Tag Type' set to Secure Hash, when ORDS sees this header, it will:

  • Call the ORDS Handler logic (as usual)
  • Calculate a hash for the response generated by the ORDS Handler logic
  • Compare the calculated hash to the hash passed in the If-None-Match header
  • If they match, it will return an empty response body (0KB) with a 304 (Not Modified) http status code. 💡This is the second and final light bulb moment
  • If the ETag values are different, then ORDS will return the response with a 200 http status code, along with a newly calculated ETag value

The key here is that ORDS returns an empty response if the ETags match. Not having to wait for ORDS to emit the payload and send it across the network significantly improves performance (especially for large payloads).

Example

Let's use an example of an iOS App that customers use to see their Oracle eBusiness Suite (EBS) sales orders. Whenever the user navigates to the Order page, the iOS App calls an ORDS REST service on the EBS instance to get the status of their Sales Orders. The URL for the REST service looks like this: https://example.com/ords/36337/orders/ (where 36337 is the customer id). With ETags in play, the iOS App can perform the following logic.

  1. Call the REST Service https://example.com/ords/36337/orders/
  2. Receive a 200 response, an ETag, and the payload of orders for the customer
  3. Process the orders payload into local tables
  4. Store the URL https://example.com/ords/36337/orders/ and the ETag in a local table

The next time the iOS App calls the orders REST service (for the same URL), it can:

  1. Lookup the locally stored ETag value for the URL
  2. Call the REST Service https://example.com/ords/36337/orders/, passing the ETag value in the If-None-Match Header
  3. If the passed in ETag matches the calculated ETag, the iOS App will get a 304 response back (along with an empty payload). In this case, there is nothing for the iOS App to do as the data in its local tables matches what is in EBS
  4. If the ETags do not match, the iOS App will store the latest ETag returned from ORDS and process the payload

How Much Faster is it?

I conducted several tests to see how much of a performance benefit ETags deliver.

Large Payload Test Service

  • I created a simple ORDS GET Handler based on the below SQL:
    SELECT * 
    FROM  all_objects
    FETCH FIRST 10000 ROWS ONLY
    
  • I set the 'Pagination Size' of the ORDS Handler to 1000
  • This resulted in a 580KB JSON response

Small Payload Test Service

  • I created a second ORDS GET Handler based on the below SQL:
    SELECT * 
    FROM  all_objects
    FETCH FIRST 10 ROWS ONLY
    
  • I set the 'Pagination Size' of the ORDS Handler to 25
  • This resulted in a 6.2KB JSON response

Test Method

I ran the following tests using Siege to call each REST service ten times (in a single thread). I ran the three tests against the Large and Small Payload services defined above.

  1. Test with ETag enabled but passing a value for If-None-Match Header that does not match the payload hash (expect a 200 HTTP Response)
    siege -c1 -r10 --header='If-None-Match:"INVALID_ETAG=="' "https://www.example.com/ords/dev/rest/blogs/etags"
    
  2. Test with ETag enabled passing a value for If-None-Match Header that does match the payload hash (expect a 304 HTTP Response)
    siege -c1 -r10 --header='If-None-Match:"AOQCC08dtaJDAjpj42nt7lAwRM/4rjsYmLoKz6TQiOcQROtzzrKLdp1dW+WuTHyXUG83L96KMJZlPJOadH/p6VQ=="' "https://www.example.com/ords/dev/rest/blogs/etags"
    
  3. Set the 'HTTP Entity Tag Type' value on the ORDS Template to 'None'. This tells ORDS not to calculate or return an ETag (expect a 200 HTTP Response). I ran this test to see what the overhead in calculating the ETag was
    siege -c1 -r10 "https://www.example.com/ords/dev/rest/blogs/etags"
    

Test Results

Large Payload

Table Showing Timings from the Three Tests on the Large Payload

  • Using an ETag was 2.7 times faster
  • If not using an ETag, turning off hashing was 1.3 times faster

Small Payload

Table Showing Timings from the Three Tests on the Small Payload

  • Using an ETag was only 1.1 times faster
  • If not using an ETag, turning off hashing was 1.2 times faster

Testing Conclusion

  • Larger payloads benefit from ETags much more than smaller ones. This is likely to be because it takes longer to transport the larger payload over the network
  • Even for smaller payloads, a 10% improvement could still be significant as long as the cost of storing and fetching the local ETag for each call does not outweigh the benefit
  • If you are not using ETags, then set 'HTTP Entity Tag Type' on the ORDS Template to None so you do not incur the hashing overhead

ETag Generation Alternative

Instead of letting ORDS calculate the ETag hash based on the response payload, you can provide a query for ORDS to run. ORDS will run the query, generate a result hash, and return the hash in the ETag header. If, for example, you only wanted to fetch new data if the count of rows in a table changes, this would be much faster than calculating a hash on the entire response. Screenshot showing Etag Calculation using SQL

Conclusion

The appropriate use of ETags can improve performance significantly and reduce the load on the client and, to a lesser extent, the ORDS server. I encourage you to explore more and use ETags where you can.

From a personal perspective, I am glad I have finally figured out ETags; you can be sure I will use them in the future.

🔗 Read More