writing transactional applications with berkeley db, java edition · 2006. 5. 25. · sleepycat...

62
Makers of Berkeley DB Berkeley DB Java Edition Transaction Processing .

Upload: others

Post on 05-Oct-2020

7 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

M a k e r s o f B e r k e l e y D B

Berkeley DB Java EditionTransaction Processing.

Page 2: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Legal Notice

This documentation is distributed under the terms of the Sleepycat public license. You may review the termsof this license at: http://www.sleepycat.com/download/oslicense.html

Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service marks ofSleepycat Software, Inc. All rights to these marks are reserved. No third-party use is permitted without theexpress prior written consent of Sleepycat Software, Inc.

Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the UnitedStates and other countries.

To obtain a copy of this document's original source code, please write to <[email protected]>.

Published 5/24/2006

Page 3: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Table of ContentsPreface .............................................................................................. iv

Conventions Used in this Book ............................................................. ivFor More Information .................................................................. v

1. Introduction ...................................................................................... 1Transaction Benefits ......................................................................... 1

A Note on System Failure ............................................................. 2Application Requirements ............................................................ 2Multi-threaded Applications ......................................................... 3

Recoverability ................................................................................ 4Performance Tuning ......................................................................... 4

2. Enabling Transactions ........................................................................... 5Opening a Transactional Environment and Database .................................. 5

3. Transaction Basics ............................................................................... 7Committing a Transaction .................................................................. 8

Non-Durable Transactions ............................................................ 9Aborting a Transaction ..................................................................... 10Auto Commit ................................................................................ 11Transactional Cursors ...................................................................... 12Secondary Indices with Transaction Applications ...................................... 13Configuring the Transaction Subsystem ................................................. 15

4. Concurrency .................................................................................... 17Which JE Handles are Free-Threaded ................................................... 18Locks, Blocks, and Deadlocks ............................................................. 18

Locks ................................................................................... 18Lock Resources ................................................................. 19Types of Locks ................................................................. 19Lock Lifetime ................................................................... 20

Blocks .................................................................................. 20Blocking and Application Performance ..................................... 21Avoiding Blocks ................................................................. 22

Deadlocks .............................................................................. 22Deadlock Avoidance ........................................................... 23

JE Lock Management ....................................................................... 24Managing JE Lock Timeouts ......................................................... 24Managing Deadlocks .................................................................. 24

Isolation ...................................................................................... 26Supported Degrees of Isolation .................................................... 26Reading Uncommitted Data ........................................................ 27Committed Reads .................................................................... 29Configuring Serializable Isolation .................................................. 31

Transactional Cursors and Concurrent Applications ................................... 33Using Cursors with Uncommitted Data ............................................ 34

Read/Modify/Write ......................................................................... 355. Backing up and Restoring Berkeley DB, Java Edition Applications ..................... 37

Normal Recovery ............................................................................ 37Checkpoints .................................................................................. 37

Page iiUsing Transactions with JE5/24/2006

Page 4: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Performing Backups ........................................................................ 38Performing a Hot Backup ........................................................... 38Performing an Offline Backup ...................................................... 38Using the DbBackup Helper Class .................................................. 39

Performing Catastrophic Recovery ....................................................... 41Hot Failover ................................................................................. 41

6. Summary and Examples ....................................................................... 43Anatomy of a Transactional Application ................................................ 43Transaction Example ....................................................................... 44

TxnGuide.java ........................................................................ 45PayloadData.java ..................................................................... 48DBWriter.java ......................................................................... 49

Page iiiUsing Transactions with JE5/24/2006

Page 5: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

PrefaceThis document describes how to use transactions with your Berkeley DB, Java Editionapplications. It is intended to describe how to transaction protect your application's data.The APIs used to perform this task are described here, as are the environmentinfrastructure and administrative tasks required by a transactional application. This bookalso describes multi-threaded JE applications and the requirements they have for deadlockdetection.

This book is aimed at the software engineer responsible for writing a transactional JEapplication.

This book assumes that you have already read and understood the concepts contained inGetting Started with Berkeley DB, Java Edition.

Conventions Used in this Book

The following typographical conventions are used within in this manual:

Class names are represented in monospaced font, as are method names. For example: "TheEnvironment.openDatabase() method returns a Database class object."

Variable or non-literal text is presented in italics. For example: "Go to your JE_HOMEdirectory."

Program examples are displayed in a monospaced font on a shaded background. Forexample:

import com.sleepycat.je.Environment;

...

// Open the environment. Allow it to be created if it does not already exist.Environment myDbEnv;

In some situations, programming examples are updated from one chapter to the next.When this occurs, the new code is presented in monospaced bold font. For example:

import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import java.io.File;

...

// Open the environment. Allow it to be created if it does not already exist.Environment myDbEnv;EnvironmentConfig envConfig = new EnvironmentConfig();envConfig.setAllowCreate(true);myDbEnv = new Environment(new File("/export/dbEnv"), envConfig);

Page ivUsing Transactions with JE5/24/2006

Page 6: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Finally, notes of special interest are represented using a note block such as this.☞For More Information

Beyond this manual, you may also find the following sources of information useful whenbuilding a transactional JE application:

• Berkeley DB Java Edition Getting Started Guide[http://www.sleepycat.com/jedocs/GettingStartedGuide/BerkeleyDB-JE-GSG.pdf]

• Berkeley DB Java Edition Javadoc [http://www.sleepycat.com/jedocs/java/index.html]

• Berkeley DB, Java Edition Getting Started with the Direct Persistence Layer[http://www.sleepycat.com/jedocs/PersistenceAPI/BerkeleyDB-JE-Persistence-GSG.pdf]

Page vUsing Transactions with JE5/24/2006

Conventions Used in this Book

Page 7: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Page viUsing Transactions with JE5/24/2006

Page 8: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 1. IntroductionThis book provides a thorough introduction and discussion on transactions as used withBerkeley DB, Java Edition (JE). It begins by offering a general overview to transactions,the guarantees they provide, and the general application infrastructure required to obtainfull transactional protection for your data.

This book also provides detailed examples on how to write a transactional application.Both single threaded and multi-threaded are discussed. A detailed description of variousbackup and recovery strategies is included in this manual, as is a discussion on performanceconsiderations for your transactional application.

You should understand the concepts from Getting Started with Berkeley DB, Java Editionbefore reading this book.

The examples presented in this book use the Berkeley DB, Java Edition API, not the DirectPersistence Layer API. However, all of the concepts presented here in terms of transactional☞benefits, transaction usage, and deadlock handling also apply to the Direct PersistenceLayer.

Transaction Benefits

Transactions offer your application's data protection from application or system failures.That is, JE transactions offer your application full ACID support:

• Atomicity

Multiple database operations are treated as a single unit of work. Once committed,all write operations performed under the protection of the transaction are saved toyour databases. Further, in the event that you abort a transaction, all write operationsperformed during the transaction are discarded. In this event, your database is leftin the state it was in before the transaction began, regardless of the number or typeof write operations you may have performed during the course of the transaction.

Note that JE transactions can span one or more database handles.

• Consistency

Your databases will never see a partially completed transaction. This is true even ifyour application fails while there are in-progress transactions. If the application orsystem fails, then either all of the database changes appear when the application nextruns, or none of them appear.

In other words, whatever consistency requirements your application has will never beviolated by JE. If, for example, your application requires every record to include anemployee ID, and your code faithfully adds that ID to its database records, then JEwill never violate that consistency requirement. The ID will remain in the databaserecords until such a time as your application chooses to delete it.

• Isolation

Page 1Using Transactions with JE5/24/2006

Page 9: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

While a transaction is in progress, your databases will appear to the transaction as ifthere are no other operations occurring outside of the transaction. That is, operationswrapped inside a transaction will always have a clean and consistent view of yourdatabases. They never have to see updates currently in progress under the protectionof another transaction. Note, however, that isolation guarantees can be increasedand relaxed from the default setting. See Isolation (page 26) for more information.

• Durability

Once committed to your databases, your modifications will persist even in the eventof an application or system failure. Note that like isolation, your durability guaranteecan be relaxed. See Non-Durable Transactions (page 9) for more information.

A Note on System Failure

From time to time this manual mentions that transactions protect your data against'system or application failure.' This is true up to a certain extent. However, not all failuresare created equal and no data protection mechanism can protect you against everyconceivable way a computing system can find to die.

Generally, when this book talks about protection against failures, it means that transactionsoffer protection against the likeliest culprits for system and application crashes. So longas your data modifications have been committed to disk, those modifications shouldpersist even if your application or OS subsequently fails. And, even if the application orOS fails in the middle of a transaction commit (or abort), the data on disk should be eitherin a consistent state, or there should be enough data available to bring your databasesinto a consistent state (via a recovery procedure, for example). You may, however, losewhatever data you were committing at the time of the failure, but your databases willbe otherwise unaffected.

Of course, if your disk fails, then the transactional benefits described in this book areonly as good as the backups you have taken.

Finally, by following the programming examples shown in this book, you can write yourcode so as to protect your data in the event that your code crashes. However, noprogramming API can protect you against logic failures in your own code; transactionscannot protect you from simply writing the wrong thing to your databases.

Application Requirements

In order to use transactions, your application has certain requirements beyond what isrequired of non-transactional protected applications. They are:

• Transaction subsystem.

In order to use transactions, you must explicitly enable the transactional subsystemfor your application, and this must be done at the time that your environment is firstcreated.

• Transaction handles.

Page 2Using Transactions with JE5/24/2006

Transaction Benefits

Page 10: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

In order to obtain the atomicity guarantee offered by the transactional subsystem(that is, combine multiple operations in a single unit of work), your application mustuse transaction handles. These handles are obtained from your Environment objects.They should normally be short-lived, and their usage is reasonably simple. To completea transaction and save the work it performed, you call its commit() method. Tocomplete a transaction and discard its work, you call its abort() method.

In addition, it is possible to use auto commit if you want to transactional protect asingle write operation. Auto commit allows a transaction to be used without obtainingan explicit transaction handle. See Auto Commit (page 11) for information on how touse auto commit.

• Database open requirements.

Your application must transaction protect the database opens, and any secondaryindex associations, if subsequent operations on the databases are to be transactionprotected. The database open and secondary index association are commonlytransaction protected using auto commit.

• Deadlock detection.

Typically transactional applications use multiple threads of control when accessingthe database. Any time multiple threads are used on a single resource, the potentialfor lock contention arises. In turn, lock contention can lead to deadlocks. See Locks,Blocks, and Deadlocks (page 18) for more information.

Therefore, transactional applications must frequently include code for detecting andresponding to deadlocks. Note that this requirement is not specific to transactions –you can certainly write concurrent non-transactional JE applications. Further, notevery transactional application uses concurrency and so not every transactionalapplication must manage deadlocks. Still, deadlock management is so frequently acharacteristic of transactional applications that we discuss it in this book. SeeConcurrency (page 17) for more information.

Multi-threaded Applications

JE is designed to support multi-threaded applications, but their usage means you mustpay careful attention to issues of concurrency. Transactions help your application'sconcurrency by providing various levels of isolation for your threads of control. In addition,JE provides mechanisms that allow you to detect and respond to deadlocks (but strictlyspeaking, this is not limited to just transactional applications).

Isolation means that database modifications made by one transaction will not normallybe seen by readers from another transaction until the first commits its changes. Differentthreads use different transaction handles, so this mechanism is normally used to provideisolation between database operations performed by different threads.

Note that JE supports different isolation levels. For example, you can configure yourapplication to see uncommitted reads, which means that one transaction can see datathat has been modified but not yet committed by another transaction. Doing this might

Page 3Using Transactions with JE5/24/2006

Transaction Benefits

Page 11: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

mean your transaction reads data "dirtied" by another transaction, but which subsequentlymight change before that other transaction commits its changes. On the other hand,lowering your isolation requirements means that your application can experience improvedthroughput due to reduced lock contention.

For more information on concurrency, on managing isolation levels, and on deadlockdetection, see Concurrency (page 17).

Recoverability

An important part of JE's transactional guarantees is durability. Durability means thatonce a transaction has been committed, the database modifications performed under itsprotection will not be lost due to system failure.

JE supports a normal recovery that runs against a subset of your log files. This is a routineprocedure used whenever your environment is first opened upon application startup, andit is intended to ensure that your database is in a consistent state. JE also supports archivalbackup and recovery in the case of catastrophic failure, such as the loss of a physical diskdrive.

This book describes several different backup procedures you can use to protect youron-disk data. These procedures range from simple offline backup strategies to hot failovers.Hot failovers provide not only a backup mechanism, but also a way to recover from a fatalhardware failure.

This book also describes the recovery procedures you should use for each of the backupstrategies that you might employ.

For a detailed description of backup and restore procedures, see the Berkeley DB JavaEdition Getting Started Guide.[http://www.sleepycat.com/jedocs/GettingStartedGuide/BerkeleyDB-JE-GSG.pdf]

Performance Tuning

From a performance perspective, the use of transactions is not free. Depending on howyou configure them, transaction commits usually require your application to perform diskI/O that a non-transactional application does not perform. Also, for multi-threadedapplications, the use of transactions can result in increased lock contention due to extralocking requirements driven by transactional isolation guarantees.

There is therefore a performance tuning component to transactional applications that isnot applicable for non-transactional applications (although some tuning considerationsdo exist whether or not your application uses transactions). Where appropriate, thesetuning considerations are introduced in the following chapters.

Page 4Using Transactions with JE5/24/2006

Recoverability

Page 12: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 2. Enabling TransactionsIn order to use transactions with your application, you must turn them on. To do this youmust:

• Turn on transactions for your environment. You do this by using theEnvironmentConfig.setTransactional() method, or by using theje.env.isTransactional je.properties parameter.

• Transaction-enable your databases. You do this by using theDatabaseConfig.setTransactional() method, and then opening the database fromwithin a transaction. Note that the common practice is for auto commit to be used totransaction-protect the database open. To use auto-commit, you must still enabletransactions as described here, but you do not have to explicitly use a transactionwhen you open your database. An example of this is given in the next section.

Opening a Transactional Environment and Database

To enable transactions for your environment, you must initialize the transactionalsubsystem:

package je.txn;

import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;

...

Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

} catch (DatabaseException de) { // Exception handling goes here}

You then create and open your database(s) as you would for a non-transactional system.The only difference is that you must set DatabaseConfig.setTransactional() to true.Note that your database open must be transactional-protected. However, if you do notgive the openDatabase() method a transaction handle, then the open is automaticallyprotected using auto commit. Typically auto commit is used for this purpose. For example:

Page 5Using Transactions with JE5/24/2006

Page 13: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true); myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. Create it if it does not already exist. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, "sampleDatabase", dbConfig);

} catch (DatabaseException de) { // Exception handling goes here}

Never close a database that has active transactions. Make sure all transactions are resolved(either committed or aborted) before closing the database.☞

Page 6Using Transactions with JE5/24/2006

Opening a TransactionalEnvironment and Database

Page 14: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 3. Transaction BasicsOnce you have enabled transactions for your environment and your databases, you canuse them to protect your database operations. You do this by acquiring a transactionhandle and then using that handle for any database operation that you want to participatein that transaction.

You obtain a transaction handle using the Environment.beginTransaction() method.

Once you have completed all of the operations that you want to include in the transaction,you must commit the transaction using the Transaction.commit() method.

If, for any reason, you want to abandon the transaction, you abort it usingTransaction.abort().

Any transaction handle that has been committed or aborted can no longer be used byyour application.

Finally, you must make sure that all transaction handles are either committed or abortedbefore closing your databases and environment.

If you only want to transaction protect a single database write operation, you can use autocommit to perform the transaction administration. When you use auto commit, you do notneed an explicit transaction handle. See Auto Commit (page 11) for more information.

For example, the following example opens a transactional-enabled environment anddatabase, obtains a transaction handle, and then performs a write operation under itsprotection. In the event of any failure in the write operation, the transaction is abortedand the database is left in a state as if no operations had ever been attempted in thefirst place.

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.Transaction;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

Page 7Using Transactions with JE5/24/2006

Page 15: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. Create it if it does not already exist. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, "sampleDatabase", dbConfig);

String keyString = "thekey"; String dataString = "thedata"; DatabaseEntry key = new DatabaseEntry(keyString.getBytes("UTF-8")); DatabaseEntry data = new DatabaseEntry(dataString.getBytes("UTF-8"));

Transaction txn = myEnv.beginTransaction(null, null);

try { myDatabase.put(txn, key, data); txn.commit(); } catch (Exception e) { if (txn != null) { txn.abort(); txn = null; } }

} catch (DatabaseException de) { // Exception handling goes here}

Committing a Transaction

In order to fully understand what is happening when you commit a transaction, you mustfirst understand a little about what JE is doing with its log files. Logging causes all databasewrite operations to be identified in log files (remember that in JE, your log files are yourdatabase files; there is no difference between the two). Enough information is writtento restore your entire BTree in the event of a system or application failure, so byperforming logging, JE ensures the integrity of your data.

Remember that all write activity made to your database is identified in JE's logs as thewrites are performed by your application. However, JE maintains logs in memory.Eventually this information is written to disk, but especially in the case of a transactionalapplication this data may be held in memory until the transaction is committed, or JEruns out of buffer space for the logging information.

When you commit a transaction, the following occurs:

Page 8Using Transactions with JE5/24/2006

Committing a Transaction

Page 16: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

• A commit record is written to the log. This indicates that the modifications made bythe transaction are now permanent. By default, this write is performed synchronouslyto disk so the commit record arrives in the log files before any other actions are taken.

• Any log information held in memory is (by default) synchronously written to disk. Notethat this requirement can be relaxed, depending on the type of commit you perform.See Non-Durable Transactions (page 9) for more information.

Note that a transaction commit only writes the BTree's leaf nodes to JE's log files. Allother internal BTree structures are left unwritten.

• All locks held by the transaction are released. This means that read operationsperformed by other transactions or threads of control can now see the modificationswithout resorting to uncommitted reads (see Reading Uncommitted Data (page 27)for more information).

To commit a transaction, you simply call Transaction.commit().

Remember that transaction commit causes only the BTree leaf nodes to be written to JE'slog files. Any other modifications made to the the BTree as a result of the transaction'sactivities are not written to the log file. This means that over time JE's normal recoverytime can greatly increase (remember that JE always runs normal recovery when it opensan environment).

For this reason, JE by default runs the checkpointer thread. This background thread runsa checkpoint on a periodic interval so as to ensure that the amount of data that needsto be recovered upon environment open is minimized. In addition, you can also run acheckpoint manually. For more information, see Checkpoints (page 37).

Note that once you have committed a transaction, the transaction handle that you usedfor the transaction is no longer valid. To perform database activities under the controlof a new transaction, you must obtain a fresh transaction handle.

Non-Durable Transactions

As previously noted, by default transaction commits are durable because they cause themodifications performed under the transaction to be synchronously recorded in youron-disk log files. However, it is possible to use non-durable transactions.

You may want non-durable transactions for performance reasons. For example, you mightbe using transactions simply for the isolation guarantee. In this case, you might want torelax the synchronized write to disk that JE normally performs as part of a transactioncommit. Doing so means that your data will still make it to disk; however, your applicationwill not necessarily have to wait for the disk I/O to complete before it can perform anotherdatabase operation. This can greatly improve throughput for some workloads.

There are several ways to relax the synchronized write requirement for your transactions:

• Specify true to the EnvironmentMutableConfig.setTxnNoSync() method. This causesJE to not synchronously force any data to disk upon transaction commit. That is, the

Page 9Using Transactions with JE5/24/2006

Committing a Transaction

Page 17: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

modifications are held entirely inside the JVM and the modifications are not forcedto the file system for long-term storage. Note, however, that the data will eventuallymake it to the filesystem (assuming no application or OS crashes) as a part of JE'smanagement of its logging buffers and/or cache.

This form of a commit provides a weak durability guarantee because data loss canoccur due to an application, JVM, or OS crash.

This behavior is specified on a per-environment handle basis. In order for yourapplication to exhibit consistent behavior, you need to specify this for all of theenvironment handles used in your application.

You can achieve this behavior on a transaction by transaction basis by usingTransaction.commitNoSync() to commit your transaction, or by specifying true to theTransactionConfig.setNoSync() method when starting the transaction.

• Specify true to the EnvironmentConfig.setTxnWriteNoSync()method. This causes datato be synchronously written to the OS's file system buffers upon transaction commit.The data will eventually be written to disk, but this occurs when the operating systemchooses to schedule the activity; the transaction commit can complete successfullybefore this disk I/O is performed by the OS.

This form of commit protects you against application and JVM crashes, but not againstOS crashes. This method offers less room for the possibility of data loss than doesEnvironmentConfig.setTxnNoSync().

This behavior is specified on a per-environment handle basis. In order for yourapplication to exhibit consistent behavior, you need to specify this for all of theenvironment handles used in your application.

You can achieve this behavior on a transaction by transaction basis by usingTransaction.commitWriteNoSync() to commit your transaction, or by specifying trueto TransactionConfig.setWriteNoSync() method when starting the transaction.

Aborting a Transaction

When you abort a transaction, all database modifications performed under the protectionof the transaction are discarded, and all locks currently held by the transaction arereleased. In this event, your data is simply left in the state that it was in before thetransaction began performing data modifications.

Note that aborting a transaction may result in disk I/O. It is possible that during the courseof your transaction, logging data and/or database records were written to backing fileson disk. For this reason, JE notes that the abort occurred in its log files so that at aminimum the database can be brought into a consistent state at recovery time.

Also, once you have aborted a transaction, the transaction handle that you used for thetransaction is no longer valid. To perform database activities under the control of a newtransaction, you must obtain a fresh transactional handle.

Page 10Using Transactions with JE5/24/2006

Aborting a Transaction

Page 18: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

To abort a transaction, call Transaction.abort().

Auto Commit

While transactions are frequently used to provide atomicity to multiple databaseoperations, it is sometimes necessary to perform a single database operation under thecontrol of a transaction. Rather than force you to obtain a transaction, perform the singlewrite operation, and then either commit or abort the transaction, you can automaticallygroup this sequence of events using auto commit.

To use auto commit:

1. Open your environment and your databases so that they support transactions. SeeEnabling Transactions (page 5) for details.

2. Do not provide a transactional handle to the method that is performing the databasewrite operation.

Note that auto commit is not available for cursors. You must always open your cursorusing a transaction if you want the cursor's operations to be transactional protected. SeeTransactional Cursors (page 12) for details on using transactional cursors.

For example, the following uses auto commit to perform the database write operation:

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true); myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. Create it if it does not already exist. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, "sampleDatabase",

Page 11Using Transactions with JE5/24/2006

Auto Commit

Page 19: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

dbConfig);

String keyString = "thekey"; String dataString = "thedata"; DatabaseEntry key = new DatabaseEntry(keyString.getBytes("UTF-8")); DatabaseEntry data = new DatabaseEntry(dataString.getBytes("UTF-8"));

// Perform the write. Because the database was opened to // support transactions, this write is performed using auto commit. myDatabase.put(null, key, data);

} catch (DatabaseException de) { // Exception handling goes here}

Transactional Cursors

You can transaction-protect your cursor operations by specifying a transaction handle atthe time that you create your cursor. Beyond that, you do not ever provide a transactionhandle directly to a cursor method.

Note that if you transaction-protect a cursor, then you must make sure that the cursoris closed before you either commit or abort the transaction. For example:

package je.txn;

import com.sleepycat.je.Cursor;import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.LockMode;import com.sleepycat.je.OperationStatus;import com.sleepycat.je.Transaction;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try {

// Database and environment opens omitted

Page 12Using Transactions with JE5/24/2006

Transactional Cursors

Page 20: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

String replacementData = "new data";

Transaction txn = myEnv.beginTransaction(null, null); Cursor cursor = null; try { // Use the transaction handle here cursor = db.openCursor(txn, null); DatabaseEntry key, data;

DatabaseEntry key, data; while(cursor.getNext(key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {

data.setData(replacementData.getBytes("UTF-8")); // No transaction handle is used on the cursor read or write // methods. cursor.putCurrent(data); }

cursor.close(); cursor = null; txn.commit(); } catch (Exception e) { if (cursor != null) { cursor.close(); } if (txn != null) { txn.abort(); txn = null; } }

} catch (DatabaseException de) { // Exception handling goes here}

Secondary Indices with Transaction Applications

You can use transactions with your secondary indices so long as you open the secondaryindex so that it is transactional.

All other aspects of using secondary indices with transactions are identical to usingsecondary indices without transactions. In addition, transaction-protecting secondarycursors is performed just as you protect normal cursors — you simply have to make surethe cursor is opened using a transaction handle, and that the cursor is closed before thehandle is either either committed or aborted. See Transactional Cursors (page 12) fordetails.

Page 13Using Transactions with JE5/24/2006

Secondary Indices withTransaction Applications

Page 21: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Note that when you use transactions to protect your database writes, your secondaryindices are protected from corruption because updates to the primary and the secondariesare performed in a single atomic transaction.

For example:

package je.txn;

import com.sleepycat.bind.tuple.TupleBinding;import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseType;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.SecondaryDatabase;import com.sleepycat.je.SecondaryConfig;

import java.io.FileNotFoundException;

...

// Environment and primary database opens omitted.

SecondaryConfig mySecConfig = new SecondaryConfig();mySecConfig.setAllowCreate(true);mySecConfig.setTransactional(true);

SecondaryDatabase mySecDb = null;try { // A fake tuple binding that is not actually implemented anywhere. // The tuple binding is dependent on the data in use. // See the Getting Started Guide for details TupleBinding myTupleBinding = new MyTupleBinding();

// Open the secondary. FullNameKeyCreator is not actually implemented // anywhere. See the Getting Started Guide for detais. FullNameKeyCreator keyCreator = new FullNameKeyCreator(myTupleBinding);

// Set the key creator on the secondary config object. mySecConfig.setKeyCreator(keyCreator);

// Perform the actual open. Because this database is configured to be // transactional, the open is automatically wrapped in a transaction. // - myEnv is the environment handle. // - myDb is the primary database handle. String secDbName = "mySecondaryDatabase"; mySecDb = myEnv.openSecondary(null, secDbName, null, myDb, mySecConfig);} catch (DatabaseException de) {

Page 14Using Transactions with JE5/24/2006

Secondary Indices withTransaction Applications

Page 22: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// Exception handling goes here ...}

Configuring the Transaction Subsystem

When you configure your transaction subsystem, you need to consider your transactiontimeout value. This value represents the longest period of time a transaction can beactive. Note, however, that transaction timeouts are checked only when JE examines itslock tables for blocked locks (see Locks, Blocks, and Deadlocks (page 18) for moreinformation). Therefore, a transaction's timeout can have expired, but the applicationwill not be notified until JE has a reason to examine its lock tables.

Be aware that some transactions may be inappropriately timed out before the transactionhas a chance to complete. You should therefore use this mechanism only if you know yourapplication might have unacceptably long transactions and you want to make sure yourapplication will not stall during their execution. (This might happen if, for example, yourtransaction blocks or requests too much data.)

Note that by default transaction timeouts are set to 0 seconds, which means that theynever time out.

To set the maximum timeout value for your transactions, use theEnvironmentConfig.setTxnTimeout() method. This method configures the entireenvironment; not just the handle used to set the configuration. Further, this value maybe set at any time during the application's lifetime.

This value can also be set using the je.txn.timeout property in your JE properties file.

For example:

package je.txn;

import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;

...

Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

// Configure a maximum transaction timeout of 1 second. myEnvConfig.setTxnTimeout(1000000);

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

Page 15Using Transactions with JE5/24/2006

Configuring the TransactionSubsystem

Page 23: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// From here, you open your databases, proceed with your // database operations, and respond to deadlocks as // is normal (omitted for brevity).

...

Page 16Using Transactions with JE5/24/2006

Configuring the TransactionSubsystem

Page 24: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 4. ConcurrencyJE offers a great deal of support for multi-threaded applications even when transactionsare not in use. Many of JE's handles are thread-safe and JE provides a flexible lockingsubsystem for managing databases in a concurrent application. Further, JE provides arobust mechanism for detecting and responding to deadlocks. All of these concepts areexplored in this chapter.

Before continuing, it is useful to define a few terms that will appear throughout thischapter:

• Thread of control

Refers to a thread that is performing work in your application. Typically, in this bookthat thread will be performing JE operations.

• Locking

When a thread of control obtains access to a shared resource, it is said to be lockingthat resource. Note that JE supports both exclusive and non-exclusive locks. SeeLocks (page 18) for more information.

• Free-threaded

Data structures and objects are free-threaded if they can be shared across threads ofcontrol without any explicit locking on the part of the application. Some books,libraries, and programming languages may use the term thread-safe for data structuresor objects that have this characteristic. The two terms mean the same thing.

For a description of free-threaded JE objects, see Which JE Handles areFree-Threaded (page 18).

• Blocked

When a thread cannot obtain a lock because some other thread already holds a lockon that object, the lock attempt is said to be blocked. See Blocks (page 20) for moreinformation.

• Deadlock

Occurs when two or more threads of control attempt to access conflicting resourcein such a way as none of the threads can any longer may further progress.

For example, if Thread A is blocked waiting for a resource held by Thread B, while atthe same time Thread B is blocked waiting for a resource held by Thread A, thenneither thread can make any forward progress. In this situation, Thread A and ThreadB are said to be deadlocked.

For more information, see Deadlocks (page 22).

Page 17Using Transactions with JE5/24/2006

Page 25: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Which JE Handles are Free-Threaded

The following describes to what extent and under what conditions individual handles arefree-threaded.

• Environment

This class is free-threaded.

• Database

This class is free-threaded.

• SecondaryDatabase

This class is free-threaded.

• Cursor

If the cursor is a transactional cursor, it can be used by multiple threads of control solong as the application serializes access to the handle. If the cursor is not atransactional cursor, it can not be shared across multiple threads of control at all.

• SecondaryCursor

Same conditions apply as for Cursor handles.

• Transaction

This class is free-threaded.

All classes found in the bind APIs (com.sleepycat.bind.*) are free-threaded.☞

Locks, Blocks, and Deadlocks

It is important to understand how locking works in a concurrent application beforecontinuing with a description of the concurrency mechanisms JE makes available to you.Blocking and deadlocking have important performance implications for your application.Consequently, this section provides a fundamental description of these concepts, andhow they affect JE operations.

Locks

When one thread of control wants to obtain access to an object, it requests a lock forthat object. This lock is what allows JE to provide your application with its transactionalisolation guarantees by ensuring that:

• no other thread of control can read that object (in the case of an exclusive lock), and

Page 18Using Transactions with JE5/24/2006

Which JE Handles areFree-Threaded

Page 26: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

• no other thread of control can modify that object (in the case of an exclusive ornon-exclusive lock).

Lock Resources

When locking occurs, there are conceptually three resources in use:

1. The locker.

This is the thing that holds the lock. In a transactional application, the locker is atransaction handle. For non-transactional operations, the locker is the current thread.

2. The lock.

This is the actual data structure that locks the object. In JE, a locked object structurein the lock manager is representative of the object that is locked.

3. The locked object.

The thing that your application actually wants to lock. In a JE application, the lockedobject is usually a database record.

JE has not set a limit for the maximum number of these resources you can use. Instead,you are only limited by the amount of memory available to your application.

The following figure shows a transaction handle, Txn A, that is holding a lock on databaserecord 002. In this graphic, Txn A is the locker, and the locked object is record 002. Onlya single lock is in use in this operation.

Txn A

001

002 003003002

Types of Locks

JE applications support both exclusive and non-exclusive locks. Exclusive locks are grantedwhen a locker wants to write to an object. For this reason, exclusive locks are alsosometimes called write locks.

An exclusive lock prevents any other locker from obtaining any sort of a lock on the object.This provides isolation by ensuring that no other locker can observe or modify an exclusivelylocked object until the locker is done writing to that object.

Page 19Using Transactions with JE5/24/2006

Locks, Blocks, and Deadlocks

Page 27: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Non-exclusive locks are granted for read-only access. For this reason, non-exclusive locksare also sometimes called read locks. Since multiple lockers can simultaneously hold readlocks on the same object, read locks are also sometimes called shared locks.

A non-exclusive lock prevents any other locker from modifying the locked object whilethe locker is still reading the object. This is how transactional cursors are able to achieverepeatable reads; by default, the cursor's transaction holds a read lock on any object thatthe cursor has examined until such a time as the transaction is committed or aborted.

In the following figure, Txn A and Txn B are both holding read locks on record 002, whileTxn C is holding a write lock on record 003:

Txn A

Txn B Txn C

001

002 003003002

Lock Lifetime

A locker holds its locks until such a time as it does not need the lock any more. What thismeans is:

1. A transaction holds any locks that it obtains until the transaction is committed oraborted.

2. All non-transaction operations hold locks until such a time as the operation iscompleted. For cursor operations, the lock is held until the cursor is moved to a newposition or closed.

Blocks

Simply put, a thread of control is blocked when it attempts to obtain a lock, but thatattempt is denied because some other thread of control holds a conflicting lock. Onceblocked, the thread of control is temporarily unable to make any forward progress untilthe requested lock is obtained or the operation requesting the lock is abandoned.

Be aware that when we talk about blocking, strictly speaking the thread is not what isattempting to obtain the lock. Rather, some object within the thread (such as a cursor)is attempting to obtain the lock. However, once a locker attempts to obtain a lock, theentire thread of control must pause until the lock request is in some way resolved.

For example, if Txn A holds a write lock (an exclusive lock) on record 002, then if Txn Btries to obtain a read or write lock on that record, the thread of control in which Txn Bis running is blocked:

Page 20Using Transactions with JE5/24/2006

Locks, Blocks, and Deadlocks

Page 28: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Txn A

Txn B

001

002 003003002

However, if Txn A only holds a read lock (a shared lock) on record 002, then only thosehandles that attempt to obtain a write lock on that record will block.

Txn A

Txn B

Txn C

001

003003002

Read locksacquired

Write lockblocked

Blocking and Application Performance

Multi-threaded applications typically perform better than simple single-threadedapplications because the application can perform one part of its workload (updating adatabase record, for example) while it is waiting for some other lengthy operation tocomplete (performing disk or network I/O, for example). This performance improvementis particularly noticeable if you use hardware that offers multiple CPUs, because thethreads can run simultaneously.

That said, concurrent applications can see reduced workload throughput if their threadsof control are seeing a large amount of lock contention. That is, if threads are blockingon lock requests, then that represents a performance penalty for your application.

Consider once again the previous diagram of a blocked write lock request. In that diagram,Txn C cannot obtain its requested write lock because Txn A and Txn B are both alreadyholding read locks on the requested record. In this case, the thread in which Txn C isrunning will pause until such a time as Txn C either obtains its write lock, or the operationthat is requesting the lock is abandoned. The fact that Txn C's thread has temporarilyhalted all forward progress represents a performance penalty for your application.

Moreover, any read locks that are requested while Txn C is waiting for its write lock willalso block until such a time as Txn C has obtained and subsequently released its writelock.

Page 21Using Transactions with JE5/24/2006

Locks, Blocks, and Deadlocks

Page 29: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Avoiding Blocks

Reducing lock contention is an important part of performance tuning your concurrent JEapplication. Applications that have multiple threads of control obtaining exclusive (write)locks are prone to contention issues. Moreover, as you increase the numbers of lockersand as you increase the time that a lock is held, you increase the chances of yourapplication seeing lock contention.

As you are designing your application, try to do the following in order to reduce lockcontention:

• Reduce the length of time your application holds locks.

Shorter lived transactions will result in shorter lock lifetimes, which will in turn helpto reduce lock contention.

In addition, by default transactional cursors hold read locks until such a time as thetransaction is completed. For this reason, try to minimize the time you keeptransactional cursors opened, or reduce your isolation levels – see below.

• If possible, access heavily accessed (read or write) items toward the end of thetransaction. This reduces the amount of time that a heavily used record is locked bythe transaction.

• Reduce your application's isolation guarantees.

By reducing your isolation guarantees, you reduce the situations in which a lock canblock another lock. Try using uncommitted reads for your read operations in order toprevent a read lock being blocked by a write lock.

In addition, for cursors you can use degree 2 (read committed) isolation, which causesthe cursor to release its read locks as soon as it is done reading the record (as opposedto holding its read locks until the transaction ends).

Be aware that reducing your isolation guarantees can have adverse consequences foryour application. Before deciding to reduce your isolation, take care to examine yourapplication's isolation requirements. For information on isolation levels, seeIsolation (page 26).

• Consider your data access patterns.

Depending on the nature of your application, this may be something that you can notdo anything about. However, if it is possible to create your threads such that theyoperate only on non-overlapping portions of your database, then you can reduce lockcontention because your threads will rarely (if ever) block on one another's locks.

Deadlocks

A deadlock occurs when two or more threads of control are blocked, each waiting on aresource held by the other thread. When this happens, there is no possibility of the threads

Page 22Using Transactions with JE5/24/2006

Locks, Blocks, and Deadlocks

Page 30: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

ever making forward progress unless some outside agent takes action to break thedeadlock.

For example, if Txn A is blocked by Txn B at the same time Txn B is blocked by Txn Athen the threads of control containing Txn A and Txn B are deadlocked; neither threadcan make any forward progress because neither thread will ever release the lock that isblocking the other thread.

Txn A

Txn B

001

003

003

002

Txn A blockedby Txn B

Txn B blockedby Txn A

Txn A and Txn B are deadlocked

When two threads of control deadlock, the only solution is to have a mechanism externalto the two threads capable of recognizing the deadlock and notifying at least one threadthat it is in a deadlock situation. Once notified, a thread of control must abandon theattempted operation in order to resolve the deadlock. JE is capable of notifying yourapplication when it detects a deadlock. See Managing Deadlocks (page 24) for moreinformation.

Note that when one locker in a thread of control is blocked waiting on a lock held byanother locker in that same thread of the control, the thread is said to be self-deadlocked.

Note that in JE, a self-deadlock can occur only if two or more transactions (lockers) areused in the same thread. A self-deadlock cannot occur for non-transactional usage, becausethe thread is the locker.

Deadlock Avoidance

The things that you do to avoid lock contention also help to reduce deadlocks (see AvoidingBlocks (page 22)). Beyond that, you should also make sure all threads access data in thesame order as all other threads. So long as threads lock records in the same basic order,there is no possibility of a deadlock (threads can still block, however).

Be aware that if you are using secondary databases (indices), then locking order is differentfor reading and writing. For this reason, if you are writing a concurrent application andyou are using secondary databases, you should expect deadlocks.

Page 23Using Transactions with JE5/24/2006

Locks, Blocks, and Deadlocks

Page 31: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

JE Lock Management

To manage locks in JE, you must do two things:

1. Manage lock timeouts.

2. Detect and respond to deadlocks.

Managing JE Lock Timeouts

Like transaction timeouts (see Configuring the Transaction Subsystem (page 15)), JE allowsyou to identify the longest period of time that it is allowed to hold a lock. This valueplays an important part in performing deadlock detection, because the only way JE canidentify a deadlock is if a lock is held past its timeout value.

However, unlike transaction timeouts, lock timeouts are on a true timer. Transactiontimeouts are only identified when JE is has a reason to examine its lock table; that is,when it is attempting to acquire a lock. If no such activity is occurring in your application,a transaction can exist for a long time past its expiration timeout. Conversely, locktimeouts are managed by a timer maintained by the JVM. Once this timer has expired,your application will be notified of the event (see the next section on deadlock detectionfor more information).

You can set the lock timeout on a transaction by transaction basis, or for the entireenvironment. To set it on a transaction basis, use Transaction.setLockTimeout(). To setit for your entire environment, use EnvironmentConfig.setLockTimeout() or use theje.lock.timeout parameter in the je.properties file.

The value that you specify for the lock timeout is in microseconds. 500000 is used bydefault.

Note that changing this value can have an affect on your application's performance. Ifyou set it too low, locks may expire and be considered deadlocked even though the threadis in fact making forward progress. This will cause your application to abort and retrytransactions unnecessarily, which can ultimately harm application throughput. If you setit too high, threads may deadlock for too long before your application receives notificationand is able to take corrective action. Again, this can harm application throughput.

Note that for single-threaded applications in which you will have extremely long-livedlocks, you may want to set this value to 0. Doing so disables lock timeouts entirely.

Managing Deadlocks

When a lock times out in JE, the thread of control holding that lock is notified of thedeadlock event via a DeadlockException exception. When this happens, the thread must:

1. Cease all read and write operations.

2. Close all open cursors.

Page 24Using Transactions with JE5/24/2006

JE Lock Management

Page 32: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

3. Abort the transaction.

4. Optionally retry the operation. If your application retries deadlocked operations, thenew attempt must be made using a new transaction.

If a thread has deadlocked, it may not make any additional database calls using thetransaction handle that has deadlocked.☞

For example:

// retry_count is a counter used to identify how many times// we've retried this operation. To avoid the potential for // endless looping, we won't retry more than MAX_DEADLOCK_RETRIES // times.

// txn is a transaction handle.// key and data are DatabaseEntry handles. Their usage is not shown here.while (retry_count < MAX_DEADLOCK_RETRIES) { try { txn = myEnv.beginTransaction(null, null); myDatabase.put(txn, key, data); txn.commit(); return 0; } catch (DeadlockException de) { try { // Abort the transaction and increment the // retry counter txn.abort(); retry_count++; if (retry_count >= MAX_DEADLOCK_RETRIES) { System.err.println("Exceeded retry limit. Giving up."); return -1; } } catch (DatabaseException ae) { System.err.println("txn abort failed: " + ae.toString()); return -1; } } catch (DatabaseException e) { try { // Abort the transaction. txn.abort(); } catch (DatabaseException ae) { System.err.println("txn abort failed: " + ae.toString()); } return -1; }}

Page 25Using Transactions with JE5/24/2006

JE Lock Management

Page 33: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Isolation

Isolation guarantees are an important aspect of transactional protection. Transactionsensure the data your transaction is working with will not be changed by some othertransaction. Moreover, the modifications made by a transaction will never be viewableoutside of that transaction until the changes have been committed.

That said, there are different degrees of isolation, and you can choose to relax yourisolation guarantees to one degree or another depending on your application'srequirements. The primary reason why you might want to do this is because ofperformance; the more isolation you ask your transactions to provide, the more lockingthat your application must do. With more locking comes a greater chance of blocking,which in turn causes your threads to pause while waiting for a lock. Therefore, by relaxingyour isolation guarantees, you can potentially improve your application's throughput.Whether you actually see any improvement depends, of course, on the nature of yourapplication's data and transactions.

Supported Degrees of Isolation

JE supports the following levels of isolation:

DefinitionANSI TermDegree

Uncommitted reads means that one transaction willnever overwrite another transaction's dirty data. Dirtydata is data that a transaction has modified but notyet committed to the underlying data store. However,uncommitted reads allows a transaction to see datadirtied by another transaction. In addition, atransaction may read data dirtied by anothertransaction, but which subsequently is aborted by thatother transaction. In this latter case, the readingtransaction may be reading data that never reallyexisted in the database.

READ UNCOMMITTED1

Committed read isolation means that degree 1 isobserved, except that dirty data is never read.

In addition, this isolation level guarantees that datawill never change so long as it is addressed by thecursor, but the data may change before the readingcursor is closed. In the case of a transaction, data atthe current cursor position will not change, but oncethe cursor moves, the previous referenced data canchange. This means that readers release read locksbefore the cursor is closed, and therefore, before thetransaction completes. Note that this level of isolationcauses the cursor to operate in exactly the same wayas it does in the absence of a transaction.

READ COMMITTED2

Page 26Using Transactions with JE5/24/2006

Isolation

Page 34: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

DefinitionANSI TermDegree

Committed read is observed, plus the data read by atransaction, T, will never be dirtied by anothertransaction before T completes. This means that bothread and write locks are not released until thetransaction completes.

This is JE's default isolation level.

REPEATABLE READ(undefined)

Committed read is observed, plus no transactions willsee phantoms. Phantoms are records returned as aresult of a search, but which were not seen by thesame transaction when the identical search criteriawas previously used.

SERIALIZABLE3

By default, JE transactions and transactional cursors offer repeatable read isolation. Youcan optionally reduce your isolation level by configuring JE to use uncommitted readisolation. See Reading Uncommitted Data (page 27) for more information. You can alsoconfigure JE to use committed read isolation. See Committed Reads (page 29) for moreinformation. Finally, you can configure your transactions and transactional cursors to useserializable isolation. See Configuring Serializable Isolation (page 31) for more information.

Reading Uncommitted Data

You can configure your application to read data that has been modified but not yetcommitted by another transaction; that is, dirty data. When you do this, you may see aperformance benefit by allowing your application to not have to block waiting for writelocks. On the other hand, the data that your application is reading may change beforethe transaction has completed.

When used with transactions, uncommitted reads means that one transaction can seedata modified but not yet committed by another transaction. When used with transactionalcursors, uncommitted reads means that any database reader can see data modified bythe cursor before the cursor's transaction has committed.

Because of this, uncommitted reads allow a transaction to read data that may subsequentlybe aborted by another transaction. In this case, the reading transaction will have readdata that never really existed in the database.

To configure your application to read uncommitted data, specify that you want to useuncommitted reads when you create a transaction or open the cursor. To do this, you usethe setReadUncommitted()method on the relevant configuration object (TransactionConfigor CursorConfig).

For example:

Page 27Using Transactions with JE5/24/2006

Isolation

Page 35: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.Transaction;import com.sleepycat.je.TransactionConfig;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, "sampleDatabase", dbConfig);

TransactionConfig txnConfig = new TransactionConfig(); txnConfig.setReadUncommitted(true); // Use uncommitted reads // for this transaction. Transaction txn = myEnv.beginTransaction(null, txnConfig);

// From here, you perform your database reads and writes as normal, // committing and aborting the transactions as is necessary, and // testing for deadlock exceptions as normal (omitted for brevity).

...

You can also configure uncommitted read isolation on a read-by-read basis by specifyingLockMode.READ_UNCOMMITTED:

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.Environment;import com.sleepycat.je.LockMode;

Page 28Using Transactions with JE5/24/2006

Isolation

Page 36: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

import com.sleepycat.je.Transaction;

...

Database myDb = null;Environment myEnv = null;Transaction txn = null;

try {

// Environment and database open omitted

...

txn = myEnv.beginTransaction(null, null);

DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry();

myDb.get(txn, theKey, theData, LockMode.READ_UNCOMMITTED);} catch (Exception e) { // Exception handling goes here}

Committed Reads

You can configure your transaction so that the data being read by a transactional cursoris consistent so long as it is being addressed by the cursor. However, once the cursor isdone reading the record, the cursor releases its lock on that record. This means that thedata the cursor has read and released may change before the cursor's transaction hascompleted.

For example, suppose you have two transactions, Ta and Tb. Suppose further that Ta hasa cursor that reads record R, but does not modify it. Normally, Tb would then be unableto write record R because Ta would be holding a read lock on it. But when you configureyour transaction for committed reads, Tb can modify record R before Ta completes, solong as the reading cursor is no longer addressing the record.

When you configure your application for this level of isolation, you may see betterperformance throughput because there are fewer read locks being held by yourtransactions. Read committed isolation is most useful when you have a cursor that isreading and/or writing records in a single direction, and that does not ever have to goback to re-read those same records. In this case, you can allow JE to release read locksas it goes, rather than hold them for the life of the transaction.

To configure your application to use committed reads, do one of the following:

Page 29Using Transactions with JE5/24/2006

Isolation

Page 37: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

• Create your transaction such that it allows committed reads. You do this by specifyingtrue to TransactionConfig.setReadCommitted().

• Specify true to CursorConfig.setReadCommitted().

For example, the following creates a transaction that allows committed reads:

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.Transaction;import com.sleepycat.je.TransactionConfig;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try { EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, "sampleDatabase", dbConfig);

// Open the transaction and enable committed reads. All cursors open // with this transaction handle will use read committed isolation. TransactionConfig txnConfig = new TransactionConfig(); txnConfig.setReadCommitted(true); // Use committed reads // for this transaction. Transaction txn = myEnv.beginTransaction(null, txnConfig);

// From here, you perform your database reads and writes as normal, // committing and aborting the transactions as is necessary, and // testing for deadlock exceptions as normal (omitted for brevity).

...

Page 30Using Transactions with JE5/24/2006

Isolation

Page 38: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

You can also configure read committed isolation on a read-by-read basis by specifyingLockMode.READ_COMMITTED:

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.Environment;import com.sleepycat.je.LockMode;import com.sleepycat.je.Transaction;

...

Database myDb = null;Environment myEnv = null;Transaction txn = null;

try {

// Environment and database open omitted

...

txn = myEnv.beginTransaction(null, null);

DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry();

myDb.get(txn, theKey, theData, LockMode.READ_COMMITTED);} catch (Exception e) { // Exception handling goes here}

Configuring Serializable Isolation

You can configure JE to use serializable isolation. Serializable isolation preventstransactions from seeing phantoms. Phantoms occur when a transaction obtains inconsistentresults when performing a given query.

Suppose a transaction performs a search, S, and as a result of that search NOTFOUND isreturned. If you are using only repeatable read isolation (the default isolation level), itis possible for the same transaction to perform S at a later point in time and returnSUCCESS instead of NOTFOUND. This can occur if another thread of control modified thedatabase in such a way as to cause S to successfully locate data, where before no datawas found. When this situation occurs, the results returned by S are said to be a phantom.

To prevent phantoms, you can use serializable isolation. Note that this causes JE toperform additional locking in order to prevent keys from being inserted until the

Page 31Using Transactions with JE5/24/2006

Isolation

Page 39: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

transaction ends. However, this additional locking can also result in reduced concurrencyfor your application, which means that your database access can be slowed.

You configure serializable isolation for all transactions in your environment by usingEnvironmentConfig.setTxnSerializableIsolation():

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;import com.sleepycat.je.Transaction;import com.sleepycat.je.LockMode;

...

Database myDb = null;Environment myEnv = null;Transaction txn = null;

try {

// Open an environment EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(true);

// Use serializable isolation envConfig.setTxnSerializableIsolation(true);

myEnv = new Environment(myHomeDirectory, envConfig);

// Database open omitted

...

txn = myEnv.beginTransaction(null, null);

DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry();

myDb.get(txn, theKey, theData, LockMode.DEFAULT); } catch (Exception e) { // Exception handling goes here}

Page 32Using Transactions with JE5/24/2006

Isolation

Page 40: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

If you do not configure serializable isolation for all transactions, you can configureserializable isolation for a specific transaction usingTransactionConfig.setSerializableIsolation():

package je.txn;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.Environment;import com.sleepycat.je.LockMode;import com.sleepycat.je.Transaction;import com.sleepycat.je.TransactionConfig;

...

Database myDb = null;Environment myEnv = null;Transaction txn = null;

try {

// Environment and database open omitted

...

TransactionConfig tc = new TransactionConfig(); tc.setSerializableIsolation(true); // Use serializable isolation txn = myEnv.beginTransaction(null, tc);

DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry();

myDb.get(txn, theKey, theData, LockMode.DEFAULT); } catch (Exception e) { // Exception handling goes here}

Transactional Cursors and Concurrent Applications

When you use transactional cursors with a concurrent application, remember that in theevent of a deadlock you must make sure that you close your cursor before you abort andretry your transaction.

Also, remember that when you are using the default isolation level, every time your cursorreads a record it locks that record until the encompassing transaction is resolved. Thismeans that walking your database with a transactional cursor increases the chance oflock contention.

Page 33Using Transactions with JE5/24/2006

Transactional Cursors andConcurrent Applications

Page 41: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

For this reason, if you must routinely walk your database with a transactional cursor,consider using a reduced isolation level such as read committed.

Using Cursors with Uncommitted Data

As described in Reading Uncommitted Data (page 27) above, it is possible to relax yourtransaction's isolation level such that it can read data modified but not yet committedby another transaction. You can configure this when you create your transaction handle,and when you do so then all cursors opened inside that transaction will automatically useuncommitted reads.

You can also do this when you create a cursor handle from within a serializable transaction.When you do this, only those cursors configured for uncommitted reads uses uncommittedreads.

The following example shows how to configure an individual cursor handle to readuncommitted data from within a serializable (full isolation) transaction. For an exampleof configuring a transaction to perform uncommitted reads in general, see ReadingUncommitted Data (page 27).

package je.txn;

import com.sleepycat.je.Cursor;import com.sleepycat.je.CursorConfig;import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;

...

Database myDatabase = null;Environment myEnv = null;try {

EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setTransactional(true);

myEnv = new Environment(new File("/my/env/home"), myEnvConfig);

// Open the database. DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); myDatabase = myEnv.openDatabase(null, // txn handle "sampleDatabase", // db file name dbConfig);

Page 34Using Transactions with JE5/24/2006

Transactional Cursors andConcurrent Applications

Page 42: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// Open the transaction. Note that this is a repeatable // read transaction. Transaction txn = myEnv.beginTransaction(null, null); Cursor cursor = null; try { // Use the transaction handle here // Get our cursor. Note that we pass the transaction // handle here. Note also that we cause the cursor // to perform uncommitted reads. CursorConfig cconfig = new CursorConfig(); cconfig.setReadUncommitted(true); cursor = db.openCursor(txn, cconfig);

// From here, you perform your cursor reads and writes // as normal, committing and aborting the transactions as // is necessary, and testing for deadlock exceptions as // normal (omitted for brevity).

...

Read/Modify/Write

If you are retrieving a record from the database for the purpose of modifying or deletingit, you should declare a read-modify-write cycle at the time that you read the record.Doing so causes JE to obtain write locks (instead of a read locks) at the time of the read.This helps to prevent deadlocks by preventing another transaction from acquiring a readlock on the same record while the read-modify-write cycle is in progress.

Note that declaring a read-modify-write cycle may actually increase the amount of blockingthat your application sees, because readers immediately obtain write locks and writelocks cannot be shared. For this reason, you should use read-modify-write cycles only ifyou are seeing a large amount of deadlocking occurring in your application.

In order to declare a read/modify/write cycle when you perform a read operation, specifycom.sleepycat.je.LockMode.RMW to the database or cursor get method.

For example:

// Begin the deadlock retry loop as is normal.while (retry_count < MAX_DEADLOCK_RETRIES) { try { txn = myEnv.beginTransaction(null, null);

... // key and data are DatabaseEntry objects. // Their usage is omitted for brevity. ...

// Read the data. Declare the read/modify/write cycle here myDatabase.get(txn, key, data, LockMode.RMW);

Page 35Using Transactions with JE5/24/2006

Read/Modify/Write

Page 43: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// Put the data. Note that you do not have to provide any // additional flags here due to the read/modify/write // cycle. Simply put the data and perform your deadlock // detection as normal. myDatabase.put(txn, key, data); txn.commit(); return 0; } catch (DeadlockException de) { // Deadlock detection and exception handling omitted // for brevity ...

Page 36Using Transactions with JE5/24/2006

Read/Modify/Write

Page 44: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 5. Backing up and RestoringBerkeley DB, Java Edition Applications

Fundamentally, you backup your databases by copying JE log files off to a safe storagelocation. To restore your database from a backup, you copy those files to an appropriatedirectory on disk and restart your JE application.

Beyond these simple activities, there are some differing backup strategies that you maywant to consider. These topics are described in this chapter.

Before continuing, before you review the information on log files and background threadsin the Getting Started with Berkeley DB, Java Edition. Those topics contain importantinformation that is basic to the following discussion on backups and restores.

Normal Recovery

Remember that internally JE databases are organized in a BTree, and that in order tooperate JE requires the complete BTree be available to it.

When database records are created, modified, or deleted, the modifications arerepresented in the BTree's leaf nodes. Beyond leaf node changes, database recordmodifications can also cause changes to other BTree nodes and structures.

Now, if your writes are transaction-protected, then every time a transaction is committedthe leaf nodes (and only the leaf nodes) modified by that transaction are written to theJE log files on disk. Also, remember that the durability of the write (whether a flush orfsync is performed) depends on the type of commit that is requested. See Non-DurableTransactions (page 9) for more information.

Normal recovery, then, is the process of recreating the entire BTree from the informationavailable in the leaf nodes. You do not have to do anything special to cause normalrecovery to be run; this occurs every time a JE environment is opened.

Checkpoints

Running normal recovery can become expensive if over time all that is ever written todisk is BTree leaf nodes. So in order to limit the time required for normal recovery, JEruns checkpoints. Checkpoints write to your log files all the internal BTree nodes andstructures modified as a part of write operations. This means that your log files containa complete BTree up to the moment in time when the checkpoint was run. The result isthat normal recovery only needs to recreate the portion of the BTree that has beenmodified since the time of the last checkpoint.

Checkpoints typically write more information to disk than do transaction commits, andso they are more expensive from a disk I/O perspective. You will therefore need to considerhow frequently to run checkpoints as a part of your performance tuning activities. Whenyou do this, balance the cost of the checkpoints against the time it will take yourapplication to restart due to the cost of running normal recovery.

Page 37Using Transactions with JE5/24/2006

Page 45: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Checkpoints are normally performed by the checkpointer background thread, which isalways running. Like all background threads, it is managed using the je.properties file.Currently, the only checkpointer property that you may want to manage isje.checkpointer.bytesInterval. This property identifies how much JE's log files can growbefore a checkpoint is run. Its value is specified in bytes. Decreasing this value causesthe checkpointer thread to run checkpoints more frequently. This will improve the timethat it takes to run recovery, but it also increases the system resources (notably, I/O)required by JE.

Note that checkpoints are also always performed when the environment is closed normally.Therefore, normal recovery only has work to do if the application crashes or otherwiseends abnormally without calling Environment.close().

Performing Backups

This section describes how to backup your JE database(s) such that catastrophic recoveryis possible.

To backup your database, you can either take a hot backup or an offline backup. A hotbackup is performed while database write operations are in progress.

Do not confuse offline and hot backups with the concept of a full and incremental backup.Both an offline and a hot backup are full backups – you back up the entire database. Theonly difference between them is how much of the contents of the in-memory cache arecontained in them. On the other hand, an incremental backup is a backup of just thoselog files modified or created since the time of the last backup. Most backup software iscapable of performing both full and incremental backups for you.

Performing a Hot Backup

To perform a hot backup of your JE databases, copy all log files (*.jdb files) from yourenvironment directory to your archival location or backup media. The files must be copiedin alphabetical order (numerical in effect). You do not have to stop any database operationsin order to do this.

To make this process a bit easier, you may want to make use of the DbBackup helper class.See Using the DbBackup Helper Class (page 39) for details.

Performing an Offline Backup

An offline backup guarantees that you have captured the database in its entirety, includingall contents of your in-memory cache, at the moment that the backup was taken. To dothis, you must make sure that no write operations are in progress and all databasemodifications have been written to your log files on disk. To obtain an offline backup:

1. Stop writing your databases.

2. Make sure all your in-memory changes have been flushed to disk. How you do thisdepends on the type of transactions that you are using:

Page 38Using Transactions with JE5/24/2006

Performing Backups

Page 46: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

• If you are using transactions that writes all dirty data to disk on commit (this isthe default behavior), you simply need to make sure all on-going transactions arecommitted or aborted.

• If you are using transactions that do not synchronously write on commit, you mustrun a checkpoint. Remember that closing your environment causes a checkpointto be run, so if your application is shutting down completely before taking thebackup, you have met this requirement.

For information on changing the transactional sync behavior, see Non-DurableTransactions (page 9). For information on running a checkpoint, seeCheckpoints (page 37).

3. If you are using durable transactions, then optionally run a checkpoint. Doing thiscan shorten the time required to restore your database from this back up.

4. Copy all log files (*.jdb) from your environment directory to your archival locationor backup media. To make this process a bit easier, you may want to make use ofthe DbBackup helper class. See the next section for details.

You can now resume normal database operations.

Using the DbBackup Helper Class

In order to simplify backup operations, JE provides the DbBackup helper class. This classstops and restarts JE background activity in an open environment. It also lets theapplication create a backup which can support restoring the environment to a specificpoint in time.

Because you do not have to stop JE write activity in order to take a backup, it is usuallynecessary to examine your log files twice before you decide that your backup is complete.This is because JE may create a new log file while you are running your backup. A secondpass over your log files allows you to ensure that no new files have been created and soyou can declare your backup complete.

For example:

time files in activity environment

t0 000000001.jdb Backup starts copying file 1 000000003.jdb 000000004.jdb

t1 000000001.jdb JE log cleaner migrates portion of file 3 to newly 000000004.jdb created file 5 and deletes file 3. Backup finishes 000000005.jdb file 1, starts copying file 4. Backup MUST include file 5 for a consistent backup!

t2 000000001.jdb Backup finishes copying file 4, starts and finishes

Page 39Using Transactions with JE5/24/2006

Performing Backups

Page 47: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

000000004.jdb file 5, has caught up. Backup ends. 000000005.jdb

DbBackup works around this problem by defining the set of files that must be copied foreach backup operation, and freezes all changes to those files. The application can copythat defined set of files and finish operation without checking for the ongoing creationof new files. Also, there will be no need to check for a newer version of the last file onthe next backup.

In the example above, if DbBackup was used at t0, the application would only have tocopy files 1, 3 and 4 to back up. On a subsequent backup, the application could start itscopying at file 5. There would be no need to check for a newer version of file 4.

The following code fragment illustrates this class' usage:

package je.gettingStarted;

...import com.sleepycat.je.util.DbBackup;...

Environment env = new Environment(...); DbBackup backupHelper = new DbBackup(env);

// Find the file number of the last file in the previous backup // persistently, by either checking the backup archive, or saving // state in a persistent file. long lastFileCopiedInPrevBackup = ...

// Start backup, find out what needs to be copied. backupHelper.startBackup(); try { String filesForBackup = backupHelper.getLogFilesInBackupSet(lastFileCopiedInPrevBackup);

// Copy the files to archival storage. myApplicationCopyMethod(filesForBackup) // Update our knowlege of the last file saved in the backup set, // so we can copy less on the next backup lastFileCopiedInPrevBackup = backupHelper.getLastFileInBackupSet(); myApplicationSaveLastFile(lastFileCopiedInBackupSet); } finally { // Remember to exit backup mode, or all log files won't be cleaned // and disk usage will bloat. backupHelper.endBackup(); }

Page 40Using Transactions with JE5/24/2006

Performing Backups

Page 48: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Performing Catastrophic Recovery

Catastrophic recovery is necessary whenever your environment and/or database havebeen lost or corrupted due to a media failure (disk failure, for example). Catastrophicrecovery is also required if normal recovery fails for any reason.

In order to perform catastrophic recovery, you must have a full backup of your databases.You will use this backup to restore your database. See the previous section for informationon running back ups.

To perform catastrophic recovery:

1. Shut down your application.

2. Delete the contents of your environment home directory (the one that experienceda catastrophic failure), if there is anything there.

3. Copy your most recent full backup into your environment home directory.

4. If you are using a backup utility that runs incremental backups of your environmentdirectory, copy any log files generated since the time of your last full backup. Besure to restore all log files in the order that they were written. The order is importantbecause it is possible the same log file appears in multiple archives, and you wantto run recovery using the most recent version of each log file.

5. Open the environment as normal. JE's normal recovery will run, which will bring yourdatabase to a consistent state relative to the changed data found in your log files.

You are now done restoring your database.

Hot Failover

As a final backup/recovery strategy, you can create a hot failover. Note that using hotfailovers requires your application to be able to specify its environment home directoryat application startup time. Most application developers allow the environment homedirectory to be identified using a command line option or a configuration or propertiesfile. If your application has its environment home hard-coded into it, you cannot use hotfailovers.

You create a hot failover by periodically backing up your database to an alternativelocation on disk. Usually this alternative location is on a separate physical drive fromwhere you normally keep your database, but if multiple drives are not available then youshould at least put the hot failover on a separate disk partition.

You failover by causing your application to reopen its environment using the failoverlocation.

Note that a hot failover should not be used as a substitute for backing up and archivingyour data to a safe location physically remote from your computing environment. Evenif your data is spread across multiple physical disks, a truly serious catastrophe (fires,

Page 41Using Transactions with JE5/24/2006

Performing CatastrophicRecovery

Page 49: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

malevolent software viruses, faulty disk controllers, and so forth) can still cause you tolose your data.

To create and maintain a hot failover:

1. Copy all log files (*.jdb) from your environment directory to the location where youwant to keep your failover. Either an offline or a hot backup can be used for thispurpose, but typically a hot failoveris initially created by taking an offline backup ofyour database. This ensures that you have captured the contents of your in-memorycache.

2. Periodically copy to your failover directory any log files that were changed or createdsince the time of your last copy. Most backup software is capable of performing thiskind of an incremental backup for you.

Note that the frequency of your incremental copies determines the amount of datathat is at risk due to catastrophic failures. For example, if you perform the incrementalcopy once an hour then at most your hot failover is an hour behind your productiondatabase, and so you are risking at most an hours worth of database changes.

3. Remove any *.jdb files from the hot failover directory that have been removed orrenamed to .del files in the primary directory. This is not necessary for consistency,but will help to reduce disk space consumed by the hot failover.

Page 42Using Transactions with JE5/24/2006

Hot Failover

Page 50: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Chapter 6. Summary and ExamplesThroughout this manual we have presented the concepts and mechanisms that you needto provide transactional protection for your application. In this chapter, we summarizethese mechanisms, and we provide a complete example of a multi-threaded transactionalJE application.

Anatomy of a Transactional Application

Transactional applications are characterized by performing the following activities:

1. Create your environment handle.

2. Open your environment, specifying that the transactional subsystem is to be used.

3. Open your database handles, indicating that they are to support transactions.

4. Spawn off worker threads. How many of these you need and how they split their JEworkload is entirely up to your application's requirements. However, any workerthreads that perform write operations against your databases will do the following:

a. Begin a transaction.

b. Perform one or more read and write operations against your databases.

c. Commit the transaction if all goes well.

d. Abort and retry the operation if a deadlock is detected.

e. Abort the transaction for most other errors.

5. On application shutdown:

a. Make sure there are no opened cursors.

b. Make sure there are no active transactions. Either abort or commit all transactionsbefore shutting down.

c. Close your databases

d. Close your environment.

Robust applications should monitor their database worker threads to make sure they havenot died unexpectedly. If a thread does terminate abnormally, you must shutdown all your☞worker threads and then run normal recovery (you will have to reopen your environment todo this). This is the only way to clear any resources (such as a lock or a mutex) that theabnormally exiting worker thread might have been holding at the time that it died.

Page 43Using Transactions with JE5/24/2006

Page 51: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

Failure to perform this recovery can cause your still-functioning worker threads to eventuallyblock forever while waiting for a lock that will never be released.

In addition to these activities, which are entirely handled by code within your application,you also need to periodically back up your log files. This is required in order to obtainthe durability guarantee made by JE's transaction ACID support. See Backing up andRestoring Berkeley DB, Java Edition Applications (page 37) for more information.

Transaction Example

The following Java code provides a fully functional example of a multi-threadedtransactional JE application. The example opens an environment and database, and thencreates 5 threads, each of which writes 500 records to the database. The keys used forthese writes are pre-determined strings, while the data is a class that contains randomlygenerated data. This means that the actual data is arbitrary and therefore uninteresting;we picked it only because it requires minimum code to implement and therefore will stayout of the way of the main points of this example.

Each thread writes 10 records under a single transaction before committing and writinganother 10 (this is repeated 50 times). At the end of each transaction, but beforecommitting, each thread calls a function that uses a cursor to read every record in thedatabase. We do this in order to make some points about database reads in a transactionalenvironment.

Of course, each writer thread performs deadlock detection as described in this manual.In addition, normal recovery is performed when the environment is opened.

To implement this example, we need three classes:

• TxnGuide.java

This is the main class for the application. It performs environment and databasemanagement, spawns threads, and creates the data that is placed in the database.See TxnGuide.java (page 45) for implementation details.

• DBWriter.java

This class extends java.lang.Thread, and as such it is our thread implementation. Itis responsible for actually reading and writing to the database. It also performs all ofour transaction management. See DBWriter.java (page 49) for implementation details.

• PayloadData.java

This is a data class used to encapsulate several data fields. It is fairly uninteresting,except that the usage of a class means that we have to use the bind APIs to serializeit for storage in the database. See PayloadData.java (page 48) for implementationdetails.

Page 44Using Transactions with JE5/24/2006

Transaction Example

Page 52: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

TxnGuide.java

The main class in our example application is used to open and close our environment anddatabase. It also spawns all the threads that we need. We start with the normal seriesof Java package and import statements, followed by our class declaration:

// File TxnGuide.java

package je.txn;

import com.sleepycat.bind.serial.StoredClassCatalog;

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseConfig;import com.sleepycat.je.DatabaseException;

import com.sleepycat.je.Environment;import com.sleepycat.je.EnvironmentConfig;

import java.io.File;import java.io.FileNotFoundException;

public class TxnGuide {

Next we declare our class' private data members. Mostly these are used for constantssuch as the name of the database that we are opening and the number of threads thatwe are spawning. However, we also declare our environment and database handles here.

private static String myEnvPath = "./"; private static String dbName = "mydb.db"; private static String cdbName = "myclassdb.db";

// DB handles private static Database myDb = null; private static Database myClassDb = null; private static Environment myEnv = null;

private static final int NUMTHREADS = 5;

Next, we implement our usage() method. This application optionally accepts a singlecommand line argument which is used to identify the environment home directory.

private static void usage() { System.out.println("TxnGuide [-h <env directory>]"); System.exit(-1); }

Now we implement our main() method. This method simply calls the methods to parsethe command line arguments and open the environment and database. It also creates thestored class catalog that we use for serializing the data that we want to store in ourdatabase. Finally, it creates and then joins the database writer threads.

Page 45Using Transactions with JE5/24/2006

Transaction Example

Page 53: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

public static void main(String args[]) { try { // Parse the arguments list parseArgs(args); // Open the environment and databases openEnv(); // Get our class catalog (used to serialize objects) StoredClassCatalog classCatalog = new StoredClassCatalog(myClassDb);

// Start the threads DBWriter[] threadArray; threadArray = new DBWriter[NUMTHREADS]; for (int i = 0; i < NUMTHREADS; i++) { threadArray[i] = new DBWriter(myEnv, myDb, classCatalog); threadArray[i].start(); }

// Join the threads. That is, wait for each thread to // complete before exiting the application. for (int i = 0; i < NUMTHREADS; i++) { threadArray[i].join(); } } catch (Exception e) { System.err.println("TxnGuide: " + e.toString()); e.printStackTrace(); } finally { closeEnv(); } System.out.println("All done."); }

Next we implement openEnv(). This method is used to open the environment and then adatabase in that environment. Along the way, we make sure that the transactionalsubsystem is correctly initialized.

For the database open, notice that we open the database such that it supports duplicaterecords. This is required purely by the data that we are writing to the database, and itis only necessary if you run the application more than once without first deleting theenvironment.

private static void openEnv() throws DatabaseException { System.out.println("opening env");

// Set up the environment. EnvironmentConfig myEnvConfig = new EnvironmentConfig(); myEnvConfig.setAllowCreate(true); myEnvConfig.setTransactional(true); // Environment handles are free-threaded by default in JE, // so we do not have to do anything to cause the

Page 46Using Transactions with JE5/24/2006

Transaction Example

Page 54: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// environment handle to be free-threaded.

// Set up the database DatabaseConfig myDbConfig = new DatabaseConfig(); myDbConfig.setAllowCreate(true); myDbConfig.setTransactional(true); myDbConfig.setSortedDuplicates(true);

// Open the environment myEnv = new Environment(new File(myEnvPath), // Env home myEnvConfig);

// Open the database. Do not provide a txn handle. This open // is auto committed because DatabaseConfig.setTransactional() // is true. myDb = myEnv.openDatabase(null, // txn handle dbName, // Database file name myDbConfig);

// Used by the bind API for serializing objects // Class database must not support duplicates myDbConfig.setSortedDuplicates(false); myClassDb = myEnv.openDatabase(null, // txn handle cdbName, // Database file name myDbConfig); }

Finally, we implement the methods used to close our environment and databases, parsethe command line arguments, and provide our class constructor. This is fairly standardcode and it is mostly uninteresting from the perspective of this manual. We include ithere purely for the purpose of completeness.

private static void closeEnv() { System.out.println("Closing env and databases"); if (myDb != null ) { try { myDb.close(); } catch (DatabaseException e) { System.err.println("closeEnv: myDb: " + e.toString()); e.printStackTrace(); } }

if (myClassDb != null ) { try { myClassDb.close(); } catch (DatabaseException e) { System.err.println("closeEnv: myClassDb: " +

Page 47Using Transactions with JE5/24/2006

Transaction Example

Page 55: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

e.toString()); e.printStackTrace(); } }

if (myEnv != null ) { try { myEnv.close(); } catch (DatabaseException e) { System.err.println("closeEnv: " + e.toString()); e.printStackTrace(); } } }

private TxnGuide() {}

private static void parseArgs(String args[]) { for(int i = 0; i < args.length; ++i) { if (args[i].startsWith("-")) { switch(args[i].charAt(1)) { case 'h': myEnvPath = new String(args[++i]); break; default: usage(); } } } }}

PayloadData.java

Before we show the implementation of the database writer thread, we need to show theclass that we will be placing into the database. This class is fairly minimal. It simply allowsyou to store and retrieve an int, a String, and a double. We will be using the Sleepycatbind API from within the writer thread to serialize instances of this class and place theminto our database.

package je.txn;

import java.io.Serializable;

public class PayloadData implements Serializable { private int oID; private String threadName; private double doubleData;

Page 48Using Transactions with JE5/24/2006

Transaction Example

Page 56: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

PayloadData(int id, String name, double data) { oID = id; threadName = name; doubleData = data; }

public double getDoubleData() { return doubleData; } public int getID() { return oID; } public String getThreadName() { return threadName; }}

DBWriter.java

DBWriter.java provides the implementation for our database writer thread. It is responsiblefor:

• All transaction management.

• Responding to deadlock exceptions.

• Providing data to be stored into the database.

• Serializing and then writing the data to the database.

In order to show off some of the ACID properties provided by JE's transactional support,DBWriter.java does some things in a less efficient way than you would probably decideto use in a true production application. First, it groups 10 database writes together in asingle transaction when you could just as easily perform one write for each transaction.If you did this, you could use auto commit for the individual database writes, which meansyour code would be slightly simpler and you would run a much smaller chance ofencountering blocked and deadlocked operations. However, by doing things this way, weare able to show transactional atomicity, as well as deadlock handling.

At the end of each transaction, DBWriter.java runs a cursor over the entire database byway of counting the number of records currently existing in the database. There are betterways to discover this information, but in this case we want to make some points regardingcursors, transactional applications, and deadlocking (we get into this in more detail laterin this section).

To begin, we provide the usual package and import statements, and we declare our class:

package je.txn;

import com.sleepycat.bind.EntryBinding;import com.sleepycat.bind.serial.StoredClassCatalog;import com.sleepycat.bind.serial.SerialBinding;import com.sleepycat.bind.tuple.StringBinding;

import com.sleepycat.je.Cursor;import com.sleepycat.je.CursorConfig;

Page 49Using Transactions with JE5/24/2006

Transaction Example

Page 57: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

import com.sleepycat.je.Database;import com.sleepycat.je.DatabaseEntry;import com.sleepycat.je.DatabaseException;import com.sleepycat.je.DeadlockException;import com.sleepycat.je.Environment;import com.sleepycat.je.LockMode;import com.sleepycat.je.OperationStatus;import com.sleepycat.je.Transaction;

import java.io.UnsupportedEncodingException;import java.util.Random;

public class DBWriter extends Thread{

Next we declare our private data members. Notice that we get handles for the environmentand the database. We also obtain a handle for an EntryBinding. We will use this to serializePayloadData class instances (see PayloadData.java (page 48)) for storage in the database.The random number generator that we instantiate is used to generate unique data forstorage in the database. The MAX_RETRY variable is used to define how many times wewill retry a transaction in the face of a deadlock. And, finally, keys is a String array thatholds the keys used for our database entries.

private Database myDb = null; private Environment myEnv = null; private EntryBinding dataBinding = null; private Random generator = new Random();

private static final int MAX_RETRY = 20;

private static String[] keys = {"key 1", "key 2", "key 3", "key 4", "key 5", "key 6", "key 7", "key 8", "key 9", "key 10"};

Next we implement our class constructor. The most interesting thing we do here isinstantiate a serial binding for serializing PayloadData instances.

// Constructor. Get our DB handles from here DBWriter(Environment env, Database db, StoredClassCatalog scc) throws DatabaseException { myDb = db; myEnv = env; dataBinding = new SerialBinding(scc, PayloadData.class); }

Now we implement our thread's run() method. This is the method that is run whenDBWriter threads are started in the main program (see TxnGuide.java (page 45)).

Page 50Using Transactions with JE5/24/2006

Transaction Example

Page 58: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

// Thread method that writes a series of records // to the database using transaction protection. // Deadlock handling is demonstrated here. public void run () {

The first thing we do is get a null transaction handle before going into our main loop.We also begin the top transaction loop here that causes our application to perform 50transactions.

Transaction txn = null;

// Perform 50 transactions for (int i=0; i<50; i++) {

Next we declare a retry variable. This is used to determine whether a deadlock shouldresult in our retrying the operation. We also declare a retry_count variable that is usedto make sure we do not retry a transaction forever in the unlikely event that the threadis unable to ever get a necessary lock. (The only thing that might cause this is if someother thread dies while holding an important lock. This is the only code that we have toguard against that because the simplicity of this application makes it highly unlikely thatit will ever occur.)

boolean retry = true; int retry_count = 0; // while loop is used for deadlock retries while (retry) {

Now we go into the try block that we use for deadlock detection. We also begin ourtransaction here.

// try block used for deadlock detection and // general db exception handling try {

// Get a transaction txn = myEnv.beginTransaction(null, null);

Now we write 10 records under the transaction that we have just begun. By combiningmultiple writes together under a single transaction, we increase the likelihood that adeadlock will occur. Normally, you want to reduce the potential for a deadlock and inthis case the way to do that is to perform a single write per transaction. In other words,we should be using auto commit to write to our database for this workload.

However, we want to show deadlock handling and by performing multiple writes pertransaction we can actually observe deadlocks occurring. We also want to underscore theidea that you can combing multiple database operations together in a single atomic unitof work. So for our example, we do the (slightly) wrong thing.

Further, notice that we store our key into a DatabaseEntry usingcom.sleepycat.bind.tuple.StringBinding to perform the serialization. Also, when weinstantiate the PayloadData object, we call getName() which gives us the string

Page 51Using Transactions with JE5/24/2006

Transaction Example

Page 59: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

representation of this thread's name, as well as Random.nextDouble() which gives us arandom double value. This latter value is used so as to avoid duplicate records in thedatabase.

// Write 10 records to the db // for each transaction for (int j = 0; j < 10; j++) { // Get the key DatabaseEntry key = new DatabaseEntry(); StringBinding.stringToEntry(keys[j], key);

// Get the data PayloadData pd = new PayloadData(i+j, getName(), generator.nextDouble()); DatabaseEntry data = new DatabaseEntry(); dataBinding.objectToEntry(pd, data);

// Do the put myDb.put(txn, key, data); }

Having completed the inner database write loop, we could simply commit the transactionand continue on to the next block of 10 writes. However, we want to first illustrate a fewpoints about transactional processing so instead we call our countRecords() method beforecalling the transaction commit. countRecords() uses a cursor to read every record in thedatabase and return a count of the number of records that it found.

Because countRecords() reads every record in the database, if used incorrectly the threadwill self-deadlock. The writer thread has just written 500 records to the database, butbecause the transaction used for that write has not yet been committed, each of those500 records are still locked by the thread's transaction. If we then simply run anon-transactional cursor over the database from within the same thread that has lockedthose 500 records, the cursor will block when it tries to read one of those transactionalprotected records. The thread immediately stops operation at that point while the cursorwaits for the read lock it has requested. Because that read lock will never be released(the thread can never make any forward progress), this represents a self-deadlock forthe thread.

There are three ways to prevent this self-deadlock:

1. We can move the call to countRecords() to a point after the thread's transaction hascommitted.

2. We can allow countRecords() to operate under the same transaction as all of thewrites were performed.

3. We can reduce our isolation guarantee for the application by allowing uncommittedreads.

Page 52Using Transactions with JE5/24/2006

Transaction Example

Page 60: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

For this example, we choose to use option 3 (uncommitted reads) to avoid the deadlock.This means that we have to open our cursor handle so that it knows to performuncommitted reads.

// commit System.out.println(getName() + " : committing txn : " + i);

// Using uncommitted reads to avoid the deadlock, so null // is passed for the transaction here. System.out.println(getName() + " : Found " + countRecords(null) + " records in the database.");

Having performed this somewhat inelegant counting of the records in the database, wecan now commit the transaction.

try { txn.commit(); txn = null; } catch (DatabaseException e) { System.err.println("Error on txn commit: " + e.toString()); } retry = false;

If all goes well with the commit, we are done and we can move on to the next batch of10 records to add to the database. However, in the event of an error, we must handleour exceptions correctly. The first of these is a deadlock exception. In the event of adeadlock, we want to abort and retry the transaction, provided that we have not alreadyexceeded our retry limit for this transaction.

} catch (DeadlockException de) { System.out.println("################# " + getName() + " : caught deadlock"); // retry if necessary if (retry_count < MAX_RETRY) { System.err.println(getName() + " : Retrying operation."); retry = true; retry_count++; } else { System.err.println(getName() + " : out of retries. Giving up."); retry = false; }

In the event of a standard, non-specific database exception, we simply log the exceptionand then give up (the transaction is not retried).

} catch (DatabaseException e) { // abort and don't retry

Page 53Using Transactions with JE5/24/2006

Transaction Example

Page 61: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

retry = false; System.err.println(getName() + " : caught exception: " + e.toString()); e.printStackTrace();

And, finally, we always abort the transaction if the transaction handle is not null. Notethat immediately after committing our transaction, we set the transaction handle to nullto guard against aborting a transaction that has already been committed.

} finally { if (txn != null) { try { txn.abort(); } catch (Exception e) { System.err.println("Error aborting txn: " + e.toString()); e.printStackTrace(); } } } } } }

The final piece of our DBWriter class is the countRecords() implementation. Notice howin this example we open the cursor such that it performs uncommitted reads:

// A method that counts every record in the database.

// Note that this method exists only for illustrative purposes. // A more straight-forward way to count the number of records in // a database is to use the Database.getStats() method. private int countRecords(Transaction txn) throws DatabaseException { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); int count = 0; Cursor cursor = null;

try { // Get the cursor CursorConfig cc = new CursorConfig(); cc.setReadUncomitted(true); cursor = myDb.openCursor(txn, cc); while (cursor.getNext(key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {

count++; } } finally { if (cursor != null) {

Page 54Using Transactions with JE5/24/2006

Transaction Example

Page 62: Writing Transactional Applications with Berkeley DB, Java Edition · 2006. 5. 25. · Sleepycat Software, Berkeley DB, Berkeley DB XML and the Sleepycat logo are trademarks or service

cursor.close(); } }

return count;

}}

This completes our transactional example. If you would like to experiment with this code,you can find the example in the following location in your JE distribution:

JE_HOME/examples/je/txn

Page 55Using Transactions with JE5/24/2006

Transaction Example