Tuesday, January 28, 2014

Node vs. Java - Web Service Performance

Summary

I recently did some comparative testing of web service implementations for a simple in-memory cache. I built functionally equivalent interfaces in Java (REST + SOAP) and Node.js (REST only) for the cache.  As expected, the Node implementation outperformed the Java variants significantly (>100% faster response times).

Cache Implementation

Figure 1 depicts the high-level structure of this cache application.  The cache supports inserts, fetches, and deletes of key/value pairs.
Figure 1
Figure 2 depicts a bit more detail on the physical layout of the application.

In the cases of the REST variants for Java and Node, cache operations are implemented as HTTP verbs (Insert = PUT, Fetch = GET, Remove = DELETE).  Stale entries are cleared from the cache using timeouts with Node and scheduled threads in Java.  Additionally, cache redundancy (loose coherence) is supported simply by utilizing REST calls between the server peers (PUT's and DELETE's).

For the Java SOAP variant, cache ops are implemented with the typical HTTP POST of SOAP envelopes.

Figure 2
Application Organization

Figure 3 below depicts the organization of the Java REST variant of the cache.  Apache Tomcat + Jersey (servlet) are leveraged.

Figure 3


Figure 4 below depicts the organization of the Java SOAP variant of the cache app.  Tomcat + Apache Axis2(servlet) are leveraged.


Figure 4


Figure 5 below depicts the Node.js implementation.  A single worker process is utilized.

Figure 5
Testing
Figure 6 depicts the test environment I used.





Java + Node REST Cache Insert Test
ab -A username:password -u restput.txt -n 1000 -c 1 https://server/ctispan/rest/key/111 > results.txt

restput.txt
value=test111

Java SOAP Cache Fetch Test (I used TCP/IP Monitor in Eclipse to figure out the SOAP formats for ab)
ab -A client:password -T "application/soap+xml; charset=UTF-8" -p soapget.xml -n 1000 -c 1 https://server/ctispan/services/CacheProxyWS.CacheProxyWSHttpSoap11Endpoint/ > results.txt

soapget.xml
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:getValue xmlns:ns1="http://server.ctispan.jwisoft.com"><ns1:key>111</ns1:key></ns1:getValue></soapenv:Body></soapenv:Envelope>

Java SOAP Cache Insert Test
ab -A client:password -T "application/soap+xml; charset=UTF-8" -p soapput.xml -n 1000 -c 1 https://server/ctispan/services/CacheProxyWS.CacheProxyWSHttpSoap11Endpoint/ > results.txt

soapput.xml
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:putValue xmlns:ns1="http://server.ctispan.jwisoft.com"><ns1:key>111</ns1:key><ns1:value>text111</ns1:value></ns1:putValue></soapenv:Body></soapenv:Envelope>


Results
Below are some graphs of the numbers ab produced.  I wasn't really surprised by the Node vs. Java results.  The Java REST vs. SOAP numbers were a little surprising.  I expected a wide margin between Java REST and SOAP (in REST's favor) due to the overhead of SOAP.  All I can surmise is the Apache Axis2 API is significantly more efficient than the Jersey API.





Sunday, January 12, 2014

Apache Cassandra Start-up Problem - UseCondCardMark


Problem

You receive the error below when attempting to start up Cassandra

Unrecognized VM option 'UseCondCardMark'

Diagnosis

The issue is that JVM option is for the server JVM, only.  You're likely attempting to start Cassandra with the 'client' JVM.

Solution

Add the '-server' option to the JVM_OPTS variable that Cassandra utilizes.  That shell variable is build up in the conf/cassandra-env.sh file.  Below I've edited that file and added the '-server' option as the first option to JVM_OPTS.

# Here we create the arguments that will get passed to the jvm when
# starting cassandra.

JVM_OPTS="$JVM_OPTS -server"

Thursday, January 9, 2014

SIP UUI Data Encoding

Per the IETF draft, strings passed via SIP UUI are encoded as hexadecimal digits representing their ASCII code.  Interestingly enough, the draft doesn't address how to pass strings from the larger character set covered by Unicode.

Example ASCII  to Hex Translation

ASCII Character string: cake
ASCII Decimal code:  99 97 107 101
ASCII Hexadecimal code (what goes on the wire): 63616B65

Below are a couple simple Java functions to perform these string translations:

Code


public class Decoder
{
 public String hexToString(String hex)
 {
  String str = "";
  if (hex != null)
   for (int i=0; i < hex.length()-1; i+=2)
    try
    {
     str += (char) Integer.parseInt(hex.substring(i, i+2),16);
    }
    catch (Exception e)
    {
     return "";
    }
  return str;
 }

 public String StringtoHex(String str)
 {
  String hex = "";
  if (str != null)
   for (int i=0; i < str.length(); i++)
    hex += Integer.toHexString((int)str.charAt(i));

  return hex;
 }

 public static void main(String[] args)
 {
  Decoder decoder = new Decoder();
  System.out.println(decoder.StringtoHex("test"));
  System.out.println(decoder.hexToString("74657374"));
}
Output

74657374

test

Friday, January 3, 2014

Implementing a Cisco GED-145 Gateway with Node.js

Summary
Cisco's GED-145 is an API + protocol definition for integrating their enterprise-grade contact center product with 3rd-party systems.    This document discusses using JavaScript with node.js to implement a GED-145 application gateway.

Background
Cisco's enterprise-grade call routing engine is called Intelligent Contact Management (ICM).  ICM is a suite of software components that provide ACD, CTI, and reporting for large-scale contact centers (thousands of agents).

The ICM routing script editor interface provides drag/drop functionality for creating call routing scripts.  Those scripts can employ of wide variety of out-of-box routing logic to deliver a call to the appropriate agent target - such as time of day, longest available agent, most appropriately skilled agent, etc.  ICM provides a mechanism to extend that routing logic using the GED-145 API.  I believe 'GED' is an acronym for 'Geotel Engineering Document'.  Geotel was the company that created ICM.  Cisco acquired Geotel way back in 1999, but some of the Geotel naming conventions clearly still exist.

GED-145 is a TCP socket-based API that provides the ability to query 3rd party systems for routing information from within an ICM routing script.  A middleware application, called an 'Application Gateway', can be developed to act as a server to the ICM routing engine and client to the 3rd party system being queried.  Figure 1 depicts the overall GED-145 architecture with a web server as the 3rd party system.


GED-145 defines a low-level byte format for messaging between the ICM Router and Application Gateway.  Figure 2 depicts the byte format for GED-145 messages.



In addition to message formats, GED-145 defines the messaging protocol between the Router and Application Gateway.  The protocol is of a simple Request/Response type.  Figure 3 shows message format and protocol for one of the simplest message and exchange types - the Heartbeat.  The Heartbeat request/response protocol provides application-level keep-alive functionality.

Implementation

Figure 4 below depicts my overall approach for this application.
  • Application Gateway - node.js TCP server application.  It acts a server to the ICM router and speaks GED-145 in that direction.  It also acts as a client to a web server and speaks REST in that direction.
  • ICM Router Simulator - node.js  TCP client application, simulating an ICM Router.  Provides a mechanism to test the GED-145 protocol and apply load to the Application Gateway.
  • Web/Rest Simulator - node.js/express.js web server providing a contrived REST interface.
  • App Logger - winston.js logging implementation.  Provides debug and error-level log messages to console and log files.
  • Message Handler - miscellaneous node.js functions to manage creation and parsing of GED-145 messages.
  • Rest Client - node.js client that is utilized within the Application Gateway.  Provides a HTTP client to the REST simulator.
  • Test Runner - node.js interface to the Router Simulator.  Implements test suites for speed testing, TCP socket error injection, and load testing.

Performance
The Application Gateway code was deployed on a Centos 6.4 VM with 1 vCPU and 1 GB vRAM.  Below are the results of the following test sets.  
  • Speed Test:  tests the latency of a series of simple Heartbeat Requests and Responses
  • Error Test:  series of Open/Close/Query/Param/Heartbeat messages that are artificially broken up during socket writes to simulate the streaming nature of TCP.  Additionally, nonsensical messages are injected.
  • Load Test:  similar message set to the Error Test, but with no invalid messages nor artificial message breaks.
2014-01-03 07:49:20.257 - Starting Raw Speed Test
Speed Test Completed
Number of Transactions: 10
Elapsed Time: 13 ms
Average Elapsed Time per transaction: 1.3 ms

2014-01-03 07:49:20.399 - Starting Error Test
2014-01-03 07:50:20.405 - Error Test Completed
Random Synthetic Message Fragmentation:0-4
Transaction Count:2569
Induced Error Count:428
Caught Error Count:428
Elapsed Time:1 min
Average number of transactions/sec: 42.812

2014-01-03 07:50:20.406 - Starting Load Test
2014-01-03 08:50:20.447 Load Test Completed
Synthetic Message Fragmentation:none
Transaction Count:233756
Error Count:0
Elapsed Time:60.001 min
Average number of transactions/sec: 64.931

RAM/CPU Usage

Pidstat was utilized to log resource usage of the app gateway process during execution.  CPU usage ranged from 1-13%.  RSS memory usage ranged from 82 - 104 MB.  V8 garbage collection kicked in at 104 MB and reduced RSS down to 84 MB.   Below is the pidstat output reflecting the high-water marks on CPU and RSS usage.

Max CPU
#      Time       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
 1388756055     16289   12.35    1.21    0.00   13.56     0    143.72      0.00 1059116  82060   8.04  node

Max RSS
#      Time       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
 1388763210     16289    6.61    1.20    0.00    7.82     0     15.03      0.00 1076988 104292  10.22  node