Archive

Posts Tagged ‘scalability’

Building RESTful services – part 2

March 26, 2012 3 comments

This is part 2 of “Building RESTful services”.  In Part 1 we talked about Level 1 and Level 2 of the Richardson Maturity Model.

Level 3 – Hypermedia

Hypermedia is really the most interesting aspect of REST and the aspect that is key to scalability and loose coupling. Sadly this is also the aspect that most services that aim to be RESTful have trouble with.

As humans using a web browser, we use this concept every single day. If you wanted to look at the sports section of msn, what do you do ? You type in http://www.msn.com in the browser, look for the hyperlink with the title “sports” and click on it. This concept of hyperlink is so simple, but is one of the most important reasons for the popularity and scalability of the web. Instead of using a hyperlink, imagine if you had to download a “document or a manual” for msn.com that explained the URI to type in to the browser for each section and every time you needed to access something you had to look at the document, find the URI corresponding to your section and type it into the browser. Now imagine if you had to do this for every single website, each having a different kind of document in a different custom format. The internet would certainly not be what it is today. Yet a majority of the APIs built today make us do this. This is where the hypermedia concept comes in.

Let’s say there is a bookstore which has an api for clients to access various lists (e.g. top sellers, new arrivals etc). To get a list of top sellers in the java category, a client may make the following call

GET /estore/lists/topsellers/software/java?count=5

and this returns the following


<books count="5">
 <book purchased="true">
     <id> 12345 </id>
     <title> Effective Java </title>
     <author> Joshua Bloch </author>
 </book>
 <book>
     <id> 23456 </id>
     <title> Java Concurrency </title>
     <author> Brian Goetz </author>
 </book>
...
...
...
 <book>
     <id> 56789 </id>
     <title> Clean Code</title>
     <author> Bob Martin </author>
 </book>
</books>

This would typically be accompanied by documentation such as

Method – Get More Details about a book
Call the following URL
/estore/books/id/

Method – Get Reviews for the book
Call the following URL
/estore/books/id//reviews

Method – Provide a “recommended” rating for the book
Call the following URL. Note that only customers who have purchased the book can provide the recommended rating (purchased flag should be true)
/estore/books/id//rating?level=&customer=

Method – Get Next set of books in the list
Call the following URL
/estore/lists/topsellers/?startIndex=&count=

It is the client’s responsibility to make sure that startIndex and count have the right values.

We’ve all seen API’s built and documented this way. This approach puts the burden of generating the URIs for subsequent actions completely on the client. These are the potential issues with the above approach –

  • Errors in constructing the URIs

The primary responsibility for constructing the URIs rests on the clients, for any action that the client wants to perform based on the list. This is a simple use case, but as things get more complicated, the likelihood of a client constructing the URI incorrectly increases a lot.

  • Tight Coupling

Since the URI formats need to be published and specified ahead of time for the clients to construct them, the service has very little flexibility in terms of changing the API. For example if the service wants to point to a completely different URL for getting reviews,  it would be tough to do so without breaking existing clients.

  • State transitions and business rules on the client side

Let’s say the service changes the business rule and any customer can now provide a rating for the book. This means that all clients that are using the current API now need to change their code to make this happen. This tightly couples business logic to client side code and severely restricts the service’s ability to iterate over business rule changes without massive coordination efforts with all clients.

This is where hypermedia shines. Take a look at the response below with additional hypermedia links (Relative links are used for brevity, but assume all the links are full links including host name)

<books>
 <book purchased="true">
     <id> 12345 </id>
     <title> Effective Java </title>
     <author> Joshua Bloch </author>
     <link rel="moreInfo" href="/estore/catalog/books/id/12345"/>
     <link rel="reviews" href="http:/reviewStore/estore/reviews/id/9912345&customer=888"/>
     <link rel="rating" href="/estore/books/id/12345/rating?customer=888"/>
 </book>
 <book>
     <id> 23456 </id>
     <title> Java Concurrency </title>
     <author> Brian Goetz </author>
     <link rel="moreInfo" href="/estore/catalog/books/id/23456" />
     <link rel="reviews" href="http:/reviewStore/estore/reviews/id/9923456&customer=888" />
 </book>
...
...
...

 <link rel="next" href="/estore/reports/topsellers/software/java?startIndex=6&count=5"/>
</books>

So what’s different here ? We have three new elements which contain a hyperlink with some metadata. Let’s look at each of them

    <link rel="moreInfo"> ... </link>
 Instead of trying to construct the link for more details, the client can simply look at the response for the “moreinfo” tag and if it exists, it knows that there is additional info available. Pretty simple.
    <link rel="reviews"> ... </link>
  Once again, instead of trying to construct the URI, the client looks for the presence of “reviews” link type. Notice that the link to the reviews can now be changed by the service if necessary. Maybe the site decides to consolidate the reviews for all their product line and also decides to track which customers look at reviews and so on. Since the URI is fully generated by the service and the client merely looks for the appropriate hyperlink, not only do the clients not break, they don’t even have to realize that the underlying hyperlink changed.
    <link rel="rating"> ... </link>

If you recall, the initial business rule was that only if the purchased=”true” flag is available, the client should display the rating link to the customer, and a change in this business rule affected the client. Instead, here notice that based on the business logic, the Service can either send back, or not send back a link called “rateBook”. The client simply looks for this link, and if it is not available, doesn’t display the rating link.

Similar benefits are realized by the presence of a “next” link. These  examples highlight the point that by adding hyperlinks to control next steps and NOT making the clients construct URIs, the system as a whole has gained a lot of flexibility.

HATEOAS

    Hypermedia As The Engine Of Application State or HATEOAS refers to allowing clients to navigate through appropriate application states using hyperlinks. The above examples touch on this a little, but let’s look at this from a slightly different point of view. Going back to our eStore example from PART 1, let’s start with an empty order

GET /estore/order/178567

<order>
    <cart>
       <items>
       </items>
       <link rel="update" href="/estore/order/178567/cart"/>
    </cart>
</order>
Note that the link with the type “update”  indicates that the only possible next step for this order is to update the cart by adding more items. The absence of links such as “payment”  or “cancel” imply that those are not valid steps at this point. Let’s say a couple of items get added to the cart by making calls such as
POST /estore/order/178567/cart
<item code="abc" qty="1"/>

//another item
POST /estore/order/178567/cart
<item code="def" qty="1"/>

 Now our order looks something like this

GET /estore/order/178567

<order>
    <cart>
        <items>
            <item code="abc" qty="1">
            <item code="def" qty="1">
        </items>
        <link rel="update" href="/estore/order/178567/cart"/>
    </cart>
    <link rel="saveForLater" href="/estore/customer/1356/archive/order/178567"/>
    <link rel="cancel" href="/estore/order/178567"/>
    <link rel="address" href="/estore/order/178567/address" type="application/vnd.estore_address+xml"/>
</order>

Notice that there are several new links as part of the order itself which indicate one of several next steps that can be done as part of the shopping process.

  •  type=saveForLater
    • The presence of the link of the type “saveForLater” indicates that one valid option for this order is for it to be saved for later, so that the customer can come back to it at some point. Note that if there were no items in the cart, this link would not be available .
  • type=cancel
    • Indicates that this order is in a state where it can be cancelled if the customer wants to, (by making a delete request – this does need to be documented as part of a simple resource documentation). Again, only available for a non empty cart
  • type=address
    • This serves as the link to enter the address for the order so that the checkout process can start.

The important point here is that the client doesn’t have to figure out from an external document as to what are the possible next states for the order. The possible next states are all captured with hyperlinks. Again, this makes for really loose coupling and provides the service a lot of flexibility in evolving business rules around Order processing without breaking existing clients. Also note that each of the link types either returns or accepts a certain content-type. While it is tempting to use application/xml, a specific format such as application/vnd.estore_order+xml, or application/vnd.estore_address+xml is preferred. Another important thing to keep in mind is that while the client is expected to use the hyperlinks from the response, the service should still guard against invalid requests for a given state.

Let’s go one more step, and assume that the customer decided to enter the address for the order.

POST /estore/order/178567/address
<address>
    <street> ... </street>
    <zip> ... </zip>
     etc etc
</address>
Now that the address for the order has been entered, let’s see what can be done with the order now.
GET /estore/order/178567
<order>
    <link rel="cart" href="/estore/order/178567/cart"/>
    <link rel="saveForLater" href="estore/customer/1356/archive/order/178567"/>
    <link rel="cancel" href="/estore/order/178567"/>
    <link rel="address" href="/estore/order/178567/address"/>
    <link rel="payment" href="/estore/order/178567/payment"/>
</order>
  • Payment
    • The presence of the link of the type “payment”  indicates that payment is a valid next step for the order in its current state. If the cart was empty with no items, or if there was no address associated with an order, our made up business rules wouldn’t have allowed us to enter payment information. But now, based on the link, the customer can proceed to payment
As you can see, the set of possible next steps are provided by the service at any given point using hyperlinks. This is HATEOAS. Now assuming the payment information was provided and successfully validated, the order now goes into a state where the only possibility is to cancel the order ( since our made up business rules allow for an order to be cancelled till it ships)
GET /estore/order/178567
<order>
    <link rel="cart" href="/estore/order/178567/cart"/>
    <link rel="cancel" href="/estore/order/178567"/>
</order>

Allowing clients to dynamically discover  next logical steps using hyperlinks, instead of upfront specification is a key part of RESTful design. Thinking in this way helps us realize a lot of benefits of  RESTful services. Finally, taking this point further, it makes a lot of sense to expose only the stable starting point for a service, and allow clients to discover capabilities on the fly. For example, our eStore could simply advertise the  well-known URI /estore/ and everything else is simply discovered by the client by following relevant links provided by the service at the right time.

GET /estore/
<estore>
   // Get current specials
   <link rel="specials" href="/estore/specials"/>
   // Starting point for all catalog related info
   <link rel="catalog" href="/estore/catalog"/>
   // POST to start a new order
   <link rel="order" href="/estore/order"/>
   // Starting point for all customer specific interaction including login
   <link rel="profile" href="/estore/customer"/>
</estore>

Conclusion

While the concept of hypermedia is simple, it is the one aspect that people building RESTful services ignore most often. Using hypermedia is really significant in making a web service loosely coupled and highly flexible, allowing it to evolve over time without breaking clients.

References

  1. http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
  2. http://www.crummy.com/writing/speaking/2008-QCon/act3.html
  3. http://en.wikipedia.org/wiki/Representational_state_transfer
  4. http://www.infoq.com/articles/rest-introduction
  5. http://restinpractice.com/default.aspx
Categories: REST

Improve Performance with http compression

March 10, 2011 1 comment

Over the past few years there has been a huge increase in the number of web services that provide or consume data. As developers optimize their web services for performance and scalability, the one aspect that gets overlooked or is taken for granted is the network bandwidth. As part of optimization adding http compression can have a huge impact on response times. In terms of reward/effort ratio, it is one of the best things to do so that clients capable of handling compression can make use of it. Also since it degrades gracefully, web service clients who are not able to make use of it will continue to work fine.

What to Compress

Most web services expose the data provided by their API as XML / JSON.  If the amount of data exceeds a few KB, it may be worth considering compression. If on the other hand the amount of data you are dealing with is fairly small ( < 2 KB) you may decide that the additional CPU cycles may not be worth it. The answer will obviously vary depending on the specifics of each application and its clients. Also it doesn’t make sense to compress jpg/gif which are already compressed.

Specifics

More often than not an http server such as apache is involved in serving up the content. In case of a java/j2ee application the actual content may be generated by tomcat/glassfish/jboss/websphere but the app server may be behind apache. An option in this case is to let apache do the http compression. All that needs to be done is to enable  mod_deflate and add the right mime types that you want apache to compress. Here is a snippet of what to add to the http conf file. Don’t forget to load mod_deflate .

AddOutputFilterByType DEFLATE text/html text/plain text/xml application/json

As long as the client request includes something like accept-encoding=gzip,deflate , apache will compress the response. The amount of compression taking place can be logged/measured using the DeflateFilterNote directive. Adding something as simple as

DeflateFilterNote Input instream
DeflateFilterNote Output outstream
DeflateFilterNote Ratio ratio
LogFormat "%t \"%r\" %>s %b out=%{outstream}n/in=%{instream}n (%{ratio}n%%)" deflateLog

helps in logging and actually figuring out how much compression is taking place. As the response size increases, the ratio of “compressed data”/”uncompressed data” decreases and can go as low as 5% (savings of 95%).

Here are the log entries for responses roughly 50K ,15K and 5K  in size and the compression levels are seen.

“GET /compression/test.jsp HTTP/1.1” 200 2687  out=2669/in=51635 (5%)
“GET /compression/test.jsp HTTP/1.1” 200 1242  out=1224/in=14361 (8%)
“GET /compression/test.jsp HTTP/1.1” 200 858  out=840/in=5182 (16%)

Glassfish

If your topology demands that there be no apache in front of your app server, compression can be enabled on the app server itself. For example on glassfish (3.0.1) a change from the admin console

Network Config –> Network Listeners –> <listener name >–> HTTP –>Compression (on)

will result in an immediate change.

For tomcat, the change is as simple as adding  compression=”on” in the server.xml for the http connector.

Apache connectors

The above two options take care of compression between the client and the http server.  However in the scenario where apache sits in front of an app server such as tomcat/glassfish (and the two are on physically separate machines),  there is data transfer happening between those machines. Whether or not this data transfer uses compression depends on how apache talks to the app server. Two of the common options for doing this include mod_jk and mod_proxy.

mod_proxy

When mod_proxy is used, the http connector on glassfish (or tomcat ) is used. If gzip compression is enabled on the http connectors as mentioned above , then data from the appserver is compressed and apache mod_deflate will not perform any compression. This can be seen by lines such as these in the apache access log

“GET /compression/test.jsp HTTP/1.1” 200 1451 out=-/in=- (-%)

On disabling the compression on the app servers, data is uncompressed between apache and the app server, and apache does the compression, which can be verified in the access log.  As mentioned above, adding the DeflateFilterNote gives a lot of useful information to figure out whats going on under the hood.

mod_jk

mod_jk uses a binary protocol and doesn’t explicitly support enabling/disabling compression. Enabling some logging in mod_jk can give more information as to what’s going on. For doing that, we can make use of the JkRequestLogFormat as part of mod_jk configuration

JkWorkersFile     comptest/jk/worker1.properties
JkLogFile     comptest/logs/mod_jk.log
……
JkRequestLogFormat “%w %V %T %b”     #add this line

Here is the output from mod_jk log for scenarios for client requests with/without the gzip,deflate headers and with compression enabled/disabled on apache (using AddOutputFilterByType DEFLATE text/xml text/plain text/html)

[Wed 23:08:02 2011] worker1 localhost 0.015625 9353 ( compression not enabled on apache, client request with gzip,deflate accept-encoding)
[Wed 23:08:02 2011] worker1 localhost 0.015625 9353 ( compression not enabled on apache, client request without gzip,deflate accept-encoding)
[Wed 23:08:02 2011] worker1 localhost 0.015625 9353 ( compression enabled on apache, client request without gzip,deflate accept-encoding)
[Wed 23:08:06 2011] worker1 localhost 0.015625 1799 ( compression enabled on apache, client request with gzip,deflate accept-encoding)

The last column shows the number of types (%b) transferred. Again, note that this log captures the mod_jk traffic details i.e. data between apache and tomcat/glassfish. Looks like as long as compression is enabled on apache and the client request includes the gzip,deflate accept-encoding, there is compression involved in the tomcat-glassfish data transfer.  Same holds for glassfish.

ServletFilter

In scenarios where an embedded servlet container is being used as opposed to a full web container (e.g. embedded jetty) the only option maybe to make use of a ServletFilter that does gzip compression. An example is the gzip filter which is part of jetty.

Client

Double check the http client that you are using to make sure that the right headers are being sent. Requests from javascript libraries are usually pretty good about it, but for java http clients the headers may need to be set explicitly.

Testing

Testing the api to make sure that the compression set up is right is trivial but often forgotten. Something as simple as

curl –compressed -I “webServiceUrl”

and verifying that that the output contains  the header

Content-Encoding: gzip

will ensure that the set up is correct.

Another useful variation to measure the bytes returned is to use

curl -i -w “\nsize= %{size_download}\n” “webServiceUrl”

Trade Offs and Conclusion

Anyone considering using compression should start with the apache mod_deflate , which mentions issues to consider with proxy servers as well as with certain browsers. Applications come in various shapes and forms and there really isn’t a one size fits all solution here. Applications vary with respect to clients, servers, hardware, critical scenarios etc just to mention a few. While there is certainly bandwidth savings as a result of using compression, it comes at the cost of cpu cycles. Given how simple it is to set up compression and try it out, the best way to figure out whether it makes sense for your application is to give it a shot – it might just make a big difference!

Categories: performance