# hp Reliable Transaction Router
Application Design Guide

Previous | Contents | Index

## RTR Events

RTR delivers status information to which client and server applications can subscribe. Status information is delivered as messages, where the type of each message is an RTR event.

RTR events are numbered. The base value for RTR events is defined by the symbol `RTR_EVTNUM_RTRBASE` ; its maximum value is defined by the symbol `RTR_EVTNUM_RTRMAX` . RTR events and event numbers are listed in the Reliable Transaction Router API manuals and in the RTR header files rtr.h and rtrapi.h.

An application can subscribe to RTR events to receive notification of external events that are of interest to the application. For example, a shadow server may need to know if it is a primary or a secondary server to perform certain work, such as uploading information to a central database, that is done at only one site.

To subscribe to all RTR events, use the range indicators `RTR_EVTNUM_RTRBASE` and `RTR_EVTNUM_RTRMAX` . RTR events are delivered as messages of type `rtr_mt_rtr_event` .

In application design, consider creating separate facilities for sending broadcasts. By separating broadcast notification from transactional traffic, performance improvements can be substantial. Facilities can further be reconfigured to place the RTR routers strategically to minimize wide-area traffic.

A server application can expect to see a primary or secondary event delivered only in certain transaction states. For more detail, see the state diagrams in Appendix C, Server States.

# Location Transparency

With location transparency, applications do not need to be modified when the hardware configuration is altered, whether changes are made to systems running RTR services or to the network topology. Client and server applications do not know the location of one another so services can be started anywhere in the network. Actual configuration binding is a system management operation at run time, through the assignment of roles (frontend/backend/router) within a given facility to the participant nodes.

For RTR to automatically take care of failover, server applications need to specify certain availability attributes for the partition.

Because RTR automatically takes care of failover, applications need not be concerned with specifying the location of server resources.

# Handling Error Conditions

RTR can provide information to an application with the RTRMessage and RTREvent classes (for the C++ API). Certain inherited methods within these classes translate RTR internal error message values to informational text meaningful to the reader. For the C API, this is done with the `rtr_error_text` call.

If an application encounters an error, it should log the error message received. Error messages are more fully described in rtrapi.h for the C++ API and in rtr.h for the C API, where each error code is explained.

For example, the following short program uses the standard C library output function to display the text of an error status code.

    Program "prog":
    #include "rtr.h"

or

    #include <rtr.h>
    main() {
     printf("%s",
      rtr_error_text(RTR_STS_NOLICENSE));
    }

When this program is run, it produces the following output:

    $run prog
     No license installed

The several hundred error or status codes reside in the rtr.h header file; status codes can come from any RTR subsystem. A few codes that an application is likely to encounter are described in Table 3-3.

Table 3-3 RTR Error Codes
Status Code | Meaning
--- | ---
`RTR_STS_COMSTAUNO` | Commitment status unobtainable. The fate of the transaction currently being committed is unobtainable; this may be due to a hardware failure.
`RTR_STS_DLKTXRES` | The transaction being processed was aborted due to deadlock with other transactions using the same servers. RTR will replay the transaction after the deadlock has been resolved and cleared.
`RTR_STS_FELINLOS` | Frontend link lost; probably due to a network failure.
`RTR_STS_INVFLAGS` | Invalid flags.
`RTR_STS_NODSTFND` | No destination found; no server had declared itself to handle the key value specified in the sent message. Probably a server down or disconnected.
`RTR_STS_REPLYDIFF` | Two servers respond with different information during a replay; transaction aborted.
`RTR_STS_TIMOUT` | Timeout expired; transaction aborted.
`RTR_STS_SRVDIED` | Probably a server image exited, for example because a node is down.
`RTR_STS_SRVDIEDVOT` | A server exited before committing a transaction.
`RTR_STS_SRVDIEDCOM` | A server exited after being told to commit a transaction.

RTR can abort a transaction at any time, so the application must be prepared to deal with such aborted transactions. Server applications are expected to roll back transactions as the need arises, and must be built to take the correct action, and subsequently carry on to deal with new transactions that are received.

A client application can also get a reject and must also be built to deal with the likely cases it will encounter. The application must be built to decide on the correct course of action in the event of a transaction abort.

# Using Locks

When using a database system with RTR, an application designer must be aware of how the database system works and how it handles database locks. Because Oracle is a frequently used database system, this section provides a short summary of Oracle locking methods. The application designer must use Oracle documentation to supplement this brief description. This material is fully discussed in the Oracle8 and Oracle8i Application Developer's Guides, specifically in the chapters on Processing SQL Statements, Explicit Data Locking, Explicitly Acquiring Row Locks, Serializable and Row Locking Parameters, User Locks, Non-Default Locking, and Concurrency Control Using Serializable Transactions. Oracle database operations are performed using Structured Query Language (SQL).

## Oracle Locking

### Privileges Required

In its own schema, an application can automatically acquire any type of table locks. However, to acquire a table lock on a table in another schema, the application must have the LOCK ANY TABLE system privilege or an object privilege such as SELECT or UPDATE for the table.

### Overriding Default Locking

By default, Oracle locks data structures automatically. However, an application can request specific data locks on rows or tables when it needs to override default locking. Explicit locking lets an application share or deny access to a table for the duration of a transaction.

An application can explicitly lock entire tables using the LOCK TABLE statement, but locking a table means that no other transaction, user, or application can access it. This can cause performance problems.

With the SELECT FOR UPDATE statement, an application explicitly locks specific rows of a table to ensure the rows do not change before an update or a delete. Oracle automatically obtains row-level locks at update or delete time, so use the FOR UPDATE clause only to lock the rows before the update or delete.

A SELECT statement with Oracle does not acquire any locks, but a SELECT ... FOR UPDATE does. For example, the following is a typical SELECT ... FOR UPDATE statement:

     SELECT partno FROM parts FOR UPDATE OF price

This statement starts a transaction to update the parts table with a price change for a specific part.

### Oracle Explicit Data Locking

To ensure data concurrency, integrity, and statement-level read consistency, Oracle always performs necessary locks. However, an application can override default locks. This can be useful when:

- An application needs transaction-level read consistency or repeatable reads . For example, when transactions must query a consistent set of data for the duration of the transaction, and the application must be sure that the data have not been changed by any other transactions. To achieve transaction-level read consistency, an application can use:
  - Explicit locks
  - Read-only transactions
  - Serializable transactions
  - Default-lock overrides
- An application requires a transaction to have exclusive access to a resource. A transaction with exclusive access need not wait for other transactions to complete.

Overrides to Oracle locks can be done at two levels:

- Transaction level
- System level

At transaction level: Transactions override Oracle default locks with the following SQL statements:

- LOCK TABLE
- SELECT ... FOR UPDATE
- SET TRANSACTION ... READ ONLY
- SET TRANSACTION ... ISOLATION LEVEL SERIALIZABLE

At system level : Oracle can start an instance with non-default locking by adjusting the following initialization parameters:

- SERIALIZABLE
- ROW_LOCKING

If an application overrides any Oracle default locks, the application itself must:

- Ensure that the overriding locking procedures work correctly.
- Guarantee data integrity.
- Ensure acceptable data concurrency.
- Ensure that deadlocks cannot occur or are handled appropriately.

### Table Locks

When a LOCK TABLE statement executes, it overrides default locking, and a transaction explicitly acquires the specified table locks. A LOCK TABLE statement on a view locks the underlying base tables (see Table 3-4).

Table 3-4 LOCK TABLE Statements
Statement | Meaning
--- | ---
LOCK TABLE tablename IN EXCLUSIVE MODE [NOWAIT]; | Acquires exclusive table locks. Locks all rows of the table. No other user can modify the table. With NOWAIT, the application acquires the table lock only if the lock is immediately available, and Oracle issues an error if not. Without NOWAIT, the transaction does not proceed until the requested table lock is acquired. If the wait for a table lock reaches the limit set by the initialization parameter DISTRIBUTED_LOCK_TIMEOUT, a distributed transaction can time out. As no data will have been modified due to the timeout, the application can proceed as if it has encountered a deadlock.
LOCK TABLE tablename IN ROW SHARE MODE; LOCK TABLE tablename IN ROW EXCLUSIVE MODE; | These offer the highest degree of concurrency. Consider if the transaction must prevent another transaction from acquiring an intervening share, share row, or exclusive table lock for a table before the table can be updated in the transaction. If another transaction acquires an intervening share, share row, or exclusive table lock, no other transactions can update the table until the locking transaction commits or rolls back.
LOCK TABLE tablename IN SHARE MODE; | Consider this share table lock if: Your transaction only queries the table and requires a consistent set of table data for the duration of the transaction (requires transaction-level read consistency for the locked table). Other transactions to update the locked table concurrently can be made to wait until all transactions with the share table locks commit or roll back. Other transactions can acquire concurrent share table locks on the same table, providing them transaction-level read consistency. Note: If multiple transactions concurrently hold share table locks for the same table, NO transaction can update the table. Thus if share table locks on the same table are common, deadlocks will be frequent and updates will not proceed. For such a case, use share row exclusive or exclusive table locks.
LOCK TABLE tablename IN SHARE ROW EXCLUSIVE MODE; | Acquire a share row exclusive table lock when: Your transaction requires both transaction-level read consistency for the specified table, and the ability to update the locked table. Other transactions can obtain explicit row locks. The application needs only a single transaction to have this behavior.

### Acquiring Row Locks

The SELECT ... FOR UPDATE statement acquires exclusive row locks of selected rows. The statement can be used to lock a row without changing the row. Acquiring row locks can also be used to ensure that only a single interactive application user updates rows at a given time. For information on using this statement with cursors or triggers, see the Oracle8 or Oracle8i documentation. To acquire a row lock only when it is immediately available, include NOWAIT in the statement.

Each row in the return set of a SELECT ... FOR UPDATE statement is individually locked. The statement waits until a previous transaction releases the lock. If a SELECT ... FOR UPDATE statement locks many rows in a table, and the table is subject to moderately frequent updates, it may improve performance to acquire an exclusive table lock rather than using row locks.

If the wait for a row lock reaches the limit set by the initialization parameter DISTRIBUTED_LOCK_TIMEOUT, a distributed transaction can time out. As no data will have been modified, the application can proceed as if it has encountered a deadlock.

### Setting SERIALIZABLE and ROW_LOCKING Parameters

How an instance handles locking is determined by the SERIALIZABLE option on the SET TRANSACTION or ALTER SESSION command, and the initialization parameter ROW_LOCKING. By default, SERIALIZABLE is set to false and ROW_LOCKING is set to always.

Normally these parameters should never be changed. However they may be used for compatibility with applications that run with earlier versions of Oracle, or for sites that must run in ANSI/ISO-compatible mode. Performance will usually suffer with non-default locking.

### Using the LOCK TABLE Statement

The application uses the LOCK TABLE statement to lock entire database tables in a specified lock mode to share or deny access to them. For example, the statement below locks the parts table in row-share mode. Row-share locks allow concurrent access to a table; they prevent other users from locking the entire table for exclusive use. Table locks are released when your transaction issues a commit or rollback.

        LOCK TABLE parts IN ROW SHARE MODE NOWAIT;

The lock mode determines which other locks can be placed on the table. For example, many users can acquire row-share locks on a table at the same time, but only one user at a time can acquire an exclusive lock. While one user has an exclusive lock on a table, no other users can insert, delete, or update rows in that table. For more information about lock modes, see the Oracle8 or Oracle8i Server Application Developer's Guide.

A table lock never keeps other users from querying a table, and a query never acquires a table lock. Only if two different transactions try to modify the same row will one transaction wait for the other to complete.

If your program includes SQL locking statements, make sure the Oracle users requesting the locks have the privileges needed to obtain the locks.

Table 3-5 Summary of Locking Options
Case | Description | SERIALIZABLE | ROW_LOCKING
--- | --- | --- | ---
0 | Default settings | FALSE | ALWAYS
1 | As Oracle Version 5 and earlier (no concurrent inserts, updates or deletes in a table) | FALSE (disabled) | INTENT
2 | ANSI compatible | Enabled | ALWAYS
3 | ASNI compatible with table-level locking (no concurrent inserts, updates or deletes in a table) | Enabled | INTENT

Table 3-6 Non-Default Locking Behavior
Statement |  | Case 0 | Case 1 |  | Case 2 |  | Case 3 |
--- | --- | --- | --- | --- | --- | --- | --- | ---
SERIALIZABLE | FALSE (disabled) | Disabled |  | Enabled |  | Enabled |
ROW_LOCKING | ALWAYS | INTENT |  | ALWAYS |  | INTENT |
|  |  | Row | Table | Row | Table | Row | Table
SELECT |  | - | - | - | S | - | S
INSERT |  | X | SRX | X | RX | X | SRX
UPDATE |  | X | SRX | X | SRX | X | SRX
DELETE |  | X | SRX | X | SRX | X | SRX
SELECT ... FOR UPDATE |  | X | RS | X | S | X | S
LOCK TABLE ... IN.. |  |  |  |  |  |
| ROW SHARE MODE | RS | RS | RS | RS | RS | RS
| ROW EXCLUSIVE MODE | RX | RX | RX | RX | RX | RX
| SHARE MODE | S | S | S | S | S | S
| SHARE ROW EXCLUSIVE MODE | SRX | SRX | SRX | SRX | SRX | SRX
| EXCLUSIVE MODE | X | X | X | X | X | X
DDL Statements |  | - | X | - | X | - | X

Modes: | X = EXLUSIVE
| RS = ROW SHARE
| RX = ROW EXCLUSIVE
| S = SHARE
| SRX = SHARE ROW EXCLUSIVE

The information in this table comes from the Oracle 8i Application Developer's Guide.

## Distributed Deadlocks

A deadlock or deadly embrace can occur when transactions lock data items in a database. The typical scenario is with two transactions txn1 and txn2 executing concurrently with the following sequence of events:

1. txn1 write-locks data item A.
2. txn2 write-locks data item B.
3. txn1 requests a lock on data item B but must wait because txn2 still has a lock on data item B.
4. txn2 requests a lock on data item A but must wait because txn1 still has a lock on data item A.

Neither txn1 nor txn2 can proceed; they are in a deadly embrace. Figure 3-7 illustrates a deadly embrace.

Figure 3-7 Deadly Embrace

With RTR, to avoid such deadlocks, follow these guidelines:

1. Always engage servers in the same order, and wait for the reply before each send.
2. Provide several concurrent servers to minimize contention. Estimate the number of concurrent servers needed by determining the volume of transactions the servers must support, considering periods of maximum activity, and allowing for growth. The larger the volume on your servers, the more likely it is that your application will benefit from using concurrent servers.

RTR attempts to resolve deadlocks by aborting one deadlocked transaction path with error code `RTR_STS_DLKTXRES` and replaying the transaction. Other paths are not affected. Server applications should be written to handle this status appropriately.

The RTR status code `RTR_STS_DLKTXRES` can occur under several environmental conditions that RTR detects and works around. The application need not take any explicit action other than releasing the resources connected with the active transaction such as doing a rollback on the database transaction.

For example, RTR may issue an `RTR_STS_DLKTXRES` status when:

- There are at least two client applications, or one client with two or more channels.
- There are multi-participant transactions, where each transaction contains two or more messages involving more than one server application.
- The number of servers is less than or equal to the number of transaction branches that can be simultaneously active.

As an example of the first case, consider clients A and B both performing transactions TCA and TCB, where both TCA and TCB include a message to both server X and server Y followed by an ACCEPT. There is only one instance of Server X and Server Y available, and due to the quirks of distributed processing, only Server X receives the message belonging to TCA and only Server Y receives the message belonging to TCB. Figure 3-8 reflects this scenario. Because Server Y has no chance of accepting TCA until TCB is processed to completion and Server X has no chance of accepting TCB until TCA is processed to completion, Server X and Y are in a distributed deadlock. In such a case, RTR selects TCA or TCB to abort with `DLKTXRES` and replays it in a different order.

Figure 3-8 Scenario for Distributed Deadlock

Sometimes RTR needs to abort a transaction and reschedule it. For example, it can happen that a state change is needed after the primary server started to process a transaction but RTR had to change its role to secondary before the transaction was completed. Thus the transaction would be executed on the other node as primary and later played to this server as secondary. RTR uses the same status code `RTR_STS_DLKTXRES` when aborting the transaction.

Previous | Next | Contents | Index