Friday, January 10, 2020

Load Balancing Techniques and Optimizations

The hosting world’s bread & butter solution for providing high availability and redundancy is load balancing. There are many different use cases for a Load Balancer (LB). It is important to know how to effectively manage your LB configuration so that it performs optimally in your environment.  The proceeding article will review some of the common practices that, when adhered to, provide a smooth and seamless high availability website/application through the use of load balancing.
Prerequisites
This article will focus on load balancing concepts and uses typical web services (HTTP, HTTPS). These services are independent of the LB device itself so they can run on any combination of operating system and server software, e.g.,  Linux, Windows, Apache, Nginx, IIS, etc. Load balancing is not limited to just web services. Any type of traffic with a client/server relationship can take advantage of load balancing.

What is Load Balancing?

Load balancing is the practice of using a network device, called a Load Balancer (LB), to distribute traffic between a back-end cluster of servers, called nodes. These nodes are virtually identical, each running the same software, services, and configurations. Broken nodes can be easily replaced by additional nodes which are also added to handle traffic growth over time. This prevents a single node from becoming overwhelmed as the LB uses specific load balancing algorithmic methods to determine which node handles subsequent requests. Load balancing is virtually invisible to the end-user, operating behind the scenes allowing a farm of servers to function as a single service or application. Load balancing comprises the backbone of most high availability solutions due to its flexibility, redundancy, and extendability.

OSI Layer Optimization

All load balancing occurs on one of two layers of The OSI Model. These layers allow for traffic balancing configurations based on different information that is contained in the network packet for that specific layer. The two layers involved in this process are the L4 Transport and L7 Application layer.
  • L4 – Transport Layer: The fourth layer allows balancing rules based on transport protocols. Balancing traffic based on details like IP Address or TCP Port are provided within the L4 layer.
  • L7 – Application Layer: The Application Layer provides many additional details that can be inspected from the packet for balancing rules. The L7 layer is where rules can be constructed based on information from HTTP Headers, SSL Session ID, HTML Form Data, Cookies, etc.
Rule of Thumb:
For efficiency use the L4 layer. L4 load balancing happens earlier in the transaction, so that traffic can be routed more quickly than L7. L7 load balancing has to inspect data from the application layer. But, it is not always possible to rely on the L4 layer, and it will depend heavily on the services and back-end node configuration.

Load Balancing Algorithmic Methods

There are several common balancing algorithmic methods which can be used in a load-balanced configuration. Selecting the right algorithm for your infrastructure is critical to load balancing optimization. There is no general, one-size-fits-all,  method for every situation. Choosing the correct approach will depend heavily upon the services, traffic, and software used in the load-balanced cluster. Below are some of the common methods used and their strengths/weaknesses from an optimization perspective.

 Round-Robin-  A simplistic load balancing approach. Traffic is sent to each node in series, one after the other, jumping back to the beginning of the list once the end is reached. (e.g., Node 1 → Node 2 → Node 3 → Repeat)
Round-Robin Pros & Cons:
+ Pros: Lower memory and CPU footprint from the Load Balancer.
– Cons: Not adaptive, sends traffic to nodes without regard for distribution.

 Least Connections   A smart balancing method which uses connection tracking to determine which of the nodes has the least number of active network connections. This tracking is managed by the load balancer device itself distributing every new connection to the back-end node with the least amount of active connections.
Least Connections Pros & Cons:
+ Pros: Adaptive, provided even distribution of workload among nodes.
– Cons: Requires larger memory and CPU footprint for connection tracking.

 Fastest Response Time   A smart balancing method which tracks each nodes network response time from health checks and routes new connections to the server with the quickest response time, regardless of any other factors.
Fastest Response Time Pros & Cons:
+ Pros: Adaptive, low memory, and CPU requirement.
– Cons: Limited workload distribution, connections and workload often fills up one node at a time.

 Random Node   A niche method which is only used in very specific scenarios and is almost never appropriate from an optimization standpoint.
Random Node Pros & Cons:
+ Pros: Requires a low memory and CPU footprint from load balancer device.
– Cons: Niche, only useful in specific scenarios. Unreliable distribution of workload.

Weighted Load Balancing
Another useful load balancing feature to consider is weighted nodes along with the balancing method. Weighted load balancing assigns additional connections to specific nodes over others dependent on each nodes weight value in the configuration. This feature is generally used when back-end nodes are not identical hardware or certain nodes receive special traffic beyond regular load-balanced traffic. Assigning a lower weight value in the form of a ratio to the weaker nodes allows them to participate in the workload at their individual capacity limits without getting overwhelmed. For example, a weight ratio of 2:2:1, would assign two connections to node 1 & node 2 for every 1 connection assigned to Node 3.
Choosing an Optimal Balancing Algorithm
When considering load balancing optimization, stick with the smarter balancing methods like Least Connections or in some cases Fastest Response TimeLeast Connections, in particular, is good at distributing workload between all available hardware without focusing too much on a single node. However, this balancing method will require a beefier load balancing device as the amount of nodes and traffic ramp up. Fastest Response Time can be a great alternative when the hardware available for the load balancer is more limited. Each has its pros and cons as listed above, but both work well as optimized balancing methods for busy server farms.
Traffic Pinning & Session Persistence
Some configurations take advantage of pinning certain traffic to specific back-end nodes depending on the traffic. Session Persistence is a widely used form of Traffic Pinning as it results in routing requests to specific servers outside of the load balancing method used. Another common practice is assigning administration or upload traffic to a specific node that propagates the changes to the rest of the server farm. It is important to consider these connection types when designing or optimizing your server cluster.
Pro Tip:
Least Connections works well with configurations that rely on Traffic Pinning and/or Session Persistence. Since these features group connections to specific nodes independent of the load balancing algorithms. These connects are included in the connection tracking of the Least Connections balancing method which allows the load balancer to proportionately assign traffic to the least busy nodes while accounting for the existing pinned traffic.

Server Farm/Cluster Scope

Some important considerations should be accounted for when designing or upgrading your server cluster. A common mistake that gets overlooked is the size of the cluster. The amount of nodes available in the cluster should not only be enough to handle regular workloads. It’s also necessary to account for both surges in workload as well as failures in the cluster. Ideally, a cluster should be large enough to remain fully operational, even when a surge in workload occurs and a critical event throws a node offline.
Rule of Thumb:
The cluster should be large enough to handle traffic surges plus at-least one extra node for redundancy.

Redundancy: Spare vs. Fail-over Node

Quite simply, redundancy is not effective without an extra node to handle the workload in the event that a critical issue occurs with another node. There are essentially two methods of handling redundancy in a load-balanced cluster.
 Spare Node   An extra node in the cluster that is not needed for the cluster to function. It resided active in the cluster, handling workload just like any other node. However, it’s there in case another node falls. The cluster can carry on without interruption, while the broken node is addressed as needed.
Spare Node Pros & Cons:
+ Pros: Seamless redundancy, there is no downtime between a critical event and the promotion of the spare.
– Cons:  Continuous use of the spare can lead to it failing along with another node when it is needed most.
 Failover Node    An extra node, that has been configured and tested to work normally within the cluster. Once tested, the extra node is assigned as a fail-over node and taken out of the active configuration. The fail-over node, then sits on standby, waiting for a critical event to occur within the cluster. The fail-over node is then automatically activated in the cluster and start handling traffic.
Spare Node Pros & Cons:
+ Pros: Almost no risk of the fail-over node having hardware problems when it is needed.
– Cons: Delay between node failure and activation of the fail-over node.
Either method provides redundancy and keeps the application/site alive during critical times. A combination of both methods can be used as well to have the benefit of both options. It boils down to preference and cost. However, adhering to at least one of these methods should keep you running at an optimal state even during hard times.

Front-loaded Permanent Redirects

One way to improve response times of a load-balanced setup is to mitigate permanent redirects. This is done by moving them onto the load balancer device directly, instead of relying on the back-end nodes to issue a redirect to the client. This can reduce connection counts on both the load balancer and the back-end nodes themselves by eliminating one segment of the redirect process. The following illustrates this further when using a common practice of forcing HTTPS via redirects.
When a back-end node is configured to force HTTPS connections, the HTTP request comes into the load balancer, then is processed by the balancing algorithm and finally sent onto the necessary back-end node as normal. The back-end node then issues the redirect, which instructs the client to reconnect over the HTTPS protocol. The new HTTPS request is also balanced by the algorithm as needed and sent to a back-end node. The net processing result of this type of redirect is:
  • x2 front-end requests (HTTP & HTTPS)
  • x2 back-end requests
  • x2 load balancing algorithmic checks
Now compare this to a front-loaded HTTPS redirect.
In the front-load configuration, the HTTP request hits the load balancer but is not processed by the balancing algorithm. Instead, it immediately issues the redirect back to the client. Then the client reconnects over HTTPS protocol, which then invokes the algorithm as needed and is sent onto the back-end node as required. The net processing result of this configuration is:
  • x2 front-end requests (HTTP & HTTPS)
  • x1 back-end request (HTTPS only)
  • x1 load balancing algorithmic checks
This example only illustrates a single request. However, load balancers typically handle hundreds or even thousands of requests concurrently. It is easier to see the overall benefit of a change like this when scaling up the example:

SSL Decryption

There are a handful of ways that load balancers are configured to handle SSL encrypted connections like HTTPS. Encrypted connections are more cumbersome on the load balancer device than non-encrypted connects. The process of validating the certificate chain and then decryption the content adds additional workload to every request handled by the load balancer. This can be mitigated depending on the configuration of the back-end cluster servers and the applications/website’s encryption requirements. The following are the common configuration scenarios and their pros/cons.
 SSL Passthrough –   The LB is configured to pass any encrypted connections through to the necessary back-end nodes. Decryption is handled by the back-end nodes only and not the LB device.
SSL Passthrough Pros & Cons:
+ Pros: Simple configuration, high security, low CPU/memory footprint. Offers end-end-encryption.
– Cons: Limited to L4 Layer load balancing. Cannot perform packet inspection.
 Decryption Only –  The LB device itself will perform the necessary decryption. Traffic is then balanced to the back-end nodes after decryption. Due to security concerns, this configuration is only recommended when the back-end nodes and load balancer device have an isolated LAN to communicate through.
Decryption Only Pros & Cons
+Pros: Allows packet inspection for L7 Layer load balancing rules. Back-end nodes do not handle decryption.
– Cons: Any machines on the network can read decrypted back-end traffic.
 Decryption + Re-encryption–  Decryption is handled by both the LB device and then re-encrypted and sent to the back-end node as needed. The back-end node also performs decryption providing full end-to-end encryption. This method is for high-security setups that also require L7 load balancing rules.
Decryption + Re-encryption Pros & Cons:
+ Pros: Maximum security with end-to-end encryption. L7 load balancing rules & full packet inspection.
– Cons: Higher workload demand on the LB device. Encryption must be set up on both the front and back-end.
The load balancing optimization stance is the less workload the LB performs, the faster it responds to requests. This makes SSL Passthrough the ideal choice as it requires the least amount of work from the LB device. However, this is not always the practical solution for some applications/sites, particularly those that require L7 layer load balancing or other packet inspection needs. Decryption Only requires less workload when compared to Decryption + Re-encryption, but should only be leveraged when running a dedicated LB on a private LAN with the back-end nodes preventing outside entities from abusing the non-encrypted back-end traffic.

Reference:





Monday, November 4, 2019

High Performance Oracle JDBC Programming




This article shows you how to take advantage of pooling connections and statements to improve performance of data-intensive Java DataBase Connectivity (JDBC) programs interacting with Oracle Database via the Oracle JDBC thin driver. In particular, it looks at the Oracle Universal Connection Pool (UCP) for JDBC, which provides a full-featured connection pool implementation for caching JDBC connections. Finally it discusses how you might benefit from statement pooling, utilizing features specific to Oracle’s JDBC drivers as well as the new JDBC 4.0 methods added to the Statement interface and available in Oracle JDBC drivers supporting Java Development Kit (JDK) 1.6 and later versions.

Setting Up Your Working Environment

To follow the examples in this article, in addition to having access to an Oracle database, you’ll need to have the following software components installed on your development machine (see "Downloads" portlet for links:
  • JDK 1.6
  • Oracle JDBC thin driver supporting JDK 1.6
  • Oracle Universal Connection Pool library
The Oracle JDBC thin driver is a Type IV JDBC driver, meaning that it’s platform-independent and does not require any extra Oracle software on the client side to interact with an Oracle database. So you can download the JAR file containing the classes of an appropriate thin driver version from the JDBC Driver Downloads page and then install the driver on your machine without having to install/upgrade any other Oracle software. To install the driver, you simply need to copy its JAR files to your local file system and then include paths to these JARs to the CLASSPATH environment variable. For example, you might include the following paths:
Copied to Clipboard
Error: Could not Copy
ORACLE_HOME/jdbc/lib/ojdbc6.jar
 ORACLE_HOME/jlib/orai18n.jar
If you have an Oracle database installed on your machine, the thin driver has been installed with your Oracle Database installation. However, because the thin driver does not depend on any additional Oracle software, you can easily upgrade to the latest release of the driver by using appropriate JAR files that can be found on the JDBC Driver Downloads page.
UCPis a new feature included in Oracle Database 11 g, starting with release 11.1.0.7. This feature is also available in Oracle Application Server, beginning with Oracle Application Server 11g Release 1. If you’re using older software that doesn’t ship the JAR file for UCP(it’s called ucp.jar) or you want to upgrade to the latest UCPrelease, you can pick up ucp.jar from the Oracle Database UCP Downloads page. This package contains UCP’s classes for inclusion in the classpath to enable the feature. The path included might look like this:
Copied to Clipboard
Error: Could not Copy
ORACLE_HOME/ucp/lib/ucp.jar

Caching JDBC Connections with UCP

If you’re developing a database-intensive application, you might benefit from using a connection pool, which enables you to reuse connections rather than create a new one each time it is requested. Connection pooling conserves resources required for creating new database connections and improves your application performance, because creating a new connection is always a performance-intensive operation.
The Oracle Universal Connection Pool for JDBC represents a full-featured implementation of a connection pool caching JDBC connections. UCP is a very useful feature, in that it lets you reuse connection objects, thus speeding the process of obtaining a connection and saving resources associated with opening new database connections.
Suppose you want to create aUCP JDBC connection pool to reuse connections established to the HR/HR Oracle Database sample schema. The following program, representing a simple example of aUCP JDBC connection pool in action, shows how you might achieve this. Here you first create a pool-enabled data source instance and then set up the connection and pool properties. Once you’ve done that, you borrow a connection from the pool and then use that connection to interact with the database. Finally, you close the connection, returning it to the pool.
Copied to Clipboard
Error: Could not Copy
/*
*A simple example illustrating aUCP JDBC connection in action
*/
import java.sql.*;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

public class UcpConnection {
public static void main(String args[]) throws SQLException {
try
{
//Creating a pool-enabled data source
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
//Setting connection properties of the data source
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL("jdbc:oracle:thin:@//localhost:1521/XE");
pds.setUser("hr");
pds.setPassword("hr");
//Setting pool properties
pds.setInitialPoolSize(5);
pds.setMinPoolSize(5);
pds.setMaxPoolSize(10);
//Borrowing a connection from the pool
Connection conn = pds.getConnection();
System.out.println("\nConnection borrowed from the pool");
//Checking the number of available and borrowed connections
int avlConnCount = pds.getAvailableConnectionsCount();
System.out.println("\nAvailable connections: " + avlConnCount);
int brwConnCount = pds.getBorrowedConnectionsCount();
System.out.println("\nBorrowed connections: " + brwConnCount);
//Working with the connection
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select user from dual");
while(rs.next())
System.out.println("\nConnected as: "+rs.getString(1));
rs.close();
//Returning the connection to the pool
conn.close();
conn=null;
System.out.println("\nConnection returned to the pool");
//Checking the number of available and borrowed connections again
avlConnCount = pds.getAvailableConnectionsCount();
System.out.println("\nAvailable connections: " + avlConnCount);
brwConnCount = pds.getBorrowedConnectionsCount();
System.out.println("\nBorrowed connections: " + brwConnCount);
}
catch(SQLException e)
{
System.out.println("\nAn SQL exception occurred : " + e.getMessage());
}
}
}
An important thing to notice here is what’s happening when a connection is closed. The output of the above program illustrates that closing a connection borrowed from a UCP JDBC connection pool actually returns that connection to the pool, where it becomes available for the next connection request.
Here is what the program output should look like:
Copied to Clipboard
Error: Could not Copy
Connection borrowed from the pool 
 Available connections: 4
 Borrowed connections: 1
 Connected as: HR
 Connection returned to the pool
 Available connections: 5
 Borrowed connections: 0

Borrowing a Connection with JNDI

Instead of creating a pool-enabled data source on the fly, as you did in the preceding example, you can create it in advance and bind it to a Java Naming and Directory Interface (JNDI) context and a logical name. Once you’ve registered a data source with JNDI, you can get an instance of it by performing a JNDI lookup, specifying the JNDI name to which the data source is bound.
Suppose you want to register a pool-enabled data source designed to reuse connections to the HR/HR database schema, associating this data source with logical name jdbc/HRPool in the JNDI tree. To do this, you must create a PoolDataSource object representing the above data source, set its properties, and then register it with a JNDI naming service. This can be done with the following Java program:
Copied to Clipboard
Error: Could not Copy
/*
*An example of how you can register
* a pool-enabled data source with JNDI
*/
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;
import javax.naming.*; 
import java.util.Hashtable; 

public class JNDIRegister { 
public static void main(String argv[]) { 
try { 
//Creating a pool-enabled data source instance and setting its properties 
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL("jdbc:oracle:thin:@//localhost:1521/XE");
pds.setUser("hr");
pds.setPassword("hr");
pds.setInitialPoolSize(5);
pds.setMinPoolSize(5);
pds.setMaxPoolSize(10);
//Registering the data source with JNDI
Hashtable env = new Hashtable(); 
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory"); 
Context ctx = new InitialContext(env); 
ctx.bind("jdbc/HRPool", pds);
}
catch (Exception e) { 
System.out.println(e); 
} 
} 
}
Before you can run this program, you have to set up Sun's file system JNDI service provider, which can be downloaded from here. Make sure to add the following JAR files to the classpath to be able to run the above program:
Copied to Clipboard
Error: Could not Copy
install_dir/sun/lib/fs/fscontext.jar;install_dir/sun/lib/fs/providerutil.jar
After you run the above program, you can utilize the jdbc/HRPool pool-enabled data source in your Java applications, whether they be JavaServer Pages, servlets, or standalone applications. The following is a standalone Java application utilizing this data source:
Copied to Clipboard
Error: Could not Copy
/*
*An example of a JNDI lookup for
* a pool-enabled data source 
*/
import java.sql.*; 
import oracle.ucp.jdbc.PoolDataSource;
import javax.naming.*; 
import java.util.Hashtable; 

public class JNDILookup {
public static void main(String argv[]) {
PoolDataSource pds;
//Performing a lookup for a pool-enabled data source registered in JNDI tree
try { 
Hashtable env = new Hashtable(); 
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env); 
pds = (PoolDataSource) ctx.lookup("jdbc/HRPool"); 
}
catch (NamingException eName) { 
System.out.println("Cannot look up " + "jdbc/HRPool" + ": " +eName); 
return; 
}
//Borrowing a connection from the data source returned by the JNDI lookup
try { 
Connection conn = pds.getConnection(); 
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select user from dual");
while(rs.next())
System.out.println("\nConnected as: "+rs.getString(1));
if (conn != null) 
conn.close(); 
} 
catch (SQLException eSQL) { 
System.out.println("Cannot obtain a connection: " + eSQL); 
} 
return; 
}
}
The first thing you do in the above program is to initialize the JNDI context, which is then used to perform a JNDI lookup for the jdbc/HROracle pool-enabled data source. Next you borrow a connection from the data source instance returned by the JNDI lookup and use it to issue a query against the database.
As you no doubt have realized, the approach discussed in this section simplifies the process of using connection pools. Registering a pool-enabled data source once and then obtaining an instance of it with a JNDI lookup when needed eliminates the need to set up the connection pool properties each time you initialize it. You just obtain a pool instance with the properties defined in advance.

High Availability and Performance

It’s important to emphasize that UCP supports new JDBC 4.0 high-availability and performance features, such as pool refreshing and connection validating, that are not related to Oracle Real Application Clusters (RAC) and therefore do not require an Oracle RAC database.
Furthermore, UCP provides the ability to validate connections on borrow. Validating connections on borrow is a useful technique, because it enables you to check whether a connection is still valid before you start using it. To help with this problem, a UCP JDBC connection pool instance has the ValidateConnectionOnBorrow property of type Boolean, which you need to set to true with the setValidateConnectionOnBorrow method:
Copied to Clipboard
Error: Could not Copy
pds.setValidateConnectionOnBorrow(true);
Then you need to specify an SQL statement you want to be issued to make sure that the connection is still valid. You can do this with the setSQLForValidateConnection method:
Copied to Clipboard
Error: Could not Copy
pds.setSQLForValidateConnection("select user from dual");
When utilizing an Oracle JDBC driver, though, there is no need to set the SQLForValidateConnection property—the pool will perform an internal ping to test the validity of the connection being borrowed.
Verifying connections on borrow is a good thing, but what if a connection becomes stale after it has been successfully validated on borrow? Is there any way to verify a connection after it has been borrowed? To address this issue, the JDBC 4.0 specification added the isValid method to the Connection interface, enabling you to test the validity of a connection when you want to.
Taking it one step further, UCP for JDBC provides the oracle.ucp.jdbc.ValidConnection interface, which includes two methods: isValid and setInvalid. These methods can be especially useful when utilized in conjunction with a retry mechanism implemented with recursion or iteration (looping). For instance, you might implement a method that will borrow and then utilize a connection, making a recursive call to itself in case the connection has become stale and consequently the operation cannot be completed. An important thing to keep in mind when implementing such a recursive mechanism is that it must provide the ability to limit the number of recursive calls to be made and that each new recursive call must reduce that number, thus preventing the possibility of endless looping.
The following is a simple program providing an example of how you might use the oracle.ucp.jdbc.ValidConnection interface methods in conjunction with a retry mechanism based on recursion.
Copied to Clipboard
Error: Could not Copy
/*
*An example of validating connections on borrow;
*this also shows the use of the ValidConnection interface's methods:
*isValid and setInvalid methods in combination with a retry mechanism
*/

import java.sql.*; 
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.ValidConnection;
import javax.naming.*; 
import java.util.Hashtable; 

public class ConnectionValidating {
public static void main(String argv[]) {
PoolDataSource pds;
//Looking up for the jdbc/HRPool pool-enabled data source registered in JNDI tree
...
//for actual code see the JNDI lookup example 
//discussed in the Borrowing a Connection with JNDI section earlier
...
try { 
//Instructing the pool to validate connections on borrow
pds.setValidateConnectionOnBorrow(true);
//Calling the getUser method that borrows a connection from the pool
//limiting the number of recursive calls to 3
System.out.println("\nConnected as :"+getUser(pds, 3));
}
catch (SQLException eSQL) { 
System.out.println("\nSQLException: " + eSQL); 
return; 
}
}
//This method borrows a connection from the pool and will make a recursive call 
//if it turns out that the borrowed connection has become unusable
private static String getUser (PoolDataSource pds, int recursiveCalls) throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String user = null;
try {
//Borrowing a connection from the pool
conn = pds.getConnection(); 
//Working with the connection
stmt = conn.createStatement();
rs = stmt.executeQuery("select user from dual");
while(rs.next())
user = rs.getString(1);
if (conn != null)
conn.close();
} 
catch (SQLException eSQL) {
if (recursiveCalls > 0 && !((ValidConnection) conn).isValid()) {
System.out.println("\nConnection is no longer valid: " + eSQL);
//Calling setInvalid often leads to an exception
//so it's a wise idea to put it in a separate try block
try {
((ValidConnection) conn).setInvalid();
} catch (SQLException conEx) {
System.out.println("\nInvalidating failed: " + conEx); 
}
conn.close();
conn = null; 
System.out.println("\nRetrying to obtain a new connection");
//making a recursive call to getUser in an attempt to obtain a valid connection
//the number of recursive calls allowed is reduced by 1
user = getUser(pds, recursiveCalls - 1);
} else {
System.out.println("\nSQLException: " + eSQL); 
}
}
finally {
return user; 
} 
}
In this example, the getUser method calls itself from within the catch clause of the try/catch statement implemented in this same method. Here, you limit the number of allowed recursive calls to three. If it fails to obtain a valid connection three times in a row, you stop trying and get out.
Aside from the generic high-availability and performance features discussed above, UCP for JDBC can be integrated with Oracle RAC features such as Fast Connection Failover (FCF) and Runtime Connection Load Balancing, making it easier to manage connections to an Oracle RAC database.
The following snippet illustrates how you can enable FCF when using a UCP JDBC connection pool managing connections to an Oracle RAC database. Note that using FCF requires you to add the Oracle Notification Service library (ons.jar) to an application’s classpath. The Oracle Notification Service library is shipped as part of Oracle Database, starting with Oracle Database 10 g.
Copied to Clipboard
Error: Could not Copy
...
//Creating a pool-enabled data source
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
//Setting pool properties
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
//Setting a RAC-specific URL
pds.setURL(
"jdbc:oracle:thin:@" +
"(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=ON)" +
"(ADDRESS=(PROTOCOL=TCP)" +
"(HOST=rachost1)(PORT=1521))" +
"(ADDRESS=(PROTOCOL=TCP)" +
"(HOST=rachost2)(PORT=1521)))" +
"(CONNECT_DATA=(SERVICE_NAME=orcl)))");
pds.setUser("usr");
pds.setPassword("pswd");
pds.setMinPoolSize(10);
pds.setMaxPoolSize(20);
//Configuring remote ONS subscription
pds.setONSConfiguration("nodes=rachost1:4200,rachost2:4200");
// Enabling Fast Connection Failover
pds.setFastConnectionFailoverEnabled(true);
Once you have a connection pool set up and FCF enabled, you can borrow a connection from the pool and create queries on it as you would in a non- RAC-specific program.
Copied to Clipboard
Error: Could not Copy
Connection conn = pds.getConnection();
 Statement stmt = conn.createStatement();
 ResultSet rs = null;
Then you can implement a retry mechanism that will check a connection for validity after a RAC-down event triggers UCP FCF actions, trying again to connect to surviving RAC instances in case the connection has become stale. The following snippet illustrates how this could be implemented within a while loop.
Copied to Clipboard
Error: Could not Copy
boolean retry = true;
while(retry)
{
try
{
//Getting a RAC connection from the pool
conn = pds.getConnection();
// Executing a query on the connection.
rs = stmt.executeQuery("select user from dual");
rs.next();
System.out.println("\nConnected as : " + rs.getString(1));
//Setting retry to false to exit the loop
retry = false;
}
catch (SQLException eSQL)
{
System.out.println("\nSQLException: " + eSQL);
// Checking connection usability after a RAC-down event triggers UCP FCF actions
if (conn == null || !((ValidConnection) conn).isValid())
{
//Closing the connection
try
{
conn.close();
}
catch (SQLException eClose)
{
System.out.println("\nException arose when closing connection: " + eClose);
}
//Setting retry to true to try again
retry = true;
}
}
Thread.sleep(1000);
}
If a connection has been successfully borrowed from the pool and the statement execution has not triggered an exception, there is no need to retry the operation and therefore the execution flow will jump out of the loop. Otherwise, it will try to reconnect and will then perform the statement execution again.

Optimizing Connection Pools

The UCP JDBC connection pool provides a set of properties you can use to optimize pooling behavior. For example, you can regulate the pool size, setting the properties controlling the initial, maximum, and minimum pool size. In the preceding sections, you have seen how you can set up these properties.
Aside from the properties controlling the pool size, there are properties controlling stale connections. For example, you can set up the pool’s MaxConnectionReuseTime property, thus configuring a maximum connection reuse time. In some environments, you may find it useful to have connections removed from the pool after a connection has been borrowed a certain number of times. You can do this by setting up the MaxConnectionReuseCount property.
You can set up the AbandonConnectionTimeout property to instruct the pool to reclaim borrowed connections after a connection has not been used for a certain amount of time. Also, you can set up the TimeToLiveConnectionTimeout property, limiting how long a borrowed connection may be used before it is reclaimed by the pool.
If you anticipate that the pool may run out of connections at some point, you can set the ConnectionWaitTimeout property to the number of seconds an application request waits for a connection when no connections are available in the pool. Also, there is the InactiveConnectionTimeout property, which enables you to specify how long an available connection can remain unborrowed before it is removed from the pool.
Another interesting property is TimeoutCheckInterval, with which you can set up the timeout check interval, controlling how often the timeout properties discussed above will be enforced. By default, this property is set to 30, meaning that the timeout check cycle runs every 30 seconds.
All the optimization features discussed so far in this section require you to set a certain property to an appropriate value in order to get the effect you want, but to enable the connection harvesting feature, which is used to ensure a certain number of available connections in the pool, you need to use a mechanism that’s a bit more complicated. This feature is explained in the rest of this section by example.
Let’s say you set up the pool’s size properties as follows:
Copied to Clipboard
Error: Could not Copy
...
 pds.setInitialPoolSize(10);
 pds.setMaxPoolSize(20);
With initialPoolSize set to 10, you will have 10 connections upon initializing the connection pool. Next, with the following code, you enable the connection harvesting feature, thus making the pool’s connections harvestable:
Copied to Clipboard
Error: Could not Copy
pds.setConnectionHarvestTriggerCount(5); 
 pds.setConnectionHarvestMaxCount(2);
The properties set above instruct the pool to reclaim two borrowed connections when the number of available connections in the pool drops to five. Let’s now create an array of five connection objects that can be then used to hold five connections borrowed from the pool:
Copied to Clipboard
Error: Could not Copy
//Creating an array of connection objects
 Connection[] conn = new Connection[5];
Before populating the above array with connections, though, you need to create an array of callback objects, each of which will be registered with a connection. A callback object must be an instance of a custom implementation of the ConnectionHarvestingCallback abstract interface. A simple implementation is shown a little later in this section.
With the following code, you create an array of five CustConnectionHarvestingCallback objects:
Copied to Clipboard
Error: Could not Copy
//Creating an array of callback objects
 CustConnectionHarvestingCallback[] callbk = new CustConnectionHarvestingCallback[5];
In the following loop, you borrow four connections from the pool and also create four callback objects, each of which is registered with a connection:
Copied to Clipboard
Error: Could not Copy
//Borrowing four connections from the pool
for (int i = 0; i < 4; i++)
{
conn[i] = pds.getConnection();
//Registering the callback object with each connection
callbk[i] = new CustConnectionHarvestingCallback(conn[i]); 
((HarvestableConnection) conn[i]).registerConnectionHarvestingCallback(callbk[i]);
}
Before you borrow the fifth connection to trigger harvesting, you can disable harvesting on a certain connection for testing purposes. You might recall that you specified two borrowed connections to be returned to the pool when the number of available connections drops to five. By default, the connection harvest feature will harvest those two connections that were borrowed first. Thus, in this example, conn[0] and conn[1] will be harvested. By setting conn[0] as nonharvestable, however, you make it harvest conn[1] and conn[2].
Copied to Clipboard
Error: Could not Copy
//Setting conn[0] as nonharvestable 
 ((HarvestableConnection) conn[0]).setConnectionHarvestable(false);
Let’s now trigger harvesting by borrowing the fifth connection from the pool.
Copied to Clipboard
Error: Could not Copy
//Borrowing the fifth connection to trigger harvesting
 conn[4] = pds.getConnection();
 callbk[4] = new CustConnectionHarvestingCallback(conn[4]);
 ((HarvestableConnection) conn[4]).registerConnectionHarvestingCallback(callbk[4]);
Recall from the discussion of the timeout check interval earlier in this section that this interval is set to 30 by default. What this means in this example is that harvesting will not be triggered immediately but within a 30-second interval.
Copied to Clipboard
Error: Could not Copy
// Waiting for harvesting to happen
 Thread.sleep(30000);
To make sure everything has worked as planned, you might want to check through the connections to see which ones have been closed and returned to the pool:
Copied to Clipboard
Error: Could not Copy
//Checking connections
for (int i = 0; i < 5; i++)
{
System.out.println("Connection " + i + " returned to the pool - " + conn[i].isClosed());
}
The above should generate output showing that conn[1] and conn[2] have been closed and therefore returned to the pool whereas the other three are still in the borrowed state.
Finally, here is how you might implement the ConnectionHarvestingCallback abstract interface so that its cleanup method closes the connection being harvested:
Copied to Clipboard
Error: Could not Copy
class CustConnectionHarvestingCallback implements ConnectionHarvestingCallback
 {
 private Connection conn = null;
 public CustConnectionHarvestingCallback(Connection conn)
 {
 this.conn = conn;
 }
 public boolean cleanup()
 {
 try {
 conn.close();
 }
 catch (Exception e) {
 return false;
 }
 return true;
 }
 }
The above is a simple example of a ConnectionHarvestingCallback abstract interface implementation. In a real-world application, you might want to use a more complicated implementation. In particular, you might need to implement more-complex logic in the cleanup method, such as rolling back the transaction associated with the connection being harvested, before closing that connection.
As you have learned in this section, there are several UCP JDBC connection pool properties you can use to optimize pooling behavior. So, it's often a good idea to experiment with pool settings to find the combination that best fits the needs of your application.

Statement Pooling

This recommendation may seem obvious, but the importance of statement pooling in data-intensive applications cannot be overstated. Oracle JDBC drivers support explicit and implicit statement caching, enabling you to cache prepared and callable statements. Implicit caching doesn’t require you to take any special action to send statements to and retrieve them from a cache—a prepared or callable statement automatically goes to the cache when you invoke the close method of that statement object. The next time you create this statement on this same connection, it will be retrieved from the cache rather than being created from scratch. If implicit caching is turned on, a statement object will be reused from the cache when the following conditions are met:
  • The SQL string used in the statement is equal to one held in the cache.
  • The statement type is also the same, meaning prepared or callable.
  • The scrollable type of the result set generated by the statement is also the same, meaning forward-only or scrollable.
Although Oracle JDBC drivers are designed with the supposition that implicit caching is enabled, this feature is not turned on by default. To enable implicit caching on the connection, you can set the implicitCachingEnabled property of the corresponding OracleConnection object to true and set the statementCacheSize property to a positive integer. This can be done as follows:
Copied to Clipboard
Error: Could not Copy
conn.setImplicitCachingEnabled(true);
 conn.setStatementCacheSize(10);
When using aUCP JDBC connection pool, you can enable statement caching by setting maxStatements property to a positive integer:
Copied to Clipboard
Error: Could not Copy
pds.setMaxStatements(10);
If you do this, statement caching will be enabled for each connection within the pool. The following program provides a simplified example of how you can use statement pooling, taking advantage of connection pooling at the same time:
Copied to Clipboard
Error: Could not Copy
/*
*An example of statement pooling in action
*/

import java.sql.*; 
import oracle.ucp.jdbc.PoolDataSource;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import javax.naming.*; 
import java.util.Hashtable; 

public class StatementPooling {
public static void main(String argv[]) {
PoolDataSource pds;
//Looking up for the jdbc/HRPool pool-enabled data source registered in the JNDI tree
...
//for actual code, see the JNDI lookup example 
//discussed in the Borrowing a Connection with JNDI section earlier
...
try {
//Enabling statement caching for the pool's connections 
pds.setMaxStatements(10); 
//Borrowing a connection from the pool
OracleConnection conn = (OracleConnection) pds.getConnection(); 
//Checking whether the implicit statement caching is enabled
if (conn.getImplicitCachingEnabled())
System.out.println("\nimplicit caching enabled"); 
else
System.out.println("\nimplicit caching disabled"); 
//Looping through calls to the getRegion private class method that executes a prepared statement
for (int i = 1; i < 5; i++ ) {
System.out.println("\n" + getRegion(conn, i));
}
//Returning the connection to the pool
if (conn != null) 
conn.close();
conn = null;
} 
catch (SQLException eSQL) { 
System.out.println("Cannot obtain a connection: " + eSQL); 
} 
}
//This method creates, executes, and then closes a prepared statement
private static String getRegion (OracleConnection conn, int region_id ) throws SQLException {
OraclePreparedStatement stmt = null;
ResultSet rs = null;
String region = null;
String sql = "SELECT * FROM regions WHERE region_id = ?"; 
try {
stmt = (OraclePreparedStatement)conn.prepareStatement(sql);
stmt.setInt(1, region_id);
rs = stmt.executeQuery();
rs.next();
region = rs.getString("REGION_NAME");
} 
catch (SQLException eSQL) {
System.out.println("\nSQLException: " + eSQL); 
}
//this code is executed under all circumstances 
finally {
if (rs != null)
rs.close ();
if (stmt != null)
//if implicit caching is enabled, the statement is not actually closed
//but is sent to the cache
stmt.close ();
return region;
}
}
}
As you can see, the getRegion method of the class shown above creates, executes, and then closes a prepared statement, returning the query result to the calling code. This method is called repeatedly in a loop running in the main method, making implicit statement caching possible. In this example, you enabled implicit caching when you called the setMaxStatements method of the connection pool instance. So calling the close method of the prepared statement in the getRegion method will actually cache the statement instead of closing it, thus letting the program reuse it on the second and subsequent calls to getRegion. To make sure it works as expected, you can add the following code to the getRegion method, putting it right after the call to the prepareStatement method of the OracleConnection object:
Copied to Clipboard
Error: Could not Copy
...
//Checking the creation state of the prepared statement
int creationState = stmt.creationState();
switch(creationState) {
case 0:
System.out.println("\nCreation state: new");
break;
case 1:
System.out.println("\nCreation state: from the implicit cache"); 
break;
case 2:
System.out.println("\nCreation state: from the explicit cache"); 
break;
}
...
If you now execute the program, you should see that the prepared statement’s creation status is new only upon the first call to getRegion—all subsequent getRegion calls reuse the statement cached implicitly.
Being able to enable statement caching for every statement on every connection within the pool is a start, but how you can you apply this technique selectively, disabling caching statements on a certain pooled connection or even disabling caching for a certain statement?
If you recall from the discussion at the beginning of this section, you can use an OracleConnection implicitCachingEnabled property to enable or disable statement caching for that particular connection. For example, you may have enabled statement caching for each connection within the pool and may then disable it on a particular connection as follows:
Copied to Clipboard
Error: Could not Copy
conn.setImplicitCachingEnabled(false);
As for disabling or enabling caching on particular statements, you can take advantage of the new JDBC 4.0 methods added to the Statement interface. In particular, to make a statement object poolable or not poolable, you can use its setPoolable method, passing in true or false, respectively. To check out the current poolable state of a statement object, you can use the isPoolable method of that object. Here is how you might prevent a particular prepared statement from going to the implicit cache:
Copied to Clipboard
Error: Could not Copy
if(stmt.isPoolable())
 stmt.setPoolable(false);
It’s interesting to note here that if implicit caching is not enabled on the connection, an attempt to enable it for a particular statement with setPoolable(true) won’t force the desired effect. Although the poolable property of a statement object is set to true by default, you still need to first turn implicit caching on for the connection or the entire connection pool, as described at the beginning of this section.

Conclusion

In this article, you learned how to take advantage of connection and statement pooling, utilizing outstanding Oracle-specific JDBC features as well as the standard JDBC 4.0 features. In particular, you looked at the Oracle Universal Connection Pool for JDBC, a new Oracle Database 11g feature providing a connection pool implementation for caching JDBC connections. Then you learned how to take advantage of statement pooling, utilizing features specific to Oracle’s JDBC drivers as well as the new JDBC 4.0 methods added to the Statement interface.
Yuli Vasiliev is a software developer, freelance author, and consultant currently specializing in open source development, Java technologies, databases, and SOA. He is the author of Beginning Database-Driven Application Development in Java EE: Using GlassFish(Apress, 2008).

Reference :