From b4ab578b6ed6d29a4f33216e061d7ae634ff4469 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 3 Aug 2026 11:44:29 -0400 Subject: [PATCH 1/3] docs(java): regenerate Java API reference from source via javadoc The lang/java binding is supported (com.sleepycat.db/bind/collections/persist, 337 sources) but the old DocBook migration left the Java API undocumented. Add a build_java_api() step to docs_src/build.py that runs javadoc over the public com.sleepycat.* packages into docs-build/html/java-api/ (a generated artifact, gitignored) so the Java reference always matches the current code rather than the frozen 2013 HTML. - javadoc scope = the historical public package set (excludes bundled ASM, compat shims, db.internal/persist.impl/util.keyrange internals); -sourcepath spans all of lang/java/src so referenced internals still resolve. - -Xdoclint:none keeps the 2005-era doc-comment HTML warnings-only under javadoc 21; version-stamped -windowtitle/-doctitle; -notimestamp for reproducible output; guarded on javadoc availability (skip-with-note like the PDF path). - flake devShell gains pkgs.jdk so CI's nix develop has javadoc. - index.md links the generated tree under API reference, noted as javadoc-generated (not part of the Markdown no-loss gate). make docs / ninja docs / docs.yml all drive build.py, so the javadoc step is wired automatically. --- docs_src/build.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++- docs_src/index.md | 11 ++++++-- flake.nix | 1 + 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/docs_src/build.py b/docs_src/build.py index 8bd6c6d33..5d078cc8e 100644 --- a/docs_src/build.py +++ b/docs_src/build.py @@ -16,6 +16,7 @@ """ import html import re +import shutil import subprocess import sys import tomllib @@ -32,6 +33,23 @@ MAN_OUT = REPO / "docs-build/man/man3" PDF_OUT = REPO / "docs-build/pdf" PDF_CSS = HERE / "_templates/pdf-print.css" +# Java binding sources -> javadoc HTML (a generated artifact, gitignored). +# Lands under the HTML site (docs-build/html/java-api/) so index.md's link +# resolves and it publishes/installs alongside the rest of the site. +JAVA_SRC = REPO / "lang/java/src" +JAVA_API_OUT = OUT / "java-api" +# Public packages, matching the set the historical Oracle Javadoc published +# (excludes the bundled ObjectWeb ASM copy, the compat shims, and the +# db.internal / persist.impl / util.keyrange internals). Referenced-but-not- +# documented internals still resolve via -sourcepath. +JAVA_PACKAGES = [ + "com.sleepycat.bind", "com.sleepycat.bind.serial", "com.sleepycat.bind.tuple", + "com.sleepycat.collections", + "com.sleepycat.db", + "com.sleepycat.persist", "com.sleepycat.persist.evolve", + "com.sleepycat.persist.model", "com.sleepycat.persist.raw", + "com.sleepycat.util", +] # API .md trees whose refentry pages become section-3 man pages. API_DIRS = [HERE / "api/c", HERE / "api/stl"] @@ -153,7 +171,6 @@ def _copy_assets(): (`![](deadlock.jpg)`) but migrate stored them under `/img/`, so flatten each `img/` into the page dir (docs-build/html//deadlock.jpg). Without this the /asset links dangle -- the link-check gate catches it.""" - import shutil for img_dir in SRC.rglob("img"): if not img_dir.is_dir(): continue @@ -483,6 +500,43 @@ def build_pdf(version, site): return built +# --- Java API reference: regenerate javadoc from lang/java/src (always current +# with the code, unlike the frozen 2013 HTML). Output is a build artifact under +# docs-build/java-api/ (gitignored), linked from index.md. Guarded on javadoc +# availability like the PDF path -- skip-with-note if the JDK is absent. + +def build_java_api(version, site): + """Run javadoc over the public com.sleepycat.* packages into + docs-build/java-api/. Returns the output dir, or None if javadoc/sources + are unavailable (skipped-with-note). The 2005-era doc comments carry + legacy HTML that javadoc 21's doclint rejects, so -Xdoclint:none keeps it + warnings-only; -sourcepath spans all of lang/java/src so referenced + internals resolve while only JAVA_PACKAGES are documented.""" + if not shutil.which("javadoc"): + print("(javadoc not found: skipping Java API reference)") + return None + if not JAVA_SRC.exists(): + print(f"(no {JAVA_SRC}: skipping Java API reference)") + return None + JAVA_API_OUT.mkdir(parents=True, exist_ok=True) + title = f"{site['project']} Java API {version}" + cmd = [ + "javadoc", "-quiet", "-Xdoclint:none", + "-d", str(JAVA_API_OUT), + "-sourcepath", str(JAVA_SRC), + "-windowtitle", title, + "-doctitle", title, + "-notimestamp", + *JAVA_PACKAGES, + ] + p = subprocess.run(cmd, capture_output=True, text=True) + if p.returncode != 0: + raise RuntimeError(f"javadoc failed:\n{p.stderr[-1500:]}") + if not (JAVA_API_OUT / "index.html").exists(): + raise RuntimeError("javadoc produced no index.html") + return JAVA_API_OUT + + def _selfcheck(): """Guard the md->man reshape: NAME/SYNOPSIS/DESCRIPTION split, heading promotion, and in-list heading demotion.""" @@ -525,6 +579,11 @@ def _selfcheck(): 'a href="../../api/c/env.md#x" b href="foo.md" c href="http://x/y.md"') assert '../../api/c/env.html#x' in got and 'foo.html' in got assert 'http://x/y.md' in got, "absolute .md URL must not be rewritten" + # Java API: the documented package set matches the historical public + # surface and excludes the bundled/internal packages. + assert "com.sleepycat.db" in JAVA_PACKAGES + assert "com.sleepycat.asm" not in JAVA_PACKAGES + assert "com.sleepycat.db.internal" not in JAVA_PACKAGES print("selfcheck ok") @@ -538,6 +597,9 @@ def main(build_pdf_too=True): print(f"built {n} HTML pages -> {OUT} (version {version})") m = build_man(version, site) print(f"built {m} man pages -> {MAN_OUT} (version {version})") + jdir = build_java_api(version, site) + if jdir: + print(f"built Java API reference -> {jdir} (version {version})") if build_pdf_too: books = build_pdf(version, site) print(f"built {len(books)} PDF books -> {PDF_OUT} (version {version})") diff --git a/docs_src/index.md b/docs_src/index.md index b01db8fdd..90c006582 100644 --- a/docs_src/index.md +++ b/docs_src/index.md @@ -16,6 +16,11 @@ transactional key/value storage engine. Generated from Markdown source by command-line utilities. - [C++ / STL API Reference](api/stl/index.html) — the `dbstl` standard-template- library containers and iterators backed by Berkeley DB. +- [Java API Reference](java-api/index.html) — the `com.sleepycat.*` Java + binding (`db`, `bind`, `collections`, `persist`). This tree is + **javadoc-generated** from `lang/java/src/` at build time (not Markdown + source), so it always matches the current code; it is not part of the + Markdown no-loss gate. ## Guides @@ -58,5 +63,7 @@ Each book is also available as a PDF (see the release assets / `docs-build/pdf/` --- -*The C#/Java language-binding manuals (Sandcastle / Javadoc, not DocBook) are -archived separately and are not part of this Markdown-sourced tree.* +*The C# language-binding manual (Sandcastle, not DocBook) is archived +separately and is not part of this Markdown-sourced tree. The Java API +reference above is regenerated from source by javadoc rather than migrated +from DocBook.* diff --git a/flake.nix b/flake.nix index eb8ef064f..60b4e6d31 100644 --- a/flake.nix +++ b/flake.nix @@ -52,6 +52,7 @@ pkgs.tcl # for the TCL test harness (--enable-test) pkgs.cbmc # bounded model checker for the formal-verification harnesses (test/cbmc) pkgs.pandoc # docs pipeline: html->md extraction + md->html/pdf/man (docs_src/build.py) + pkgs.jdk # javadoc: Java API reference (build_java_api in docs_src/build.py) # Docs validation toolchain (docs_src/build.py + .github/workflows/docs.yml): pkgs.python3Packages.weasyprint # HTML->PDF (build_pdf; no TeX needed) pkgs.poppler-utils # pdfinfo/pdftotext: PDF page-count + title check From 7150fd4082ac0495c09b33fa9338e16cde1a428c Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 3 Aug 2026 12:00:10 -0400 Subject: [PATCH 2/3] docs(gsg): add C++/Java Getting-Started guide variants The three Getting-Started guides (gsg, gsg_txn, gsg_db_rep) were migrated C-variant-only. Extract the CXX and JAVA variants from the gh-pages DocBook archive with the proven docs_src/_migrate/extract.py into cxx/ and java/ subdirs, each with its own _meta.toml (reading order derived from the source index.html TOC). The guide landings become C/C++/Java language pickers and docs_src/index.md links all three variants; each variant index reciprocates. Retention (verify.py vs gh-pages source, --threshold 0.99): guide variant pages word-retention hard-drops gsg C++ 37 100.00% 0 gsg Java 62 100.00% 0 gsg_txn C++ 38 100.00% 0 gsg_txn Java 39 100.00% 0 gsg_db_rep C++ 26 100.00% 0 gsg_db_rep Java 25 100.00% 0 gsg_txn's 7 diagram JPEGs are copied into each variant's img/ (build.py flattens img/ into the page dir, as for the C variant). Per-language code samples are preserved verbatim. build.py picks up the .md automatically; each variant subdir's titled _meta.toml also yields its own PDF book. The javadoc-generated java-api/ tree carries doc-comment cross-links to the old DocBook layout that dangle here; it is a build artifact outside the no-loss gate, so docs.yml's lychee link-check excludes it via --exclude-path. Internal link check: 0 errors across the full site (java-api excluded). --- .github/workflows/docs.yml | 11 +- docs_src/guides/gsg/cxx/CoreCursorUsage.md | 181 ++++++ docs_src/guides/gsg/cxx/CoreDBAdmin.md | 64 ++ docs_src/guides/gsg/cxx/CoreDbCXXUsage.md | 123 ++++ docs_src/guides/gsg/cxx/CoreEnvUsage.md | 107 ++++ docs_src/guides/gsg/cxx/Cursors.md | 66 ++ docs_src/guides/gsg/cxx/DBEntry.md | 67 ++ docs_src/guides/gsg/cxx/DBOpenFlags.md | 32 + docs_src/guides/gsg/cxx/DbCXXUsage.md | 392 +++++++++++ docs_src/guides/gsg/cxx/DeleteEntryWCursor.md | 50 ++ docs_src/guides/gsg/cxx/Positioning.md | 280 ++++++++ docs_src/guides/gsg/cxx/PutEntryWCursor.md | 100 +++ .../guides/gsg/cxx/ReplacingEntryWCursor.md | 57 ++ docs_src/guides/gsg/cxx/_meta.toml | 43 ++ docs_src/guides/gsg/cxx/accessmethods.md | 80 +++ docs_src/guides/gsg/cxx/btree.md | 185 ++++++ docs_src/guides/gsg/cxx/cachesize.md | 14 + docs_src/guides/gsg/cxx/concepts.md | 34 + docs_src/guides/gsg/cxx/coreExceptions.md | 14 + docs_src/guides/gsg/cxx/coredbclose.md | 37 ++ docs_src/guides/gsg/cxx/coreindexusage.md | 369 +++++++++++ docs_src/guides/gsg/cxx/databaseLimits.md | 12 + docs_src/guides/gsg/cxx/databases.md | 60 ++ docs_src/guides/gsg/cxx/dbErrorReporting.md | 93 +++ docs_src/guides/gsg/cxx/dbconfig.md | 110 ++++ docs_src/guides/gsg/cxx/environments.md | 36 ++ docs_src/guides/gsg/cxx/gettingit.md | 12 + docs_src/guides/gsg/cxx/index.md | 162 +++++ docs_src/guides/gsg/cxx/indexes.md | 105 +++ docs_src/guides/gsg/cxx/introduction.md | 68 ++ docs_src/guides/gsg/cxx/joins.md | 117 ++++ docs_src/guides/gsg/cxx/keyCreator.md | 123 ++++ docs_src/guides/gsg/cxx/moreinfo.md | 32 + docs_src/guides/gsg/cxx/preface.md | 58 ++ docs_src/guides/gsg/cxx/readSecondary.md | 37 ++ docs_src/guides/gsg/cxx/returns.md | 12 + docs_src/guides/gsg/cxx/secondaryCursor.md | 41 ++ docs_src/guides/gsg/cxx/secondaryDelete.md | 66 ++ docs_src/guides/gsg/cxx/usingDbt.md | 138 ++++ docs_src/guides/gsg/index.md | 2 + docs_src/guides/gsg/java/CoreEnvUsage.md | 99 +++ docs_src/guides/gsg/java/CoreJavaUsage.md | 117 ++++ docs_src/guides/gsg/java/Cursors.md | 85 +++ docs_src/guides/gsg/java/DBAdmin.md | 54 ++ docs_src/guides/gsg/java/DBEntry.md | 94 +++ .../guides/gsg/java/DeleteEntryWCursor.md | 56 ++ docs_src/guides/gsg/java/Env.md | 84 +++ docs_src/guides/gsg/java/EnvClose.md | 28 + docs_src/guides/gsg/java/EnvProps.md | 113 ++++ docs_src/guides/gsg/java/Positioning.md | 317 +++++++++ docs_src/guides/gsg/java/PutEntryWCursor.md | 86 +++ .../guides/gsg/java/ReplacingEntryWCursor.md | 53 ++ docs_src/guides/gsg/java/_meta.toml | 68 ++ docs_src/guides/gsg/java/accessmethods.md | 84 +++ docs_src/guides/gsg/java/baseapi.md | 116 ++++ docs_src/guides/gsg/java/bindAPI.md | 488 ++++++++++++++ docs_src/guides/gsg/java/btree.md | 196 ++++++ docs_src/guides/gsg/java/cachesize.md | 14 + docs_src/guides/gsg/java/coreExceptions.md | 26 + docs_src/guides/gsg/java/coredbclose.md | 31 + docs_src/guides/gsg/java/cursorJavaUsage.md | 199 ++++++ docs_src/guides/gsg/java/dataaccessorclass.md | 52 ++ docs_src/guides/gsg/java/databaseLimits.md | 12 + docs_src/guides/gsg/java/databases.md | 64 ++ docs_src/guides/gsg/java/dbErrorReporting.md | 57 ++ docs_src/guides/gsg/java/dbconfig.md | 110 ++++ docs_src/guides/gsg/java/dbprops.md | 75 +++ docs_src/guides/gsg/java/dbtJavaUsage.md | 551 ++++++++++++++++ docs_src/guides/gsg/java/dpl.md | 78 +++ docs_src/guides/gsg/java/dpl_delete.md | 47 ++ docs_src/guides/gsg/java/dpl_entityjoin.md | 89 +++ docs_src/guides/gsg/java/dpl_example.md | 126 ++++ .../guides/gsg/java/dpl_exampledatabaseput.md | 227 +++++++ .../gsg/java/dpl_exampleinventoryread.md | 179 ++++++ docs_src/guides/gsg/java/dpl_replace.md | 40 ++ docs_src/guides/gsg/java/dplindexcreate.md | 132 ++++ docs_src/guides/gsg/java/getmultiple.md | 144 +++++ docs_src/guides/gsg/java/gettingit.md | 12 + docs_src/guides/gsg/java/index.md | 280 ++++++++ docs_src/guides/gsg/java/indexes.md | 152 +++++ docs_src/guides/gsg/java/introduction.md | 82 +++ docs_src/guides/gsg/java/inventoryclass.md | 86 +++ docs_src/guides/gsg/java/javadplconcepts.md | 154 +++++ docs_src/guides/gsg/java/javaindexusage.md | 378 +++++++++++ docs_src/guides/gsg/java/joins.md | 174 +++++ docs_src/guides/gsg/java/keyCreator.md | 193 ++++++ docs_src/guides/gsg/java/moreinfo.md | 34 + docs_src/guides/gsg/java/mydbenv-persist.md | 97 +++ docs_src/guides/gsg/java/persist_access.md | 97 +++ docs_src/guides/gsg/java/persist_first.md | 123 ++++ docs_src/guides/gsg/java/persist_index.md | 67 ++ docs_src/guides/gsg/java/persistobject.md | 46 ++ docs_src/guides/gsg/java/preface.md | 56 ++ docs_src/guides/gsg/java/readSecondary.md | 42 ++ docs_src/guides/gsg/java/returns.md | 12 + docs_src/guides/gsg/java/saveret.md | 22 + docs_src/guides/gsg/java/secondaryCursor.md | 54 ++ docs_src/guides/gsg/java/secondaryDelete.md | 45 ++ docs_src/guides/gsg/java/secondaryProps.md | 18 + docs_src/guides/gsg/java/simpleda.md | 43 ++ docs_src/guides/gsg/java/simpleget.md | 117 ++++ docs_src/guides/gsg/java/simpleput.md | 145 +++++ docs_src/guides/gsg/java/usingDbt.md | 177 +++++ docs_src/guides/gsg_db_rep/cxx/_meta.toml | 32 + docs_src/guides/gsg_db_rep/cxx/addfeatures.md | 50 ++ docs_src/guides/gsg_db_rep/cxx/apioverview.md | 46 ++ docs_src/guides/gsg_db_rep/cxx/autoinit.md | 12 + docs_src/guides/gsg_db_rep/cxx/bulk.md | 32 + docs_src/guides/gsg_db_rep/cxx/c2ctransfer.md | 26 + docs_src/guides/gsg_db_rep/cxx/elections.md | 56 ++ .../guides/gsg_db_rep/cxx/electiontimes.md | 34 + .../guides/gsg_db_rep/cxx/exampledoloop.md | 373 +++++++++++ .../gsg_db_rep/cxx/fmwrkconnectretry.md | 8 + .../gsg_db_rep/cxx/fwrkmasterreplica.md | 184 ++++++ .../guides/gsg_db_rep/cxx/fwrkpermmessage.md | 124 ++++ docs_src/guides/gsg_db_rep/cxx/heartbeats.md | 16 + docs_src/guides/gsg_db_rep/cxx/index.md | 144 +++++ .../guides/gsg_db_rep/cxx/introduction.md | 102 +++ docs_src/guides/gsg_db_rep/cxx/manageblock.md | 12 + docs_src/guides/gsg_db_rep/cxx/moreinfo.md | 28 + .../guides/gsg_db_rep/cxx/permmessages.md | 86 +++ docs_src/guides/gsg_db_rep/cxx/preface.md | 60 ++ .../guides/gsg_db_rep/cxx/processingloop.md | 109 ++++ .../guides/gsg_db_rep/cxx/rep_init_code.md | 228 +++++++ .../guides/gsg_db_rep/cxx/repadvantage.md | 40 ++ docs_src/guides/gsg_db_rep/cxx/repapp.md | 86 +++ .../gsg_db_rep/cxx/repmgr_init_example_c.md | 340 ++++++++++ docs_src/guides/gsg_db_rep/cxx/rywc.md | 20 + .../gsg_db_rep/cxx/simpleprogramlisting.md | 432 +++++++++++++ docs_src/guides/gsg_db_rep/cxx/txnapp.md | 62 ++ docs_src/guides/gsg_db_rep/index.md | 2 + docs_src/guides/gsg_db_rep/java/_meta.toml | 31 + .../guides/gsg_db_rep/java/addfeatures.md | 50 ++ .../guides/gsg_db_rep/java/apioverview.md | 46 ++ docs_src/guides/gsg_db_rep/java/autoinit.md | 12 + docs_src/guides/gsg_db_rep/java/bulk.md | 32 + .../guides/gsg_db_rep/java/c2ctransfer.md | 26 + docs_src/guides/gsg_db_rep/java/elections.md | 56 ++ .../guides/gsg_db_rep/java/electiontimes.md | 34 + .../guides/gsg_db_rep/java/exampledoloop.md | 481 ++++++++++++++ .../gsg_db_rep/java/fmwrkconnectretry.md | 8 + .../gsg_db_rep/java/fwrkmasterreplica.md | 254 ++++++++ .../guides/gsg_db_rep/java/fwrkpermmessage.md | 115 ++++ docs_src/guides/gsg_db_rep/java/heartbeats.md | 16 + docs_src/guides/gsg_db_rep/java/index.md | 142 ++++ .../guides/gsg_db_rep/java/introduction.md | 102 +++ .../guides/gsg_db_rep/java/manageblock.md | 12 + docs_src/guides/gsg_db_rep/java/moreinfo.md | 30 + .../guides/gsg_db_rep/java/permmessages.md | 86 +++ docs_src/guides/gsg_db_rep/java/preface.md | 58 ++ .../guides/gsg_db_rep/java/processingloop.md | 67 ++ .../guides/gsg_db_rep/java/repadvantage.md | 40 ++ docs_src/guides/gsg_db_rep/java/repapp.md | 246 +++++++ .../gsg_db_rep/java/repmgr_init_example_c.md | 380 +++++++++++ docs_src/guides/gsg_db_rep/java/rywc.md | 20 + .../gsg_db_rep/java/simpleprogramlisting.md | 379 +++++++++++ docs_src/guides/gsg_db_rep/java/txnapp.md | 60 ++ docs_src/guides/gsg_txn/cxx/_meta.toml | 44 ++ docs_src/guides/gsg_txn/cxx/abortresults.md | 12 + docs_src/guides/gsg_txn/cxx/apireq.md | 38 ++ .../guides/gsg_txn/cxx/architectrecovery.md | 108 ++++ docs_src/guides/gsg_txn/cxx/autocommit.md | 94 +++ docs_src/guides/gsg_txn/cxx/backuprestore.md | 98 +++ .../guides/gsg_txn/cxx/blocking_deadlocks.md | 160 +++++ docs_src/guides/gsg_txn/cxx/enabletxn.md | 231 +++++++ docs_src/guides/gsg_txn/cxx/envopen.md | 109 ++++ docs_src/guides/gsg_txn/cxx/exclusivelock.md | 18 + docs_src/guides/gsg_txn/cxx/filemanagement.md | 149 +++++ docs_src/guides/gsg_txn/cxx/hotfailover.md | 50 ++ docs_src/guides/gsg_txn/cxx/img/deadlock.jpg | Bin 0 -> 12599 bytes docs_src/guides/gsg_txn/cxx/img/readblock.jpg | Bin 0 -> 10504 bytes .../guides/gsg_txn/cxx/img/rwlocks1-pdf.jpg | Bin 0 -> 192136 bytes docs_src/guides/gsg_txn/cxx/img/rwlocks1.jpg | Bin 0 -> 7428 bytes .../guides/gsg_txn/cxx/img/simplelock-pdf.jpg | Bin 0 -> 129293 bytes .../guides/gsg_txn/cxx/img/simplelock.jpg | Bin 0 -> 4453 bytes .../guides/gsg_txn/cxx/img/writeblock.jpg | Bin 0 -> 6369 bytes docs_src/guides/gsg_txn/cxx/index.md | 170 +++++ .../guides/gsg_txn/cxx/inmem_txnexample_c.md | 396 ++++++++++++ docs_src/guides/gsg_txn/cxx/introduction.md | 50 ++ docs_src/guides/gsg_txn/cxx/isolation.md | 294 +++++++++ .../guides/gsg_txn/cxx/lockingsubsystem.md | 280 ++++++++ docs_src/guides/gsg_txn/cxx/logconfig.md | 140 ++++ docs_src/guides/gsg_txn/cxx/logfileremoval.md | 42 ++ docs_src/guides/gsg_txn/cxx/maxtxns.md | 70 ++ docs_src/guides/gsg_txn/cxx/moreinfo.md | 28 + .../guides/gsg_txn/cxx/multithread-intro.md | 14 + docs_src/guides/gsg_txn/cxx/nestedtxn.md | 34 + docs_src/guides/gsg_txn/cxx/nodurabletxn.md | 28 + docs_src/guides/gsg_txn/cxx/perftune-intro.md | 10 + docs_src/guides/gsg_txn/cxx/preface.md | 62 ++ .../guides/gsg_txn/cxx/readmodifywrite.md | 44 ++ docs_src/guides/gsg_txn/cxx/recovery-intro.md | 18 + docs_src/guides/gsg_txn/cxx/recovery.md | 132 ++++ docs_src/guides/gsg_txn/cxx/reversesplit.md | 76 +++ docs_src/guides/gsg_txn/cxx/sysfailure.md | 18 + docs_src/guides/gsg_txn/cxx/txn_ccursor.md | 79 +++ docs_src/guides/gsg_txn/cxx/txnconcurrency.md | 104 +++ docs_src/guides/gsg_txn/cxx/txncursor.md | 69 ++ docs_src/guides/gsg_txn/cxx/txnexample_c.md | 474 ++++++++++++++ docs_src/guides/gsg_txn/cxx/txnindices.md | 41 ++ docs_src/guides/gsg_txn/cxx/txnnowait.md | 24 + docs_src/guides/gsg_txn/cxx/usingtxns.md | 149 +++++ docs_src/guides/gsg_txn/cxx/wrapup.md | 74 +++ docs_src/guides/gsg_txn/index.md | 2 + docs_src/guides/gsg_txn/java/_meta.toml | 45 ++ docs_src/guides/gsg_txn/java/abortresults.md | 12 + docs_src/guides/gsg_txn/java/apireq.md | 44 ++ .../guides/gsg_txn/java/architectrecovery.md | 82 +++ docs_src/guides/gsg_txn/java/autocommit.md | 72 +++ docs_src/guides/gsg_txn/java/backuprestore.md | 98 +++ .../guides/gsg_txn/java/blocking_deadlocks.md | 160 +++++ docs_src/guides/gsg_txn/java/enabletxn.md | 219 +++++++ docs_src/guides/gsg_txn/java/envopen.md | 128 ++++ docs_src/guides/gsg_txn/java/exclusivelock.md | 18 + .../guides/gsg_txn/java/filemanagement.md | 246 +++++++ docs_src/guides/gsg_txn/java/hotfailover.md | 50 ++ docs_src/guides/gsg_txn/java/img/deadlock.jpg | Bin 0 -> 12599 bytes .../guides/gsg_txn/java/img/readblock.jpg | Bin 0 -> 10504 bytes .../guides/gsg_txn/java/img/rwlocks1-pdf.jpg | Bin 0 -> 192136 bytes docs_src/guides/gsg_txn/java/img/rwlocks1.jpg | Bin 0 -> 7428 bytes .../gsg_txn/java/img/simplelock-pdf.jpg | Bin 0 -> 129293 bytes .../guides/gsg_txn/java/img/simplelock.jpg | Bin 0 -> 4453 bytes .../guides/gsg_txn/java/img/writeblock.jpg | Bin 0 -> 6369 bytes docs_src/guides/gsg_txn/java/index.md | 188 ++++++ .../gsg_txn/java/inmem_txnexample_java.md | 420 ++++++++++++ docs_src/guides/gsg_txn/java/introduction.md | 50 ++ docs_src/guides/gsg_txn/java/isolation.md | 606 ++++++++++++++++++ .../guides/gsg_txn/java/lockingsubsystem.md | 246 +++++++ docs_src/guides/gsg_txn/java/logconfig.md | 127 ++++ .../guides/gsg_txn/java/logfileremoval.md | 42 ++ docs_src/guides/gsg_txn/java/maxtxns.md | 71 ++ docs_src/guides/gsg_txn/java/moreinfo.md | 30 + .../guides/gsg_txn/java/multithread-intro.md | 14 + docs_src/guides/gsg_txn/java/nestedtxn.md | 33 + docs_src/guides/gsg_txn/java/nodurabletxn.md | 30 + .../guides/gsg_txn/java/perftune-intro.md | 10 + docs_src/guides/gsg_txn/java/preface.md | 60 ++ .../guides/gsg_txn/java/readmodifywrite.md | 76 +++ .../guides/gsg_txn/java/recovery-intro.md | 18 + docs_src/guides/gsg_txn/java/recovery.md | 136 ++++ docs_src/guides/gsg_txn/java/reversesplit.md | 121 ++++ docs_src/guides/gsg_txn/java/sysfailure.md | 18 + docs_src/guides/gsg_txn/java/txn_ccursor.md | 159 +++++ .../guides/gsg_txn/java/txnconcurrency.md | 116 ++++ docs_src/guides/gsg_txn/java/txncursor.md | 138 ++++ .../guides/gsg_txn/java/txnexample_dpl.md | 507 +++++++++++++++ .../guides/gsg_txn/java/txnexample_java.md | 533 +++++++++++++++ docs_src/guides/gsg_txn/java/txnindices.md | 73 +++ docs_src/guides/gsg_txn/java/txnnowait.md | 26 + docs_src/guides/gsg_txn/java/usingtxns.md | 208 ++++++ docs_src/guides/gsg_txn/java/wrapup.md | 88 +++ docs_src/index.md | 9 + 252 files changed, 25571 insertions(+), 3 deletions(-) create mode 100644 docs_src/guides/gsg/cxx/CoreCursorUsage.md create mode 100644 docs_src/guides/gsg/cxx/CoreDBAdmin.md create mode 100644 docs_src/guides/gsg/cxx/CoreDbCXXUsage.md create mode 100644 docs_src/guides/gsg/cxx/CoreEnvUsage.md create mode 100644 docs_src/guides/gsg/cxx/Cursors.md create mode 100644 docs_src/guides/gsg/cxx/DBEntry.md create mode 100644 docs_src/guides/gsg/cxx/DBOpenFlags.md create mode 100644 docs_src/guides/gsg/cxx/DbCXXUsage.md create mode 100644 docs_src/guides/gsg/cxx/DeleteEntryWCursor.md create mode 100644 docs_src/guides/gsg/cxx/Positioning.md create mode 100644 docs_src/guides/gsg/cxx/PutEntryWCursor.md create mode 100644 docs_src/guides/gsg/cxx/ReplacingEntryWCursor.md create mode 100644 docs_src/guides/gsg/cxx/_meta.toml create mode 100644 docs_src/guides/gsg/cxx/accessmethods.md create mode 100644 docs_src/guides/gsg/cxx/btree.md create mode 100644 docs_src/guides/gsg/cxx/cachesize.md create mode 100644 docs_src/guides/gsg/cxx/concepts.md create mode 100644 docs_src/guides/gsg/cxx/coreExceptions.md create mode 100644 docs_src/guides/gsg/cxx/coredbclose.md create mode 100644 docs_src/guides/gsg/cxx/coreindexusage.md create mode 100644 docs_src/guides/gsg/cxx/databaseLimits.md create mode 100644 docs_src/guides/gsg/cxx/databases.md create mode 100644 docs_src/guides/gsg/cxx/dbErrorReporting.md create mode 100644 docs_src/guides/gsg/cxx/dbconfig.md create mode 100644 docs_src/guides/gsg/cxx/environments.md create mode 100644 docs_src/guides/gsg/cxx/gettingit.md create mode 100644 docs_src/guides/gsg/cxx/index.md create mode 100644 docs_src/guides/gsg/cxx/indexes.md create mode 100644 docs_src/guides/gsg/cxx/introduction.md create mode 100644 docs_src/guides/gsg/cxx/joins.md create mode 100644 docs_src/guides/gsg/cxx/keyCreator.md create mode 100644 docs_src/guides/gsg/cxx/moreinfo.md create mode 100644 docs_src/guides/gsg/cxx/preface.md create mode 100644 docs_src/guides/gsg/cxx/readSecondary.md create mode 100644 docs_src/guides/gsg/cxx/returns.md create mode 100644 docs_src/guides/gsg/cxx/secondaryCursor.md create mode 100644 docs_src/guides/gsg/cxx/secondaryDelete.md create mode 100644 docs_src/guides/gsg/cxx/usingDbt.md create mode 100644 docs_src/guides/gsg/java/CoreEnvUsage.md create mode 100644 docs_src/guides/gsg/java/CoreJavaUsage.md create mode 100644 docs_src/guides/gsg/java/Cursors.md create mode 100644 docs_src/guides/gsg/java/DBAdmin.md create mode 100644 docs_src/guides/gsg/java/DBEntry.md create mode 100644 docs_src/guides/gsg/java/DeleteEntryWCursor.md create mode 100644 docs_src/guides/gsg/java/Env.md create mode 100644 docs_src/guides/gsg/java/EnvClose.md create mode 100644 docs_src/guides/gsg/java/EnvProps.md create mode 100644 docs_src/guides/gsg/java/Positioning.md create mode 100644 docs_src/guides/gsg/java/PutEntryWCursor.md create mode 100644 docs_src/guides/gsg/java/ReplacingEntryWCursor.md create mode 100644 docs_src/guides/gsg/java/_meta.toml create mode 100644 docs_src/guides/gsg/java/accessmethods.md create mode 100644 docs_src/guides/gsg/java/baseapi.md create mode 100644 docs_src/guides/gsg/java/bindAPI.md create mode 100644 docs_src/guides/gsg/java/btree.md create mode 100644 docs_src/guides/gsg/java/cachesize.md create mode 100644 docs_src/guides/gsg/java/coreExceptions.md create mode 100644 docs_src/guides/gsg/java/coredbclose.md create mode 100644 docs_src/guides/gsg/java/cursorJavaUsage.md create mode 100644 docs_src/guides/gsg/java/dataaccessorclass.md create mode 100644 docs_src/guides/gsg/java/databaseLimits.md create mode 100644 docs_src/guides/gsg/java/databases.md create mode 100644 docs_src/guides/gsg/java/dbErrorReporting.md create mode 100644 docs_src/guides/gsg/java/dbconfig.md create mode 100644 docs_src/guides/gsg/java/dbprops.md create mode 100644 docs_src/guides/gsg/java/dbtJavaUsage.md create mode 100644 docs_src/guides/gsg/java/dpl.md create mode 100644 docs_src/guides/gsg/java/dpl_delete.md create mode 100644 docs_src/guides/gsg/java/dpl_entityjoin.md create mode 100644 docs_src/guides/gsg/java/dpl_example.md create mode 100644 docs_src/guides/gsg/java/dpl_exampledatabaseput.md create mode 100644 docs_src/guides/gsg/java/dpl_exampleinventoryread.md create mode 100644 docs_src/guides/gsg/java/dpl_replace.md create mode 100644 docs_src/guides/gsg/java/dplindexcreate.md create mode 100644 docs_src/guides/gsg/java/getmultiple.md create mode 100644 docs_src/guides/gsg/java/gettingit.md create mode 100644 docs_src/guides/gsg/java/index.md create mode 100644 docs_src/guides/gsg/java/indexes.md create mode 100644 docs_src/guides/gsg/java/introduction.md create mode 100644 docs_src/guides/gsg/java/inventoryclass.md create mode 100644 docs_src/guides/gsg/java/javadplconcepts.md create mode 100644 docs_src/guides/gsg/java/javaindexusage.md create mode 100644 docs_src/guides/gsg/java/joins.md create mode 100644 docs_src/guides/gsg/java/keyCreator.md create mode 100644 docs_src/guides/gsg/java/moreinfo.md create mode 100644 docs_src/guides/gsg/java/mydbenv-persist.md create mode 100644 docs_src/guides/gsg/java/persist_access.md create mode 100644 docs_src/guides/gsg/java/persist_first.md create mode 100644 docs_src/guides/gsg/java/persist_index.md create mode 100644 docs_src/guides/gsg/java/persistobject.md create mode 100644 docs_src/guides/gsg/java/preface.md create mode 100644 docs_src/guides/gsg/java/readSecondary.md create mode 100644 docs_src/guides/gsg/java/returns.md create mode 100644 docs_src/guides/gsg/java/saveret.md create mode 100644 docs_src/guides/gsg/java/secondaryCursor.md create mode 100644 docs_src/guides/gsg/java/secondaryDelete.md create mode 100644 docs_src/guides/gsg/java/secondaryProps.md create mode 100644 docs_src/guides/gsg/java/simpleda.md create mode 100644 docs_src/guides/gsg/java/simpleget.md create mode 100644 docs_src/guides/gsg/java/simpleput.md create mode 100644 docs_src/guides/gsg/java/usingDbt.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/_meta.toml create mode 100644 docs_src/guides/gsg_db_rep/cxx/addfeatures.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/apioverview.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/autoinit.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/bulk.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/c2ctransfer.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/elections.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/electiontimes.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/exampledoloop.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/fmwrkconnectretry.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/fwrkmasterreplica.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/fwrkpermmessage.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/heartbeats.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/index.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/introduction.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/manageblock.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/moreinfo.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/permmessages.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/preface.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/processingloop.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/rep_init_code.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/repadvantage.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/repapp.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/repmgr_init_example_c.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/rywc.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/simpleprogramlisting.md create mode 100644 docs_src/guides/gsg_db_rep/cxx/txnapp.md create mode 100644 docs_src/guides/gsg_db_rep/java/_meta.toml create mode 100644 docs_src/guides/gsg_db_rep/java/addfeatures.md create mode 100644 docs_src/guides/gsg_db_rep/java/apioverview.md create mode 100644 docs_src/guides/gsg_db_rep/java/autoinit.md create mode 100644 docs_src/guides/gsg_db_rep/java/bulk.md create mode 100644 docs_src/guides/gsg_db_rep/java/c2ctransfer.md create mode 100644 docs_src/guides/gsg_db_rep/java/elections.md create mode 100644 docs_src/guides/gsg_db_rep/java/electiontimes.md create mode 100644 docs_src/guides/gsg_db_rep/java/exampledoloop.md create mode 100644 docs_src/guides/gsg_db_rep/java/fmwrkconnectretry.md create mode 100644 docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md create mode 100644 docs_src/guides/gsg_db_rep/java/fwrkpermmessage.md create mode 100644 docs_src/guides/gsg_db_rep/java/heartbeats.md create mode 100644 docs_src/guides/gsg_db_rep/java/index.md create mode 100644 docs_src/guides/gsg_db_rep/java/introduction.md create mode 100644 docs_src/guides/gsg_db_rep/java/manageblock.md create mode 100644 docs_src/guides/gsg_db_rep/java/moreinfo.md create mode 100644 docs_src/guides/gsg_db_rep/java/permmessages.md create mode 100644 docs_src/guides/gsg_db_rep/java/preface.md create mode 100644 docs_src/guides/gsg_db_rep/java/processingloop.md create mode 100644 docs_src/guides/gsg_db_rep/java/repadvantage.md create mode 100644 docs_src/guides/gsg_db_rep/java/repapp.md create mode 100644 docs_src/guides/gsg_db_rep/java/repmgr_init_example_c.md create mode 100644 docs_src/guides/gsg_db_rep/java/rywc.md create mode 100644 docs_src/guides/gsg_db_rep/java/simpleprogramlisting.md create mode 100644 docs_src/guides/gsg_db_rep/java/txnapp.md create mode 100644 docs_src/guides/gsg_txn/cxx/_meta.toml create mode 100644 docs_src/guides/gsg_txn/cxx/abortresults.md create mode 100644 docs_src/guides/gsg_txn/cxx/apireq.md create mode 100644 docs_src/guides/gsg_txn/cxx/architectrecovery.md create mode 100644 docs_src/guides/gsg_txn/cxx/autocommit.md create mode 100644 docs_src/guides/gsg_txn/cxx/backuprestore.md create mode 100644 docs_src/guides/gsg_txn/cxx/blocking_deadlocks.md create mode 100644 docs_src/guides/gsg_txn/cxx/enabletxn.md create mode 100644 docs_src/guides/gsg_txn/cxx/envopen.md create mode 100644 docs_src/guides/gsg_txn/cxx/exclusivelock.md create mode 100644 docs_src/guides/gsg_txn/cxx/filemanagement.md create mode 100644 docs_src/guides/gsg_txn/cxx/hotfailover.md create mode 100644 docs_src/guides/gsg_txn/cxx/img/deadlock.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/readblock.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/rwlocks1-pdf.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/rwlocks1.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/simplelock-pdf.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/simplelock.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/img/writeblock.jpg create mode 100644 docs_src/guides/gsg_txn/cxx/index.md create mode 100644 docs_src/guides/gsg_txn/cxx/inmem_txnexample_c.md create mode 100644 docs_src/guides/gsg_txn/cxx/introduction.md create mode 100644 docs_src/guides/gsg_txn/cxx/isolation.md create mode 100644 docs_src/guides/gsg_txn/cxx/lockingsubsystem.md create mode 100644 docs_src/guides/gsg_txn/cxx/logconfig.md create mode 100644 docs_src/guides/gsg_txn/cxx/logfileremoval.md create mode 100644 docs_src/guides/gsg_txn/cxx/maxtxns.md create mode 100644 docs_src/guides/gsg_txn/cxx/moreinfo.md create mode 100644 docs_src/guides/gsg_txn/cxx/multithread-intro.md create mode 100644 docs_src/guides/gsg_txn/cxx/nestedtxn.md create mode 100644 docs_src/guides/gsg_txn/cxx/nodurabletxn.md create mode 100644 docs_src/guides/gsg_txn/cxx/perftune-intro.md create mode 100644 docs_src/guides/gsg_txn/cxx/preface.md create mode 100644 docs_src/guides/gsg_txn/cxx/readmodifywrite.md create mode 100644 docs_src/guides/gsg_txn/cxx/recovery-intro.md create mode 100644 docs_src/guides/gsg_txn/cxx/recovery.md create mode 100644 docs_src/guides/gsg_txn/cxx/reversesplit.md create mode 100644 docs_src/guides/gsg_txn/cxx/sysfailure.md create mode 100644 docs_src/guides/gsg_txn/cxx/txn_ccursor.md create mode 100644 docs_src/guides/gsg_txn/cxx/txnconcurrency.md create mode 100644 docs_src/guides/gsg_txn/cxx/txncursor.md create mode 100644 docs_src/guides/gsg_txn/cxx/txnexample_c.md create mode 100644 docs_src/guides/gsg_txn/cxx/txnindices.md create mode 100644 docs_src/guides/gsg_txn/cxx/txnnowait.md create mode 100644 docs_src/guides/gsg_txn/cxx/usingtxns.md create mode 100644 docs_src/guides/gsg_txn/cxx/wrapup.md create mode 100644 docs_src/guides/gsg_txn/java/_meta.toml create mode 100644 docs_src/guides/gsg_txn/java/abortresults.md create mode 100644 docs_src/guides/gsg_txn/java/apireq.md create mode 100644 docs_src/guides/gsg_txn/java/architectrecovery.md create mode 100644 docs_src/guides/gsg_txn/java/autocommit.md create mode 100644 docs_src/guides/gsg_txn/java/backuprestore.md create mode 100644 docs_src/guides/gsg_txn/java/blocking_deadlocks.md create mode 100644 docs_src/guides/gsg_txn/java/enabletxn.md create mode 100644 docs_src/guides/gsg_txn/java/envopen.md create mode 100644 docs_src/guides/gsg_txn/java/exclusivelock.md create mode 100644 docs_src/guides/gsg_txn/java/filemanagement.md create mode 100644 docs_src/guides/gsg_txn/java/hotfailover.md create mode 100644 docs_src/guides/gsg_txn/java/img/deadlock.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/readblock.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/rwlocks1-pdf.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/rwlocks1.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/simplelock-pdf.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/simplelock.jpg create mode 100644 docs_src/guides/gsg_txn/java/img/writeblock.jpg create mode 100644 docs_src/guides/gsg_txn/java/index.md create mode 100644 docs_src/guides/gsg_txn/java/inmem_txnexample_java.md create mode 100644 docs_src/guides/gsg_txn/java/introduction.md create mode 100644 docs_src/guides/gsg_txn/java/isolation.md create mode 100644 docs_src/guides/gsg_txn/java/lockingsubsystem.md create mode 100644 docs_src/guides/gsg_txn/java/logconfig.md create mode 100644 docs_src/guides/gsg_txn/java/logfileremoval.md create mode 100644 docs_src/guides/gsg_txn/java/maxtxns.md create mode 100644 docs_src/guides/gsg_txn/java/moreinfo.md create mode 100644 docs_src/guides/gsg_txn/java/multithread-intro.md create mode 100644 docs_src/guides/gsg_txn/java/nestedtxn.md create mode 100644 docs_src/guides/gsg_txn/java/nodurabletxn.md create mode 100644 docs_src/guides/gsg_txn/java/perftune-intro.md create mode 100644 docs_src/guides/gsg_txn/java/preface.md create mode 100644 docs_src/guides/gsg_txn/java/readmodifywrite.md create mode 100644 docs_src/guides/gsg_txn/java/recovery-intro.md create mode 100644 docs_src/guides/gsg_txn/java/recovery.md create mode 100644 docs_src/guides/gsg_txn/java/reversesplit.md create mode 100644 docs_src/guides/gsg_txn/java/sysfailure.md create mode 100644 docs_src/guides/gsg_txn/java/txn_ccursor.md create mode 100644 docs_src/guides/gsg_txn/java/txnconcurrency.md create mode 100644 docs_src/guides/gsg_txn/java/txncursor.md create mode 100644 docs_src/guides/gsg_txn/java/txnexample_dpl.md create mode 100644 docs_src/guides/gsg_txn/java/txnexample_java.md create mode 100644 docs_src/guides/gsg_txn/java/txnindices.md create mode 100644 docs_src/guides/gsg_txn/java/txnnowait.md create mode 100644 docs_src/guides/gsg_txn/java/usingtxns.md create mode 100644 docs_src/guides/gsg_txn/java/wrapup.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 188d9889e..575bd467c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -89,11 +89,15 @@ jobs: # 6. INTERNAL LINK CHECK (hard): every link into migrated content must # resolve. Deferred-tree + un-migrated-asset links are excluded (see - # docs_src/_migrate/lychee.toml). External links are the advisory job. + # docs_src/_migrate/lychee.toml). The javadoc-generated java-api/ tree + # is a build artifact (not migrated Markdown); its doc-comment + # cross-links to the old DocBook paths dangle in this layout, so it is + # excluded from the source set via --exclude-path. External links are + # the advisory job. - name: Internal link check (lychee, offline) run: | nix develop --command bash -c \ - 'shopt -s globstar; lychee --offline --config docs_src/_migrate/lychee.toml --no-progress "docs-build/html/**/*.html"' + 'shopt -s globstar; lychee --offline --config docs_src/_migrate/lychee.toml --exclude-path docs-build/html/java-api --no-progress "docs-build/html/**/*.html"' # 7. MAN-LINT (hard): 0 ERRORS from mandoc across every generated .3 # (STYLE/WARNING are fine). @@ -192,7 +196,8 @@ jobs: nix develop --command bash -c ' shopt -s globstar lychee --no-progress --scheme http --scheme https \ - --exclude "localhost" --max-concurrency 8 \ + --exclude "localhost" --exclude-path docs-build/html/java-api \ + --max-concurrency 8 \ "docs-build/html/**/*.html" || true' # ---------------------------------------------------------------------------- diff --git a/docs_src/guides/gsg/cxx/CoreCursorUsage.md b/docs_src/guides/gsg/cxx/CoreCursorUsage.md new file mode 100644 index 000000000..1e845e7ce --- /dev/null +++ b/docs_src/guides/gsg/cxx/CoreCursorUsage.md @@ -0,0 +1,181 @@ +--- +title: "Cursor Example" +api-name: "Cursor Example" +source: docs/gsg/CXX/CoreCursorUsage.html +--- +## Cursor Example + +In Database Usage Example we wrote an application that loaded two databases with vendor and inventory information. In this example, we will write an application to display all of the items in the inventory database. As a part of showing any given inventory item, we will look up the vendor who can provide the item and show the vendor's contact information. + +Specifically, the `example_database_read` application does the following: + +1. Opens the the inventory and vendor databases that were created by our `example_database_load` application. See example_database_load for information on how that application creates the databases and writes data to them. + +2. Obtains a cursor from the inventory database. + +3. Steps through the inventory database, displaying each record as it goes. + +4. Gets the name of the vendor for that inventory item from the inventory record. + +5. Uses the vendor name to look up the vendor record in the vendor database. + +6. Displays the vendor record. + +Remember that you can find the complete implementation of this application in: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 4.1 example_database_read** + +To begin, we include the necessary header files and perform our forward declarations. We also write our `usage()` function. + +``` c +// File: example_database_read.cpp +#include +#include +#include + +#include "MyDb.hpp" +#include "gettingStartedCommon.hpp" + +// Forward declarations +int show_all_records(MyDb &inventoryDB, MyDb &vendorDB); +int show_vendor(MyDb &vendorDB, const char *vendor); +``` + +Next we write our `main()` function. Note that it is somewhat unnecessarily complicated here because we will be extending it in the next chapter to perform inventory item lookups. + +``` c +// Displays all inventory items and the associated vendor record. +int +main (int argc, char *argv[]) +{ + // Initialize the path to the database files + std::string databaseHome("./"); + + // Database names + std::string vDbName("vendordb.db"); + std::string iDbName("inventorydb.db"); + + // Parse the command line arguments + // Omitted for brevity + + try + { + // Open all databases. + MyDb inventoryDB(databaseHome, iDbName); + MyDb vendorDB(databaseHome, vDbName); + + show_all_records(inventoryDB, vendorDB); + } catch(DbException &e) { + std::cerr << "Error reading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(e.get_errno()); + } catch(std::exception &e) { + std::cerr << "Error reading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(-1); + } + + return(0); +} // End main +``` + +Next we need to write the `show_all_records()` function. This function displays all of the inventory records found in the inventory database. Once it shows the inventory record, it retrieves the vendor's name from that record and uses it to look up and display the appropriate vendor record: + +``` c +// Shows all the records in the inventory database. +// For each inventory record shown, the appropriate +// vendor record is also displayed. +int +show_all_records(MyDb &inventoryDB, MyDb &vendorDB) +{ + // Get a cursor to the inventory db + Dbc *cursorp; + try { + inventoryDB.getDb().cursor(NULL, &cursorp, 0); + + // Iterate over the inventory database, from the first record + // to the last, displaying each in turn + Dbt key, data; + int ret; + while ((ret = cursorp->get(&key, &data, DB_NEXT)) == 0 ) + { + InventoryData inventoryItem(data.get_data()); + inventoryItem.show(); + + show_vendor(vendorDB, inventoryItem.getVendor().c_str()); + } + } catch(DbException &e) { + inventoryDB.getDb().err(e.get_errno(), + "Error in show_all_records"); + cursorp->close(); + throw e; + } catch(std::exception &e) { + cursorp->close(); + throw e; + } + + cursorp->close(); + return (0); +} +``` + +Note that the `InventoryData` class that we use here is described in InventoryData Class. + +Having displayed the inventory record, we now want to display the vendor record corresponding to this record. In this case we do not need to use a cursor to display the vendor record. Using a cursor here complicates our code slightly for no good gain. Instead, we simply perform a `get()` directly against the vendor database. + +``` c +// Shows a vendor record. Each vendor record is an instance of +// a vendor structure. See loadVendorDB() in +// example_database_load for how this structure was originally +// put into the database. +int +show_vendor(MyDb &vendorDB, const char *vendor) +{ + Dbt data; + VENDOR my_vendor; + + try { + // Set the search key to the vendor's name + // vendor is explicitly cast to char * to stop a compiler + // complaint. + Dbt key((char *)vendor, strlen(vendor) + 1); + + // Make sure we use the memory we set aside for the VENDOR + // structure rather than the memory that DB allocates. + // Some systems may require structures to be aligned in memory + // in a specific way, and DB may not get it right. + + data.set_data(&my_vendor); + data.set_ulen(sizeof(VENDOR)); + data.set_flags(DB_DBT_USERMEM); + + // Get the record + vendorDB.getDb().get(NULL, &key, &data, 0); + std::cout << " " << my_vendor.street << "\n" + << " " << my_vendor.city << ", " + << my_vendor.state << "\n" + << " " << my_vendor.zipcode << "\n" + << " " << my_vendor.phone_number << "\n" + << " Contact: " << my_vendor.sales_rep << "\n" + << " " << my_vendor.sales_rep_phone + << std::endl; + + } catch(DbException &e) { + vendorDB.getDb().err(e.get_errno(), "Error in show_vendor"); + throw e; + } catch(std::exception &e) { + throw e; + } + return (0); +} +``` + + + +That completes the implementation of `example_database_read()`. In the next chapter, we will extend this application to make use of a secondary database so that we can query the inventory database for a specific inventory item. diff --git a/docs_src/guides/gsg/cxx/CoreDBAdmin.md b/docs_src/guides/gsg/cxx/CoreDBAdmin.md new file mode 100644 index 000000000..9443659ff --- /dev/null +++ b/docs_src/guides/gsg/cxx/CoreDBAdmin.md @@ -0,0 +1,64 @@ +--- +title: "Administrative Methods" +api-name: "Administrative Methods" +source: docs/gsg/CXX/CoreDBAdmin.html +--- +## Administrative Methods + +The following `Db` methods may be useful to you when managing DB databases: + +- `Db::get_open_flags()` + + Returns the current open flags. It is an error to use this method on an unopened database. + + ``` c + #include + ... + Db db(NULL, 0); + u_int32_t open_flags; + + // Database open and subsequent operations omitted for clarity + + db.get_open_flags(&open_flags); + ``` + +- `Db::remove()` + + Removes the specified database. If no value is given for the *`database`* parameter, then the entire file referenced by this method is removed. + + Never remove a database that has handles opened for it. Never remove a file that contains databases with opened handles. + + ``` c + #include + ... + Db db(NULL, 0); + + // Database handle creation omitted for clarity + + db.remove("mydb.db", // Database file to remove + NULL, // Database to remove. This is + // NULL so the entire file is + // removed. + 0); // Flags. None used. + ``` + +- `Db::rename()` + + Renames the specified database. If no value is given for the *`database`* parameter, then the entire file referenced by this method is renamed. + + Never rename a database that has handles opened for it. Never rename a file that contains databases with opened handles. + + ``` c + #include + ... + Db db(NULL, 0); + + // Database handle creation omitted for clarity + + db.rename("mydb.db", // Database file to rename + NULL, // Database to rename. This is + // NULL so the entire file is + // renamed. + "newdb.db", // New database file name + 0); // Flags. None used. + ``` diff --git a/docs_src/guides/gsg/cxx/CoreDbCXXUsage.md b/docs_src/guides/gsg/cxx/CoreDbCXXUsage.md new file mode 100644 index 000000000..e2cb8edd2 --- /dev/null +++ b/docs_src/guides/gsg/cxx/CoreDbCXXUsage.md @@ -0,0 +1,123 @@ +--- +title: "Database Example" +api-name: "Database Example" +source: docs/gsg/CXX/CoreDbCXXUsage.html +--- +## Database Example + +Throughout this book we will build a couple of applications that load and retrieve inventory data from DB databases. While we are not yet ready to begin reading from or writing to our databases, we can at least create the class that we will use to manage our databases. + +Note that subsequent examples in this book will build on this code to perform the more interesting work of writing to and reading from the databases. + +Note that you can find the complete implementation of these functions in: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 2.1 MyDb Class** + +To manage our database open and close activities, we encapsulate them in the `MyDb` class. There are several good reasons to do this, the most important being that we can ensure our databases are closed by putting that activity in the `MyDb` class destructor. + +To begin, we create our class definition: + +``` c +// File: MyDb.hpp +#include + +class MyDb +{ +public: + // Constructor requires a path to the database, + // and a database name. + MyDb(std::string &path, std::string &dbName); + + // Our destructor just calls our private close method. + ~MyDb() { close(); } + + inline Db &getDb() {return db_;} + +private: + Db db_; + std::string dbFileName_; + u_int32_t cFlags_; + + // Make sure the default constructor is private + // We don't want it used. + MyDb() : db_(NULL, 0) {} + + // We put our database close activity here. + // This is called from our destructor. In + // a more complicated example, we might want + // to make this method public, but a private + // method is more appropriate for this example. + void close(); +}; +``` + +Next we need the implementation for the constructor: + +``` c +// File: MyDb.cpp +#include "MyDb.hpp" + +// Class constructor. Requires a path to the location +// where the database is located, and a database name +MyDb::MyDb(std::string &path, std::string &dbName) + : db_(NULL, 0), // Instantiate Db object + dbFileName_(path + dbName), // Database file name + cFlags_(DB_CREATE) // If the database doesn't yet exist, + // allow it to be created. +{ + try + { + // Redirect debugging information to std::cerr + db_.set_error_stream(&std::cerr); + + // Open the database + db_.open(NULL, dbFileName_.c_str(), NULL, DB_BTREE, cFlags_, 0); + } + // DbException is not a subclass of std::exception, so we + // need to catch them both. + catch(DbException &e) + { + std::cerr << "Error opening database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } + catch(std::exception &e) + { + std::cerr << "Error opening database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } +} +``` + +And then we need the implementation for the `close()` method: + +``` c +// Private member used to close a database. Called from the class +// destructor. +void +MyDb::close() +{ + // Close the db + try + { + db_.close(0); + std::cout << "Database " << dbFileName_ + << " is closed." << std::endl; + } + catch(DbException &e) + { + std::cerr << "Error closing database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } + catch(std::exception &e) + { + std::cerr << "Error closing database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } +} +``` diff --git a/docs_src/guides/gsg/cxx/CoreEnvUsage.md b/docs_src/guides/gsg/cxx/CoreEnvUsage.md new file mode 100644 index 000000000..aa6d00153 --- /dev/null +++ b/docs_src/guides/gsg/cxx/CoreEnvUsage.md @@ -0,0 +1,107 @@ +--- +title: "Managing Databases in Environments" +api-name: "Managing Databases in Environments" +source: docs/gsg/CXX/CoreEnvUsage.html +--- +## Managing Databases in Environments + +In Environments, we introduced environments. While environments are not used in the example built in this book, they are so commonly used for a wide class of DB applications that it is necessary to show their basic usage, if only from a completeness perspective. + +To use an environment, you must first open it. At open time, you must identify the directory in which it resides. This directory must exist prior to the open attempt. You can also identify open properties, such as whether the environment can be created if it does not already exist. + +You will also need to initialize the in-memory cache when you open your environment. + +For example, to create an environment handle and open an environment: + +``` c +#include +... +u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_MPOOL; // Initialize the in-memory cache. + +std::string envHome("/export1/testEnv"); +DbEnv myEnv(0); + +try { + myEnv.open(envHome.c_str(), env_flags, 0); +} catch(DbException &e) { + std::cerr << "Error opening database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} catch(std::exception &e) { + std::cerr << "Error opening database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} +``` + +Once an environment is opened, you can open databases in it. Note that by default databases are stored in the environment's home directory, or relative to that directory if you provide any sort of a path in the database's file name: + +``` c +#include +... +u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_MPOOL; // Initialize the in-memory cache. +std::string envHome("/export1/testEnv"); + +u_int32_t db_flags = DB_CREATE; // If the database does not + // exist, create it. +std::string dbName("mydb.db"); +DbEnv myEnv(0); +Db *myDb; + +try { + myEnv.open(envHome.c_str(), env_flags, 0); + myDb = new Db(&myEnv, 0); + myDb->open(NULL, + dbName.c_str(), + NULL, + DB_BTREE, + db_flags, + 0); +} catch(DbException &e) { + std::cerr << "Error opening database environment: " + << envHome + << " and database " + << dbName << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} catch(std::exception &e) { + std::cerr << "Error opening database environment: " + << envHome + << " and database " + << dbName << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} +``` + +When you are done with an environment, you must close it. It is recommended that before closing an environment, you close any open databases. + +``` c +try { + if (myDb != NULL) { + myDb->close(0); + } + myEnv.close(0); + +} catch(DbException &e) { + std::cerr << "Error closing database environment: " + << envHome + << " or database " + << dbName << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} catch(std::exception &e) { + std::cerr << "Error closing database environment: " + << envHome + << " or database " + << dbName << std::endl; + std::cerr << e.what() << std::endl; + exit( -1 ); +} +``` diff --git a/docs_src/guides/gsg/cxx/Cursors.md b/docs_src/guides/gsg/cxx/Cursors.md new file mode 100644 index 000000000..431d6f806 --- /dev/null +++ b/docs_src/guides/gsg/cxx/Cursors.md @@ -0,0 +1,66 @@ +--- +title: "Chapter 4. Using Cursors" +api-name: "Chapter 4. Using Cursors" +source: docs/gsg/CXX/Cursors.html +--- +## Chapter 4. Using Cursors + +**Table of Contents** + + [Opening and Closing Cursors](Cursors.md#openCursor) + + [Getting Records Using the Cursor](Positioning.md) + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + + [Putting Records Using Cursors](PutEntryWCursor.md) + + [Deleting Records Using Cursors](DeleteEntryWCursor.md) + + [Replacing Records Using Cursors](ReplacingEntryWCursor.md) + + [Cursor Example](CoreCursorUsage.md) + +Cursors provide a mechanism by which you can iterate over the records in a database. Using cursors, you can get, put, and delete database records. If a database allows duplicate records, then cursors are the easiest way that you can access anything other than the first record for a given key. + +This chapter introduces cursors. It explains how to open and close them, how to use them to modify databases, and how to use them with duplicate records. + +## Opening and Closing Cursors + +Cursors are managed using the `Dbc` class. To use a cursor, you must open it using the `Db::cursor()` method. + +For example: + +``` c +#include + +... + +Dbc *cursorp; +Db my_database(NULL, 0); + +// Database open omitted for clarity + +// Get a cursor +my_database.cursor(NULL, &cursorp, 0); +``` + +When you are done with the cursor, you should close it. To close a cursor, call the `Dbc::close()` method. Note that closing your database while cursors are still opened within the scope of the DB handle, especially if those cursors are writing to the database, can have unpredictable results. It is recommended that you close all cursor handles after their use to ensure concurrency and to release resources such as page locks. + +``` c +#include + +... + +Dbc *cursorp; +Db my_database(NULL, 0); + +// Database and cursor open omitted for clarity + +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` diff --git a/docs_src/guides/gsg/cxx/DBEntry.md b/docs_src/guides/gsg/cxx/DBEntry.md new file mode 100644 index 000000000..914137ecc --- /dev/null +++ b/docs_src/guides/gsg/cxx/DBEntry.md @@ -0,0 +1,67 @@ +--- +title: "Chapter 3. Database Records" +api-name: "Chapter 3. Database Records" +source: docs/gsg/CXX/DBEntry.html +--- +## Chapter 3. Database Records + +**Table of Contents** + + [Using Database Records](DBEntry.md#usingDbEntry) + + [Reading and Writing Database Records](usingDbt.md) + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#CoreDatabaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + + [Database Usage Example](DbCXXUsage.md) + +DB records contain two parts — a key and some data. Both the key and its corresponding data are encapsulated in `Dbt` class objects. Therefore, to access a DB record, you need two such objects, one for the key and one for the data. + +`Dbt` objects provide a `void *` data member that you use to point to your data, and another member that identifies the data length. They can therefore be used to store anything from simple primitive data to complex class objects so long as the information you want to store resides in a single contiguous block of memory. + +This chapter describes `Dbt` usage. It also introduces storing and retrieving key/value pairs from a database. + +## Using Database Records + +Each database record is comprised of two `Dbt` objects — one for the key and another for the data. + +``` c +#include +#include + +... + +float money = 122.45; +char *description = "Grocery bill."; + +Dbt key(&money, sizeof(float)); +Dbt data(description, strlen(description)+1); +``` + +Note that in the following example we do not allow DB to assign the memory for the retrieval of the money value. The reason why is that some systems may require float values to have a specific alignment, and the memory as returned by DB may not be properly aligned (the same problem may exist for structures on some systems). We tell DB to use our memory instead of its own by specifying the `DB_DBT_USERMEM` flag. Be aware that when we do this, we must also identify how much user memory is available through the use of the `ulen` field. + +``` c +#include +#include + +... + +Dbt key, data; +float money; +char *description; + +key.set_data(&money); +key.set_ulen(sizeof(float)); +key.set_flags(DB_DBT_USERMEM); + +// Database retrieval code goes here + +// Money is set into the memory that we supplied. +description = (char *)data.get_data(); +``` diff --git a/docs_src/guides/gsg/cxx/DBOpenFlags.md b/docs_src/guides/gsg/cxx/DBOpenFlags.md new file mode 100644 index 000000000..7b120f571 --- /dev/null +++ b/docs_src/guides/gsg/cxx/DBOpenFlags.md @@ -0,0 +1,32 @@ +--- +title: "Database Open Flags" +api-name: "Database Open Flags" +source: docs/gsg/CXX/DBOpenFlags.html +--- +## Database Open Flags + +The following are the flags that you may want to use at database open time. Note that this list is not exhaustive — it includes only those flags likely to be of interest for introductory, single-threaded database applications. For a complete list of the flags available to you, see the *Berkeley DB C++ API Reference Guide.* + +### Note + +To specify more than one flag on the call to `Db::open()`, you must bitwise inclusively OR them together: + +``` c +u_int32_t open_flags = DB_CREATE | DB_EXCL; +``` + +- `DB_CREATE` + + If the database does not currently exist, create it. By default, the database open fails if the database does not already exist. + +- `DB_EXCL` + + Exclusive database creation. Causes the database open to fail if the database already exists. This flag is only meaningful when used with `DB_CREATE`. + +- `DB_RDONLY` + + Open the database for read operations only. Causes any subsequent database write operations to fail. + +- `DB_TRUNCATE` + + Physically truncate (empty) the on-disk file that contains the database. Causes DB to delete all databases physically contained in that file. diff --git a/docs_src/guides/gsg/cxx/DbCXXUsage.md b/docs_src/guides/gsg/cxx/DbCXXUsage.md new file mode 100644 index 000000000..1b8e6354b --- /dev/null +++ b/docs_src/guides/gsg/cxx/DbCXXUsage.md @@ -0,0 +1,392 @@ +--- +title: "Database Usage Example" +api-name: "Database Usage Example" +source: docs/gsg/CXX/DbCXXUsage.html +--- +## Database Usage Example + +In Database Example we created a class that opens and closes a database for us. We now make use of that class to load inventory data into two databases that we will use for our inventory system. + +Again, remember that you can find the complete implementation for these functions in: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 3.1 VENDOR Structure** + +We want to store data related to an inventory system. There are two types of information that we want to manage: inventory data and related vendor contact information. To manage this information, we could have created a structure for each type of data, but to illustrate storing mixed data without a structure we refrain from creating one for the inventory data. + +We now show the definition of the VENDOR structure. Note that the VENDOR structure uses fixed-length fields. This is not necessary and in fact could represent a waste of resources if the number of vendors stored in our database scales to very large numbers. However, for simplicity we use fixed-length fields anyway, especially given that our sample data contains so few vendor records. + +``` c +// File: gettingStartedCommon.hpp +#define MAXFIELD 20 +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number + char sales_rep[MAXFIELD]; // Name of sales representative + char sales_rep_phone[MAXFIELD]; // Sales rep's phone number +} VENDOR; +``` + + +**Example 3.2 InventoryData Class** + +In order to manage our actual inventory data, we create a class that encapsulates the data that we want to store for each inventory record. Beyond simple data encapsulation, this class is also capable of marshaling the inventory data into a single contiguous buffer for the purposes of storing in that data in a DB database. + +We also provide two constructors for this class. The default constructor simply initializes all our data members for us. A second constructor is also provided that is capable of populating our data members from a `void *`. This second constructor is not really needed until the next chapter where we show how to read data from the databases, but we include it here for the purpose of completeness anyway. + +To simplify things a bit, we include the entire implementation for this class in `gettingStartedCommon.hpp` along with our `VENDOR` structure definition. + +To begin, we create the public getter and setter methods that we use with our class' private members. We also show the implementation of the method that we use to initialize all our private members. + +``` c +class InventoryData +{ +public: + inline void setPrice(double price) {price_ = price;} + inline void setQuantity(long quantity) {quantity_ = quantity;} + inline void setCategory(std::string &category) + {category_ = category;} + inline void setName(std::string &name) {name_ = name;} + inline void setVendor(std::string &vendor) {vendor_ = vendor;} + inline void setSKU(std::string &sku) {sku_ = sku;} + + inline double& getPrice() {return(price_);} + inline long& getQuantity() {return(quantity_);} + inline std::string& getCategory() {return(category_);} + inline std::string& getName() {return(name_);} + inline std::string& getVendor() {return(vendor_);} + inline std::string& getSKU() {return(sku_);} + + // Initialize our data members + void clear() + { + price_ = 0.0; + quantity_ = 0; + category_.clear(); + name_.clear(); + vendor_.clear(); + sku_.clear(); + } +``` + +Next we implement our constructors. The default constructor simply calls the `clear()`. The second constructor takes a `void *` as an argument, which it then uses to initialize the data members. Note, again, that we will not actually use this second constructor in this chapter, but we show it here just to be complete anyway. + +``` c + // Default constructor + InventoryData() { clear(); } + + // Constructor from a void * + // For use with the data returned from a bdb get + InventoryData(void *buffer) + { + char *buf = (char *)buffer; + + price_ = *((double *)buf); + bufLen_ = sizeof(double); + + quantity_ = *((long *)(buf + bufLen_)); + bufLen_ += sizeof(long); + + name_ = buf + bufLen_; + bufLen_ += name_.size() + 1; + + sku_ = buf + bufLen_; + bufLen_ += sku_.size() + 1; + + category_ = buf + bufLen_; + bufLen_ += category_.size() + 1; + + vendor_ = buf + bufLen_; + bufLen_ += vendor_.size() + 1; + } +``` + +Next we provide a couple of methods for returning the class' buffer and the size of the buffer. These are used for actually storing the class' data in a DB database. + +``` c + // Marshalls this classes data members into a single + // contiguous memory location for the purpose of storing + // the data in a database. + char * + getBuffer() + { + // Zero out the buffer + memset(databuf_, 0, 500); + // Now pack the data into a single contiguous memory location + // for storage. + bufLen_ = 0; + int dataLen = 0; + + dataLen = sizeof(double); + memcpy(databuf_, &price_, dataLen); + bufLen_ += dataLen; + + dataLen = sizeof(long); + memcpy(databuf_ + bufLen_, &quantity_, dataLen); + bufLen_ += dataLen; + + packString(databuf_, name_); + packString(databuf_, sku_); + packString(databuf_, category_); + packString(databuf_, vendor_); + + return (databuf_); + } + + // Returns the size of the buffer. Used for storing + // the buffer in a database. + inline int getBufferSize() { return (bufLen_); } +``` + +Our last public method is a utility method that we use to get the class to show itself. + +``` c + // Utility function used to show the contents of this class + void + show() { + std::cout << "\nName: " << name_ << std::endl; + std::cout << " SKU: " << sku_ << std::endl; + std::cout << " Price: " << price_ << std::endl; + std::cout << " Quantity: " << quantity_ << std::endl; + std::cout << " Category: " << category_ << std::endl; + std::cout << " Vendor: " << vendor_ << std::endl; + } +``` + +Finally, we provide a private method that is used to help us pack data into our buffer, and we declare our private data members. + +``` c +private: + + // Utility function that appends a char * to the end of + // the buffer. + void + packString(char *buffer, std::string &theString) + { + int string_size = theString.size() + 1; + memcpy(buffer+bufLen_, theString.c_str(), string_size); + bufLen_ += string_size; + } + + // Data members + std::string category_, name_, vendor_, sku_; + double price_; + long quantity_; + int bufLen_; + char databuf_[500]; +}; +``` + + +**Example 3.3 example_database_load** + +Our initial sample application loads database information from several flat files. To save space, we won't show all the details of this example program. However, as always you can find the complete implementation for this program here: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +We begin with the normal include directives and forward declarations: + +``` c +// File: example_database_load.cpp +#include +#include +#include + +#include "MyDb.hpp" +#include "gettingStartedCommon.hpp" + +// Forward declarations +void loadVendorDB(MyDb&, std::string&); +void loadInventoryDB(MyDb&, std::string&); +``` + +Next we begin our `main()` function with the variable declarations and command line parsing that is normal for most command line applications: + +``` c +// Loads the contents of vendors.txt and inventory.txt into +// Berkeley DB databases. +int +main(int argc, char *argv[]) +{ + // Initialize the path to the database files + std::string basename("./"); + std::string databaseHome("./"); + + // Database names + std::string vDbName("vendordb.db"); + std::string iDbName("inventorydb.db"); + + // Parse the command line arguments here and determine + // the location of the flat text files containing the + // inventory data here. This step is omitted for clarity. + + // Identify the full name for our input files, which should + // also include some path information. + std::string inventoryFile = basename + "inventory.txt"; + std::string vendorFile = basename + "vendors.txt"; + + try + { + // Open all databases. + MyDb inventoryDB(databaseHome, iDbName); + MyDb vendorDB(databaseHome, vDbName); + + // Load the vendor database + loadVendorDB(vendorDB, vendorFile); + + // Load the inventory database + loadInventoryDB(inventoryDB, inventoryFile); + } catch(DbException &e) { + std::cerr << "Error loading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(e.get_errno()); + } catch(std::exception &e) { + std::cerr << "Error loading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(-1); + } + + return(0); +} // End main +``` + +Note that we do not explicitly close our databases here. This is because the databases are encapsulated in `MyDb` class objects, and those objects are on the stack. When they go out of scope, their destructors will cause the database close to occur. + +Notice that there is not a lot to this function because we have pushed off all the database activity to other places. + +Next we show the implementation of `loadVendorDB()`. We load this data by scanning (line by line) the contents of the `vendors.txt` file into a VENDOR structure. Once we have a line scanned into the structure, we can store that structure into our vendors database. + +Note that we use the vendor's name as the key here. In doing so, we assume that the vendor's name is unique in our database. If it was not, we would either have to select a different key, or architect our application such that it could cope with multiple vendor records with the same name. + +``` c +// Loads the contents of the vendors.txt file into a database +void +loadVendorDB(MyDb &vendorDB, std::string &vendorFile) +{ + std::ifstream inFile(vendorFile.c_str(), std::ios::in); + if ( !inFile ) + { + std::cerr << "Could not open file '" << vendorFile + << "'. Giving up." << std::endl; + throw std::exception(); + } + + VENDOR my_vendor; + while (!inFile.eof()) + { + std::string stringBuf; + std::getline(inFile, stringBuf); + memset(&my_vendor, 0, sizeof(VENDOR)); + + // Scan the line into the structure. + // Convenient, but not particularly safe. + // In a real program, there would be a lot more + // defensive code here. + sscanf(stringBuf.c_str(), + "%20[^#]#%20[^#]#%20[^#]#%3[^#]#%6[^#]#%13[^#]#%20[^#]#%20[^\n]", + my_vendor.name, my_vendor.street, + my_vendor.city, my_vendor.state, + my_vendor.zipcode, my_vendor.phone_number, + my_vendor.sales_rep, my_vendor.sales_rep_phone); + + Dbt key(my_vendor.name, strlen(my_vendor.name) + 1); + Dbt data(&my_vendor, sizeof(VENDOR)); + + vendorDB.getDb().put(NULL, &key, &data, 0); + } + inFile.close(); +} +``` + +Finally, we need to write the `loadInventoryDB()` function. To load the inventory information, we read in each line of the inventory.txt file, obtain each field from it, then we load this data into an `InventoryData` instance. + +To help us obtain the various fields from each line of input, we also create a simple helper function that locates the position of the first a field delimiter (a pound (#) sign) from a line of input. + +Note that we could have simply decided to store our inventory data in a structure very much like the VENDOR structure that we use above. However, by storing this data in the `InventoryData` class, which identifies the size of the data that it contains, we can use the smallest amount of space possible for the data that we are storing. The result is that our cache can be smaller than it might otherwise be and our database will take less space on disk than if we used a structure with fixed-length fields. + +For a trivial dataset such as what we use for these examples, these resource savings are negligible. But if we were storing hundreds of millions of records, then the cost savings may become significant. + +``` c +// Used to locate the first pound sign (a field delimiter) +// in the input string. +int +getNextPound(std::string &theString, std::string &substring) +{ + int pos = theString.find("#"); + substring.assign(theString, 0, pos); + theString.assign(theString, pos + 1, theString.size()); + return (pos); +} + +// Loads the contents of the inventory.txt file into a database +void +loadInventoryDB(MyDb &inventoryDB, std::string &inventoryFile) +{ + InventoryData inventoryData; + std::string substring; + int nextPound; + + std::ifstream inFile(inventoryFile.c_str(), std::ios::in); + if (!inFile) + { + std::cerr << "Could not open file '" << inventoryFile + << "'. Giving up." << std::endl; + throw std::exception(); + } + + while (!inFile.eof()) + { + inventoryData.clear(); + std::string stringBuf; + std::getline(inFile, stringBuf); + + // Now parse the line + if (!stringBuf.empty()) + { + nextPound = getNextPound(stringBuf, substring); + inventoryData.setName(substring); + + nextPound = getNextPound(stringBuf, substring); + inventoryData.setSKU(substring); + + nextPound = getNextPound(stringBuf, substring); + inventoryData.setPrice(strtod(substring.c_str(), 0)); + + nextPound = getNextPound(stringBuf, substring); + inventoryData.setQuantity(strtol(substring.c_str(), 0, 10)); + + nextPound = getNextPound(stringBuf, substring); + inventoryData.setCategory(substring); + + nextPound = getNextPound(stringBuf, substring); + inventoryData.setVendor(substring); + + void *buff = (void *)inventoryData.getSKU().c_str(); + int size = inventoryData.getSKU().size()+1; + Dbt key(buff, size); + + buff = inventoryData.getBuffer(); + size = inventoryData.getBufferSize(); + Dbt data(buff, size); + + inventoryDB.getDb().put(NULL, &key, &data, 0); + } + } + inFile.close(); +} +``` + +In the next chapter we provide an example that shows how to read the inventory and vendor databases. diff --git a/docs_src/guides/gsg/cxx/DeleteEntryWCursor.md b/docs_src/guides/gsg/cxx/DeleteEntryWCursor.md new file mode 100644 index 000000000..5dcd747ee --- /dev/null +++ b/docs_src/guides/gsg/cxx/DeleteEntryWCursor.md @@ -0,0 +1,50 @@ +--- +title: "Deleting Records Using Cursors" +api-name: "Deleting Records Using Cursors" +source: docs/gsg/CXX/DeleteEntryWCursor.html +--- +## Deleting Records Using Cursors + +To delete a record using a cursor, simply position the cursor to the record that you want to delete and then call `Dbc::del()`. + +For example: + +``` c +#include +#include + +... + +char *key1str = "My first string"; +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // Database open omitted + + // Get the cursor + my_database.cursor(NULL, &cursorp, 0); + + // Set up our DBTs + Dbt key(key1str, strlen(key1str) + 1); + Dbt data; + + // Iterate over the database, deleting each record in turn. + int ret; + while ((ret = cursorp->get(&key, &data, + DB_SET)) == 0) { + cursorp->del(0); + } + +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Cursors must be closed +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` diff --git a/docs_src/guides/gsg/cxx/Positioning.md b/docs_src/guides/gsg/cxx/Positioning.md new file mode 100644 index 000000000..d2214b9f9 --- /dev/null +++ b/docs_src/guides/gsg/cxx/Positioning.md @@ -0,0 +1,280 @@ +--- +title: "Getting Records Using the Cursor" +api-name: "Getting Records Using the Cursor" +source: docs/gsg/CXX/Positioning.html +--- +## Getting Records Using the Cursor + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + +To iterate over database records, from the first record to the last, simply open the cursor and then use the `Dbc::get()` method. Note that you need to supply the `DB_NEXT` flag to this method. For example: + +``` c +#include + +... + +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // Database open omitted for clarity + + // Get a cursor + my_database.cursor(NULL, &cursorp, 0); + + Dbt key, data; + int ret; + + // Iterate over the database, retrieving each record in turn. + while ((ret = cursorp->get(&key, &data, DB_NEXT)) == 0) { + // Do interesting things with the Dbts here. + } + if (ret != DB_NOTFOUND) { + // ret should be DB_NOTFOUND upon exiting the loop. + // Dbc::get() will by default throw an exception if any + // significant errors occur, so by default this if block + // can never be reached. + } +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Cursors must be closed +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` + +To iterate over the database from the last record to the first, use `DB_PREV` instead of `DB_NEXT`: + +``` c +#include + +... + +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // Database open omitted for clarity + + // Get a cursor + my_database.cursor(NULL, &cursorp, 0); + + Dbt key, data; + int ret; + // Iterate over the database, retrieving each record in turn. + while ((ret = cursorp->get(&key, &data, DB_PREV)) == 0) { + // Do interesting things with the Dbts here. + } + if (ret != DB_NOTFOUND) { + // ret should be DB_NOTFOUND upon exiting the loop. + // Dbc::get() will by default throw an exception if any + // significant errors occur, so by default this if block + // can never be reached. + } +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Cursors must be closed +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` + +### Searching for Records + +You can use cursors to search for database records. You can search based on just a key, or you can search based on both the key and the data. You can also perform partial matches if your database supports sorted duplicate sets. In all cases, the key and data parameters of these methods are filled with the key and data values of the database record to which the cursor is positioned as a result of the search. + +Also, if the search fails, then cursor's state is left unchanged and `DB_NOTFOUND` is returned. + +To use a cursor to search for a record, use Dbt::get(). When you use this method, you can provide the following flags: + +### Note + +Notice in the following list that the cursor flags use the keyword `SET` when the cursor examines just the key portion of the records (in this case, the cursor is set to the record whose key matches the value provided to the cursor). Moreover, when the cursor uses the keyword `GET`, then the cursor is positioned to both the key *and* the data values provided to the cursor. + +Regardless of the keyword you use to get a record with a cursor, the cursor's key and data `Dbt`s are filled with the data retrieved from the record to which the cursor is positioned. + +- `DB_SET` + + Moves the cursor to the first record in the database with the specified key. + +- `DB_SET_RANGE` + + Identical to `DB_SET` unless you are using the BTree access. In this case, the cursor moves to the first record in the database whose key is greater than or equal to the specified key. This comparison is determined by the comparison function that you provide for the database. If no comparison function is provided, then the default lexicographical sorting is used. + + For example, suppose you have database records that use the following Strings as keys: + + ``` c + Alabama + Alaska + Arizona + ``` + + Then providing a search key of `Alaska` moves the cursor to the second key noted above. Providing a key of `Al` moves the cursor to the first key (`Alabama`), providing a search key of `Alas` moves the cursor to the second key (`Alaska`), and providing a key of `Ar` moves the cursor to the last key (`Arizona`). + +- `DB_GET_BOTH` + + Moves the cursor to the first record in the database that uses the specified key and data. + +- `DB_GET_BOTH_RANGE` + + Moves the cursor to the first record in the database whose key matches the specified key and whose data is greater than or equal to the specified data. If the database supports duplicate records, then on matching the key, the cursor is moved to the duplicate record with the smallest data that is greater than or equal to the specified data. + + For example, suppose your database uses BTree and it has database records that use the following key/data pairs: + + ``` c + Alabama/Athens + Alabama/Florence + Alaska/Anchorage + Alaska/Fairbanks + Arizona/Avondale + Arizona/Florence + ``` + + then providing: + + | a search key of ... | and a search data of ... | moves the cursor to ... | + |---------------------|--------------------------|-------------------------| + | Alaska | Fa | Alaska/Fairbanks | + | Arizona | Fl | Arizona/Florence | + | Alaska | An | Alaska/Anchorage | + +For example, assuming a database containing sorted duplicate records of U.S. States/U.S Cities key/data pairs (both as Strings), then the following code fragment can be used to position the cursor to any record in the database and print its key/data values: + +``` c +#include +#include + +... + +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // database open omitted for clarity + + // Get a cursor + my_database.cursor(NULL, &cursorp, 0); + + // Search criteria + char *search_key = "Alaska"; + char *search_data = "Fa"; + + // Set up our DBTs + Dbt key(search_key, strlen(search_key) + 1); + Dbt data(search_data, strlen(search_data) + 1); + + // Position the cursor to the first record in the database whose + // key matches the search key and whose data begins with the search + // data. + int ret = cursorp->get(&key, &data, DB_GET_BOTH_RANGE); + if (!ret) { + // Do something with the data + } +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Close the cursor +if (cursorp != NULL) + cursorp->close(); + +// Close the database +my_database.close(0); +``` + +### Working with Duplicate Records + +A record is a duplicate of another record if the two records share the same key. For duplicate records, only the data portion of the record is unique. + +Duplicate records are supported only for the BTree or Hash access methods. For information on configuring your database to use duplicate records, see Allowing Duplicate Records. + +If your database supports duplicate records, then it can potentially contain multiple records that share the same key. By default, normal database get operations will only return the first such record in a set of duplicate records. Typically, subsequent duplicate records are accessed using a cursor. The following `Dbc::get()` flags are interesting when working with databases that support duplicate records: + +- `DB_NEXT`, `DB_PREV` + + Shows the next/previous record in the database, regardless of whether it is a duplicate of the current record. For an example of using these methods, see Getting Records Using the Cursor. + +- `DB_GET_BOTH_RANGE` + + Useful for seeking the cursor to a specific record, regardless of whether it is a duplicate record. See Searching for Records for more information. + +- `DB_NEXT_NODUP`, `DB_PREV_NODUP` + + Gets the next/previous non-duplicate record in the database. This allows you to skip over all the duplicates in a set of duplicate records. If you call `Dbc::get()` with `DB_PREV_NODUP`, then the cursor is positioned to the last record for the previous key in the database. For example, if you have the following records in your database: + + ``` c + Alabama/Athens + Alabama/Florence + Alaska/Anchorage + Alaska/Fairbanks + Arizona/Avondale + Arizona/Florence + ``` + + and your cursor is positioned to `Alaska/Fairbanks`, and you then call `Dbc::get()` with `DB_PREV_NODUP`, then the cursor is positioned to Alabama/Florence. Similarly, if you call `Dbc::get()` with `DB_NEXT_NODUP`, then the cursor is positioned to the first record corresponding to the next key in the database. + + If there is no next/previous key in the database, then `DB_NOTFOUND` is returned, and the cursor is left unchanged. + +- `DB_NEXT_DUP` + + Gets the next record that shares the current key. If the cursor is positioned at the last record in the duplicate set and you call `Dbc::get()` with `DB_NEXT_DUP`, then `DB_NOTFOUND` is returned and the cursor is left unchanged. + +For example, the following code fragment positions a cursor to a key and displays it and all its duplicates. + +``` c +#include +#include + +... + +char *search_key = "Al"; + +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // database open omitted for clarity + + // Get a cursor + my_database.cursor(NULL, &cursorp, 0); + + // Set up our DBTs + Dbt key(search_key, strlen(search_key) + 1); + Dbt data; + + // Position the cursor to the first record in the database whose + // key and data begin with the correct strings. + int ret = cursorp->get(&key, &data, DB_SET); + while (ret != DB_NOTFOUND) { + std::cout << "key: " << (char *)key.get_data() + << "data: " << (char *)data.get_data()<< std::endl; + ret = cursorp->get(&key, &data, DB_NEXT_DUP); + } +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Close the cursor +if (cursorp != NULL) + cursorp->close(); + +// Close the database +my_database.close(0); +``` diff --git a/docs_src/guides/gsg/cxx/PutEntryWCursor.md b/docs_src/guides/gsg/cxx/PutEntryWCursor.md new file mode 100644 index 000000000..dbff067f0 --- /dev/null +++ b/docs_src/guides/gsg/cxx/PutEntryWCursor.md @@ -0,0 +1,100 @@ +--- +title: "Putting Records Using Cursors" +api-name: "Putting Records Using Cursors" +source: docs/gsg/CXX/PutEntryWCursor.html +--- +## Putting Records Using Cursors + +You can use cursors to put records into the database. DB's behavior when putting records into the database differs depending on the flags that you use when writing the record, on the access method that you are using, and on whether your database supports sorted duplicates. + +Note that when putting records to the database using a cursor, the cursor is positioned at the record you inserted. + +You use `Dbc::put()` to put (write) records to the database. You can use the following flags with this method: + +- `DB_NODUPDATA` + + If the provided key already exists in the database, then this method returns `DB_KEYEXIST`. + + If the key does not exist, then the order that the record is put into the database is determined by the insertion order in use by the database. If a comparison function has been provided to the database, the record is inserted in its sorted location. Otherwise (assuming BTree), lexicographical sorting is used, with shorter items collating before longer items. + + This flag can only be used for the BTree and Hash access methods, and only if the database has been configured to support sorted duplicate data items (`DB_DUPSORT` was specified at database creation time). + + This flag cannot be used with the Queue or Recno access methods. + + For more information on duplicate records, see Allowing Duplicate Records. + +- `DB_KEYFIRST` + + For databases that do not support duplicates, this method behaves exactly the same as if a default insertion was performed. If the database supports duplicate records, and a duplicate sort function has been specified, the inserted data item is added in its sorted location. If the key already exists in the database and no duplicate sort function has been specified, the inserted data item is added as the first of the data items for that key. + +- `DB_KEYLAST` + + Behaves exactly as if `DB_KEYFIRST` was used, except that if the key already exists in the database and no duplicate sort function has been specified, the inserted data item is added as the last of the data items for that key. + +For example: + +``` c +#include +#include + +... + +char *key1str = "My first string"; +char *data1str = "My first data"; +char *key2str = "A second string"; +char *data2str = "My second data"; +char *data3str = "My third data"; + +Db my_database(NULL, 0); +Dbc *cursorp; + +try { + // Set up our DBTs + Dbt key1(key1str, strlen(key1str) + 1); + Dbt data1(data1str, strlen(data1str) + 1); + + Dbt key2(key2str, strlen(key2str) + 1); + Dbt data2(data2str, strlen(data2str) + 1); + Dbt data3(data3str, strlen(data3str) + 1); + + // Database open omitted + + // Get the cursor + my_database.cursor(NULL, &cursorp, 0); + + // Assuming an empty database, this first put places + // "My first string"/"My first data" in the first + // position in the database + int ret = cursorp->put(&key1, &data1, DB_KEYFIRST); + + // This put places "A second string"/"My second data" in the + // the database according to its key sorts against the key + // used for the currently existing database record. Most likely + // this record would appear first in the database. + ret = cursorp->put(&key2, &data2, + DB_KEYFIRST); /* Added according to sort order */ + + // If duplicates are not allowed, the currently existing record that + // uses "key2" is overwritten with the data provided on this put. + // That is, the record "A second string"/"My second data" becomes + // "A second string"/"My third data" + // + // If duplicates are allowed, then "My third data" is placed in the + // duplicates list according to how it sorts against "My second data". + ret = cursorp->put(&key2, &data3, + DB_KEYFIRST); // If duplicates are not allowed, record + // is overwritten with new data. Otherwise, + // the record is added to the beginning of + // the duplicates list. +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Cursors must be closed +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` diff --git a/docs_src/guides/gsg/cxx/ReplacingEntryWCursor.md b/docs_src/guides/gsg/cxx/ReplacingEntryWCursor.md new file mode 100644 index 000000000..97c8583bc --- /dev/null +++ b/docs_src/guides/gsg/cxx/ReplacingEntryWCursor.md @@ -0,0 +1,57 @@ +--- +title: "Replacing Records Using Cursors" +api-name: "Replacing Records Using Cursors" +source: docs/gsg/CXX/ReplacingEntryWCursor.html +--- +## Replacing Records Using Cursors + +You replace the data for a database record by using `Dbc::put()` with the `DB_CURRENT` flag. + +``` c +#include +#include + +... + +Db my_database(NULL, 0); +Dbc *cursorp; + +int ret; +char *key1str = "My first string"; +char *replacement_data = "replace me"; + +try { + // Database open omitted + + // Get the cursor + my_database.cursor(NULL, &cursorp, 0); + + // Set up our DBTs + Dbt key(key1str, strlen(key1str) + 1); + Dbt data; + + // Position the cursor */ + ret = cursorp->get(&key, &data, DB_SET); + if (ret == 0) { + data.set_data(replacement_data); + data.set_size(strlen(replacement_data) + 1); + cursorp->put(&key, &data, DB_CURRENT); + } +} catch(DbException &e) { + my_database.err(e.get_errno(), "Error!"); +} catch(std::exception &e) { + my_database.errx("Error! %s", e.what()); +} + +// Cursors must be closed +if (cursorp != NULL) + cursorp->close(); + +my_database.close(0); +``` + +Note that you cannot change a record's key using this method; the key parameter is always ignored when you replace a record. + +When replacing the data portion of a record, if you are replacing a record that is a member of a sorted duplicates set, then the replacement will be successful only if the new record sorts identically to the old record. This means that if you are replacing a record that is a member of a sorted duplicates set, and if you are using the default lexicographic sort, then the replacement will fail due to violating the sort order. However, if you provide a custom sort routine that, for example, sorts based on just a few bytes out of the data item, then potentially you can perform a direct replacement and still not violate the restrictions described here. + +Under these circumstances, if you want to replace the data contained by a duplicate record, and you are not using a custom sort routine, then delete the record and create a new record with the desired key and data. diff --git a/docs_src/guides/gsg/cxx/_meta.toml b/docs_src/guides/gsg/cxx/_meta.toml new file mode 100644 index 000000000..03fe52cd0 --- /dev/null +++ b/docs_src/guides/gsg/cxx/_meta.toml @@ -0,0 +1,43 @@ +# Nav/index metadata for the gsg C++ variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Getting Started with Berkeley DB (C++)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "concepts", + "accessmethods", + "databaseLimits", + "environments", + "coreExceptions", + "returns", + "gettingit", + "databases", + "coredbclose", + "DBOpenFlags", + "CoreDBAdmin", + "dbErrorReporting", + "CoreEnvUsage", + "CoreDbCXXUsage", + "DBEntry", + "usingDbt", + "DbCXXUsage", + "Cursors", + "Positioning", + "PutEntryWCursor", + "DeleteEntryWCursor", + "ReplacingEntryWCursor", + "CoreCursorUsage", + "indexes", + "keyCreator", + "readSecondary", + "secondaryDelete", + "secondaryCursor", + "joins", + "coreindexusage", + "dbconfig", + "cachesize", + "btree", +] diff --git a/docs_src/guides/gsg/cxx/accessmethods.md b/docs_src/guides/gsg/cxx/accessmethods.md new file mode 100644 index 000000000..791884793 --- /dev/null +++ b/docs_src/guides/gsg/cxx/accessmethods.md @@ -0,0 +1,80 @@ +--- +title: "Access Methods" +api-name: "Access Methods" +source: docs/gsg/CXX/accessmethods.html +--- +## Access Methods + + [Selecting Access Methods](accessmethods.md#selectAM) + + [Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + + [Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + +While this manual will focus primarily on the BTree access method, it is still useful to briefly describe all of the access methods that DB makes available. + +Note that an access method can be selected only when the database is created. Once selected, actual API usage is generally identical across all access methods. That is, while some exceptions exist, mechanically you interact with the library in the same way regardless of which access method you have selected. + +The access method that you should choose is gated first by what you want to use as a key, and then secondly by the performance that you see for a given access method. + +The following are the available access methods: + + + + + + + + + + + + + + + + + + + + + + + + + + +
Access MethodDescription
BTree

Data is stored in a sorted, balanced tree structure. Both the key and the data for BTree records can be arbitrarily complex. That is, they can contain single values such as an integer or a string, or complex types such as a structure. Also, although not the default behavior, it is possible for two records to use keys that compare as equals. When this occurs, the records are considered to be duplicates of one another.

Hash

Data is stored in an extended linear hash table. Like BTree, the key and the data used for Hash records can be of arbitrarily complex data. Also, like BTree, duplicate records are optionally supported.

Queue

Data is stored in a queue as fixed-length records. Each record uses a logical record number as its key. This access method is designed for fast inserts at the tail of the queue, and it has a special operation that deletes and returns a record from the head of the queue.

+

This access method is unusual in that it provides record level locking. This can provide beneficial performance improvements in applications requiring concurrent access to the queue.

Recno

Data is stored in either fixed or variable-length records. Like Queue, Recno records use logical record numbers as keys.

+ +### Selecting Access Methods + +To select an access method, you should first consider what you want to use as a key for you database records. If you want to use arbitrary data (even strings), then you should use either BTree or Hash. If you want to use logical record numbers (essentially integers) then you should use Queue or Recno. + +Once you have made this decision, you must choose between either BTree or Hash, or Queue or Recno. This decision is described next. + +### Choosing between BTree and Hash + +For small working datasets that fit entirely in memory, there is no difference between BTree and Hash. Both will perform just as well as the other. In this situation, you might just as well use BTree, if for no other reason than the majority of DB applications use BTree. + +Note that the main concern here is your working dataset, not your entire dataset. Many applications maintain large amounts of information but only need to access some small portion of that data with any frequency. So what you want to consider is the data that you will routinely use, not the sum total of all the data managed by your application. + +However, as your working dataset grows to the point where you cannot fit it all into memory, then you need to take more care when choosing your access method. Specifically, choose: + +- BTree if your keys have some locality of reference. That is, if they sort well and you can expect that a query for a given key will likely be followed by a query for one of its neighbors. + +- Hash if your dataset is extremely large. For any given access method, DB must maintain a certain amount of internal information. However, the amount of information that DB must maintain for BTree is much greater than for Hash. The result is that as your dataset grows, this internal information can dominate the cache to the point where there is relatively little space left for application data. As a result, BTree can be forced to perform disk I/O much more frequently than would Hash given the same amount of data. + + Moreover, if your dataset becomes so large that DB will almost certainly have to perform disk I/O to satisfy a random request, then Hash will definitely out perform BTree because it has fewer internal records to search through than does BTree. + +### Choosing between Queue and Recno + +Queue or Recno are used when the application wants to use logical record numbers for the primary database key. Logical record numbers are essentially integers that uniquely identify the database record. They can be either mutable or fixed, where a mutable record number is one that might change as database records are stored or deleted. Fixed logical record numbers never change regardless of what database operations are performed. + +When deciding between Queue and Recno, choose: + +- Queue if your application requires high degrees of concurrency. Queue provides record-level locking (as opposed to the page-level locking that the other access methods use), and this can result in significantly faster throughput for highly concurrent applications. + + Note, however, that Queue provides support only for fixed length records. So if the size of the data that you want to store varies widely from record to record, you should probably choose an access method other than Queue. + +- Recno if you want mutable record numbers. Queue is only capable of providing fixed record numbers. Also, Recno provides support for databases whose permanent storage is a flat text file. This is useful for applications looking for fast, temporary storage while the data is being read or modified. diff --git a/docs_src/guides/gsg/cxx/btree.md b/docs_src/guides/gsg/cxx/btree.md new file mode 100644 index 000000000..f408cb20d --- /dev/null +++ b/docs_src/guides/gsg/cxx/btree.md @@ -0,0 +1,185 @@ +--- +title: "BTree Configuration" +api-name: "BTree Configuration" +source: docs/gsg/CXX/btree.html +--- +## BTree Configuration + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) + +In going through the previous chapters in this book, you may notice that we touch on some topics that are specific to BTree, but we do not cover those topics in any real detail. In this section, we will discuss configuration issues that are unique to BTree. + +Specifically, in this section we describe: + +- Allowing duplicate records. + +- Setting comparator callbacks. + +### Allowing Duplicate Records + +BTree databases can contain duplicate records. One record is considered to be a duplicate of another when both records use keys that compare as equal to one another. + +By default, keys are compared using a lexicographical comparison, with shorter keys collating higher than longer keys. You can override this default using the `Db::set_bt_compare()` method. See the next section for details. + +By default, DB databases do not allow duplicate records. As a result, any attempt to write a record that uses a key equal to a previously existing record results in the previously existing record being overwritten by the new record. + +Allowing duplicate records is useful if you have a database that contains records keyed by a commonly occurring piece of information. It is frequently necessary to allow duplicate records for secondary databases. + +For example, suppose your primary database contained records related to automobiles. You might in this case want to be able to find all the automobiles in the database that are of a particular color, so you would index on the color of the automobile. However, for any given color there will probably be multiple automobiles. Since the index is the secondary key, this means that multiple secondary database records will share the same key, and so the secondary database must support duplicate records. + +#### Sorted Duplicates + +Duplicate records can be stored in sorted or unsorted order. You can cause DB to automatically sort your duplicate records by specifying the `DB_DUPSORT` flag at database creation time. + +If sorted duplicates are supported, then the sorting function specified on `Db::set_dup_compare()` is used to determine the location of the duplicate record in its duplicate set. If no such function is provided, then the default lexicographical comparison is used. + +#### Unsorted Duplicates + +For performance reasons, BTrees should always contain sorted records. (BTrees containing unsorted entries must potentially spend a great deal more time locating an entry than does a BTree that contains sorted entries). That said, DB provides support for suppressing automatic sorting of duplicate records because it may be that your application is inserting records that are already in a sorted order. + +That is, if the database is configured to support unsorted duplicates, then the assumption is that your application will manually perform the sorting. In this event, expect to pay a significant performance penalty. Any time you place records into the database in a sort order not know to DB, you will pay a performance penalty + +That said, this is how DB behaves when inserting records into a database that supports non-sorted duplicates: + +- If your application simply adds a duplicate record using `Db::put()`, then the record is inserted at the end of its sorted duplicate set. + +- If a cursor is used to put the duplicate record to the database, then the new record is placed in the duplicate set according to the flags that are provided on the `Dbc::put()` method. The relevant flags are: + + - `DB_AFTER` + + The data provided on the call to `Dbc::put()` is placed into the database as a duplicate record. The key used for this operation is the key used for the record to which the cursor currently refers. Any key provided on the call to `Dbc::put()` is therefore ignored. + + The duplicate record is inserted into the database immediately after the cursor's current position in the database. + + This flag is ignored if sorted duplicates are supported for the database. + + - `DB_BEFORE` + + Behaves the same as `DB_AFTER` except that the new record is inserted immediately before the cursor's current location in the database. + + - `DB_KEYFIRST` + + If the key provided on the call to `Dbc::put()` already exists in the database, and the database is configured to use duplicates without sorting, then the new record is inserted as the first entry in the appropriate duplicates list. + + - `DB_KEYLAST` + + Behaves identically to `DB_KEYFIRST` except that the new duplicate record is inserted as the last record in the duplicates list. + +#### Configuring a Database to Support Duplicates + +Duplicates support can only be configured at database creation time. You do this by specifying the appropriate flags to `Db::set_flags()` before the database is opened for the first time. + +The flags that you can use are: + +- `DB_DUP` + + The database supports non-sorted duplicate records. + +- `DB_DUPSORT` + + The database supports sorted duplicate records. Note that this flag also sets the `DB_DUP` flag for you. + +The following code fragment illustrates how to configure a database to support sorted duplicate records: + +``` c +#include +... + +Db db(NULL, 0); +const char *file_name = "myd.db"; + +try { + // Configure the database for sorted duplicates + db.set_flags(DB_DUPSORT); + + // Now open the database + db.open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name (unneeded) + DB_BTREE, // Database type (using btree) + DB_CREATE, // Open flags + 0); // File mode. Using defaults +} catch(DbException &e) { + db.err(e.get_errno(), "Database '%s' open failed.", file_name); +} catch(std::exception &e) { + db.errx("Error opening database: %s : %s\n", file_name, e.what()); +} + +... + +try { + db.close(0); +} catch(DbException &e) { + db.err(e.get_errno(), "Database '%s' close failed.", file_name); +} catch(std::exception &e) { + db.errx("Error closing database: %s : %s\n", file_name, e.what()); +} +``` + +### Setting Comparison Functions + +By default, DB uses a lexicographical comparison function where shorter records collate before longer records. For the majority of cases, this comparison works well and you do not need to manage it in any way. + +However, in some situations your application's performance can benefit from setting a custom comparison routine. You can do this either for database keys, or for the data if your database supports sorted duplicate records. + +Some of the reasons why you may want to provide a custom sorting function are: + +- Your database is keyed using strings and you want to provide some sort of language-sensitive ordering to that data. Doing so can help increase the locality of reference that allows your database to perform at its best. + +- You are using a little-endian system (such as x86) and you are using integers as your database's keys. Berkeley DB stores keys as byte strings and little-endian integers do not sort well when viewed as byte strings. There are several solutions to this problem, one being to provide a custom comparison function. See http://download.oracle.com/docs/cd/E17076_02/html/programmer_reference/am_misc_faq.html for more information. + +- You you do not want the entire key to participate in the comparison, for whatever reason. In this case, you may want to provide a custom comparison function so that only the relevant bytes are examined. + +#### Creating Comparison Functions + +You set a BTree's key comparison function using `Db::set_bt_compare()`. You can also set a BTree's duplicate data comparison function using `Db::set_dup_compare()`. + +You cannot use these methods after the database has been opened. Also, if the database already exists when it is opened, the function provided to these methods must be the same as that historically used to create the database or corruption can occur. + +The value that you provide to the `set_bt_compare()` method is a pointer to a function that has the following signature: + +``` c +int (*function)(Db *db, const Dbt *key1, const Dbt *key2) +``` + +This function must return an integer value less than, equal to, or greater than 0. If key1 is considered to be greater than key2, then the function must return a value that is greater than 0. If the two are equal, then the function must return 0, and if the first key is less than the second then the function must return a negative value. + +The function that you provide to `set_dup_compare()` works in exactly the same way, except that the `Dbt` parameters hold record data items instead of keys. + +For example, an example routine that is used to sort integer keys in the database is: + +``` c +int +compare_int(Db *dbp, const Dbt *a, const Dbt *b) +{ + int ai, bi; + + // Returns: + // < 0 if a < b + // = 0 if a = b + // > 0 if a > b + memcpy(&ai, a->get_data(), sizeof(int)); + memcpy(&bi, b->get_data(), sizeof(int)); + return (ai - bi); +} +``` + +Note that the data must first be copied into memory that is appropriately aligned, as Berkeley DB does not guarantee any kind of alignment of the underlying data, including for comparison routines. When writing comparison routines, remember that databases created on machines of different architectures may have different integer byte orders, for which your code may need to compensate. + +To cause DB to use this comparison function: + +``` c +#include +#include + +... + +Db db(NULL, 0); + +// Set up the btree comparison function for this database +db.set_bt_compare(compare_int); + +// Database open call follows sometime after this. +``` diff --git a/docs_src/guides/gsg/cxx/cachesize.md b/docs_src/guides/gsg/cxx/cachesize.md new file mode 100644 index 000000000..96358c046 --- /dev/null +++ b/docs_src/guides/gsg/cxx/cachesize.md @@ -0,0 +1,14 @@ +--- +title: "Selecting the Cache Size" +api-name: "Selecting the Cache Size" +source: docs/gsg/CXX/cachesize.html +--- +## Selecting the Cache Size + +Cache size is important to your application because if it is set to too small of a value, your application's performance will suffer from too much disk I/O. On the other hand, if your cache is too large, then your application will use more memory than it actually needs. Moreover, if your application uses too much memory, then on most operating systems this can result in your application being swapped out of memory, resulting in extremely poor performance. + +You select your cache size using either `Db::set_cachesize()`, or `DbEnv::set_cachesize()`, depending on whether you are using a database environment or not. You cache size must be a power of 2, but it is otherwise limited only by available memory and performance considerations. + +Selecting a cache size is something of an art, but fortunately you can change it any time, so it can be easily tuned to your application's changing data requirements. The best way to determine how large your cache needs to be is to put your application into a production environment and watch to see how much disk I/O is occurring. If your application is going to disk quite a lot to retrieve database records, then you should increase the size of your cache (provided that you have enough memory to do so). + +You can use the `db_stat` command line utility with the `-m` option to gauge the effectiveness of your cache. In particular, the number of pages found in the cache is shown, along with a percentage value. The closer to 100% that you can get, the better. If this value drops too low, and you are experiencing performance problems, then you should consider increasing the size of your cache, assuming you have memory to support it. diff --git a/docs_src/guides/gsg/cxx/concepts.md b/docs_src/guides/gsg/cxx/concepts.md new file mode 100644 index 000000000..0360472ef --- /dev/null +++ b/docs_src/guides/gsg/cxx/concepts.md @@ -0,0 +1,34 @@ +--- +title: "Berkeley DB Concepts" +api-name: "Berkeley DB Concepts" +source: docs/gsg/CXX/concepts.html +--- +## Berkeley DB Concepts + +Before continuing, it is useful to describe some of the larger concepts that you will encounter when building a DB application. + +Conceptually, DB databases contain *records*. Logically each record represents a single entry in the database. Each such record contains two pieces of information: a key and a data. This manual will on occasion describe a *a record's key* or a *record's data* when it is necessary to speak to one or the other portion of a database record. + +Because of the key/data pairing used for DB databases, they are sometimes thought of as a two-column table. However, data (and sometimes keys, depending on the access method) can hold arbitrarily complex data. Frequently, C structures and other such mechanisms are stored in the record. This effectively turns a 2-column table into a table with *n* columns, where *n-1* of those columns are provided by the structure's fields. + +Note that a DB database is very much like a table in a relational database system in that most DB applications use more than one database (just as most relational databases use more than one table). + +Unlike relational systems, however, a DB database contains a single collection of records organized according to a given access method (BTree, Queue, Hash, and so forth). In a relational database system, the underlying access method is generally hidden from you. + +In any case, frequently DB applications are designed so that a single database stores a specific type of data (just as in a relational database system, a single table holds entries containing a specific set of fields). Because most applications are required to manage multiple kinds of data, a DB application will often use multiple databases. + +For example, consider an accounting application. This kind of an application may manage data based on bank accounts, checking accounts, stocks, bonds, loans, and so forth. An accounting application will also have to manage information about people, banking institutions, customer accounts, and so on. In a traditional relational database, all of these different kinds of information would be stored and managed using a (probably very) complex series of tables. In a DB application, all of this information would instead be divided out and managed using multiple databases. + +DB applications can efficiently use multiple databases using an optional mechanism called an *environment*. For more information, see Environments. + +You interact with most DB APIs using special structures that contain pointers to functions. These callbacks are called *methods* because they look so much like a method on a C++ class. The variable that you use to access these methods is often referred to as a *handle*. For example, to use a database you will obtain a handle to that database. + +Retrieving a record from a database is sometimes called *getting the record* because the method that you use to retrieve the records is called `get()`. Similarly, storing database records is sometimes called *putting the record* because you use the `put()` method to do this. + +When you store, or put, a record to a database using its handle, the record is stored according to whatever sort order is in use by the database. Sorting is mostly performed based on the key, but sometimes the data is considered too. If you put a record using a key that already exists in the database, then the existing record is replaced with the new data. However, if the database supports duplicate records (that is, records with identical keys but different data), then that new record is stored as a duplicate record and any existing records are not overwritten. + +If a database supports duplicate records, then you can use a database handle to retrieve only the first record in a set of duplicate records. + +In addition to using a database handle, you can also read and write data using a special mechanism called a *cursor*. Cursors are essentially iterators that you can use to walk over the records in a database. You can use cursors to iterate over a database from the first record to the last, and from the last to the first. You can also use cursors to seek to a record. In the event that a database supports duplicate records, cursors are the only way you can access all the records in a set of duplicates. + +Finally, DB provides a special kind of a database called a *secondary database*. Secondary databases serve as an index into normal databases (called primary database to distinguish them from secondaries). Secondary databases are interesting because DB records can hold complex data types, but seeking to a given record is performed only based on that record's key. If you wanted to be able to seek to a record based on some piece of information that is not the key, then you enable this through the use of secondary databases. diff --git a/docs_src/guides/gsg/cxx/coreExceptions.md b/docs_src/guides/gsg/cxx/coreExceptions.md new file mode 100644 index 000000000..8be014b2c --- /dev/null +++ b/docs_src/guides/gsg/cxx/coreExceptions.md @@ -0,0 +1,14 @@ +--- +title: "Exception Handling" +api-name: "Exception Handling" +source: docs/gsg/CXX/coreExceptions.html +--- +## Exception Handling + +Before continuing, it is useful to spend a few moments on exception handling in DB with the C++ API. + +By default, most DB methods throw `DbException` in the event of a serious error. + +You can obtain the DB error number for a `DbException` by using `DbException::get_errno()`. You can also obtain the informational message associated with that error number using `DbException::what()`. + +If for some reason you do not want to manage `DbException` objects in your `try` blocks, you can configure DB to suppress them by setting `DB_CXX_NO_EXCEPTIONS` for your database and environment handles. In this event, you must manage your DB error conditions using the integer value returned by all DB methods. Be aware that this manual assumes that you want to manage your error conditions using `DbException` objects. diff --git a/docs_src/guides/gsg/cxx/coredbclose.md b/docs_src/guides/gsg/cxx/coredbclose.md new file mode 100644 index 000000000..6c3c0de07 --- /dev/null +++ b/docs_src/guides/gsg/cxx/coredbclose.md @@ -0,0 +1,37 @@ +--- +title: "Closing Databases" +api-name: "Closing Databases" +source: docs/gsg/CXX/coredbclose.html +--- +## Closing Databases + +Once you are done using the database, you must close it. You use the `Db::close()` method to do this. + +Closing a database causes it to become unusable until it is opened again. It is recommended that you close any open cursors before closing your database. Active cursors during a database close can cause unexpected results, especially if any of those cursors are writing to the database. You should always make sure that all your database accesses have completed before closing your database. + +Cursors are described in Using Cursors later in this manual. + +Be aware that when you close the last open handle for a database, then by default its cache is flushed to disk. This means that any information that has been modified in the cache is guaranteed to be written to disk when the last handle is closed. You can manually perform this operation using the `Db::sync()` method, but for normal shutdown operations it is not necessary. For more information about syncing your cache, see Data Persistence. + +The following code fragment illustrates a database close: + +``` c +#include + +... + +Db db(NULL, 0); + + // Database open and access operations happen here. + +try { + // Close the database + db.close(0); +// DbException is not subclassed from std::exception, so +// need to catch both of these. +} catch(DbException &e) { + // Error handling code goes here +} catch(std::exception &e) { + // Error handling code goes here +} +``` diff --git a/docs_src/guides/gsg/cxx/coreindexusage.md b/docs_src/guides/gsg/cxx/coreindexusage.md new file mode 100644 index 000000000..06502b699 --- /dev/null +++ b/docs_src/guides/gsg/cxx/coreindexusage.md @@ -0,0 +1,369 @@ +--- +title: "Secondary Database Example" +api-name: "Secondary Database Example" +source: docs/gsg/CXX/coreindexusage.html +--- +## Secondary Database Example + + [Secondary Databases with example_database_load](coreindexusage.md#edlWIndexes) + + [Secondary Databases with example_database_read](coreindexusage.md#edrWIndexes) + +In previous chapters in this book, we built applications that load and display several DB databases. In this example, we will extend those examples to use secondary databases. Specifically: + +- In Database Usage Example we built an application that can open and load data into several databases. In Secondary Databases with example_database_load we will extend that application to also open a secondary database for the purpose of indexing inventory item names. + +- In Cursor Example we built an application to display our inventory database (and related vendor information). In Secondary Databases with example_database_read we will extend that application to show inventory records based on the index we cause to be loaded using `example_database_load`. + +### Secondary Databases with example_database_load + +In order to update `example_database_load` to maintain an index of inventory item names, all we really need to do is: + +1. Create a new database to be used as a secondary database. + +2. Associate our new database to the inventory primary database. + +We also need a function that can create our secondary keys for us. + +Because DB maintains secondary databases for us; once this work is done we need not make any other changes to `example_database_load`. + +Remember that you can find the complete implementation of these functions in: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +To begin, we go to `gettingStartedCommon.hpp` and we write our secondary key extractor function. This is a fairly trivial function to write because we have already done most of the work when we wrote the `InventoryData` class. Recall that when we wrote that class, we provided a constructor that accepts a pointer to a buffer and unpacks the contents of the buffer for us (see InventoryData Class for the implementation). We now make use of that constructor. + +``` c +// File: gettingStartedCommon.hpp +// Forward declarations +class Db; +class Dbt; + +// Used to extract an inventory item's name from an +// inventory database record. This function is used to create +// keys for secondary database records. +int +get_item_name(Db *dbp, const Dbt *pkey, const Dbt *pdata, Dbt *skey) +{ + // Obtain the buffer location where the we placed the item's name. In + // this example, the item's name is located in the primary data. It is + // the first string in the buffer after the price (a double) and + // the quantity (a long). + size_t offset = sizeof(double) + sizeof(long); + char * itemname = (char *)pdata->get_data() + offset; + + // unused + (void)pkey; + + // If the offset is beyond the end of the data, then there is a + // problem with the buffer contained in pdata, or there's a + // programming error in how the buffer is marshalled/unmarshalled. + // This should never happen! + if (offset > pdata->get_size()) { + dbp->errx("get_item_name: buffer sizes do not match!"); + // When we return non-zero, the index record is not + // added/updated. + return (-1); + } + // Now set the secondary key's data to be the item name + + skey->set_data(itemname); + skey->set_size(strlen(itemname) + 1); + + return (0); +}; +``` + +Having written our key extractor callback, we now need to make a trivial update to our `MyDb` implementation. Because an item name is used by multiple inventory records, we need our secondary database to support sorted duplicates. We therefore must update `MyDb` to handle this detail. + +The `MyDb` class definition changes to add a boolean to the constructor (remember that new code is in **`bold`**): + +``` c +// File: MyDb.hpp +#include + +class MyDb +{ +public: + // Constructor requires a path to the database, + // and a database name. + MyDb(std::string &path, std::string &dbName, + bool isSecondary = false); + + // Our destructor just calls our private close method. + ~MyDb() { close(); } + + inline Db &getDb() {return db_;} + +private: + Db db_; + std::string dbFileName_; + u_int32_t cFlags_; + + // Make sure the default constructor is private + // We don't want it used. + MyDb() : db_(0, 0) {} + + // We put our database close activity here. + // This is called from our destructor. In + // a more complicated example, we might want + // to make this method public, but a private + // method is more appropriate for this example. + void close(); +}; +``` + +And the implementation changes slightly to take advantage of the new boolean. Note that to save space, we just show the constructor where the code actually changes: + +``` c +// File: MyDb.cpp +#include "MyDb.hpp" + +// Class constructor. Requires a path to the location +// where the database is located, and a database name +MyDb::MyDb(std::string &path, std::string &dbName, + bool isSecondary) + : db_(NULL, 0), // Instantiate Db object + dbFileName_(path + dbName), // Database file name + cFlags_(DB_CREATE) // If the database doesn't yet exist, + // allow it to be created. +{ + try + { + // Redirect debugging information to std::cerr + db_.set_error_stream(&std::cerr); + + // If this is a secondary database, support + // sorted duplicates + if (isSecondary) + db_.set_flags(DB_DUPSORT); + + // Open the database + db_.open(NULL, dbFileName_.c_str(), NULL, DB_BTREE, cFlags_, 0); + } + // DbException is not a subclass of std::exception, so we + // need to catch them both. + catch(DbException &e) + { + std::cerr << "Error opening database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } + catch(std::exception &e) + { + std::cerr << "Error opening database: " << dbFileName_ << "\n"; + std::cerr << e.what() << std::endl; + } +} +``` + +That done, we can now update `example_database_load` to open our new secondary database and associate it to the inventory database. + +To save space, we do not show the entire implementation for this program here. Instead, we show just the `main()` function, which is where all our modifications occur. To see the rest of the implementation for this command, see example_database_load. + +``` c +// Loads the contents of vendors.txt and inventory.txt into +// Berkeley DB databases. +int +main(int argc, char *argv[]) +{ + // Initialize the path to the database files + std::string basename("./"); + std::string databaseHome("./"); + + // Database names + std::string vDbName("vendordb.db"); + std::string iDbName("inventorydb.db"); + std::string itemSDbName("itemname.sdb"); + + // Parse the command line arguments here and determine + // the location of the flat text files containing the + // inventory data here. This step is omitted for clarity. + + // Identify the full name for our input files, which should + // also include some path information. + std::string inventoryFile = basename + "inventory.txt"; + std::string vendorFile = basename + "vendors.txt"; + + try + { + // Open all databases. + MyDb inventoryDB(databaseHome, iDbName); + MyDb vendorDB(databaseHome, vDbName); + MyDb itemnameSDB(databaseHome, itemSDbName, true); + + // Associate the primary and the secondary + inventoryDB.getDb().associate(NULL, + &(itemnameSDB.getDb()), + get_item_name, + 0); + + // Load the vendor database + loadVendorDB(vendorDB, vendorFile); + + // Load the inventory database + loadInventoryDB(inventoryDB, inventoryFile); + } catch(DbException &e) { + std::cerr << "Error loading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(e.get_errno()); + } catch(std::exception &e) { + std::cerr << "Error loading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(-1); + } + + return(0); +} // End main +``` + +Note that the order in which we instantiate our `MyDb` class instances is important. In general you want to close a secondary database before closing the primary with which it is associated. This is particularly true for multi-threaded or multi-processed applications where the database closes are not single threaded. Even so, it is a good habit to adopt, even for simple applications such as this one. Here, we ensure that the databases are closed in the desired order by opening the secondary database last. This works because our `MyDb` objects are on the stack, and therefore the last one opened is the first one closed. + +That completes our update to `example_database_load`. Now when this program is called, it will automatically index inventory items based on their names. We can then query for those items using the new index. We show how to do that in the next section. + +### Secondary Databases with example_database_read + +In Cursor Example we wrote an application that displays every inventory item in the Inventory database. In this section, we will update that example to allow us to search for and display an inventory item given a specific name. To do this, we will make use of the secondary database that `example_database_load` now creates. + +The update to `example_database_read` is relatively modest. We need to open the new secondary database in exactly the same way was we do for `example_database_load`. We also need to add a command line parameter on which we can specify the item name, and we will need a new function in which we will perform the query and display the results. + +To begin, we add a single forward declaration to the application, and update our usage function slightly: + +``` c +// File: example_database_read.cpp +#include +#include +#include + +#include "MyDb.hpp" +#include "gettingStartedCommon.hpp" + +// Forward declarations +int show_all_records(MyDb &inventoryDB, MyDb &vendorDB); +int show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName); +int show_vendor(MyDb &vendorDB, const char *vendor); +``` + +Next, we update `main()` to open the new secondary database and accept the new command line switch. We also need a new variable to contain the item's name. + +The final update to the `main()` entails a little bit of logic to determine whether we want to display all available inventory items, or just the ones that match a name provided on the `-i` command line parameter. + +``` c +// Displays all inventory items and the associated vendor record. +int +main (int argc, char *argv[]) +{ + // Initialize the path to the database files + std::string databaseHome("./"); + std::string itemName; + + // Database names + std::string vDbName("vendordb.db"); + std::string iDbName("inventorydb.db"); + std::string itemSDbName("itemname.sdb"); + + // Parse the command line arguments + // Omitted for brevity + + try + { + // Open all databases. + MyDb inventoryDB(databaseHome, iDbName); + MyDb vendorDB(databaseHome, vDbName); + MyDb itemnameSDB(databaseHome, itemSDbName, true); + + // Associate the secondary to the primary + inventoryDB.getDb().associate(NULL, + &(itemnameSDB.getDb()), + get_item_name, + 0); + + if (itemName.empty()) + { + show_all_records(inventoryDB, vendorDB); + } else { + show_item(itemnameSDB, vendorDB, itemName); + } + } catch(DbException &e) { + std::cerr << "Error reading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(e.get_errno()); + } catch(std::exception &e) { + std::cerr << "Error reading databases. " << std::endl; + std::cerr << e.what() << std::endl; + return(-1); + } + + return(0); +} // End main +``` + +The only other thing that we need to add to the application is the implementation of the `show_item()` function. + +### Note + +In the interest of space, we refrain from showing the other functions used by this application. For their implementation, please see Cursor Example. Alternatively, you can see the entire implementation of this application in: + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +``` c +// Shows the records in the inventory database that +// have a specific item name. For each inventory record +// shown, the appropriate vendor record is also displayed. +int +show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName) +{ + // Get a cursor to the itemname secondary db + Dbc *cursorp; + + try { + itemnameSDB.getDb().cursor(NULL, &cursorp, 0); + + // Get the search key. This is the name on the inventory + // record that we want to examine. + std::cout << "Looking for " << itemName << std::endl; + Dbt key((void *)itemName.c_str(), itemName.length() + 1); + Dbt data; + + // Position the cursor to the first record in the secondary + // database that has the appropriate key. + int ret = cursorp->get(&key, &data, DB_SET); + if (!ret) { + do { + InventoryData inventoryItem(data.get_data()); + inventoryItem.show(); + + show_vendor(vendorDB, inventoryItem.getVendor().c_str()); + + } while(cursorp->get(&key, &data, DB_NEXT_DUP) == 0); + } else { + std::cerr << "No records found for '" << itemName + << "'" << std::endl; + } + } catch(DbException &e) { + itemnameSDB.getDb().err(e.get_errno(), "Error in show_item"); + cursorp->close(); + throw e; + } catch(std::exception &e) { + itemnameSDB.getDb().errx("Error in show_item: %s", e.what()); + cursorp->close(); + throw e; + } + + cursorp->close(); + return (0); +} +``` + +This completes our update to `example_inventory_read`. Using this update, you can now search for and show all inventory items that match a particular name. For example: + +``` c + example_inventory_read -i "Zulu Nut" +``` diff --git a/docs_src/guides/gsg/cxx/databaseLimits.md b/docs_src/guides/gsg/cxx/databaseLimits.md new file mode 100644 index 000000000..d22954a96 --- /dev/null +++ b/docs_src/guides/gsg/cxx/databaseLimits.md @@ -0,0 +1,12 @@ +--- +title: "Database Limits and Portability" +api-name: "Database Limits and Portability" +source: docs/gsg/CXX/databaseLimits.html +--- +## Database Limits and Portability + +Berkeley DB provides support for managing everything from very small databases that fit entirely in memory, to extremely large databases holding millions of records and terabytes of data. DB databases can store up to 256 terabytes of data. Individual record keys or record data can store up to 4 gigabytes of data. + +DB's databases store data in a binary format that is portable across platforms, even of differing endian-ness. Be aware, however, that portability aside, some performance issues can crop up in the event that you are using little endian architecture. See Setting Comparison Functions for more information. + +Also, DB's databases and data structures are designed for concurrent access — they are thread-safe, and they share well across multiple processes. That said, in order to allow multiple processes to share databases and the cache, DB makes use of mechanisms that do not work well on network-shared drives (NFS or Windows networks shares, for example). For this reason, you cannot place your DB databases and environments on network-mounted drives. diff --git a/docs_src/guides/gsg/cxx/databases.md b/docs_src/guides/gsg/cxx/databases.md new file mode 100644 index 000000000..5fa67c38f --- /dev/null +++ b/docs_src/guides/gsg/cxx/databases.md @@ -0,0 +1,60 @@ +--- +title: "Chapter 2. Databases" +api-name: "Chapter 2. Databases" +source: docs/gsg/CXX/databases.html +--- +## Chapter 2. Databases + +**Table of Contents** + + [Opening Databases](databases.md#DBOpen) + + [Closing Databases](coredbclose.md) + + [Database Open Flags](DBOpenFlags.md) + + [Administrative Methods](CoreDBAdmin.md) + + [Error Reporting Functions](dbErrorReporting.md) + + [Managing Databases in Environments](CoreEnvUsage.md) + + [Database Example](CoreDbCXXUsage.md) + +In Berkeley DB, a database is a collection of *records*. Records, in turn, consist of key/data pairings. + +Conceptually, you can think of a database as containing a two-column table where column 1 contains a key and column 2 contains data. Both the key and the data are managed using `Dbt` class instances (see Database Records for details on this class ). So, fundamentally, using a DB database involves putting, getting, and deleting database records, which in turns involves efficiently managing information encapsulated by `Dbt` objects. The next several chapters of this book are dedicated to those activities. + +## Opening Databases + +You open a database by instantiating a `Db` object and then calling its `open()` method. + +Note that by default, DB does not create databases if they do not already exist. To override this behavior, specify the DB_CREATE flag on the `open()` method. + +The following code fragment illustrates a database open: + +``` c +#include + +... + +Db db(NULL, 0); // Instantiate the Db object + +u_int32_t oFlags = DB_CREATE; // Open flags; + +try { + // Open the database + db.open(NULL, // Transaction pointer + "my_db.db", // Database file name + NULL, // Optional logical database name + DB_BTREE, // Database access method + oFlags, // Open flags + 0); // File mode (using defaults) +// DbException is not subclassed from std::exception, so +// need to catch both of these. +} catch(DbException &e) { + // Error handling code goes here +} catch(std::exception &e) { + // Error handling code goes here +} +``` diff --git a/docs_src/guides/gsg/cxx/dbErrorReporting.md b/docs_src/guides/gsg/cxx/dbErrorReporting.md new file mode 100644 index 000000000..500a3cc23 --- /dev/null +++ b/docs_src/guides/gsg/cxx/dbErrorReporting.md @@ -0,0 +1,93 @@ +--- +title: "Error Reporting Functions" +api-name: "Error Reporting Functions" +source: docs/gsg/CXX/dbErrorReporting.html +--- +## Error Reporting Functions + +To simplify error reporting and handling, the `Db` class offers several useful methods. + +- `set_error_stream()` + + Sets the C++ `ostream` to be used for displaying error messages issued by the DB library. + +- `set_errcall()` + + Defines the function that is called when an error message is issued by DB. The error prefix and message are passed to this callback. It is up to the application to display this information correctly. + +- `set_errfile()` + + Sets the C library `FILE *` to be used for displaying error messages issued by the DB library. + +- `set_errpfx()` + + Sets the prefix used for any error messages issued by the DB library. + +- `err()` + + Issues an error message. The error message is sent to the callback function as defined by `set_errcall`. If that method has not been used, then the error message is sent to the file defined by `set_errfile()` or `set_error_stream()`. If none of these methods have been used, then the error message is sent to standard error. + + The error message consists of the prefix string (as defined by `set_errpfx()`), an optional `printf`-style formatted message, the error message, and a trailing newline. + +- `errx()` + + Behaves identically to `err()` except that the DB message text associated with the supplied error value is not appended to the error string. + +In addition, you can use the `db_strerror()` function to directly return the error string that corresponds to a particular error number. + +For example, to send all error messages for a given database handle to a callback for handling, first create your callback. Do something like this: + +``` c +/* + * Function called to handle any database error messages + * issued by DB. + */ +void +my_error_handler(const DbEnv *dbenv, const char *error_prefix, + const char *msg) +{ + /* + * Put your code to handle the error prefix and error + * message here. Note that one or both of these parameters + * may be NULL depending on how the error message is issued + * and how the DB handle is configured. + */ +} +``` + +And then register the callback as follows: + +``` c +#include +... + +Db db(NULL, 0); +std::string dbFileName("my_db.db"); + +try +{ + // Set up error handling for this database + db.set_errcall(my_error_handler); + db.set_errpfx("my_example_program"); +``` + +And to issue an error message: + +``` c + // Open the database + db.open(NULL, dbFileName.c_str(), NULL, DB_BTREE, DB_CREATE, 0); +} + // Must catch both DbException and std::exception + catch(DbException &e) + { + db.err(e.get_errno(), "Database open failed %s", + dbFileName.c_str()); + throw e; + } + catch(std::exception &e) + { + // No DB error number available, so use errx + db.errx("Error opening database: %s", e.what()); + throw e; + } +``` diff --git a/docs_src/guides/gsg/cxx/dbconfig.md b/docs_src/guides/gsg/cxx/dbconfig.md new file mode 100644 index 000000000..39e1dd89a --- /dev/null +++ b/docs_src/guides/gsg/cxx/dbconfig.md @@ -0,0 +1,110 @@ +--- +title: "Chapter 6. Database Configuration" +api-name: "Chapter 6. Database Configuration" +source: docs/gsg/CXX/dbconfig.html +--- +## Chapter 6. Database Configuration + +**Table of Contents** + + [Setting the Page Size](dbconfig.md#pagesize) + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + + [Selecting the Cache Size](cachesize.md) + + [BTree Configuration](btree.md) + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) + +This chapter describes some of the database and cache configuration issues that you need to consider when building your DB database. In most cases, there is very little that you need to do in terms of managing your databases. However, there are configuration issues that you need to be concerned with, and these are largely dependent on the access method that you are choosing for your database. + +The examples and descriptions throughout this document have mostly focused on the BTree access method. This is because the majority of DB applications use BTree. For this reason, where configuration issues are dependent on the type of access method in use, this chapter will focus on BTree only. For configuration descriptions surrounding the other access methods, see the *Berkeley DB Programmer's Reference Guide*. + +## Setting the Page Size + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + +Internally, DB stores database entries on pages. Page sizes are important because they can affect your application's performance. + +DB pages can be between 512 bytes and 64K bytes in size. The size that you select must be a power of 2. You set your database's page size using `Db::set_pagesize()`. + +Note that a database's page size can only be selected at database creation time. + +When selecting a page size, you should consider the following issues: + +- Overflow pages. + +- Locking + +- Disk I/O. + +These topics are discussed next. + +### Overflow Pages + +Overflow pages are used to hold a key or data item that cannot fit on a single page. You do not have to do anything to cause overflow pages to be created, other than to store data that is too large for your database's page size. Also, the only way you can prevent overflow pages from being created is to be sure to select a page size that is large enough to hold your database entries. + +Because overflow pages exist outside of the normal database structure, their use is expensive from a performance perspective. If you select too small of a page size, then your database will be forced to use an excessive number of overflow pages. This will significantly harm your application's performance. + +For this reason, you want to select a page size that is at least large enough to hold multiple entries given the expected average size of your database entries. In BTree's case, for best results select a page size that can hold at least 4 such entries. + +You can see how many overflow pages your database is using by using the `Db::stat()` method, or by examining your database using the `db_stat` command line utility. + +### Locking + +Locking and multi-threaded access to DB databases is built into the product. However, in order to enable the locking subsystem and in order to provide efficient sharing of the cache between databases, you must use an *environment*. Environments and multi-threaded access are not fully described in this manual (see the Berkeley DB Programmer's Reference Manual for information), however, we provide some information on sizing your pages in a multi-threaded/multi-process environment in the interest of providing a complete discussion on the topic. + +If your application is multi-threaded, or if your databases are accessed by more than one process at a time, then page size can influence your application's performance. The reason why is that for most access methods (Queue is the exception), DB implements page-level locking. This means that the finest locking granularity is at the page, not at the record. + +In most cases, database pages contain multiple database records. Further, in order to provide safe access to multiple threads or processes, DB performs locking on pages as entries on those pages are read or written. + +As the size of your page increases relative to the size of your database entries, the number of entries that are held on any given page also increase. The result is that the chances of two or more readers and/or writers wanting to access entries on any given page also increases. + +When two or more threads and/or processes want to manage data on a page, lock contention occurs. Lock contention is resolved by one thread (or process) waiting for another thread to give up its lock. It is this waiting activity that is harmful to your application's performance. + +It is possible to select a page size that is so large that your application will spend excessive, and noticeable, amounts of time resolving lock contention. Note that this scenario is particularly likely to occur as the amount of concurrency built into your application increases. + +Oh the other hand, if you select too small of a page size, then that that will only make your tree deeper, which can also cause performance penalties. The trick, therefore, is to select a reasonable page size (one that will hold a sizeable number of records) and then reduce the page size if you notice lock contention. + +You can examine the number of lock conflicts and deadlocks occurring in your application by examining your database environment lock statistics. Either use the `DbEnv::lock_stat()` `Environment.getLockStats()` method, or use the `db_stat` command line utility. The number of unavailable locks that your application waited for is held in the lock statistic's `st_lock_wait` field. + +### IO Efficiency + +Page size can affect how efficient DB is at moving data to and from disk. For some applications, especially those for which the in-memory cache can not be large enough to hold the entire working dataset, IO efficiency can significantly impact application performance. + +Most operating systems use an internal block size to determine how much data to move to and from disk for a single I/O operation. This block size is usually equal to the filesystem's block size. For optimal disk I/O efficiency, you should select a database page size that is equal to the operating system's I/O block size. + +Essentially, DB performs data transfers based on the database page size. That is, it moves data to and from disk a page at a time. For this reason, if the page size does not match the I/O block size, then the operating system can introduce inefficiencies in how it responds to DB's I/O requests. + +For example, suppose your page size is smaller than your operating system block size. In this case, when DB writes a page to disk it is writing just a portion of a logical filesystem page. Any time any application writes just a portion of a logical filesystem page, the operating system brings in the real filesystem page, over writes the portion of the page not written by the application, then writes the filesystem page back to disk. The net result is significantly more disk I/O than if the application had simply selected a page size that was equal to the underlying filesystem block size. + +Alternatively, if you select a page size that is larger than the underlying filesystem block size, then the operating system may have to read more data than is necessary to fulfill a read request. Further, on some operating systems, requesting a single database page may result in the operating system reading enough filesystem blocks to satisfy the operating system's criteria for read-ahead. In this case, the operating system will be reading significantly more data from disk than is actually required to fulfill DB's read request. + +### Note + +While transactions are not discussed in this manual, a page size other than your filesystem's block size can affect transactional guarantees. The reason why is that page sizes larger than the filesystem's block size causes DB to write pages in block size increments. As a result, it is possible for a partial page to be written as the result of a transactional commit. For more information, see http://download.oracle.com/docs/cd/E17076_02/html/programmer_reference/transapp_reclimit.html. + +### Page Sizing Advice + +Page sizing can be confusing at first, so here are some general guidelines that you can use to select your page size. + +In general, and given no other considerations, a page size that is equal to your filesystem block size is the ideal situation. + +If your data is designed such that 4 database entries cannot fit on a single page (assuming BTree), then grow your page size to accommodate your data. Once you've abandoned matching your filesystem's block size, the general rule is that larger page sizes are better. + +The exception to this rule is if you have a great deal of concurrency occurring in your application. In this case, the closer you can match your page size to the ideal size needed for your application's data, the better. Doing so will allow you to avoid unnecessary contention for page locks. diff --git a/docs_src/guides/gsg/cxx/environments.md b/docs_src/guides/gsg/cxx/environments.md new file mode 100644 index 000000000..67f90728d --- /dev/null +++ b/docs_src/guides/gsg/cxx/environments.md @@ -0,0 +1,36 @@ +--- +title: "Environments" +api-name: "Environments" +source: docs/gsg/CXX/environments.html +--- +## Environments + +This manual is meant as an introduction to the Berkeley DB library. Consequently, it describes how to build a very simple, single-threaded application and so this manual omits a great many powerful aspects of the DB database engine that are not required by simple applications. One of these is important enough that it warrants a brief overview here: environments. + +While environments are frequently not used by applications running in embedded environments where every byte counts, they will be used by virtually any other DB application requiring anything other than the bare minimum functionality. + +An *environment* is essentially an encapsulation of one or more databases. You open an environment and then you open databases in that environment. When you do so, the databases are created/located in a location relative to the environment's home directory. + +Environments offer a great many features that a stand-alone DB database cannot offer: + +- Multi-database files. + + It is possible in DB to contain multiple databases in a single physical file on disk. This is desirable for those application that open more than a few handful of databases. However, in order to have more than one database contained in a single physical file, your application *must* use an environment. + +- Multi-thread and multi-process support + + When you use an environment, resources such as the in-memory cache and locks can be shared by all of the databases opened in the environment. The environment allows you to enable subsystems that are designed to allow multiple threads and/or processes to access DB databases. For example, you use an environment to enable the concurrent data store (CDS), the locking subsystem, and/or the shared memory buffer pool. + +- Transactional processing + + DB offers a transactional subsystem that allows for full ACID-protection of your database writes. You use environments to enable the transactional subsystem, and then subsequently to obtain transaction IDs. + +- High availability (replication) support + + DB offers a replication subsystem that enables single-master database replication with multiple read-only copies of the replicated data. You use environments to enable and then manage this subsystem. + +- Logging subsystem + + DB offers write-ahead logging for applications that want to obtain a high-degree of recoverability in the face of an application or system crash. Once enabled, the logging subsystem allows the application to perform two kinds of recovery ("normal" and "catastrophic") through the use of the information contained in the log files. + +For more information on these topics, see the *Berkeley DB Getting Started with Transaction Processing* guide and the *Berkeley DB Getting Started with Replicated Applications* guide. diff --git a/docs_src/guides/gsg/cxx/gettingit.md b/docs_src/guides/gsg/cxx/gettingit.md new file mode 100644 index 000000000..6fb1377b3 --- /dev/null +++ b/docs_src/guides/gsg/cxx/gettingit.md @@ -0,0 +1,12 @@ +--- +title: "Getting and Using DB" +api-name: "Getting and Using DB" +source: docs/gsg/CXX/gettingit.html +--- +## Getting and Using DB + +You can obtain DB by visiting the Berkeley DB download page: http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +To install DB, untar or unzip the distribution to the directory of your choice. You will then need to build the product binaries. For information on building DB, see *DB_INSTALL*`/docs/index.html`, where *DB_INSTALL* is the directory where you unpacked DB. On that page, you will find links to platform-specific build instructions. + +That page also contains links to more documentation for DB. In particular, you will find links for the *Berkeley DB Programmer's Reference Guide* as well as the API reference documentation. diff --git a/docs_src/guides/gsg/cxx/index.md b/docs_src/guides/gsg/cxx/index.md new file mode 100644 index 000000000..959671453 --- /dev/null +++ b/docs_src/guides/gsg/cxx/index.md @@ -0,0 +1,162 @@ +--- +title: "Getting Started with Berkeley DB" +api-name: "Getting Started with Berkeley DB" +source: docs/gsg/CXX/index.html +--- +# Getting Started with Berkeley DB + +**Language:** [C](../index.md) · C++ (this page) · [Java](../java/index.md) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + + [Preface](preface.md) + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + + [1. Introduction to Berkeley DB](introduction.md) + + [About This Manual](introduction.md#aboutthismanual) + + [Berkeley DB Concepts](concepts.md) + + [Access Methods](accessmethods.md) + + [Selecting Access Methods](accessmethods.md#selectAM) + + [Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + + [Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + + [Database Limits and Portability](databaseLimits.md) + + [Environments](environments.md) + + [Exception Handling](coreExceptions.md) + + [Error Returns](returns.md) + + [Getting and Using DB](gettingit.md) + + [2. Databases](databases.md) + + [Opening Databases](databases.md#DBOpen) + + [Closing Databases](coredbclose.md) + + [Database Open Flags](DBOpenFlags.md) + + [Administrative Methods](CoreDBAdmin.md) + + [Error Reporting Functions](dbErrorReporting.md) + + [Managing Databases in Environments](CoreEnvUsage.md) + + [Database Example](CoreDbCXXUsage.md) + + [3. Database Records](DBEntry.md) + + [Using Database Records](DBEntry.md#usingDbEntry) + + [Reading and Writing Database Records](usingDbt.md) + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#CoreDatabaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + + [Database Usage Example](DbCXXUsage.md) + + [4. Using Cursors](Cursors.md) + + [Opening and Closing Cursors](Cursors.md#openCursor) + + [Getting Records Using the Cursor](Positioning.md) + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + + [Putting Records Using Cursors](PutEntryWCursor.md) + + [Deleting Records Using Cursors](DeleteEntryWCursor.md) + + [Replacing Records Using Cursors](ReplacingEntryWCursor.md) + + [Cursor Example](CoreCursorUsage.md) + + [5. Secondary Databases](indexes.md) + + [Opening and Closing Secondary Databases](indexes.md#CoreDbAssociate) + + [Implementing Key Extractors](keyCreator.md) + + [Working with Multiple Keys](keyCreator.md#multikeys) + + [Reading Secondary Databases](readSecondary.md) + + [Deleting Secondary Database Records](secondaryDelete.md) + + [Using Cursors with Secondary Databases](secondaryCursor.md) + + [Database Joins](joins.md) + + [Using Join Cursors](joins.md#joinUsage) + + [Secondary Database Example](coreindexusage.md) + + [Secondary Databases with example_database_load](coreindexusage.md#edlWIndexes) + + [Secondary Databases with example_database_read](coreindexusage.md#edrWIndexes) + + [6. Database Configuration](dbconfig.md) + + [Setting the Page Size](dbconfig.md#pagesize) + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + + [Selecting the Cache Size](cachesize.md) + + [BTree Configuration](btree.md) + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) + +**List of Examples** + +2.1. [MyDb Class](CoreDbCXXUsage.md#MyDb-cxx) + +3.1. [VENDOR Structure](DbCXXUsage.md#VENDORCXXStruct) + +3.2. [InventoryData Class](DbCXXUsage.md#InventoryData) + +3.3. [example_database_load](DbCXXUsage.md#exampledbload-cxx) + +4.1. [example_database_read](CoreCursorUsage.md#CoreEIR-cxx) diff --git a/docs_src/guides/gsg/cxx/indexes.md b/docs_src/guides/gsg/cxx/indexes.md new file mode 100644 index 000000000..cae4ceff9 --- /dev/null +++ b/docs_src/guides/gsg/cxx/indexes.md @@ -0,0 +1,105 @@ +--- +title: "Chapter 5. Secondary Databases" +api-name: "Chapter 5. Secondary Databases" +source: docs/gsg/CXX/indexes.html +--- +## Chapter 5. Secondary Databases + +**Table of Contents** + + [Opening and Closing Secondary Databases](indexes.md#CoreDbAssociate) + + [Implementing Key Extractors](keyCreator.md) + + [Working with Multiple Keys](keyCreator.md#multikeys) + + [Reading Secondary Databases](readSecondary.md) + + [Deleting Secondary Database Records](secondaryDelete.md) + + [Using Cursors with Secondary Databases](secondaryCursor.md) + + [Database Joins](joins.md) + + [Using Join Cursors](joins.md#joinUsage) + + [Secondary Database Example](coreindexusage.md) + + [Secondary Databases with example_database_load](coreindexusage.md#edlWIndexes) + + [Secondary Databases with example_database_read](coreindexusage.md#edrWIndexes) + +Usually you find database records by means of the record's key. However, the key that you use for your record will not always contain the information required to provide you with rapid access to the data that you want to retrieve. For example, suppose your database contains records related to users. The key might be a string that is some unique identifier for the person, such as a user ID. Each record's data, however, would likely contain a complex object containing details about people such as names, addresses, phone numbers, and so forth. While your application may frequently want to query a person by user ID (that is, by the information stored in the key), it may also on occasion want to locate people by, say, their name. + +Rather than iterate through all of the records in your database, examining each in turn for a given person's name, you create indexes based on names and then just search that index for the name that you want. You can do this using secondary databases. In DB, the database that contains your data is called a *primary database*. A database that provides an alternative set of keys to access that data is called a *secondary database*. In a secondary database, the keys are your alternative (or secondary) index, and the data corresponds to a primary record's key. + +You create a secondary database by creating the database, opening it, and then *associating* the database with the *primary* database (that is, the database for which you are creating the index). As a part of associating the secondary database to the primary, you must provide a callback that is used to create the secondary database keys. Typically this callback creates a key based on data found in the primary database record's key or data. + +Once opened, DB manages secondary databases for you. Adding or deleting records in your primary database causes DB to update the secondary as necessary. Further, changing a record's data in the primary database may cause DB to modify a record in the secondary, depending on whether the change forces a modification of a key in the secondary database. + +Note that you can not write directly to a secondary database. Any attempt to write to a secondary database results in a non-zero status return. To change the data referenced by a secondary record, modify the primary database instead. The exception to this rule is that delete operations are allowed on the secondary database. See Deleting Secondary Database Records for more information. + +### Note + +Secondary database records are updated/created by DB only if the key creator callback function returns `0`. If a value other than `0` is returned, then DB will not add the key to the secondary database, and in the event of a record update it will remove any existing key. Note that the callback can use either `DB_DONOTINDEX` or some error code outside of DB's name space to indicate that the entry should not be indexed. + +See Implementing Key Extractors for more information. + +When you read a record from a secondary database, DB automatically returns the data and optionally the key from the corresponding record in the primary database. + +## Opening and Closing Secondary Databases + +You manage secondary database opens and closes in the same way as you would any normal database. The only difference is that: + +- You must associate the secondary to a primary database using `Db::associate()`. + +- When closing your databases, it is a good idea to make sure you close your secondaries before closing your primaries. This is particularly true if your database closes are not single threaded. + +When you associate a secondary to a primary database, you must provide a callback that is used to generate the secondary's keys. These callbacks are described in the next section. + +For example, to open a secondary database and associate it to a primary database: + +``` c +#include + +... + +Db my_database(NULL, 0); // Primary +Db my_index(NULL, 0); // Secondary + +// Open the primary +my_database.open(NULL, // Transaction pointer + "my_db.db", // On-disk file that holds the database. + NULL, // Optional logical database name + DB_BTREE, // Database access method + DB_CREATE, // Open flags + 0); // File mode (using defaults) + +// Setup the secondary to use sorted duplicates. +// This is often desirable for secondary databases. +my_index.set_flags(DB_DUPSORT); + +// Open the secondary +my_index.open(NULL, // Transaction pointer + "my_secondary.db", // On-disk file that holds the database. + NULL, // Optional logical database name + DB_BTREE, // Database access method + DB_CREATE, // Open flags. + 0); // File mode (using defaults) + +// Now associate the primary and the secondary +my_database.associate(NULL, // Txn id + &my_index, // Associated secondary database + get_sales_rep, // Callback used for key extraction. + // This is described in the next + // section. + 0); // Flags +``` + +Closing the primary and secondary databases is accomplished exactly as you would for any database: + +``` c +// Close the secondary before the primary +my_index.close(0); +my_database.close(0); +``` diff --git a/docs_src/guides/gsg/cxx/introduction.md b/docs_src/guides/gsg/cxx/introduction.md new file mode 100644 index 000000000..75a0b06da --- /dev/null +++ b/docs_src/guides/gsg/cxx/introduction.md @@ -0,0 +1,68 @@ +--- +title: "Chapter 1. Introduction to Berkeley DB" +api-name: "Chapter 1. Introduction to Berkeley DB" +source: docs/gsg/CXX/introduction.html +--- +## Chapter 1. Introduction to Berkeley DB + +**Table of Contents** + + [About This Manual](introduction.md#aboutthismanual) + + [Berkeley DB Concepts](concepts.md) + + [Access Methods](accessmethods.md) + + [Selecting Access Methods](accessmethods.md#selectAM) + + [Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + + [Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + + [Database Limits and Portability](databaseLimits.md) + + [Environments](environments.md) + + [Exception Handling](coreExceptions.md) + + [Error Returns](returns.md) + + [Getting and Using DB](gettingit.md) + +Welcome to Berkeley DB (DB). DB is a general-purpose embedded database engine that is capable of providing a wealth of data management services. It is designed from the ground up for high-throughput applications requiring in-process, bullet-proof management of mission-critical data. DB can gracefully scale from managing a few bytes to terabytes of data. For the most part, DB is limited only by your system's available physical resources. + +You use DB through a series of programming APIs which give you the ability to read and write your data, manage your database(s), and perform other more advanced activities such as managing transactions. + +Because DB is an embedded database engine, it is extremely fast. You compile and link it into your application in the same way as you would any third-party library. This means that DB runs in the same process space as does your application, allowing you to avoid the high cost of interprocess communications incurred by stand-alone database servers. + +To further improve performance, DB offers an in-memory cache designed to provide rapid access to your most frequently used data. Once configured, cache usage is transparent. It requires very little attention on the part of the application developer. + +Beyond raw speed, DB is also extremely configurable. It provides several different ways of organizing your data in its databases. Known as *access methods*, each such data organization mechanism provides different characteristics that are appropriate for different data management profiles. (Note that this manual focuses almost entirely on the BTree access method as this is the access method used by the vast majority of DB applications). + +To further improve its configurability, DB offers many different subsystems, each of which can be used to extend DB's capabilities. For example, many applications require write-protection of their data so as to ensure that data is never left in an inconsistent state for any reason (such as software bugs or hardware failures). For those applications, a transaction subsystem can be enabled and used to transactional-protect database writes. + +The list of operating systems on which DB is available is too long to detail here. Suffice to say that it is available on all major commercial operating systems, as well as on many embedded platforms. + +Finally, DB is available in a wealth of programming languages. DB is officially supported in C, C++, and Java, but the library is also available in many other languages, especially scripting languages such as Perl and Python. + +### Note + +Before going any further, it is important to mention that DB is not a relational database (although you could use it to build a relational database). Out of the box, DB does not provide higher-level features such as triggers, or a high-level query language such as SQL. Instead, DB provides just those minimal APIs required to store and retrieve your data as efficiently as possible. + +## About This Manual + +This manual introduces DB. As such, this book does not examine intermediate or advanced features such as threaded library usage or transactional usage. Instead, this manual provides a step-by-step introduction to DB's basic concepts and library usage. + +Specifically, this manual introduces DB environments, databases, database records, and storage and retrieval of database records. This book also introduces cursors and their usage, and it describes secondary databases. + +For the most part, this manual focuses on the BTree access method. A chapter is given at the end of this manual that describes some of the concepts involving BTree usage, such as duplicate record management and comparison routines. + +Examples are given throughout this book that are designed to illustrate API usage. At the end of each chapter, a complete example is given that is designed to reinforce the concepts covered in that chapter. In addition to being presented in this book, these final programs are also available in the DB software distribution. You can find them in + +``` c +DB_INSTALL/examples_cxx/getting_started +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +This book uses the C++ programming languages for its examples. Note that versions of this book exist for the C and Java languages as well. diff --git a/docs_src/guides/gsg/cxx/joins.md b/docs_src/guides/gsg/cxx/joins.md new file mode 100644 index 000000000..34979723c --- /dev/null +++ b/docs_src/guides/gsg/cxx/joins.md @@ -0,0 +1,117 @@ +--- +title: "Database Joins" +api-name: "Database Joins" +source: docs/gsg/CXX/joins.html +--- +## Database Joins + + [Using Join Cursors](joins.md#joinUsage) + +If you have two or more secondary databases associated with a primary database, then you can retrieve primary records based on the intersection of multiple secondary entries. You do this using a join cursor. + +Throughout this document we have presented a structure that stores information on grocery vendors. That structure is fairly simple with a limited number of data members, few of which would be interesting from a query perspective. But suppose, instead, that we were storing information on something with many more characteristics that can be queried, such as an automobile. In that case, you may be storing information such as color, number of doors, fuel mileage, automobile type, number of passengers, make, model, and year, to name just a few. + +In this case, you would still likely be using some unique value to key your primary entries (in the United States, the automobile's VIN would be ideal for this purpose). You would then create a structure that identifies all the characteristics of the automobiles in your inventory. + +To query this data, you might then create multiple secondary databases, one for each of the characteristics that you want to query. For example, you might create a secondary for color, another for number of doors, another for number of passengers, and so forth. Of course, you will need a unique key extractor function for each such secondary database. You do all of this using the concepts and techniques described throughout this chapter. + +Once you have created this primary database and all interesting secondaries, what you have is the ability to retrieve automobile records based on a single characteristic. You can, for example, find all the automobiles that are red. Or you can find all the automobiles that have four doors. Or all the automobiles that are minivans. + +The next most natural step, then, is to form compound queries, or joins. For example, you might want to find all the automobiles that are red, and that were built by Toyota, and that are minivans. You can do this using a join cursor. + +### Using Join Cursors + +To use a join cursor: + +- Open two or more cursors for secondary databases that are associated with the same primary database. + +- Position each such cursor to the secondary key value in which you are interested. For example, to build on the previous description, the cursor for the color database is positioned to the `red` records while the cursor for the model database is positioned to the `minivan` records, and the cursor for the make database is positioned to `Toyota`. + +- Create an array of cursors, and place in it each of the cursors that are participating in your join query. Note that this array must be null terminated. + +- Obtain a join cursor. You do this using the `Db::join()` method. You must pass this method the array of secondary cursors that you opened and positioned in the previous steps. + +- Iterate over the set of matching records until the return code is not `0`. + +- Close your cursor. + +- If you are done with them, close all your cursors. + +For example: + +``` c +#include +#include + +... + +// Exception handling omitted + +int ret; + +Db automotiveDB(NULL, 0); +Db automotiveColorDB(NULL, 0); +Db automotiveMakeDB(NULL, 0); +Db automotiveTypeDB(NULL, 0); + +// Database and secondary database opens omitted for brevity. +// Assume a primary database: +// automotiveDB +// Assume 3 secondary databases: +// automotiveColorDB -- secondary database based on automobile color +// automotiveMakeDB -- secondary database based on the manufacturer +// automotiveTypeDB -- secondary database based on automobile type + +// Position the cursors +Dbc *color_curs; +automotiveColorDB.cursor(NULL, &color_curs, 0); +char *the_color = "red"; +Dbt key(the_color, strlen(the_color) + 1); +Dbt data; +if ((ret = color_curs->get(&key, &data, DB_SET)) != 0) { + // Error handling goes here +} + +Dbc *make_curs; +automotiveMakeDB.cursor(NULL, &make_curs, 0); +char *the_make = "Toyota"; +key.set_data(the_make); +key.set_size(strlen(the_make) + 1); +if ((ret = make_curs->get(&key, &data, DB_SET)) != 0) { + // Error handling goes here +} + +Dbc *type_curs; +automotiveTypeDB.cursor(NULL, &type_curs, 0); +char *the_type = "minivan"; +key.set_data(the_type); +key.set_size(strlen(the_type) + 1); +if ((ret = type_curs->get(&key, &data, DB_SET)) != 0) { + // Error handling goes here +} + +// Set up the cursor array +Dbc *carray[4]; +carray[0] = color_curs; +carray[1] = make_curs; +carray[2] = type_curs; +carray[3] = NULL; + +// Create the join +Dbc *join_curs; +if ((ret = automotiveDB.join(carray, &join_curs, 0)) != 0) { + // Error handling goes here +} + +// Iterate using the join cursor +while ((ret = join_curs->get(&key, &data, 0)) == 0) { + // Do interesting things with the key and data +} + +// If we exited the loop because we ran out of records, +// then it has completed successfully. +if (ret == DB_NOTFOUND) { + // Close all our cursors and databases as is appropriate, and + // then exit with a normal exit status (0). +} +``` diff --git a/docs_src/guides/gsg/cxx/keyCreator.md b/docs_src/guides/gsg/cxx/keyCreator.md new file mode 100644 index 000000000..a31710383 --- /dev/null +++ b/docs_src/guides/gsg/cxx/keyCreator.md @@ -0,0 +1,123 @@ +--- +title: "Implementing Key Extractors" +api-name: "Implementing Key Extractors" +source: docs/gsg/CXX/keyCreator.html +--- +## Implementing Key Extractors + + [Working with Multiple Keys](keyCreator.md#multikeys) + +You must provide every secondary database with a class that creates keys from primary records. You identify this class when you associate your secondary database to your primary. + +You can create keys using whatever data you want. Typically you will base your key on some information found in a record's data, but you can also use information found in the primary record's key. How you build your keys is entirely dependent upon the nature of the index that you want to maintain. + +You implement a key extractor by writing a function that extracts the necessary information from a primary record's key or data. This function must conform to a specific prototype, and it must be provided as a callback to the `associate()` method. + +For example, suppose your primary database records contain data that uses the following structure: + +``` c +typedef struct vendor { + char name[MAXFIELD]; /* Vendor name */ + char street[MAXFIELD]; /* Street name and number */ + char city[MAXFIELD]; /* City */ + char state[3]; /* Two-digit US state code */ + char zipcode[6]; /* US zipcode */ + char phone_number[13]; /* Vendor phone number */ + char sales_rep[MAXFIELD]; /* Name of sales representative */ + char sales_rep_phone[MAXFIELD]; /* Sales rep's phone number */ +} VENDOR; +``` + +Further suppose that you want to be able to query your primary database based on the name of a sales representative. Then you would write a function that looks like this: + +``` c +#include + +... + +int +get_sales_rep(Db *sdbp, // secondary db handle + const Dbt *pkey, // primary db record's key + const Dbt *pdata, // primary db record's data + Dbt *skey) // secondary db record's key +{ + VENDOR *vendor; + + // First, extract the structure contained in the primary's data + vendor = (VENDOR *)pdata->get_data(); + + // Now set the secondary key's data to be the representative's name + skey->set_data(vendor->sales_rep); + skey->set_size(strlen(vendor->sales_rep) + 1); + + // Return 0 to indicate that the record can be created/updated. + return (0); +} +``` + +In order to use this function, you provide it on the `associate()` method after the primary and secondary databases have been created and opened: + +``` c +db.associate(NULL, // TXN id + &sdb, // Secondary database + get_sales_rep, // Callback used for key creation. + 0); // Flags +``` + +### Working with Multiple Keys + +Until now we have only discussed indexes as if there is a one-to-one relationship between the secondary key and the primary database record. In fact, it is possible to generate multiple keys for any given record, provided that you take appropriate steps in your key creator to do so. + +For example, suppose you had a database that contained information about books. Suppose further that you sometimes want to look up books by author. Because sometimes books have multiple authors, you may want to return multiple secondary keys for every book that you index. + +To do this, you write a key extractor that returns a Dbt whose `data` member points to an array of Dbts. Each such member of this array contains a single secondary key. In addition, the Dbt returned by your key extractor must have a size field equal to the number of elements contained in the Dbt array. Also, the flag field for the Dbt returned by the callback must include `DB_DBT_MULTIPLE`. For example: + +### Note + +It is important that the array of secondary keys created by your callback not contain repeats. That is, every element in the array must be unique. If the array does not contain a unique set, then the secondary can get out of sync with the primary. + +``` c +int +my_callback(Db *dbp, const Dbt *pkey, const Dbt *pdata, Dbt *skey) +{ + Dbt *tmpdbt; + char *tmpdata1, tmpdata2; + + // This example skips the step of extracting the data you + // want to use for building your secondary keys from the + // pkey or pdata Dbt. + + // Assume for the purpose of this example that the data + // is temporarily stored in two variables, + // tmpdata1 and tmpdata2. + + // Create an array of Dbts that is large enough for the + // number of keys that you want to return. In this case, + // we go with an array of size two. + + tmpdbt = malloc(sizeof(Dbt) * 2); + memset(tmpdbt, 0, sizeof(Dbt) * 2); + + // Now assign secondary keys to each element of the array. + tmpdbt[0].set_data(tmpdata1); + tmpdbt[0].set_size((u_int32_t)strlen(tmpdbt[0].data) + 1); + tmpdbt[1].set_data(tmpdata2); + tmpdbt[1].set_size((u_int32_t)strlen(tmpdbt[1].data) + 1); + + // Now we set flags for the returned Dbt. DB_DBT_MULTIPLE is + // required in order for DB to know that the Dbt references an + // array. In addition, we set DB_DBT_APPMALLOC because we + // dynamically allocated memory for the Dbt's data field. + // DB_DBT_APPMALLOC causes DB to release that memory once it + // is done with the returned Dbt. + skey->set_flags(DB_DBT_MULTIPLE | DB_DBT_APPMALLOC); + + // Point the results data field to the arrays of Dbts + skey->set_data(tmpdbt); + + // Indicate the returned array is of size 2 + skey->size = 2; + + return (0); +} +``` diff --git a/docs_src/guides/gsg/cxx/moreinfo.md b/docs_src/guides/gsg/cxx/moreinfo.md new file mode 100644 index 000000000..cc314cc5d --- /dev/null +++ b/docs_src/guides/gsg/cxx/moreinfo.md @@ -0,0 +1,32 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg/CXX/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a DB application: + +- Getting Started with Transaction Processing for C++ + +- Berkeley DB Getting Started with Replicated Applications for C++ + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB Installation and Build Guide + +- Berkeley DB Getting Started with the SQL APIs + +- Berkeley DB C++ API Reference Guide + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg/cxx/preface.md b/docs_src/guides/gsg/cxx/preface.md new file mode 100644 index 000000000..c69ca9d20 --- /dev/null +++ b/docs_src/guides/gsg/cxx/preface.md @@ -0,0 +1,58 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg/CXX/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +Welcome to Berkeley DB (DB). This document introduces Berkeley DB 11*g* Release 2, which provides DB library version 11.2.5.3. + +This document is intended to provide a rapid introduction to the DB API set and related concepts. The goal of this document is to provide you with an efficient mechanism with which you can evaluate DB against your project's technical requirements. As such, this document is intended for C++ developers and senior software architects who are looking for an in-process data management solution. No prior experience with Berkeley DB is expected or required. + +## 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: "`Db::open()` is a `Db` class method." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number +} VENDOR; +``` + +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: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number + char sales_rep[MAXFIELD]; // Name of sales representative + char sales_rep_phone[MAXFIELD]; // Sales rep's phone number +} VENDOR; +``` + +### Note + +Finally, notes of interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg/cxx/readSecondary.md b/docs_src/guides/gsg/cxx/readSecondary.md new file mode 100644 index 000000000..4b0730022 --- /dev/null +++ b/docs_src/guides/gsg/cxx/readSecondary.md @@ -0,0 +1,37 @@ +--- +title: "Reading Secondary Databases" +api-name: "Reading Secondary Databases" +source: docs/gsg/CXX/readSecondary.html +--- +## Reading Secondary Databases + +Like a primary database, you can read records from your secondary database either by using the `Db::get()` or `Db::pget()` methods, or by using a cursor on the secondary database. The main difference between reading secondary and primary databases is that when you read a secondary database record, the secondary record's data is not returned to you. Instead, the primary key and data corresponding to the secondary key are returned to you. + +For example, assuming your secondary database contains keys related to a person's full name: + +``` c +#include +#include + +... + +// The string to search for +char *search_name = "John Doe"; + +// Instantiate our Dbt's +Dbt key(search_name, strlen(search_name) + 1); +Dbt pkey, pdata; // Primary key and data + +Db my_secondary_database(NULL, 0); +// Primary and secondary database opens omitted for brevity + +// Returns the key from the secondary database, and the data from the +// associated primary database entry. +my_secondary_database.get(NULL, &key, &pdata, 0); + +// Returns the key from the secondary database, and the key and data +// from the associated primary database entry. +my_secondary_database.pget(NULL, &key, &pkey, &pdata, 0); +``` + +Note that, just like a primary database, if your secondary database supports duplicate records then `Db::get()` and `Db::pget()` only return the first record found in a matching duplicates set. If you want to see all the records related to a specific secondary key, then use a cursor opened on the secondary database. Cursors are described in Using Cursors. diff --git a/docs_src/guides/gsg/cxx/returns.md b/docs_src/guides/gsg/cxx/returns.md new file mode 100644 index 000000000..a2fcedcd3 --- /dev/null +++ b/docs_src/guides/gsg/cxx/returns.md @@ -0,0 +1,12 @@ +--- +title: "Error Returns" +api-name: "Error Returns" +source: docs/gsg/CXX/returns.html +--- +## Error Returns + +In addition to exceptions, the DB interfaces always return a value of 0 on success. If the operation does not succeed for any reason, the return value will be non-zero. + +If a system error occurred (for example, DB ran out of disk space, or permission to access a file was denied, or an illegal argument was specified to one of the interfaces), DB returns an `errno` value. All of the possible values of `errno` are greater than 0. + +If the operation did not fail due to a system error, but was not successful either, DB returns a special error value. For example, if you tried to retrieve data from the database and the record for which you are searching does not exist, DB would return `DB_NOTFOUND`, a special error value that means the requested key does not appear in the database. All of the possible special error values are less than 0. diff --git a/docs_src/guides/gsg/cxx/secondaryCursor.md b/docs_src/guides/gsg/cxx/secondaryCursor.md new file mode 100644 index 000000000..934d8b998 --- /dev/null +++ b/docs_src/guides/gsg/cxx/secondaryCursor.md @@ -0,0 +1,41 @@ +--- +title: "Using Cursors with Secondary Databases" +api-name: "Using Cursors with Secondary Databases" +source: docs/gsg/CXX/secondaryCursor.html +--- +## Using Cursors with Secondary Databases + +Just like cursors on a primary database, you can use cursors on secondary databases to iterate over the records in a secondary database. Like cursors used with primary databases, you can also use cursors with secondary databases to search for specific records in a database, to seek to the first or last record in the database, to get the next duplicate record, and so forth. For a complete description on cursors and their capabilities, see Using Cursors. + +However, when you use cursors with secondary databases: + +- Any data returned is the data contained on the primary database record referenced by the secondary record. + +- You cannot use `DB_GET_BOTH` and related flags with `Db::get()` and a secondary database. Instead, you must use `Db::pget()`. Also, in that case the primary and secondary key given on the call to `Db::pget()` must match the secondary key and associated primary record key in order for that primary record to be returned as a result of the call. + +For example, suppose you are using the databases, classes, and key extractors described in Implementing Key Extractors . Then the following searches for a person's name in the secondary database, and deletes all secondary and primary records that use that name. + +``` c +#include + +... + +Db my_database(NULL, 0); +Db my_index(NULL, 0); + +// Get a cursor on the secondary database +Dbc *cursorp; +my_index.cursor(NULL, &cursorp, 0); + +// Name to delete +char *search_name = "John Doe"; + +// Instantiate Dbts as normal +Dbt key(search_name, strlen(search_name) + 1); +Dbt data; + + +// Position the cursor +while (cursorp->get(&key, &data, DB_SET) == 0) + cursorp->del(0); +``` diff --git a/docs_src/guides/gsg/cxx/secondaryDelete.md b/docs_src/guides/gsg/cxx/secondaryDelete.md new file mode 100644 index 000000000..9c98391a3 --- /dev/null +++ b/docs_src/guides/gsg/cxx/secondaryDelete.md @@ -0,0 +1,66 @@ +--- +title: "Deleting Secondary Database Records" +api-name: "Deleting Secondary Database Records" +source: docs/gsg/CXX/secondaryDelete.html +--- +## Deleting Secondary Database Records + +In general, you will not modify a secondary database directly. In order to modify a secondary database, you should modify the primary database and simply allow DB to manage the secondary modifications for you. + +However, as a convenience, you can delete secondary database records directly. Doing so causes the associated primary key/data pair to be deleted. This in turn causes DB to delete all secondary database records that reference the primary record. + +You can use the `Db::del()` method to delete a secondary database record. Note that if your secondary database contains duplicate records, then deleting a record from the set of duplicates causes all of the duplicates to be deleted as well. + +### Note + +You can delete a secondary database record using the previously described mechanism only if the primary database is opened for write access. + +For example: + +``` c +#include +#include + +... + +Db my_database(NULL, 0); // Primary +Db my_index(NULL, 0); // Secondary + +// Open the primary +my_database.open(NULL, // Transaction pointer + "my_db.db", // On-disk file that holds the database. + NULL, // Optional logical database name + DB_BTREE, // Database access method + DB_CREATE, // Open flags + 0); // File mode (using defaults) + +// Setup the secondary to use sorted duplicates. +// This is often desireable for secondary databases. +my_index.set_flags(DB_DUPSORT); + +// Open the secondary +my_index.open(NULL, // Transaction pointer + "my_secondary.db", // On-disk file that holds the database. + NULL, // Optional logical database name + DB_BTREE, // Database access method + DB_CREATE, // Open flags. + 0); // File mode (using defaults) + +// Now associate the primary and the secondary +my_database.associate(NULL, // Txn id + &my_index, // Associated secondary database + get_sales_rep, // Callback used for key extraction. + 0); // Flags + +// Name to delete +char *search_name = "John Doe"; + +// Get a search key +Dbt key(search_name, strlen(search_name) + 1); + +// Now delete the secondary record. This causes the associated primary +// record to be deleted. If any other secondary databases have secondary +// records referring to the deleted primary record, then those secondary +// records are also deleted. +my_index.del(NULL, &key, 0); +``` diff --git a/docs_src/guides/gsg/cxx/usingDbt.md b/docs_src/guides/gsg/cxx/usingDbt.md new file mode 100644 index 000000000..23de429aa --- /dev/null +++ b/docs_src/guides/gsg/cxx/usingDbt.md @@ -0,0 +1,138 @@ +--- +title: "Reading and Writing Database Records" +api-name: "Reading and Writing Database Records" +source: docs/gsg/CXX/usingDbt.html +--- +## Reading and Writing Database Records + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#CoreDatabaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + +When reading and writing database records, be aware that there are some slight differences in behavior depending on whether your database supports duplicate records. Two or more database records are considered to be duplicates of one another if they share the same key. The collection of records sharing the same key are called a *duplicates set.* In DB, a given key is stored only once for a single duplicates set. + +By default, DB databases do not support duplicate records. Where duplicate records are supported, cursors (see below) are typically used to access all of the records in the duplicates set. + +DB provides two basic mechanisms for the storage and retrieval of database key/data pairs: + +- The `Db::put()` and `Db::get()` methods provide the easiest access for all non-duplicate records in the database. These methods are described in this section. + +- Cursors provide several methods for putting and getting database records. Cursors and their database access methods are described in Using Cursors. + +### Writing Records to the Database + +Records are stored in the database using whatever organization is required by the access method that you have selected. In some cases (such as BTree), records are stored in a sort order that you may want to define (see Setting Comparison Functions for more information). + +In any case, the mechanics of putting and getting database records do not change once you have selected your access method, configured your sorting routines (if any), and opened your database. From your code's perspective, a simple database put and get is largely the same no matter what access method you are using. + +You use `Db::put()` to put, or write, a database record. This method requires you to provide the record's key and data in the form of a pair of `Dbt` objects. You can also provide one or more flags that control DB's behavior for the database write. + +Of the flags available to this method, `DB_NOOVERWRITE` may be interesting to you. This flag disallows overwriting (replacing) an existing record in the database. If the provided key already exists in the database, then this method returns `DB_KEYEXIST` even if the database supports duplicates. + +For example: + +``` c +#include +#include + +... + +char *description = "Grocery bill."; +float money = 122.45; + +Db my_database(NULL, 0); +// Database open omitted for clarity + +Dbt key(&money, sizeof(float)); +Dbt data(description, strlen(description) + 1); + +int ret = my_database.put(NULL, &key, &data, DB_NOOVERWRITE); +if (ret == DB_KEYEXIST) { + my_database.err(ret, "Put failed because key %f already exists", + money); +} +``` + +### Getting Records from the Database + +You can use the `Db::get()` method to retrieve database records. Note that if your database supports duplicate records, then by default this method will only return the first record in a duplicate set. For this reason, if your database supports duplicates, the common solution is to use a cursor to retrieve records from it. Cursors are described in Using Cursors. + +(You can also retrieve a set of duplicate records using a bulk get. To do this, you use the `DB_MULTIPLE` flag on the call to `Db::get()`. For more information, see the DB Programmer's Reference Guide). + +By default, `Db::get()` returns the first record found whose key matches the key provide on the call to this method. If your database supports duplicate records, you can change this behavior slightly by supplying the `DB_GET_BOTH` flag. This flag causes `DB::get()` to return the first record that matches the provided key and data. + +If the specified key and/or data does not exist in the database, this method returns `DB_NOTFOUND`. + +``` c +#include +#include + +... +#define DESCRIPTION_SIZE 199 +float money; +char description[DESCRIPTION_SIZE + 1]; + +Db my_database(NULL, 0); +// Database open omitted for clarity + +money = 122.45; + +Dbt key, data; + +key.set_data(&money); +key.set_size(sizeof(float)); + +data.set_data(description); +data.set_ulen(DESCRIPTION_SIZE + 1); +data.set_flags(DB_DBT_USERMEM); + +my_database.get(NULL, &key, &data, 0); + +// Description is set into the memory that we supplied. +``` + +Note that in this example, the `data.size` field would be automatically set to the size of the retrieved data. + +### Deleting Records + +You can use the `Db::del()` method to delete a record from the database. If your database supports duplicate records, then all records associated with the provided key are deleted. To delete just one record from a list of duplicates, use a cursor. Cursors are described in Using Cursors. + +You can also delete every record in the database by using `Db::truncate().` + +For example: + +``` c +#include + +... + +Db my_database(NULL, 0); +// Database open omitted for clarity + +float money = 122.45; +Dbt key(&money, sizeof(float)); + +my_database.del(NULL, &key, 0); +``` + +### Data Persistence + +When you perform a database modification, your modification is made in the in-memory cache. This means that your data modifications are not necessarily flushed to disk, and so your data may not appear in the database after an application restart. + +Note that as a normal part of closing a database, its cache is written to disk. However, in the event of an application or system failure, there is no guarantee that your databases will close cleanly. In this event, it is possible for you to lose data. Under extremely rare circumstances, it is also possible for you to experience database corruption. + +Therefore, if you care if your data is durable across system failures, and to guard against the rare possibility of database corruption, you should use transactions to protect your database modifications. Every time you commit a transaction, DB ensures that the data will not be lost due to application or system failure. Transaction usage is described in the *Berkeley DB Getting Started with Transaction Processing* guide. + +If you do not want to use transactions, then the assumption is that your data is of a nature that it need not exist the next time your application starts. You may want this if, for example, you are using DB to cache data relevant only to the current application runtime. + +If, however, you are not using transactions for some reason and you still want some guarantee that your database modifications are persistent, then you should periodically call `Db::sync()`. Syncs cause any dirty entries in the in-memory cache and the operating system's file cache to be written to disk. As such, they are quite expensive and you should use them sparingly. + +Remember that by default a sync is performed any time a non-transactional database is closed cleanly. (You can override this behavior by specifying `DB_NOSYNC` on the call to `Db::close()`.) That said, you can manually run a sync by calling `Db::sync().` + +### Note + +If your application or system crashes and you are not using transactions, then you should either discard and recreate your databases, or verify them. You can verify a database using Db::verify(). If your databases do not verify cleanly, use the **db_dump** command to salvage as much of the database as is possible. Use either the `-R` or `-r` command line options to control how aggressive **db_dump** should be when salvaging your databases. diff --git a/docs_src/guides/gsg/index.md b/docs_src/guides/gsg/index.md index 101f38340..84508bd5c 100644 --- a/docs_src/guides/gsg/index.md +++ b/docs_src/guides/gsg/index.md @@ -5,6 +5,8 @@ source: docs/gsg/C/index.html --- # Getting Started with Berkeley DB +**Language:** C (this page) · [C++](cxx/index.md) · [Java](java/index.md) + **Legal Notice** This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html diff --git a/docs_src/guides/gsg/java/CoreEnvUsage.md b/docs_src/guides/gsg/java/CoreEnvUsage.md new file mode 100644 index 000000000..bbf52769f --- /dev/null +++ b/docs_src/guides/gsg/java/CoreEnvUsage.md @@ -0,0 +1,99 @@ +--- +title: "Managing Databases in Environments" +api-name: "Managing Databases in Environments" +source: docs/gsg/JAVA/CoreEnvUsage.html +--- +## Managing Databases in Environments + +In Database Environments, we introduced environments. While environments are not used in the example built in this book, they are so commonly used for a wide class of DB applications that it is necessary to show their basic usage, if only from a completeness perspective. + +To use an environment, you must first open it. At open time, you must identify the directory in which it resides. This directory must exist prior to the open attempt. You can also identify open properties, such as whether the environment can be created if it does not already exist. + +You will also need to initialize the in-memory cache when you open your environment. + +For example, to create an environment handle and open an environment: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +File envHome = new File("/export1/testEnv"); +try { + EnvironmentConfig envConf = new EnvironmentConfig(); + envConf.setAllowCreate(true); // If the environment does not + // exist, create it. + envConf.setInitializeCache(true); // Initialize the in-memory + // cache. + + myEnv = new Environment(envHome, envConf); +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +Once an environment is opened, you can open databases in it. Note that by default databases are stored in the environment's home directory, or relative to that directory if you provide any sort of a path in the database's file name: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +Database myDb = null; +File envHome = new File("/export1/testEnv"); +String dbFileName = new String("mydb.db".getBytes("UTF-8"), "UTF-8"); + +try { + EnvironmentConfig envConf = new EnvironmentConfig(); + envConf.setAllowCreate(true); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setAllowCreate(true); + dbConfig.setType(DatabaseType.BTREE); + + myEnv = new Environment(envHome, envConf); + myDb = myEnv.openDatabase(null, dbFileName, null, dbConfig); +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +When you are done with an environment, you must close it. It is recommended that before closing an environment, you close any open databases. + +``` c +finally { + try { + if (myDb != null) { + myDb.close(); + } + + if (myEnv != null) { + myEnv.close(); + } + } catch (DatabaseException de) { + // Exception handling goes here + } +} +``` diff --git a/docs_src/guides/gsg/java/CoreJavaUsage.md b/docs_src/guides/gsg/java/CoreJavaUsage.md new file mode 100644 index 000000000..fb0d5ea15 --- /dev/null +++ b/docs_src/guides/gsg/java/CoreJavaUsage.md @@ -0,0 +1,117 @@ +--- +title: "Database Example" +api-name: "Database Example" +source: docs/gsg/JAVA/CoreJavaUsage.html +--- +## Database Example + +Throughout this book we will build a couple of applications that load and retrieve inventory data from DB databases. While we are not yet ready to begin reading from or writing to our databases, we can at least create the class that we will use to manage our databases. + +Note that subsequent examples in this book will build on this code to perform the more interesting work of writing to and reading from the databases. + +Note that you can find the complete implementation of these functions in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 7.1 MyDbs Class** + +To manage our database open and close activities, we encapsulate them in the `MyDbs` class. There are several good reasons to do this, the most important being that we can ensure our databases are closed by putting that activity in the `MyDbs` class destructor. + +To begin, we import some needed classes: + +``` c +// File: MyDbs.java +package db.GettingStarted; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; + +import java.io.FileNotFoundException; +``` + +And then we write our class declaration and provided some necessary private data members: + +``` c +public class MyDbs { + + // The databases that our application uses + private Database vendorDb = null; + private Database inventoryDb = null; + + private String vendordb = "VendorDB.db"; + private String inventorydb = "InventoryDB.db"; + + // Our constructor does nothing + public MyDbs() {} +``` + +Next we need a `setup()` method. This is where we configure and open our databases. + +``` c + // The setup() method opens all our databases + // for us. + public void setup(String databasesHome) + throws DatabaseException { + + DatabaseConfig myDbConfig = new DatabaseConfig(); + + myDbConfig.setErrorStream(System.err); + myDbConfig.setErrorPrefix("MyDbs"); + myDbConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + + // Now open, or create and open, our databases + // Open the vendors and inventory databases + try { + vendordb = databasesHome + "/" + vendordb; + vendorDb = new Database(vendordb, + null, + myDbConfig); + + inventorydb = databasesHome + "/" + inventorydb; + inventoryDb = new Database(inventorydb, + null, + myDbConfig); + } catch(FileNotFoundException fnfe) { + System.err.println("MyDbs: " + fnfe.toString()); + System.exit(-1); + } + } +``` + +Finally, we provide some getter methods, and our `close()` method. + +``` c + // getter methods + public Database getVendorDB() { + return vendorDb; + } + + public Database getInventoryDB() { + return inventoryDb; + } + + // Close the databases + public void close() { + try { + if (vendorDb != null) { + vendorDb.close(); + } + + if (inventoryDb != null) { + inventoryDb.close(); + } + } catch(DatabaseException dbe) { + System.err.println("Error closing MyDbs: " + + dbe.toString()); + System.exit(-1); + } + } +} +``` diff --git a/docs_src/guides/gsg/java/Cursors.md b/docs_src/guides/gsg/java/Cursors.md new file mode 100644 index 000000000..efcb2ba3d --- /dev/null +++ b/docs_src/guides/gsg/java/Cursors.md @@ -0,0 +1,85 @@ +--- +title: "Chapter 9. Using Cursors" +api-name: "Chapter 9. Using Cursors" +source: docs/gsg/JAVA/Cursors.html +--- +## Chapter 9. Using Cursors + +**Table of Contents** + + [Opening and Closing Cursors](Cursors.md#openCursor) + + [Getting Records Using the Cursor](Positioning.md) + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + + [Putting Records Using Cursors](PutEntryWCursor.md) + + [Deleting Records Using Cursors](DeleteEntryWCursor.md) + + [Replacing Records Using Cursors](ReplacingEntryWCursor.md) + + [Cursor Example](cursorJavaUsage.md) + +Cursors provide a mechanism by which you can iterate over the records in a database. Using cursors, you can get, put, and delete database records. If a database allows duplicate records, then cursors are the easiest way that you can access anything other than the first record for a given key. + +This chapter introduces cursors. It explains how to open and close them, how to use them to modify databases, and how to use them with duplicate records. + +## Opening and Closing Cursors + +To use a cursor, you must open it using the `Database.openCursor()` method. When you open a cursor, you can optionally pass it a `CursorConfig` object to set cursor properties. The cursor properties that you can set allows you to control the isolation level that the cursor will obey. See the *Berkeley DB Getting Started with Transaction Processing* guide for more information. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseException; + +import java.io.FileNotFoundException; + +... +Database myDatabase = null; +Cursor myCursor = null; + +try { + myDatabase = new Database("myDB", null, null); + + myCursor = myDatabase.openCursor(null, null); +} catch (FileNotFoundException fnfe) { + // Exception handling goes here ... +} catch (DatabaseException dbe) { + // Exception handling goes here ... +} +``` + +To close the cursor, call the `Cursor.close()` method. Note that if you close a database that has cursors open in it, then it will throw an exception and close any open cursors for you. For best results, close your cursors from within a `finally` block. However, it is recommended that you always close all cursor handles immediately after their use to ensure concurrency and to release resources such as page locks. + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; + +... +try { + ... +} catch ... { +} finally { + try { + if (myCursor != null) { + myCursor.close(); + } + + if (myDatabase != null) { + myDatabase.close(); + } + } catch(DatabaseException dbe) { + System.err.println("Error in close: " + dbe.toString()); + } +} +``` diff --git a/docs_src/guides/gsg/java/DBAdmin.md b/docs_src/guides/gsg/java/DBAdmin.md new file mode 100644 index 000000000..3c433967e --- /dev/null +++ b/docs_src/guides/gsg/java/DBAdmin.md @@ -0,0 +1,54 @@ +--- +title: "Administrative Methods" +api-name: "Administrative Methods" +source: docs/gsg/JAVA/DBAdmin.html +--- +## Administrative Methods + +Both the `Environment` and `Database` classes provide methods that are useful for manipulating databases. These methods are: + +- `Database.getDatabaseName()` + + Returns the database's name. + + ``` c + String dbName = myDatabase.getDatabaseName(); + ``` + +- `Database.rename()` + + Renames the specified database. If no value is given for the *`database`* parameter, then the entire file referenced by this method is renamed. + + Never rename a database that has handles opened for it. Never rename a file that contains databases with opened handles. + + ``` c + import java.io.FileNotFoundException; + ... + myDatabase.close(); + try { + myDatabase.rename("mydb.db", // Database file to rename + null, // Database to rename. Not used so + // the entire file is renamed. + "newdb.db", // New name to use. + null); // DatabaseConfig object. + // None provided. + } catch (FileNotFoundException fnfe) { + // Exception handling goes here + } + ``` + +- `Environment.truncateDatabase()` + + Deletes every record in the database and optionally returns the number of records that were deleted. Note that it is much less expensive to truncate a database without counting the number of records deleted than it is to truncate and count. + + ``` c + int numDiscarded = + myEnv.truncate(null, // txn handle + myDatabase.getDatabaseName(), // database name + true); // If true, then the + // number of records + // deleted are counted. + System.out.println("Discarded " + numDiscarded + + " records from database " + + myDatabase.getDatabaseName()); + ``` diff --git a/docs_src/guides/gsg/java/DBEntry.md b/docs_src/guides/gsg/java/DBEntry.md new file mode 100644 index 000000000..f129221ea --- /dev/null +++ b/docs_src/guides/gsg/java/DBEntry.md @@ -0,0 +1,94 @@ +--- +title: "Chapter 8. Database Records" +api-name: "Chapter 8. Database Records" +source: docs/gsg/JAVA/DBEntry.html +--- +## Chapter 8. Database Records + +**Table of Contents** + + [Using Database Records](DBEntry.md#usingDbEntry) + + [Reading and Writing Database Records](usingDbt.md) + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#databaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + + [Using the BIND APIs](bindAPI.md) + + [Numerical and String Objects](bindAPI.md#bindPrimitive) + + [Serializable Complex Objects](bindAPI.md#object2dbt) + + [Custom Tuple Bindings](bindAPI.md#customTuple) + + [Database Usage Example](dbtJavaUsage.md) + +DB records contain two parts — a key and some data. Both the key and its corresponding data are encapsulated in `DatabaseEntry` class objects. Therefore, to access a DB record, you need two such objects, one for the key and one for the data. + +`DatabaseEntry` can hold any kind of data from simple Java primitive types to complex Java objects so long as that data can be represented as a Java `byte` array. Note that due to performance considerations, you should not use Java serialization to convert a Java object to a `byte` array. Instead, use the Bind APIs to perform this conversion (see Using the BIND APIs for more information). + +This chapter describes how you can convert both Java primitives and Java class objects into and out of `byte` arrays. It also introduces storing and retrieving key/value pairs from a database. In addition, this chapter describes how you can use comparators to influence how DB sorts its database records. + +## Using Database Records + +Each database record is comprised of two `DatabaseEntry` objects — one for the key and another for the data. The key and data information are passed to- and returned from DB using `DatabaseEntry` objects as `byte` arrays. Using `DatabaseEntry`s allows DB to change the underlying byte array as well as return multiple values (that is, key and data). Therefore, using `DatabaseEntry` instances is mostly an exercise in efficiently moving your keys and your data in and out of `byte` arrays. + +For example, to store a database record where both the key and the data are Java `String` objects, you instantiate a pair of `DatabaseEntry` objects: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; + +... + +String aKey = "key"; +String aData = "data"; + +try { + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(aData.getBytes("UTF-8")); +} catch (Exception e) { + // Exception handling goes here +} + + // Storing the record is described later in this chapter +``` + +### Note + +Notice that we specify `UTF-8` when we retrieve the `byte` array from our `String` object. Without parameters, `String.getBytes()` uses the Java system's default encoding. You should never use a system's default encoding when storing data in a database because the encoding can change. + +When the record is retrieved from the database, the method that you use to perform this operation populates two `DatabaseEntry` instances for you, one for the key and another for the data. Assuming Java `String` objects, you retrieve your data from the `DatabaseEntry` as follows: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; + +... + +// theKey and theData are DatabaseEntry objects. Database +// retrieval is described later in this chapter. For now, +// we assume some database get method has populated these +// objects for us. + +// Use DatabaseEntry.getData() to retrieve the encapsulated Java +// byte array. + +byte[] myKey = theKey.getData(); +byte[] myData = theData.getData(); + +String key = new String(myKey, "UTF-8"); +String data = new String(myData, "UTF-8"); +``` + +There are a large number of mechanisms that you can use to move data in and out of `byte` arrays. To help you with this activity, DB provides the bind APIs. These APIs allow you to efficiently store both primitive data types and complex objects in `byte` arrays. + +The next section describes basic database put and get operations. A basic understanding of database access is useful when describing database storage of more complex data such as is supported by the bind APIs. Basic bind API usage is then described in Using the BIND APIs. diff --git a/docs_src/guides/gsg/java/DeleteEntryWCursor.md b/docs_src/guides/gsg/java/DeleteEntryWCursor.md new file mode 100644 index 000000000..e0d5eba2e --- /dev/null +++ b/docs_src/guides/gsg/java/DeleteEntryWCursor.md @@ -0,0 +1,56 @@ +--- +title: "Deleting Records Using Cursors" +api-name: "Deleting Records Using Cursors" +source: docs/gsg/JAVA/DeleteEntryWCursor.html +--- +## Deleting Records Using Cursors + +To delete a record using a cursor, simply position the cursor to the record that you want to delete and then call + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + // Create DatabaseEntry objects + // searchKey is some String. + DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(); + + // Open a cursor using a database handle + cursor = myDatabase.openCursor(null, null); + + // Position the cursor. Ignoring the return value for clarity + OperationStatus retVal = cursor.getSearchKey(theKey, theData, + LockMode.DEFAULT); + + // Count the number of records using the given key. If there is only + // one, delete that record. + if (cursor.count() == 1) { + System.out.println("Deleting " + + new String(theKey.getData(), "UTF-8") + + "|" + + new String(theData.getData(), "UTF-8")); + cursor.delete(); + } +} catch (Exception e) { + // Exception handling goes here +} finally { + // Make sure to close the cursor + cursor.close(); +} +``` diff --git a/docs_src/guides/gsg/java/Env.md b/docs_src/guides/gsg/java/Env.md new file mode 100644 index 000000000..5d4def109 --- /dev/null +++ b/docs_src/guides/gsg/java/Env.md @@ -0,0 +1,84 @@ +--- +title: "Chapter 2. Database Environments" +api-name: "Chapter 2. Database Environments" +source: docs/gsg/JAVA/Env.html +--- +## Chapter 2. Database Environments + +**Table of Contents** + + [Opening Database Environments](Env.md#EnvOpen) + + [Closing Database Environments](EnvClose.md) + + [Environment Properties](EnvProps.md) + + [The EnvironmentConfig Class](EnvProps.md#envconfig) + + [EnvironmentMutableConfig](EnvProps.md#envhandleconfig) + +Environments are optional, but very commonly used, for Berkeley DB applications built using the base API. If you are using the DPL, then environments are required. + +Database environments encapsulate one or more databases. This encapsulation provides your threads with efficient access to your databases by allowing a single in-memory cache to be used for each of the databases contained in the environment. This encapsulation also allows you to group operations performed against multiple databases inside a single transactions (see the *Berkeley DB, Java Edition Getting Started with Transaction Processing* guide for more information). + +Most commonly you use database environments to create and open databases (you close individual databases using the individual database handles). You can also use environments to delete and rename databases. For transactional applications, you use the environment to start transactions. For non-transactional applications, you use the environment to sync your in-memory cache to disk. + +## Opening Database Environments + +You open a database environment by instantiating an `Environment` object. You must provide to the constructor the name of the on-disk directory where the environment is to reside. This directory location must exist or the open will fail. + +By default, the environment is not created for you if it does not exist. Set the creation property to `true` if you want the environment to be created. For example: + +``` c + +import com.sleepycat.je.DatabaseException; +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 myDbEnvironment = null; + +try { + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + myDbEnvironment = new Environment(new File("/export/dbEnv"), + envConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here +} +``` + +``` c +package db.gettingStarted; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +// Open the environment. Allow it to be created if it does not already +// exist. +Environment myDbEnvironment = null; + +try { + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + myDbEnvironment = new Environment(new File("/export/dbEnv"), + envConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { +// Exception handling goes here +} +``` + +Your application can open and use as many environments as you have disk and memory to manage, although most applications will use just one environment. Also, you can instantiate multiple `Environment` objects for the same physical environment. diff --git a/docs_src/guides/gsg/java/EnvClose.md b/docs_src/guides/gsg/java/EnvClose.md new file mode 100644 index 000000000..ff44354e2 --- /dev/null +++ b/docs_src/guides/gsg/java/EnvClose.md @@ -0,0 +1,28 @@ +--- +title: "Closing Database Environments" +api-name: "Closing Database Environments" +source: docs/gsg/JAVA/EnvClose.html +--- +## Closing Database Environments + +You close your environment by calling the `Environment.close()` method. This method performs a checkpoint, so it is not necessary to perform a sync or a checkpoint explicitly before calling it. For information on checkpoints, see the *Berkeley DB, Java Edition Getting Started with Transaction Processing* guide. For information on syncs, see the *Getting Started with Transaction Processing for Java* guide. + +``` c +import com.sleepycat.db.DatabaseException; + +import com.sleepycat.db.Environment; + +... + +try { + if (myDbEnvironment != null) { + myDbEnvironment.close(); + } +} catch (DatabaseException dbe) { + // Exception handling goes here +} +``` + +You should close your environment(s) only after all other database activities have completed. It is recommended that you close any databases currently open in the environment prior to closing the environment. + +Closing the last environment handle in your application causes all internal data structures to be released. If there are any opened databases or stores, then DB will complain before closing them as well. At this time, any open cursors are also closed, and any on-going transactions are aborted. However, it is recommended that you always close all cursor handles immediately after their use to ensure concurrency and to release resources such as page locks. diff --git a/docs_src/guides/gsg/java/EnvProps.md b/docs_src/guides/gsg/java/EnvProps.md new file mode 100644 index 000000000..4ea475632 --- /dev/null +++ b/docs_src/guides/gsg/java/EnvProps.md @@ -0,0 +1,113 @@ +--- +title: "Environment Properties" +api-name: "Environment Properties" +source: docs/gsg/JAVA/EnvProps.html +--- +## Environment Properties + + [The EnvironmentConfig Class](EnvProps.md#envconfig) + + [EnvironmentMutableConfig](EnvProps.md#envhandleconfig) + +You set properties for the `Environment` using the `EnvironmentConfig` class. You can also set properties for a specific `Environment` instance using `EnvironmentMutableConfig`. + +### The EnvironmentConfig Class + +The `EnvironmentConfig` class makes a large number of fields and methods available to you. Describing all of these tuning parameters is beyond the scope of this manual. However, there are a few properties that you are likely to want to set. They are described here. + +Note that for each of the properties that you can commonly set, there is a corresponding getter method. Also, you can always retrieve the `EnvironmentConfig` object used by your environment using the `Environment.getConfig()` method. + +You set environment configuration parameters using the following methods on the `EnvironmentConfig` class: + +- `EnvironmentConfig.setAllowCreate()` + + If `true`, the database environment is created when it is opened. If `false`, environment open fails if the environment does not exist. This property has no meaning if the database environment already exists. Default is `false`. + +- `EnvironmentConfig.setReadOnly()` + + If `true`, then all databases opened in this environment must be opened as read-only. If you are writing a multi-process application, then all but one of your processes must set this value to `true`. Default is `false`. + +- `EnvironmentConfig.setTransactional()` + + If `true`, configures the database environment to support transactions. Default is `false`. + +For example: + +``` c +package db.gettingStarted; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myDatabaseEnvironment = null; +try { + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + envConfig.setTransactional(true); + myDatabaseEnvironment = + new Environment(new File("/export/dbEnv"), envConfig); +} catch (DatabaseException dbe) { + System.err.println(dbe.toString()); + System.exit(1); +} catch (FileNotFoundException fnfe) { + System.err.println(fnfe.toString()); + System.exit(-1); +} +``` + +### EnvironmentMutableConfig + +`EnvironmentMutableConfig` manages properties that can be reset after the `Environment` object has been constructed. In addition, `EnvironmentConfig` extends `EnvironmentMutableConfig`, so you can set these mutable properties at `Environment` construction time if necessary. + +The `EnvironmentMutableConfig` class allows you to set the following properties: + +- `setCachePercent()` + + Determines the percentage of JVM memory available to the DB cache. See Selecting the Cache Size for more information. + +- `setCacheSize()` + + Determines the total amount of memory available to the database cache. See Selecting the Cache Size for more information. + +- `setTxnNoSync()` + + Determines whether change records created due to a transaction commit are written to the backing log files on disk. A value of `true` causes the data to not be flushed to disk. See the *Getting Started with Transaction Processing for Java* guide for more information. + +- `setTxnWriteNoSync()` + + Determines whether logs are flushed on transaction commit (the logs are still written, however). By setting this value to `true`, you potentially gain better performance than if you flush the logs on commit, but you do so by losing some of your transaction durability guarantees. See the *Getting Started with Transaction Processing for Java* guide for more information. + +There is also a corresponding getter method (`getTxnNoSync()`). Moreover, you can always retrieve your environment's `EnvironmentMutableConfig` object by using the `Environment.getMutableConfig()` method. + +For example: + +``` c +package db.gettingStarted; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentMutableConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +try { + Environment myEnv = new Environment(new File("/export/dbEnv"), null); + EnvironmentMutableConfig envMutableConfig = + new EnvironmentMutableConfig(); + envMutableConfig.setTxnNoSync(true); + myEnv.setMutableConfig(envMutableConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/Positioning.md b/docs_src/guides/gsg/java/Positioning.md new file mode 100644 index 000000000..f8a92d393 --- /dev/null +++ b/docs_src/guides/gsg/java/Positioning.md @@ -0,0 +1,317 @@ +--- +title: "Getting Records Using the Cursor" +api-name: "Getting Records Using the Cursor" +source: docs/gsg/JAVA/Positioning.html +--- +## Getting Records Using the Cursor + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + +To iterate over database records, from the first record to the last, simply open the cursor and then use the `Cursor.getNext()` method. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Cursor; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Cursor cursor = null; +try { + ... + Database myDatabase = null; + // Database open omitted for brevity + ... + + // Open the cursor. + cursor = myDatabase.openCursor(null, null); + + // Cursors need a pair of DatabaseEntry objects to operate. These hold + // the key and data found at any given position in the database. + DatabaseEntry foundKey = new DatabaseEntry(); + DatabaseEntry foundData = new DatabaseEntry(); + + // To iterate, just call getNext() until the last database record has + // been read. All cursor operations return an OperationStatus, so just + // read until we no longer see OperationStatus.SUCCESS + while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == + OperationStatus.SUCCESS) { + // getData() on the DatabaseEntry objects returns the byte array + // held by that object. We use this to get a String value. If the + // DatabaseEntry held a byte array representation of some other + // data type (such as a complex object) then this operation would + // look considerably different. + String keyString = new String(foundKey.getData(), "UTF-8"); + String dataString = new String(foundData.getData(), "UTF-8"); + System.out.println("Key | Data : " + keyString + " | " + + dataString + ""); + } +} catch (DatabaseException de) { + System.err.println("Error accessing database." + de); +} finally { + // Cursors must be closed. + cursor.close(); +} +``` + +To iterate over the database from the last record to the first, instantiate the cursor, and then use `Cursor.getPrev()` until you read the first record in the database. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + + // Open the cursor. + cursor = myDatabase.openCursor(null, null); + + // Get the DatabaseEntry objects that the cursor will use. + DatabaseEntry foundKey = new DatabaseEntry(); + DatabaseEntry foundData = new DatabaseEntry(); + + // Iterate from the last record to the first in the database + while (cursor.getPrev(foundKey, foundData, LockMode.DEFAULT) == + OperationStatus.SUCCESS) { + + String theKey = new String(foundKey.getData(), "UTF-8"); + String theData = new String(foundData.getData(), "UTF-8"); + System.out.println("Key | Data : " + theKey + " | " + + theData + ""); + } +} catch (DatabaseException de) { + System.err.println("Error accessing database." + de); +} finally { + // Cursors must be closed. + cursor.close(); +} +``` + +### Searching for Records + +You can use cursors to search for database records. You can search based on just a key, or you can search based on both the key and the data. You can also perform partial matches if your database supports sorted duplicate sets. In all cases, the key and data parameters of these methods are filled with the key and data values of the database record to which the cursor is positioned as a result of the search. + +Also, if the search fails, then cursor's state is left unchanged and `OperationStatus.NOTFOUND` is returned. + +The following `Cursor` methods allow you to perform database searches: + +- `Cursor.getSearchKey()` + + Moves the cursor to the first record in the database with the specified key. + +- `Cursor.getSearchKeyRange()` + + Identical to `Cursor.getSearchKey()` unless you are using the BTree access. In this case, the cursor moves to the first record in the database whose key is greater than or equal to the specified key. This comparison is determined by the comparator that you provide for the database. If no comparator is provided, then the default lexicographical sorting is used. + + For example, suppose you have database records that use the following Strings as keys: + + ``` c + Alabama + Alaska + Arizona + ``` + + Then providing a search key of `Alaska` moves the cursor to the second key noted above. Providing a key of `Al` moves the cursor to the first key (`Alabama`), providing a search key of `Alas` moves the cursor to the second key (`Alaska`), and providing a key of `Ar` moves the cursor to the last key (`Arizona`). + +- `Cursor.getSearchBoth()` + + Moves the cursor to the first record in the database that uses the specified key and data. + +- `Cursor.getSearchBothRange()` + + Moves the cursor to the first record in the database whose key matches the specified key and whose data is greater than or equal to the specified data. If the database supports duplicate records, then on matching the key, the cursor is moved to the duplicate record with the smallest data that is greater than or equal to the specified data. + + For example, suppose your database uses BTree and it has database records that use the following key/data pairs: + + ``` c + Alabama/Athens + Alabama/Florence + Alaska/Anchorage + Alaska/Fairbanks + Arizona/Avondale + Arizona/Florence + ``` + + then providing: + + | a search key of ... | and a search data of ... | moves the cursor to ... | + |---------------------|--------------------------|-------------------------| + | Alaska | Fa | Alaska/Fairbanks | + | Arizona | Fl | Arizona/Florence | + | Alaska | An | Alaska/Anchorage | + +For example, assuming a database containing sorted duplicate records of U.S. States/U.S Cities key/data pairs (both as Strings), then the following code fragment can be used to position the cursor to any record in the database and print its key/data values: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +// For this example, hard code the search key and data +String searchKey = "Alaska"; +String searchData = "Fa"; + +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + + // Open the cursor. + cursor = myDatabase.openCursor(null, null); + + DatabaseEntry theKey = + new DatabaseEntry(searchKey.getBytes("UTF-8")); + DatabaseEntry theData = + new DatabaseEntry(searchData.getBytes("UTF-8")); + + // Open a cursor using a database handle + cursor = myDatabase.openCursor(null, null); + + // Perform the search + OperationStatus retVal = cursor.getSearchBothRange(theKey, theData, + LockMode.DEFAULT); + // NOTFOUND is returned if a record cannot be found whose key + // matches the search key AND whose data begins with the search data. + if (retVal == OperationStatus.NOTFOUND) { + System.out.println(searchKey + "/" + searchData + + " not matched in database " + + myDatabase.getDatabaseName()); + } else { + // Upon completing a search, the key and data DatabaseEntry + // parameters for getSearchBothRange() are populated with the + // key/data values of the found record. + String foundKey = new String(theKey.getData(), "UTF-8"); + String foundData = new String(theData.getData(), "UTF-8"); + System.out.println("Found record " + foundKey + "/" + foundData + + "for search key/data: " + searchKey + + "/" + searchData); + } + +} catch (Exception e) { + // Exception handling goes here +} finally { + // Make sure to close the cursor + cursor.close(); +} +``` + +### Working with Duplicate Records + +A record is a duplicate of another record if the two records share the same key. For duplicate records, only the data portion of the record is unique. + +Duplicate records are supported only for the BTree or Hash access methods. For information on configuring your database to use duplicate records, see Allowing Duplicate Records. + +If your database supports duplicate records, then it can potentially contain multiple records that share the same key. By default, normal database get operations will only return the first such record in a set of duplicate records. Typically, subsequent duplicate records are accessed using a cursor. The following `Cursor` methods are interesting when working with databases that support duplicate records: + +- `Cursor.getNext()`, `Cursor.getPrev()` + + Shows the next/previous record in the database, regardless of whether it is a duplicate of the current record. For an example of using these methods, see Getting Records Using the Cursor. + +- `Cursor.getSearchBothRange()` + + Useful for seeking the cursor to a specific record, regardless of whether it is a duplicate record. See Searching for Records for more information. + +- `Cursor.getNextNoDup()`, `Cursor.getPrevNoDup()` + + Gets the next/previous non-duplicate record in the database. This allows you to skip over all the duplicates in a set of duplicate records. If you call `Cursor.getPrevNoDup()`, then the cursor is positioned to the last record for the previous key in the database. For example, if you have the following records in your database: + + ``` c + Alabama/Athens + Alabama/Florence + Alaska/Anchorage + Alaska/Fairbanks + Arizona/Avondale + Arizona/Florence + ``` + + and your cursor is positioned to `Alaska/Fairbanks`, and you then call `Cursor.getPrevNoDup()`, then the cursor is positioned to Alabama/Florence. Similarly, if you call `Cursor.getNextNoDup()`, then the cursor is positioned to the first record corresponding to the next key in the database. + + If there is no next/previous key in the database, then `OperationStatus.NOTFOUND` is returned, and the cursor is left unchanged. + +- Gets the next record that shares the current key. If the cursor is positioned at the last record in the duplicate set and you call `Cursor.getNextDup()`, then `OperationStatus.NOTFOUND` is returned and the cursor is left unchanged. Likewise, if you call `getPrevDup()` and the cursor is positioned at the first record in the duplicate set, then `OperationStatus.NOTFOUND` is returned and the cursor is left unchanged. + +- `Cursor.count()` + + Returns the total number of records that share the current key. + +For example, the following code fragment positions a cursor to a key and displays it and all its duplicates. Note that the following code fragment assumes that the database contains only String objects for the keys and data. + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + + // Create DatabaseEntry objects + // searchKey is some String. + DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(); + + // Open a cursor using a database handle + cursor = myDatabase.openCursor(null, null); + + // Position the cursor + // Ignoring the return value for clarity + OperationStatus retVal = cursor.getSearchKey(theKey, theData, + LockMode.DEFAULT); + + // Count the number of duplicates. If the count is greater than 1, + // print the duplicates. + if (cursor.count() > 1) { + while (retVal == OperationStatus.SUCCESS) { + String keyString = new String(theKey.getData(), "UTF-8"); + String dataString = new String(theData.getData(), "UTF-8"); + System.out.println("Key | Data : " + keyString + " | " + + dataString + ""); + + retVal = cursor.getNextDup(theKey, theData, LockMode.DEFAULT); + } + } +} catch (Exception e) { + // Exception handling goes here +} finally { + // Make sure to close the cursor + cursor.close(); +} +``` diff --git a/docs_src/guides/gsg/java/PutEntryWCursor.md b/docs_src/guides/gsg/java/PutEntryWCursor.md new file mode 100644 index 000000000..db4ad01a6 --- /dev/null +++ b/docs_src/guides/gsg/java/PutEntryWCursor.md @@ -0,0 +1,86 @@ +--- +title: "Putting Records Using Cursors" +api-name: "Putting Records Using Cursors" +source: docs/gsg/JAVA/PutEntryWCursor.html +--- +## Putting Records Using Cursors + +You can use cursors to put records into the database. DB's behavior when putting records into the database differs depending on the flags that you use when writing the record, on the access method that you are using, and on whether your database supports sorted duplicates. + +Note that when putting records to the database using a cursor, the cursor is positioned at the record you inserted. + +- `Cursor.putNoDupData()` + + If the provided key already exists in the database, then this method returns `OperationStatus.KEYEXIST`. + + If the key does not exist, then the order that the record is put into the database is determined by the insertion order in use by the database. If a comparison function has been provided to the database, the record is inserted in its sorted location. Otherwise (assuming BTree), lexicographical sorting is used, with shorter items collating before longer items. + + This flag can only be used for the BTree and Hash access methods, and only if the database has been configured to support sorted duplicate data items (`DB_DUPSORT` was specified at database creation time). + + This flag cannot be used with the Queue or Recno access methods. + + For more information on duplicate records, see Allowing Duplicate Records. + +- `Cursor.putNoOverwrite()` + + If the provided key already exists in the database, then this method returns . + + If the key does not exist, then the order that the record is put into the database is determined by the BTree (key) comparator in use by the database. + +- `Cursor.putKeyFirst()` + + For databases that do not support duplicates, this method behaves exactly the same as if a default insertion was performed. If the database supports duplicate records, and a duplicate sort function has been specified, the inserted data item is added in its sorted location. If the key already exists in the database and no duplicate sort function has been specified, the inserted data item is added as the first of the data items for that key. + +- `Cursor.putKeyLast()` + + Behaves exactly as if `Cursor.putKeyFirst()` was used, except that if the key already exists in the database and no duplicate sort function has been specified, the inserted data item is added as the last of the data items for that key. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.OperationStatus; + +... + +// Create the data to put into the database +String key1str = "My first string"; +String data1str = "My first data"; +String key2str = "My second string"; +String data2str = "My second data"; +String data3str = "My third data"; + +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + + DatabaseEntry key1 = new DatabaseEntry(key1str.getBytes("UTF-8")); + DatabaseEntry data1 = new DatabaseEntry(data1str.getBytes("UTF-8")); + DatabaseEntry key2 = new DatabaseEntry(key2str.getBytes("UTF-8")); + DatabaseEntry data2 = new DatabaseEntry(data2str.getBytes("UTF-8")); + DatabaseEntry data3 = new DatabaseEntry(data3str.getBytes("UTF-8")); + + // Open a cursor using a database handle + cursor = myDatabase.openCursor(null, null); + + // Assuming an empty database. + + OperationStatus retVal = cursor.put(key1, data1); // SUCCESS + retVal = cursor.put(key2, data2); // SUCCESS + retVal = cursor.put(key2, data3); // SUCCESS if dups allowed, + // KEYEXIST if not. + +} catch (Exception e) { + // Exception handling goes here +} finally { + // Make sure to close the cursor + cursor.close(); +} +``` diff --git a/docs_src/guides/gsg/java/ReplacingEntryWCursor.md b/docs_src/guides/gsg/java/ReplacingEntryWCursor.md new file mode 100644 index 000000000..e172ac7ca --- /dev/null +++ b/docs_src/guides/gsg/java/ReplacingEntryWCursor.md @@ -0,0 +1,53 @@ +--- +title: "Replacing Records Using Cursors" +api-name: "Replacing Records Using Cursors" +source: docs/gsg/JAVA/ReplacingEntryWCursor.html +--- +## Replacing Records Using Cursors + +You replace the data for a database record by using `Cursor.putCurrent()`. + +``` c +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... +Cursor cursor = null; +Database myDatabase = null; +try { + ... + // Database open omitted for brevity + ... + // Create DatabaseEntry objects + // searchKey is some String. + DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(); + + // Open a cursor using a database handle + cursor = myDatabase.openCursor(null, null); + + // Position the cursor. Ignoring the return value for clarity + OperationStatus retVal = cursor.getSearchKey(theKey, theData, + LockMode.DEFAULT); + + // Replacement data + String replaceStr = "My replacement string"; + DatabaseEntry replacementData = + new DatabaseEntry(replaceStr.getBytes("UTF-8")); + cursor.putCurrent(replacementData); +} catch (Exception e) { + // Exception handling goes here +} finally { + // Make sure to close the cursor + cursor.close(); +} +``` + +Note that you cannot change a record's key using this method; the key parameter is always ignored when you replace a record. + +When replacing the data portion of a record, if you are replacing a record that is a member of a sorted duplicates set, then the replacement will be successful only if the new record sorts identically to the old record. This means that if you are replacing a record that is a member of a sorted duplicates set, and if you are using the default lexicographic sort, then the replacement will fail due to violating the sort order. However, if you provide a custom sort routine that, for example, sorts based on just a few bytes out of the data item, then potentially you can perform a direct replacement and still not violate the restrictions described here. + +Under these circumstances, if you want to replace the data contained by a duplicate record, and you are not using a custom sort routine, then delete the record and create a new record with the desired key and data. diff --git a/docs_src/guides/gsg/java/_meta.toml b/docs_src/guides/gsg/java/_meta.toml new file mode 100644 index 000000000..750566d14 --- /dev/null +++ b/docs_src/guides/gsg/java/_meta.toml @@ -0,0 +1,68 @@ +# Nav/index metadata for the gsg Java variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Getting Started with Berkeley DB (Java)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "javadplconcepts", + "accessmethods", + "databaseLimits", + "coreExceptions", + "returns", + "gettingit", + "Env", + "EnvClose", + "EnvProps", + "dpl", + "persist_first", + "persistobject", + "saveret", + "persist_index", + "dplindexcreate", + "persist_access", + "simpleda", + "simpleput", + "simpleget", + "getmultiple", + "dpl_entityjoin", + "dpl_delete", + "dpl_replace", + "dpl_example", + "inventoryclass", + "mydbenv-persist", + "dataaccessorclass", + "dpl_exampledatabaseput", + "dpl_exampleinventoryread", + "baseapi", + "databases", + "coredbclose", + "dbprops", + "DBAdmin", + "dbErrorReporting", + "CoreEnvUsage", + "CoreJavaUsage", + "DBEntry", + "usingDbt", + "bindAPI", + "dbtJavaUsage", + "Cursors", + "Positioning", + "PutEntryWCursor", + "DeleteEntryWCursor", + "ReplacingEntryWCursor", + "cursorJavaUsage", + "indexes", + "keyCreator", + "secondaryProps", + "readSecondary", + "secondaryDelete", + "secondaryCursor", + "joins", + "javaindexusage", + "dbconfig", + "cachesize", + "btree", +] diff --git a/docs_src/guides/gsg/java/accessmethods.md b/docs_src/guides/gsg/java/accessmethods.md new file mode 100644 index 000000000..d0055c89f --- /dev/null +++ b/docs_src/guides/gsg/java/accessmethods.md @@ -0,0 +1,84 @@ +--- +title: "Access Methods" +api-name: "Access Methods" +source: docs/gsg/JAVA/accessmethods.html +--- +## Access Methods + + [Selecting Access Methods](accessmethods.md#selectAM) + + [Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + + [Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + +While this manual will focus primarily on the BTree access method, it is still useful to briefly describe all of the access methods that DB makes available. + +### Note + +If you are using the DPL, be aware that it only supports the BTree access method. For that reason, you can skip this section. + +Note that an access method can be selected only when the database is created. Once selected, actual API usage is generally identical across all access methods. That is, while some exceptions exist, mechanically you interact with the library in the same way regardless of which access method you have selected. + +The access method that you should choose is gated first by what you want to use as a key, and then secondly by the performance that you see for a given access method. + +The following are the available access methods: + + + + + + + + + + + + + + + + + + + + + + + + + + +
Access MethodDescription
BTree

Data is stored in a sorted, balanced tree structure. Both the key and the data for BTree records can be arbitrarily complex. That is, they can contain single values such as an integer or a string, or complex types such as a structure. Also, although not the default behavior, it is possible for two records to use keys that compare as equals. When this occurs, the records are considered to be duplicates of one another.

Hash

Data is stored in an extended linear hash table. Like BTree, the key and the data used for Hash records can be of arbitrarily complex data. Also, like BTree, duplicate records are optionally supported.

Queue

Data is stored in a queue as fixed-length records. Each record uses a logical record number as its key. This access method is designed for fast inserts at the tail of the queue, and it has a special operation that deletes and returns a record from the head of the queue.

+

This access method is unusual in that it provides record level locking. This can provide beneficial performance improvements in applications requiring concurrent access to the queue.

Recno

Data is stored in either fixed or variable-length records. Like Queue, Recno records use logical record numbers as keys.

+ +### Selecting Access Methods + +To select an access method, you should first consider what you want to use as a key for you database records. If you want to use arbitrary data (even strings), then you should use either BTree or Hash. If you want to use logical record numbers (essentially integers) then you should use Queue or Recno. + +Once you have made this decision, you must choose between either BTree or Hash, or Queue or Recno. This decision is described next. + +### Choosing between BTree and Hash + +For small working datasets that fit entirely in memory, there is no difference between BTree and Hash. Both will perform just as well as the other. In this situation, you might just as well use BTree, if for no other reason than the majority of DB applications use BTree. + +Note that the main concern here is your working dataset, not your entire dataset. Many applications maintain large amounts of information but only need to access some small portion of that data with any frequency. So what you want to consider is the data that you will routinely use, not the sum total of all the data managed by your application. + +However, as your working dataset grows to the point where you cannot fit it all into memory, then you need to take more care when choosing your access method. Specifically, choose: + +- BTree if your keys have some locality of reference. That is, if they sort well and you can expect that a query for a given key will likely be followed by a query for one of its neighbors. + +- Hash if your dataset is extremely large. For any given access method, DB must maintain a certain amount of internal information. However, the amount of information that DB must maintain for BTree is much greater than for Hash. The result is that as your dataset grows, this internal information can dominate the cache to the point where there is relatively little space left for application data. As a result, BTree can be forced to perform disk I/O much more frequently than would Hash given the same amount of data. + + Moreover, if your dataset becomes so large that DB will almost certainly have to perform disk I/O to satisfy a random request, then Hash will definitely out perform BTree because it has fewer internal records to search through than does BTree. + +### Choosing between Queue and Recno + +Queue or Recno are used when the application wants to use logical record numbers for the primary database key. Logical record numbers are essentially integers that uniquely identify the database record. They can be either mutable or fixed, where a mutable record number is one that might change as database records are stored or deleted. Fixed logical record numbers never change regardless of what database operations are performed. + +When deciding between Queue and Recno, choose: + +- Queue if your application requires high degrees of concurrency. Queue provides record-level locking (as opposed to the page-level locking that the other access methods use), and this can result in significantly faster throughput for highly concurrent applications. + + Note, however, that Queue provides support only for fixed length records. So if the size of the data that you want to store varies widely from record to record, you should probably choose an access method other than Queue. + +- Recno if you want mutable record numbers. Queue is only capable of providing fixed record numbers. Also, Recno provides support for databases whose permanent storage is a flat text file. This is useful for applications looking for fast, temporary storage while the data is being read or modified. diff --git a/docs_src/guides/gsg/java/baseapi.md b/docs_src/guides/gsg/java/baseapi.md new file mode 100644 index 000000000..217ab0410 --- /dev/null +++ b/docs_src/guides/gsg/java/baseapi.md @@ -0,0 +1,116 @@ +--- +title: "Part II. Programming with the Base API" +api-name: "Part II. Programming with the Base API" +source: docs/gsg/JAVA/baseapi.html +--- +# Part II. Programming with the Base API + +This section discusses application that are built using the DB base API. Note that most DB applications can probably be written using the DPL (see Programming with the Direct Persistence Layer for more information). However, if you want to use Java 1.4 for your DB application, or if you are porting an application from the Berkeley DB API, then the base API is right for you. + +**Table of Contents** + + [7. Databases](databases.md) + + [Opening Databases](databases.md#DBOpen) + + [Closing Databases](coredbclose.md) + + [Database Properties](dbprops.md) + + [Administrative Methods](DBAdmin.md) + + [Error Reporting Functions](dbErrorReporting.md) + + [Managing Databases in Environments](CoreEnvUsage.md) + + [Database Example](CoreJavaUsage.md) + + [8. Database Records](DBEntry.md) + + [Using Database Records](DBEntry.md#usingDbEntry) + + [Reading and Writing Database Records](usingDbt.md) + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#databaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + + [Using the BIND APIs](bindAPI.md) + + [Numerical and String Objects](bindAPI.md#bindPrimitive) + + [Serializable Complex Objects](bindAPI.md#object2dbt) + + [Custom Tuple Bindings](bindAPI.md#customTuple) + + [Database Usage Example](dbtJavaUsage.md) + + [9. Using Cursors](Cursors.md) + + [Opening and Closing Cursors](Cursors.md#openCursor) + + [Getting Records Using the Cursor](Positioning.md) + + [Searching for Records](Positioning.md#cursorsearch) + + [Working with Duplicate Records](Positioning.md#getdups) + + [Putting Records Using Cursors](PutEntryWCursor.md) + + [Deleting Records Using Cursors](DeleteEntryWCursor.md) + + [Replacing Records Using Cursors](ReplacingEntryWCursor.md) + + [Cursor Example](cursorJavaUsage.md) + + [10. Secondary Databases](indexes.md) + + [Opening and Closing Secondary Databases](indexes.md#DbAssociate) + + [Implementing Key Creators](keyCreator.md) + + [Working with Multiple Keys](keyCreator.md#multikeys) + + [Secondary Database Properties](secondaryProps.md) + + [Reading Secondary Databases](readSecondary.md) + + [Deleting Secondary Database Records](secondaryDelete.md) + + [Using Secondary Cursors](secondaryCursor.md) + + [Database Joins](joins.md) + + [Using Join Cursors](joins.md#joinUsage) + + [JoinCursor Properties](joins.md#joinconfig) + + [Secondary Database Example](javaindexusage.md) + + [Opening Secondary Databases with MyDbs](javaindexusage.md#secondaryMyDbs) + + [Using Secondary Databases with ExampleDatabaseRead](javaindexusage.md#exampleReadJavaSecondaries) + + [11. Database Configuration](dbconfig.md) + + [Setting the Page Size](dbconfig.md#pagesize) + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + + [Selecting the Cache Size](cachesize.md) + + [BTree Configuration](btree.md) + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) diff --git a/docs_src/guides/gsg/java/bindAPI.md b/docs_src/guides/gsg/java/bindAPI.md new file mode 100644 index 000000000..3885cf9c4 --- /dev/null +++ b/docs_src/guides/gsg/java/bindAPI.md @@ -0,0 +1,488 @@ +--- +title: "Using the BIND APIs" +api-name: "Using the BIND APIs" +source: docs/gsg/JAVA/bindAPI.html +--- +## Using the BIND APIs + + [Numerical and String Objects](bindAPI.md#bindPrimitive) + + [Serializable Complex Objects](bindAPI.md#object2dbt) + + [Custom Tuple Bindings](bindAPI.md#customTuple) + +Except for Java String and boolean types, efficiently moving data in and out of Java byte arrays for storage in a database can be a nontrivial operation. To help you with this problem, DB provides the Bind APIs. While these APIs are described in detail in the *Berkeley DB Collections Tutorial*, this section provides a brief introduction to using the Bind APIs with: + +- Single field numerical and string objects + + Use this if you want to store a single numerical or string object, such as `Long`, `Double`, or `String`. + +- Complex objects that implement Java serialization. + + Use this if you are storing objects that implement `Serializable` and if you do not need to sort them. + +- Non-serialized complex objects. + + If you are storing objects that do not implement serialization, you can create your own custom tuple bindings. Note that you should use custom tuple bindings even if your objects are serializable if you want to sort on that data. + +### Numerical and String Objects + +You can use the Bind APIs to store primitive data in a `DatabaseEntry` object. That is, you can store a single field containing one of the following types: + +- `String` + +- `Character` + +- `Boolean` + +- `Byte` + +- `Short` + +- `Integer` + +- `Long` + +- `Float` + +- `Double` + +To store primitive data using the Bind APIs: + +1. Create an `EntryBinding` object. + + When you do this, you use `TupleBinding.getPrimitiveBinding()` to return an appropriate binding for the conversion. + +2. Use the `EntryBinding` object to place the numerical object on the `DatabaseEntry`. + +Once the data is stored in the DatabaseEntry, you can put it to the database in whatever manner you wish. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; + +... + +Database myDatabase = null; +// Database open omitted for clarity. + +// Need a key for the put. +try { + String aKey = "myLong"; + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + + // Now build the DatabaseEntry using a TupleBinding + Long myLong = new Long(123456789l); + DatabaseEntry theData = new DatabaseEntry(); + EntryBinding myBinding = TupleBinding.getPrimitiveBinding(Long.class); + myBinding.objectToEntry(myLong, theData); + + // Now store it + myDatabase.put(null, theKey, theData); +} catch (Exception e) { + // Exception handling goes here +} +``` + +Retrieval from the `DatabaseEntry` object is performed in much the same way: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Database myDatabase = null; +// Database open omitted for clarity + +try { + // Need a key for the get + String aKey = "myLong"; + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + + // Need a DatabaseEntry to hold the associated data. + DatabaseEntry theData = new DatabaseEntry(); + + // Bindings need only be created once for a given scope + EntryBinding myBinding = TupleBinding.getPrimitiveBinding(Long.class); + + // Get it + OperationStatus retVal = myDatabase.get(null, theKey, theData, + LockMode.DEFAULT); + String retKey = null; + if (retVal == OperationStatus.SUCCESS) { + // Recreate the data. + // Use the binding to convert the byte array contained in theData + // to a Long type. + Long theLong = (Long) myBinding.entryToObject(theData); + retKey = new String(theKey.getData(), "UTF-8"); + System.out.println("For key: '" + retKey + "' found Long: '" + + theLong + "'."); + } else { + System.out.println("No record found for key '" + retKey + "'."); + } +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Serializable Complex Objects + +Frequently your application requires you to store and manage objects for your record data and/or keys. You may need to do this if you are caching objects created by another process. You may also want to do this if you want to store multiple data values on a record. When used with just primitive data, or with objects containing a single data member, DB database records effectively represent a single row in a two-column table. By storing a complex object in the record, you can turn each record into a single row in an *n*-column table, where *n* is the number of data members contained by the stored object(s). + +In order to store objects in a DB database, you must convert them to and from a `byte` array. The first instinct for many Java programmers is to do this using Java serialization. While this is functionally a correct solution, the result is poor space-performance because this causes the class information to be stored on every such database record. This information can be quite large and it is redundant — the class information does not vary for serialized objects of the same type. + +In other words, directly using serialization to place your objects into byte arrays means that you will be storing a great deal of unnecessary information in your database, which ultimately leads to larger databases and more expensive disk I/O. + +The easiest way for you to solve this problem is to use the Bind APIs to perform the serialization for you. Doing so causes the extra object information to be saved off to a unique `Database` dedicated for that purpose. This means that you do not have to duplicate that information on each record in the `Database` that your application is using to store its information. + +Note that when you use the Bind APIs to perform serialization, you still receive all the benefits of serialization. You can still use arbitrarily complex object graphs, and you still receive built-in class evolution through the serialVersionUID (SUID) scheme. All of the Java serialization rules apply without modification. For example, you can implement Externalizable instead of Serializable. + +#### Usage Caveats + +Before using the Bind APIs to perform serialization, you may want to consider writing your own custom tuple bindings. Specifically, avoid serialization if: + +- If you need to sort based on the objects your are storing. The sort order is meaningless for the byte arrays that you obtain through serialization. Consequently, you should not use serialization for keys if you care about their sort order. You should also not use serialization for record data if your `Database` supports duplicate records and you care about sort order. + +- You want to minimize the size of your byte arrays. Even when using the Bind APIs to perform the serialization the resulting `byte` array may be larger than necessary. You can achieve more compact results by building your own custom tuple binding. + +- You want to optimize for speed. In general, custom tuple bindings are faster than serialization at moving data in and out of `byte` arrays. + +For information on building your own custom tuple binding, see Custom Tuple Bindings. + +#### Serializing Objects + +To store a serializable complex object using the Bind APIs: + +1. Implement java.io.Serializable in the class whose instances that you want to store. + +2. Open (create) your databases. You need two. The first is the database that you use to store your data. The second is used to store the class information. + +3. Instantiate a class catalog. You do this with `com.sleepycat.bind.serial.StoredClassCatalog`, and at that time you must provide a handle to an open database that is used to store the class information. + +4. Create an entry binding that uses `com.sleepycat.bind.serial.SerialBinding`. + +5. Instantiate an instance of the object that you want to store, and place it in a `DatabaseEntry` using the entry binding that you created in the previous step. + +For example, suppose you want to store a long, double, and a String as a record's data. Then you might create a class that looks something like this: + +``` c +package db.GettingStarted; + +import java.io.Serializable; + +public class MyData implements Serializable { + private long longData; + private double doubleData; + private String description; + + MyData() { + longData = 0; + doubleData = 0.0; + description = null; + } + + public void setLong(long data) { + longData = data; + } + + public void setDouble(double data) { + doubleData = data; + } + + public void setDescription(String data) { + description = data; + } + + public long getLong() { + return longData; + } + + public double getDouble() { + return doubleData; + } + + public String getDescription() { + return description; + } +} +``` + +You can then store instances of this class as follows: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.serial.StoredClassCatalog; +import com.sleepycat.bind.serial.SerialBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseType; +... + +// The key data. +String aKey = "myData"; + +// The data data +MyData data2Store = new MyData(); +data2Store.setLong(123456789l); +data2Store.setDouble(1234.9876543); +data2Store.setDescription("A test instance of this class"); + +try { + // Open the database that you will use to store your data + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setAllowCreate(true); + myDbConfig.setSortedDuplicates(true); + myDbConfig.setType(DatabaseType.BTREE); + Database myDatabase = new Database("myDb", null, myDbConfig); + + // Open the database that you use to store your class information. + // The db used to store class information does not require duplicates + // support. + myDbConfig.setSortedDuplicates(false); + Database myClassDb = new Database("classDb", null, myDbConfig); + + // Instantiate the class catalog + StoredClassCatalog classCatalog = new StoredClassCatalog(myClassDb); + + // Create the binding + EntryBinding dataBinding = new SerialBinding(classCatalog, + MyData.class); + + // Create the DatabaseEntry for the key + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + + // Create the DatabaseEntry for the data. Use the EntryBinding object + // that was just created to populate the DatabaseEntry + DatabaseEntry theData = new DatabaseEntry(); + dataBinding.objectToEntry(data2Store, theData); + + // Put it as normal + myDatabase.put(null, theKey, theData); + + // Database and environment close omitted for brevity +} catch (Exception e) { + // Exception handling goes here +} +``` + +#### Deserializing Objects + +Once an object is stored in the database, you can retrieve the `MyData` objects from the retrieved `DatabaseEntry` using the Bind APIs in much the same way as is described above. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.serial.StoredClassCatalog; +import com.sleepycat.bind.serial.SerialBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.LockMode; + +... + +// The key data. +String aKey = "myData"; + +try { + // Open the database that stores your data + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setAllowCreate(false); + myDbConfig.setType(DatabaseType.BTREE); + Database myDatabase = new Database("myDb", null, myDbConfig); + + // Open the database that stores your class information. + Database myClassDb = new Database("classDb", null, myDbConfig); + + // Instantiate the class catalog + StoredClassCatalog classCatalog = new StoredClassCatalog(myClassDb); + + // Create the binding + EntryBinding dataBinding = new SerialBinding(classCatalog, + MyData.class); + + // Create DatabaseEntry objects for the key and data + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(); + + // Do the get as normal + myDatabase.get(null, theKey, theData, LockMode.DEFAULT); + + // Recreate the MyData object from the retrieved DatabaseEntry using + // the EntryBinding created above + MyData retrievedData = (MyData) dataBinding.entryToObject(theData); + + // Database and environment close omitted for brevity +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Custom Tuple Bindings + +If you want to store complex objects in your database, then you can use tuple bindings to do this. While they are more work to write and maintain than if you were to use serialization, the `byte` array conversion is faster. In addition, custom tuple bindings should allow you to create `byte` arrays that are smaller than those created by serialization. Custom tuple bindings also allow you to optimize your BTree comparisons, whereas serialization does not. + +For information on using serialization to store complex objects, see Serializable Complex Objects. + +To store complex objects using a custom tuple binding: + +1. Implement the class whose instances that you want to store. Note that you do not have to implement the Serializable interface. + +2. Write a tuple binding using the `com.sleepycat.bind.tuple.TupleBinding` class. + +3. Open (create) your database. Unlike serialization, you only need one. + +4. Create an entry binding that uses the tuple binding that you implemented in step 2. + +5. Instantiate an instance of the object that you want to store, and place it in a `DatabaseEntry` using the entry binding that you created in the previous step. + +For example, suppose you want to your keys to be instances of the following class: + +``` c +package db.GettingStarted; + +public class MyData2 { + private long longData; + private Double doubleData; + private String description; + + public MyData2() { + longData = 0; + doubleData = new Double(0.0); + description = ""; + } + + public void setLong(long data) { + longData = data; + } + + public void setDouble(Double data) { + doubleData = data; + } + + public void setString(String data) { + description = data; + } + + public long getLong() { + return longData; + } + + public Double getDouble() { + return doubleData; + } + + public String getString() { + return description; + } +} +``` + +In this case, you need to write a tuple binding for the `MyData2` class. When you do this, you must implement the `TupleBinding.objectToEntry()` and `TupleBinding.entryToObject()` abstract methods. Remember the following as you implement these methods: + +- You use `TupleBinding.objectToEntry()` to convert objects to `byte` arrays. You use `com.sleepycat.bind.tuple.TupleOutput` to write primitive data types to the `byte` array. Note that `TupleOutput` provides methods that allows you to work with numerical types (`long`, `double`, `int`, and so forth) and not the corresponding `java.lang` numerical classes. + +- The order that you write data to the `byte` array in `TupleBinding.objectToEntry()` is the order that it appears in the array. So given the `MyData2` class as an example, if you write `description`, `doubleData`, and then `longData`, then the resulting byte array will contain these data elements in that order. This means that your records will sort based on the value of the `description` data member and then the `doubleData` member, and so forth. If you prefer to sort based on, say, the `longData` data member, write it to the byte array first. + +- You use `TupleBinding.entryToObject()` to convert the `byte` array back into an instance of your original class. You use `com.sleepycat.bind.tuple.TupleInput` to get data from the `byte` array. + +- The order that you read data from the `byte` array must be exactly the same as the order in which it was written. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class MyTupleBinding extends TupleBinding { + + // Write a MyData2 object to a TupleOutput + public void objectToEntry(Object object, TupleOutput to) { + + MyData2 myData = (MyData2)object; + + // Write the data to the TupleOutput (a DatabaseEntry). + // Order is important. The first data written will be + // the first bytes used by the default comparison routines. + to.writeDouble(myData.getDouble().doubleValue()); + to.writeLong(myData.getLong()); + to.writeString(myData.getString()); + } + + // Convert a TupleInput to a MyData2 object + public Object entryToObject(TupleInput ti) { + + // Data must be read in the same order that it was + // originally written. + Double theDouble = new Double(ti.readDouble()); + long theLong = ti.readLong(); + String theString = ti.readString(); + + MyData2 myData = new MyData2(); + myData.setDouble(theDouble); + myData.setLong(theLong); + myData.setString(theString); + + return myData; + } +} +``` + +In order to use the tuple binding, instantiate the binding and then use: + +- `MyTupleBinding.objectToEntry()` to convert a MyData2 object to a `DatabaseEntry`. + +- `MyTupleBinding.entryToObject()` to convert a `DatabaseEntry` to a `MyData2` object. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.DatabaseEntry; + +... + +TupleBinding keyBinding = new MyTupleBinding(); + +MyData2 theKeyData = new MyData2(); +theKeyData.setLong(123456789l); +theKeyData.setDouble(new Double(12345.6789)); +theKeyData.setString("My key data"); + +DatabaseEntry myKey = new DatabaseEntry(); + +try { + // Store theKeyData in the DatabaseEntry + keyBinding.objectToEntry(theKeyData, myKey); + + ... + // Database put and get activity omitted for clarity + ... + + // Retrieve the key data + theKeyData = (MyData2) keyBinding.entryToObject(myKey); +} catch (Exception e) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/btree.md b/docs_src/guides/gsg/java/btree.md new file mode 100644 index 000000000..dd5fda527 --- /dev/null +++ b/docs_src/guides/gsg/java/btree.md @@ -0,0 +1,196 @@ +--- +title: "BTree Configuration" +api-name: "BTree Configuration" +source: docs/gsg/JAVA/btree.html +--- +## BTree Configuration + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) + +In going through the previous chapters in this book, you may notice that we touch on some topics that are specific to BTree, but we do not cover those topics in any real detail. In this section, we will discuss configuration issues that are unique to BTree. + +Specifically, in this section we describe: + +- Allowing duplicate records. + +- Setting comparator callbacks. + +### Allowing Duplicate Records + +BTree databases can contain duplicate records. One record is considered to be a duplicate of another when both records use keys that compare as equal to one another. + +By default, keys are compared using a lexicographical comparison, with shorter keys collating higher than longer keys. You can override this default using the `DatabaseConfig.setBtreeComparator()` method. See the next section for details. + +By default, DB databases do not allow duplicate records. As a result, any attempt to write a record that uses a key equal to a previously existing record results in the previously existing record being overwritten by the new record. + +Allowing duplicate records is useful if you have a database that contains records keyed by a commonly occurring piece of information. It is frequently necessary to allow duplicate records for secondary databases. + +For example, suppose your primary database contained records related to automobiles. You might in this case want to be able to find all the automobiles in the database that are of a particular color, so you would index on the color of the automobile. However, for any given color there will probably be multiple automobiles. Since the index is the secondary key, this means that multiple secondary database records will share the same key, and so the secondary database must support duplicate records. + +#### Sorted Duplicates + +Duplicate records can be stored in sorted or unsorted order. You can cause DB to automatically sort your duplicate records by setting `DatabaseConfig.setSortedDuplicates()` to `true`. Note that this property must be set prior to database creation time and it cannot be changed afterwards. + +If sorted duplicates are supported, then the `java.util.Comparator` implementation identified to `DatabaseConfig.setDuplicateComparator()` is used to determine the location of the duplicate record in its duplicate set. If no such function is provided, then the default lexicographical comparison is used. + +#### Unsorted Duplicates + +For performance reasons, BTrees should always contain sorted records. (BTrees containing unsorted entries must potentially spend a great deal more time locating an entry than does a BTree that contains sorted entries). That said, DB provides support for suppressing automatic sorting of duplicate records because it may be that your application is inserting records that are already in a sorted order. + +That is, if the database is configured to support unsorted duplicates, then the assumption is that your application will manually perform the sorting. In this event, expect to pay a significant performance penalty. Any time you place records into the database in a sort order not know to DB, you will pay a performance penalty + +That said, this is how DB behaves when inserting records into a database that supports non-sorted duplicates: + +- If your application simply adds a duplicate record using `Database.put()`, then the record is inserted at the end of its sorted duplicate set. + +- If a cursor is used to put the duplicate record to the database, then the new record is placed in the duplicate set according to the actual method used to perform the put. The relevant methods are: + + - `Cursor.putAfter()` + + The data is placed into the database as a duplicate record. The key used for this operation is the key used for the record to which the cursor currently refers. Any key provided on the call is therefore ignored. + + The duplicate record is inserted into the database immediately after the cursor's current position in the database. + + - `Cursor.putBefore()` + + Behaves the same as `Cursor.putAfter()` except that the new record is inserted immediately before the cursor's current location in the database. + + - `Cursor.putKeyFirst()` + + If the key already exists in the database, and the database is configured to use duplicates without sorting, then the new record is inserted as the first entry in the appropriate duplicates list. + + - `Cursor.putKeyLast()` + + Behaves identically to `Cursor.putKeyFirst()` except that the new duplicate record is inserted as the last record in the duplicates list. + +#### Configuring a Database to Support Duplicates + +Duplicates support can only be configured at database creation time. You do this by specifying the appropriate `DatabaseConfig` method before the database is opened for the first time. + +The methods that you can use are: + +- `DatabaseConfig.setUnsortedDuplicates()` + + The database supports non-sorted duplicate records. + +- `DatabaseConfig.setSortedDuplicates()` + + The database supports sorted duplicate records. Note that this flag also sets the flag for you. + +The following code fragment illustrates how to configure a database to support sorted duplicate records: + +``` c +package db.GettingStarted; + +import java.io.FileNotFoundException; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; + +... + +Database myDb = null; + +try { + // Typical configuration settings + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + + // Configure for sorted duplicates + myDbConfig.setSortedDuplicates(true); + + // Open the database + myDb = new Database("mydb.db", null, myDbConfig); +} catch(DatabaseException dbe) { + System.err.println("MyDbs: " + dbe.toString()); + System.exit(-1); +} catch(FileNotFoundException fnfe) { + System.err.println("MyDbs: " + fnfe.toString()); + System.exit(-1); +} +``` + +### Setting Comparison Functions + +By default, DB uses a lexicographical comparison function where shorter records collate before longer records. For the majority of cases, this comparison works well and you do not need to manage it in any way. + +However, in some situations your application's performance can benefit from setting a custom comparison routine. You can do this either for database keys, or for the data if your database supports sorted duplicate records. + +Some of the reasons why you may want to provide a custom sorting function are: + +- Your database is keyed using strings and you want to provide some sort of language-sensitive ordering to that data. Doing so can help increase the locality of reference that allows your database to perform at its best. + +- You are using a little-endian system (such as x86) and you are using integers as your database's keys. Berkeley DB stores keys as byte strings and little-endian integers do not sort well when viewed as byte strings. There are several solutions to this problem, one being to provide a custom comparison function. See http://download.oracle.com/docs/cd/E17076_02/html/programmer_reference/am_misc_faq.html for more information. + +- You you do not want the entire key to participate in the comparison, for whatever reason. In this case, you may want to provide a custom comparison function so that only the relevant bytes are examined. + +#### Creating Java Comparators + +You set a BTree's key comparator using `DatabaseConfig.setBtreeComparator()`. You can also set a BTree's duplicate data comparison function using `DatabaseConfig.setDuplicateComparator()`. + +If the database already exists when it is opened, the comparator provided to these methods must be the same as that historically used to create the database or corruption can occur. + +You override the default comparison function by providing a Java `Comparator` class to the database. The Java `Comparator` interface requires you to implement the `Comparator.compare()` method (see http://download.oracle.com/javase/1.4.2/docs/api/java/util/Comparator.html for details). + +DB hands your `Comparator.compare()` method the `byte` arrays that you stored in the database. If you know how your data is organized in the `byte` array, then you can write a comparison routine that directly examines the contents of the arrays. Otherwise, you have to reconstruct your original objects, and then perform the comparison. + +For example, suppose you want to perform unicode lexical comparisons instead of UTF-8 byte-by-byte comparisons. Then you could provide a comparator that uses `String.compareTo()`, which performs a Unicode comparison of two strings (note that for single-byte roman characters, Unicode comparison and UTF-8 byte-by-byte comparisons are identical – this is something you would only want to do if you were using multibyte unicode characters with DB). In this case, your comparator would look like the following: + +``` c +package db.GettingStarted; + +import java.util.Comparator; + +public class MyDataComparator implements Comparator { + + public MyDataComparator() {} + + public int compare(Object d1, Object d2) { + + byte[] b1 = (byte[])d1; + byte[] b2 = (byte[])d2; + + String s1 = new String(b1); + String s2 = new String(b2); + return s1.compareTo(s2); + } +} +``` + +To use this comparator: + +``` c +package db.GettingStarted; + +import java.io.FileNotFoundException; +import java.util.Comparator; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; + +... + +Database myDatabase = null; +try { + // Get the database configuration object + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setAllowCreate(true); + + // Set the duplicate comparator class + MyDataComparator mdc = new MyDataComparator(); + myDbConfig.setDuplicateComparator(mdc); + + // Open the database that you will use to store your data + myDbConfig.setSortedDuplicates(true); + myDatabase = new Database("myDb", null, myDbConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/cachesize.md b/docs_src/guides/gsg/java/cachesize.md new file mode 100644 index 000000000..b24899ad8 --- /dev/null +++ b/docs_src/guides/gsg/java/cachesize.md @@ -0,0 +1,14 @@ +--- +title: "Selecting the Cache Size" +api-name: "Selecting the Cache Size" +source: docs/gsg/JAVA/cachesize.html +--- +## Selecting the Cache Size + +Cache size is important to your application because if it is set to too small of a value, your application's performance will suffer from too much disk I/O. On the other hand, if your cache is too large, then your application will use more memory than it actually needs. Moreover, if your application uses too much memory, then on most operating systems this can result in your application being swapped out of memory, resulting in extremely poor performance. + +You select your cache size using either `DatabaseConfig.setCacheSize()`, or `EnvironmentConfig.setCacheSize()`, depending on whether you are using a database environment or not. You cache size must be a power of 2, but it is otherwise limited only by available memory and performance considerations. + +Selecting a cache size is something of an art, but fortunately you can change it any time, so it can be easily tuned to your application's changing data requirements. The best way to determine how large your cache needs to be is to put your application into a production environment and watch to see how much disk I/O is occurring. If your application is going to disk quite a lot to retrieve database records, then you should increase the size of your cache (provided that you have enough memory to do so). + +You can use the `db_stat` command line utility with the `-m` option to gauge the effectiveness of your cache. In particular, the number of pages found in the cache is shown, along with a percentage value. The closer to 100% that you can get, the better. If this value drops too low, and you are experiencing performance problems, then you should consider increasing the size of your cache, assuming you have memory to support it. diff --git a/docs_src/guides/gsg/java/coreExceptions.md b/docs_src/guides/gsg/java/coreExceptions.md new file mode 100644 index 000000000..1b73b49be --- /dev/null +++ b/docs_src/guides/gsg/java/coreExceptions.md @@ -0,0 +1,26 @@ +--- +title: "Exception Handling" +api-name: "Exception Handling" +source: docs/gsg/JAVA/coreExceptions.html +--- +## Exception Handling + +Before continuing, it is useful to spend a few moments on exception handling in DB with the java. + +Most DB methods throw `DatabaseException` in the event of a serious error. So your DB code must either catch this exception or declare it to be throwable. Be aware that `DatabaseException` extends `java.lang.Exception`. For example: + +``` c +import com.sleepycat.db.DatabaseException; + + ... +try +{ + // DB and other code goes here +} +catch(DatabaseException e) +{ + // DB error handling goes here +} +``` + +You can obtain the DB error number for a `DatabaseException` by using `DatabaseException.getErrno()`. You can also obtain any error message associated with that error using `DatabaseException.getMessage()`. diff --git a/docs_src/guides/gsg/java/coredbclose.md b/docs_src/guides/gsg/java/coredbclose.md new file mode 100644 index 000000000..fa2d312bd --- /dev/null +++ b/docs_src/guides/gsg/java/coredbclose.md @@ -0,0 +1,31 @@ +--- +title: "Closing Databases" +api-name: "Closing Databases" +source: docs/gsg/JAVA/coredbclose.html +--- +## Closing Databases + +Once you are done using the database, you must close it. You use the method to do this. + +Closing a database causes it to become unusable until it is opened again. It is recommended that you close any open cursors before closing your database. Active cursors during a database close can cause unexpected results, especially if any of those cursors are writing to the database. You should always make sure that all your database accesses have completed before closing your database. + +Cursors are described in Using Cursors later in this manual. + +Be aware that when you close the last open handle for a database, then by default its cache is flushed to disk. This means that any information that has been modified in the cache is guaranteed to be written to disk when the last handle is closed. You can manually perform this operation using the `Database.sync()` method, but for normal shutdown operations it is not necessary. For more information about syncing your cache, see Data Persistence. + +The following code fragment illustrates a database close: + +``` c +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Database; + +... + +try { + if (myDatabase != null) { + myDatabase.close(); + } +} catch (DatabaseException dbe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/cursorJavaUsage.md b/docs_src/guides/gsg/java/cursorJavaUsage.md new file mode 100644 index 000000000..6bdf70f61 --- /dev/null +++ b/docs_src/guides/gsg/java/cursorJavaUsage.md @@ -0,0 +1,199 @@ +--- +title: "Cursor Example" +api-name: "Cursor Example" +source: docs/gsg/JAVA/cursorJavaUsage.html +--- +## Cursor Example + +In Database Usage Example we wrote an application that loaded two `Database` objects with vendor and inventory information. In this example, we will use those databases to display all of the items in the inventory database. As a part of showing any given inventory item, we will look up the vendor who can provide the item and show the vendor's contact information. + +To do this, we create the `ExampleDatabaseRead` application. This application reads and displays all inventory records by: + +1. Opening the inventory, vendor, and class catalog `Database` objects. We do this using the `MyDbs` class. See Stored Class Catalog Management with MyDbs for a description of this class. + +2. Obtaining a cursor from the inventory `Database`. + +3. Steps through the `Database`, displaying each record as it goes. + +4. To display the Inventory record, the custom tuple binding that we created in InventoryBinding.java is used. + +5. `Database.get()` is used to obtain the vendor that corresponds to the inventory item. + +6. A serial binding is used to convert the `DatabaseEntry` returned by the `get()` to a Vendor object. + +7. The contents of the Vendor object are displayed. + +We implemented the `Vendor` class in Vendor.java. We implemented the `Inventory` class in Inventory.java. + +The full implementation of `ExampleDatabaseRead` can be found in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 9.1 ExampleDatabaseRead.java** + +To begin, we import the necessary classes: + +``` c +// file ExampleDatabaseRead.java +package db.GettingStarted; + +import java.io.File; +import java.io.IOException; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.serial.SerialBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Cursor; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +``` + +Next we declare our class and set up some global variables. Note a `MyDbs` object is instantiated here. We can do this because its constructor never throws an exception. See Database Example for its implementation details. + +``` c +public class ExampleDatabaseRead { + + private static String myDbsPath = "./"; + + // Encapsulates the database environment and databases. + private static MyDbs myDbs = new MyDbs(); + + private static TupleBinding inventoryBinding; + private static EntryBinding vendorBinding; +``` + +Next we create the `ExampleDatabaseRead.usage()` and `ExampleDatabaseRead.main()` methods. We perform almost all of our exception handling from `ExampleDatabaseRead.main()`, and so we must catch `DatabaseException` because the `com.sleepycat.db.*` APIs throw them. + +``` c + private static void usage() { + System.out.println("ExampleDatabaseRead [-h ]" + + "[-s ]"); + System.exit(-1); + } + + public static void main(String args[]) { + ExampleDatabaseRead edr = new ExampleDatabaseRead(); + try { + edr.run(args); + } catch (DatabaseException dbe) { + System.err.println("ExampleDatabaseRead: " + dbe.toString()); + dbe.printStackTrace(); + } finally { + myDbs.close(); + } + System.out.println("All done."); + } +``` + +In `ExampleDatabaseRead.run()`, we call `MyDbs.setup()` to open our databases. Then we create the bindings that we need for using our data objects with `DatabaseEntry` objects. + +``` c + private void run(String args[]) + throws DatabaseException { + // Parse the arguments list + parseArgs(args); + + myDbs.setup(myDbsPath); + + // Setup our bindings. + inventoryBinding = new InventoryBinding(); + vendorBinding = + new SerialBinding(myDbs.getClassCatalog(), + Vendor.class); + + showAllInventory(); + } +``` + +Now we write the loop that displays the `Inventory` records. We do this by opening a cursor on the inventory database and iterating over all its contents, displaying each as we go. + +``` c + private void showAllInventory() + throws DatabaseException { + // Get a cursor + Cursor cursor = myDbs.getInventoryDB().openCursor(null, null); + + // DatabaseEntry objects used for reading records + DatabaseEntry foundKey = new DatabaseEntry(); + DatabaseEntry foundData = new DatabaseEntry(); + + try { // always want to make sure the cursor gets closed + while (cursor.getNext(foundKey, foundData, + LockMode.DEFAULT) == OperationStatus.SUCCESS) { + Inventory theInventory = + (Inventory)inventoryBinding.entryToObject(foundData); + displayInventoryRecord(foundKey, theInventory); + } + } catch (Exception e) { + System.err.println("Error on inventory cursor:"); + System.err.println(e.toString()); + e.printStackTrace(); + } finally { + cursor.close(); + } + } +``` + +We use `ExampleDatabaseRead.displayInventoryRecord()` to actually show the record. This method first displays all the relevant information from the retrieved Inventory object. It then uses the vendor database to retrieve and display the vendor. Because the vendor database is keyed by vendor name, and because each inventory object contains this key, it is trivial to retrieve the appropriate vendor record. + +``` c + private void displayInventoryRecord(DatabaseEntry theKey, + Inventory theInventory) + throws DatabaseException { + String theSKU = null; + try { + theSKU = new String(theKey.getData(), "UTF-8"); + } catch(java.io.UnsupportedEncodingException e) { + /* Handle the exception here. */ + } + System.out.println(theSKU + ":"); + System.out.println("\t " + theInventory.getItemName()); + System.out.println("\t " + theInventory.getCategory()); + System.out.println("\t " + theInventory.getVendor()); + System.out.println("\t\tNumber in stock: " + + theInventory.getVendorInventory()); + System.out.println("\t\tPrice per unit: " + + theInventory.getVendorPrice()); + System.out.println("\t\tContact: "); + + DatabaseEntry searchKey = null; + try { + searchKey = + new DatabaseEntry(theInventory.getVendor().getBytes("UTF-8")); + } catch (IOException willNeverOccur) {} + DatabaseEntry foundVendor = new DatabaseEntry(); + + if (myDbs.getVendorDB().get(null, searchKey, foundVendor, + LockMode.DEFAULT) != OperationStatus.SUCCESS) { + System.out.println("Could not find vendor: " + + theInventory.getVendor() + "."); + System.exit(-1); + } else { + Vendor theVendor = + (Vendor)vendorBinding.entryToObject(foundVendor); + System.out.println("\t\t " + theVendor.getAddress()); + System.out.println("\t\t " + theVendor.getCity() + ", " + + theVendor.getState() + " " + theVendor.getZipcode()); + System.out.println("\t\t Business Phone: " + + theVendor.getBusinessPhoneNumber()); + System.out.println("\t\t Sales Rep: " + + theVendor.getRepName()); + System.out.println("\t\t " + + theVendor.getRepPhoneNumber()); + } + } +``` + +The remainder of this application provides a utility method used to parse the command line options. From the perspective of this document, this is relatively uninteresting. You can see how this is implemented by looking at: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. diff --git a/docs_src/guides/gsg/java/dataaccessorclass.md b/docs_src/guides/gsg/java/dataaccessorclass.md new file mode 100644 index 000000000..1bd1f3c2a --- /dev/null +++ b/docs_src/guides/gsg/java/dataaccessorclass.md @@ -0,0 +1,52 @@ +--- +title: "DataAccessor.java" +api-name: "DataAccessor.java" +source: docs/gsg/JAVA/dataaccessorclass.html +--- +## DataAccessor.java + +Now that we have implemented our data classes, we can write a class that will provide convenient access to our primary and secondary indexes. Note that like our data classes, this class is shared by both our example programs. + +If you compare this class against our `Vendor` and `Inventory` class implementations, you will see that the primary and secondary indices declared there are referenced by this class. + +See Vendor.java and Inventory.java for those implementations. + +``` c +package persist.gettingStarted; + +import java.io.File; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.PrimaryIndex; +import com.sleepycat.persist.SecondaryIndex; + +public class DataAccessor { + // Open the indices + public DataAccessor(EntityStore store) + throws DatabaseException { + + // Primary key for Inventory classes + inventoryBySku = store.getPrimaryIndex( + String.class, Inventory.class); + + // Secondary key for Inventory classes + // Last field in the getSecondaryIndex() method must be + // the name of a class member; in this case, an Inventory.class + // data member. + inventoryByName = store.getSecondaryIndex( + inventoryBySku, String.class, "itemName"); + + // Primary key for Vendor class + vendorByName = store.getPrimaryIndex( + String.class, Vendor.class); + } + + // Inventory Accessors + PrimaryIndex inventoryBySku; + SecondaryIndex inventoryByName; + + // Vendor Accessors + PrimaryIndex vendorByName; +} +``` diff --git a/docs_src/guides/gsg/java/databaseLimits.md b/docs_src/guides/gsg/java/databaseLimits.md new file mode 100644 index 000000000..344835386 --- /dev/null +++ b/docs_src/guides/gsg/java/databaseLimits.md @@ -0,0 +1,12 @@ +--- +title: "Database Limits and Portability" +api-name: "Database Limits and Portability" +source: docs/gsg/JAVA/databaseLimits.html +--- +## Database Limits and Portability + +Berkeley DB provides support for managing everything from very small databases that fit entirely in memory, to extremely large databases holding millions of records and terabytes of data. DB databases can store up to 256 terabytes of data. Individual record keys or record data can store up to 4 gigabytes of data. + +DB's databases store data in a binary format that is portable across platforms, even of differing endian-ness. Be aware, however, that portability aside, some performance issues can crop up in the event that you are using little endian architecture. See Setting Comparison Functions for more information. + +Also, DB's databases and data structures are designed for concurrent access — they are thread-safe, and they share well across multiple processes. That said, in order to allow multiple processes to share databases and the cache, DB makes use of mechanisms that do not work well on network-shared drives (NFS or Windows networks shares, for example). For this reason, you cannot place your DB databases and environments on network-mounted drives. diff --git a/docs_src/guides/gsg/java/databases.md b/docs_src/guides/gsg/java/databases.md new file mode 100644 index 000000000..443ed4d84 --- /dev/null +++ b/docs_src/guides/gsg/java/databases.md @@ -0,0 +1,64 @@ +--- +title: "Chapter 7. Databases" +api-name: "Chapter 7. Databases" +source: docs/gsg/JAVA/databases.html +--- +## Chapter 7. Databases + +**Table of Contents** + + [Opening Databases](databases.md#DBOpen) + + [Closing Databases](coredbclose.md) + + [Database Properties](dbprops.md) + + [Administrative Methods](DBAdmin.md) + + [Error Reporting Functions](dbErrorReporting.md) + + [Managing Databases in Environments](CoreEnvUsage.md) + + [Database Example](CoreJavaUsage.md) + +In Berkeley DB, a database is a collection of *records*. Records, in turn, consist of key/data pairings. + +Conceptually, you can think of a `Database` as containing a two-column table where column 1 contains a key and column 2 contains data. Both the key and the data are managed using `DatabaseEntry` class instances (see Database Records for details on this class ). So, fundamentally, using a DB `Database` involves putting, getting, and deleting database records, which in turns involves efficiently managing information encapsulated by `DatabaseEntry` objects. The next several chapters of this book are dedicated to those activities. + +Also, note that in the previous section of this book, Programming with the Direct Persistence Layer, we described the DPL The DPL handles all database management for you, including creating all primary and secondary databases as is required by your application. That said, if you are using the DPL you can access the underlying database for a given index if necessary. See the Javadoc for the DPL for more information. + +## Opening Databases + +You open a database by instantiating a `Database` object. + +Note that by default, DB does not create databases if they do not already exist. To override this behavior, set the creation property to true. + +The following code fragment illustrates a database open: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; + +import java.io.FileNotFoundException; +... + +Database myDatabase = null; + +... + +try { + // Open the database. Create it if it does not already exist. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setAllowCreate(true); + myDatabase = new Database ("sampleDatabase.db", + null, + dbConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/dbErrorReporting.md b/docs_src/guides/gsg/java/dbErrorReporting.md new file mode 100644 index 000000000..3fcdfdc65 --- /dev/null +++ b/docs_src/guides/gsg/java/dbErrorReporting.md @@ -0,0 +1,57 @@ +--- +title: "Error Reporting Functions" +api-name: "Error Reporting Functions" +source: docs/gsg/JAVA/dbErrorReporting.html +--- +## Error Reporting Functions + +To simplify error reporting and handling, the `DatabaseConfig` class offers several useful methods. + +- `DatabaseConfig.setErrorStream()` + + Sets the Java `OutputStream` to be used for displaying error messages issued by the DB library. + +- `DatabaseConfig.setMessageHandler()` + + Defines the message handler that is called when an error message is issued by DB. The error prefix and message are passed to this callback. It is up to the application to display this information correctly. + + Note that the message handler must be an implementation of the `com.sleepycat.db.MessageHandler` interface. + +- `DatabaseConfig.setErrorPrefix()` + + Sets the prefix used for any error messages issued by the DB library. + +For example, to send all your error messages to a particular message handler, first implement the handler: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.MessageHandler; + +public class MyMessageHandler implements MessageHandler { + + // Our constructor does nothing + public MyMessageHandler() {} + + public void message(Environment dbenv, String message) + { + // Put your special message handling code here + } + +} +``` + +And then set up your database to use the message handler by identifying it on the database's `DatabaseConfig` object: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseConfig; + +... + +DatabaseConfig myDbConfig = new DatabaseConfig(); +MyMessageHandler mmh = new MyMessageHandler(); +myDbConfig.setMessageHandler(mmh); +``` diff --git a/docs_src/guides/gsg/java/dbconfig.md b/docs_src/guides/gsg/java/dbconfig.md new file mode 100644 index 000000000..831837bec --- /dev/null +++ b/docs_src/guides/gsg/java/dbconfig.md @@ -0,0 +1,110 @@ +--- +title: "Chapter 11. Database Configuration" +api-name: "Chapter 11. Database Configuration" +source: docs/gsg/JAVA/dbconfig.html +--- +## Chapter 11. Database Configuration + +**Table of Contents** + + [Setting the Page Size](dbconfig.md#pagesize) + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + + [Selecting the Cache Size](cachesize.md) + + [BTree Configuration](btree.md) + + [Allowing Duplicate Records](btree.md#duplicateRecords) + + [Setting Comparison Functions](btree.md#comparators) + +This chapter describes some of the database and cache configuration issues that you need to consider when building your DB database. In most cases, there is very little that you need to do in terms of managing your databases. However, there are configuration issues that you need to be concerned with, and these are largely dependent on the access method that you are choosing for your database. + +The examples and descriptions throughout this document have mostly focused on the BTree access method. This is because the majority of DB applications use BTree. For this reason, where configuration issues are dependent on the type of access method in use, this chapter will focus on BTree only. For configuration descriptions surrounding the other access methods, see the *Berkeley DB Programmer's Reference Guide*. + +## Setting the Page Size + + [Overflow Pages](dbconfig.md#overflowpages) + + [Locking](dbconfig.md#Locking) + + [IO Efficiency](dbconfig.md#IOEfficiency) + + [Page Sizing Advice](dbconfig.md#pagesizeAdvice) + +Internally, DB stores database entries on pages. Page sizes are important because they can affect your application's performance. + +DB pages can be between 512 bytes and 64K bytes in size. The size that you select must be a power of 2. You set your database's page size using `DatabaseConfig.setPageSize()`. + +Note that a database's page size can only be selected at database creation time. + +When selecting a page size, you should consider the following issues: + +- Overflow pages. + +- Locking + +- Disk I/O. + +These topics are discussed next. + +### Overflow Pages + +Overflow pages are used to hold a key or data item that cannot fit on a single page. You do not have to do anything to cause overflow pages to be created, other than to store data that is too large for your database's page size. Also, the only way you can prevent overflow pages from being created is to be sure to select a page size that is large enough to hold your database entries. + +Because overflow pages exist outside of the normal database structure, their use is expensive from a performance perspective. If you select too small of a page size, then your database will be forced to use an excessive number of overflow pages. This will significantly harm your application's performance. + +For this reason, you want to select a page size that is at least large enough to hold multiple entries given the expected average size of your database entries. In BTree's case, for best results select a page size that can hold at least 4 such entries. + +You can see how many overflow pages your database is using by obtaining a `DatabaseStats` object using the `Database.getStats()` method, or by examining your database using the `db_stat` command line utility. + +### Locking + +Locking and multi-threaded access to DB databases is built into the product. However, in order to enable the locking subsystem and in order to provide efficient sharing of the cache between databases, you must use an *environment*. Environments and multi-threaded access are not fully described in this manual (see the Berkeley DB Programmer's Reference Manual for information), however, we provide some information on sizing your pages in a multi-threaded/multi-process environment in the interest of providing a complete discussion on the topic. + +If your application is multi-threaded, or if your databases are accessed by more than one process at a time, then page size can influence your application's performance. The reason why is that for most access methods (Queue is the exception), DB implements page-level locking. This means that the finest locking granularity is at the page, not at the record. + +In most cases, database pages contain multiple database records. Further, in order to provide safe access to multiple threads or processes, DB performs locking on pages as entries on those pages are read or written. + +As the size of your page increases relative to the size of your database entries, the number of entries that are held on any given page also increase. The result is that the chances of two or more readers and/or writers wanting to access entries on any given page also increases. + +When two or more threads and/or processes want to manage data on a page, lock contention occurs. Lock contention is resolved by one thread (or process) waiting for another thread to give up its lock. It is this waiting activity that is harmful to your application's performance. + +It is possible to select a page size that is so large that your application will spend excessive, and noticeable, amounts of time resolving lock contention. Note that this scenario is particularly likely to occur as the amount of concurrency built into your application increases. + +Oh the other hand, if you select too small of a page size, then that that will only make your tree deeper, which can also cause performance penalties. The trick, therefore, is to select a reasonable page size (one that will hold a sizeable number of records) and then reduce the page size if you notice lock contention. + +You can examine the number of lock conflicts and deadlocks occurring in your application by examining your database environment lock statistics. Either use the method, or use the `db_stat` command line utility. The number of unavailable locks that your application waited for is held in the lock statistic's `st_lock_wait` field. + +### IO Efficiency + +Page size can affect how efficient DB is at moving data to and from disk. For some applications, especially those for which the in-memory cache can not be large enough to hold the entire working dataset, IO efficiency can significantly impact application performance. + +Most operating systems use an internal block size to determine how much data to move to and from disk for a single I/O operation. This block size is usually equal to the filesystem's block size. For optimal disk I/O efficiency, you should select a database page size that is equal to the operating system's I/O block size. + +Essentially, DB performs data transfers based on the database page size. That is, it moves data to and from disk a page at a time. For this reason, if the page size does not match the I/O block size, then the operating system can introduce inefficiencies in how it responds to DB's I/O requests. + +For example, suppose your page size is smaller than your operating system block size. In this case, when DB writes a page to disk it is writing just a portion of a logical filesystem page. Any time any application writes just a portion of a logical filesystem page, the operating system brings in the real filesystem page, over writes the portion of the page not written by the application, then writes the filesystem page back to disk. The net result is significantly more disk I/O than if the application had simply selected a page size that was equal to the underlying filesystem block size. + +Alternatively, if you select a page size that is larger than the underlying filesystem block size, then the operating system may have to read more data than is necessary to fulfill a read request. Further, on some operating systems, requesting a single database page may result in the operating system reading enough filesystem blocks to satisfy the operating system's criteria for read-ahead. In this case, the operating system will be reading significantly more data from disk than is actually required to fulfill DB's read request. + +### Note + +While transactions are not discussed in this manual, a page size other than your filesystem's block size can affect transactional guarantees. The reason why is that page sizes larger than the filesystem's block size causes DB to write pages in block size increments. As a result, it is possible for a partial page to be written as the result of a transactional commit. For more information, see http://download.oracle.com/docs/cd/E17076_02/html/programmer_reference/transapp_reclimit.html. + +### Page Sizing Advice + +Page sizing can be confusing at first, so here are some general guidelines that you can use to select your page size. + +In general, and given no other considerations, a page size that is equal to your filesystem block size is the ideal situation. + +If your data is designed such that 4 database entries cannot fit on a single page (assuming BTree), then grow your page size to accommodate your data. Once you've abandoned matching your filesystem's block size, the general rule is that larger page sizes are better. + +The exception to this rule is if you have a great deal of concurrency occurring in your application. In this case, the closer you can match your page size to the ideal size needed for your application's data, the better. Doing so will allow you to avoid unnecessary contention for page locks. diff --git a/docs_src/guides/gsg/java/dbprops.md b/docs_src/guides/gsg/java/dbprops.md new file mode 100644 index 000000000..f96e4877f --- /dev/null +++ b/docs_src/guides/gsg/java/dbprops.md @@ -0,0 +1,75 @@ +--- +title: "Database Properties" +api-name: "Database Properties" +source: docs/gsg/JAVA/dbprops.html +--- +## Database Properties + +You can set database properties using the `DatabaseConfig` class. For each of the properties that you can set, there is a corresponding getter method. Also, you can always retrieve the `DatabaseConfig` object used by your database using the `Database.getConfig()` method. + +There are a large number of properties that you can set using this class (see the javadoc for a complete listing). From the perspective of this manual, some of the more interesting properties are: + +- `DatabaseConfig.setAllowCreate()` + + If `true`, the database is created when it is opened. If false, the database open fails if the database does not exist. This property has no meaning if the database currently exists. Default is `false`. + +- `DatabaseConfig.setBtreeComparator()` + + Sets the class that is used to compare the keys found on two database records. This class is used to determine the sort order for two records in the database. By default, byte for byte comparison is used. For more information, see Setting Comparison Functions. + +- `DatabaseConfig.setDuplicateComparator()` + + Sets the class that is used to compare two duplicate records in the database. For more information, see Setting Comparison Functions. + +- `DatabaseConfig.setSortedDuplicates()` + + If `true`, duplicate records are allowed in the database. If this value is `false`, then putting a duplicate record into the database results in an error return from the put call. Note that this property can be set only at database creation time. Default is `false`. + + Note that your database must not support duplicates if it is to be associated with one or more secondary indices. Secondaries are described in Secondary Databases. + +- `DatabaseConfig.setExclusiveCreate()` + + If `true`, the database open fails if the database currently exists. That is, the open must result in the creation of a new database. Default is `false`. + +- `DatabaseConfig.setReadOnly()` + + If true, the database is opened for read activities only. Default is `false`. + +- `DatabaseConfig.setTruncate()` + + If true, the database is truncated; that is, it is emptied of all content. + +- `DatabaseConfig.setType()` + + Identifies the type of database that you want to create. This manual will exclusively use `DatabaseType.BTREE`. + +In addition to these, there are also methods that allow you to control the IO stream used for error reporting purposes. These are described later in this manual. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; + +import java.io.FileNotFoundException; + +... +Database myDatabase = null; +try { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setAllowCreate(true); + dbConfig.setSortedDuplicates(true); + dbConfig.setType(DatabaseType.BTREE); + myDatabase = new Database("sampleDatabase.db", + null, + dbConfig); +} catch (DatabaseException dbe) { + // Exception handling goes here. +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/dbtJavaUsage.md b/docs_src/guides/gsg/java/dbtJavaUsage.md new file mode 100644 index 000000000..8220136a3 --- /dev/null +++ b/docs_src/guides/gsg/java/dbtJavaUsage.md @@ -0,0 +1,551 @@ +--- +title: "Database Usage Example" +api-name: "Database Usage Example" +source: docs/gsg/JAVA/dbtJavaUsage.html +--- +## Database Usage Example + +In MyDbs Class we created a class that opens and closes databases for us. We now make use of that class to load inventory data into two databases that we will use for our inventory system. + +Again, remember that you can find the complete implementation for these functions in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +Note that in this example, we are going to save two types of information. First there are a series of inventory records that identify information about some food items (fruits, vegetables, and desserts). These records identify particulars about each item such as the vendor that the item can be obtained from, how much the vendor has in stock, the price per unit, and so forth. + +We also want to manage vendor contact information, such as the vendor's address and phone number, the sales representative's name and his phone number, and so forth. + +**Example 8.1 Inventory.java** + +All Inventory data is encapsulated in an instance of the following class. Note that because this class is not serializable, we need a custom tuple binding in order to place it on a `DatabaseEntry` object. Because the `TupleInput` and `TupleOutput` classes used by custom tuple bindings support Java numerical types and not Java numerical classes, we use `int` and `float` here instead of the corresponding `Integer` and `Float` classes. + +``` c +// File Inventory.java +package db.GettingStarted; + +public class Inventory { + + private String sku; + private String itemName; + private String category; + private String vendor; + private int vendorInventory; + private float vendorPrice; + + public void setSku(String data) { + sku = data; + } + + public void setItemName(String data) { + itemName = data; + } + + public void setCategory(String data) { + category = data; + } + + public void setVendorInventory(int data) { + vendorInventory = data; + } + + public void setVendor(String data) { + vendor = data; + } + + public void setVendorPrice(float data) { + vendorPrice = data; + } + + public String getSku() { return sku; } + public String getItemName() { return itemName; } + public String getCategory() { return category; } + public int getVendorInventory() { return vendorInventory; } + public String getVendor() { return vendor; } + public float getVendorPrice() { return vendorPrice; } + +} +``` + + +**Example 8.2 Vendor.java** + +The data for vendor records are stored in instances of the following class. Notice that we are using serialization with this class for no other reason than to demonstrate serializing a class instance. + +``` c +// File Vendor.java +package db.GettingStarted; + +import java.io.Serializable; + +public class Vendor implements Serializable { + + private String repName; + private String address; + private String city; + private String state; + private String zipcode; + private String bizPhoneNumber; + private String repPhoneNumber; + private String vendor; + + public void setRepName(String data) { + repName = data; + } + + public void setAddress(String data) { + address = data; + } + + public void setCity(String data) { + city = data; + } + + public void setState(String data) { + state = data; + } + + public void setZipcode(String data) { + zipcode = data; + } + + public void setBusinessPhoneNumber(String data) { + bizPhoneNumber = data; + } + + public void setRepPhoneNumber(String data) { + repPhoneNumber = data; + } + + public void setVendorName(String data) { + vendor = data; + } + + ... + // Corresponding getter methods omitted for brevity. + // See examples/je/gettingStarted/Vendor.java + // for a complete implementation of this class. + +} +``` + + + +Because we will not be using serialization to convert our `Inventory` objects to a `DatabaseEntry` object, we need a custom tuple binding: + +**Example 8.3 InventoryBinding.java** + +``` c +// File InventoryBinding.java +package db.GettingStarted; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class InventoryBinding extends TupleBinding { + + // Implement this abstract method. Used to convert + // a DatabaseEntry to an Inventory object. + public Object entryToObject(TupleInput ti) { + + String sku = ti.readString(); + String itemName = ti.readString(); + String category = ti.readString(); + String vendor = ti.readString(); + int vendorInventory = ti.readInt(); + float vendorPrice = ti.readFloat(); + + Inventory inventory = new Inventory(); + inventory.setSku(sku); + inventory.setItemName(itemName); + inventory.setCategory(category); + inventory.setVendor(vendor); + inventory.setVendorInventory(vendorInventory); + inventory.setVendorPrice(vendorPrice); + + return inventory; + } + + // Implement this abstract method. Used to convert a + // Inventory object to a DatabaseEntry object. + public void objectToEntry(Object object, TupleOutput to) { + + Inventory inventory = (Inventory)object; + + to.writeString(inventory.getSku()); + to.writeString(inventory.getItemName()); + to.writeString(inventory.getCategory()); + to.writeString(inventory.getVendor()); + to.writeInt(inventory.getVendorInventory()); + to.writeFloat(inventory.getVendorPrice()); + } +} +``` + + + +In order to store the data identified above, we write the `ExampleDatabaseLoad` application. This application loads the inventory and vendor databases for you. + +Inventory information is stored in a `Database` dedicated for that purpose. The key for each such record is a product SKU. The inventory data stored in this database are objects of the `Inventory` class (see Inventory.java for more information). `ExampleDatabaseLoad` loads the inventory database as follows: + +1. Reads the inventory data from a flat text file prepared in advance for this purpose. + +2. Uses `java.lang.String` to create a key based on the item's SKU. + +3. Uses an `Inventory` class instance for the record data. This object is stored on a `DatabaseEntry` object using `InventoryBinding`, a custom tuple binding that we implemented above. + +4. Saves each record to the inventory database. + +Vendor information is also stored in a `Database` dedicated for that purpose. The vendor data stored in this database are objects of the `Vendor` class (see Vendor.java for more information). To load this `Database`, `ExampleDatabaseLoad` does the following: + +1. Reads the vendor data from a flat text file prepared in advance for this purpose. + +2. Uses the vendor's name as the record's key. + +3. Uses a `Vendor` class instance for the record data. This object is stored on a `DatabaseEntry` object using `com.sleepycat.bind.serial.SerialBinding`. + +**Example 8.4 Stored Class Catalog Management with MyDbs** + +Before we can write `ExampleDatabaseLoad`, we need to update `MyDbs.java` to support the class catalogs that we need for this application. + +To do this, we start by importing an additional class to support stored class catalogs: + +``` c +// File: MyDbs.java +package db.GettingStarted; + +import com.sleepycat.bind.serial.StoredClassCatalog; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; + +import java.io.FileNotFoundException; +``` + +We also need to add two additional private data members to this class. One supports the database used for the class catalog, and the other is used as a handle for the class catalog itself. + +``` c +public class MyDbs { + + // The databases that our application uses + private Database vendorDb = null; + private Database inventoryDb = null; + private Database classCatalogDb = null; + + // Needed for object serialization + private StoredClassCatalog classCatalog; + + private String vendordb = "VendorDB.db"; + private String inventorydb = "InventoryDB.db"; + private String classcatalogdb = "ClassCatalogDB.db"; + + // Our constructor does nothing + public MyDbs() {} +``` + +Next we need to update the `MyDbs.setup()` method to open the class catalog database and create the class catalog. + +``` c + // The setup() method opens all our databases + // for us. + public void setup(String databasesHome) + throws DatabaseException { + + DatabaseConfig myDbConfig = new DatabaseConfig(); + + ... + // Database configuration omitted for brevity + ... + + // Now open, or create and open, our databases + // Open the vendors and inventory databases + try { + vendordb = databasesHome + "/" + vendordb; + vendorDb = new Database(vendordb, + null, + myDbConfig); + + inventorydb = databasesHome + "/" + inventorydb; + inventoryDb = new Database(inventorydb, + null, + myDbConfig); + + // Open the class catalog db. This is used to + // optimize class serialization. + classcatalogdb = databasesHome + "/" + classcatalogdb; + classCatalogDb = new Database(classcatalogdb, + null, + myDbConfig); + + } catch(FileNotFoundException fnfe) { + System.err.println("MyDbs: " + fnfe.toString()); + System.exit(-1); + } + } +``` + +Finally we need a getter method to return the class catalog. Note that we do not provide a getter for the catalog database itself – our application has no need for that. + +We also update our `close()` to close our class catalog. + +``` c + // getter methods + public Database getVendorDB() { + return vendorDb; + } + + public Database getInventoryDB() { + return inventoryDb; + } + + public StoredClassCatalog getClassCatalog() { + return classCatalog; + } +``` + +Finally, we need our `close()` method: + +``` c + + // Close the databases + public void close() { + try { + if (vendorDb != null) { + vendorDb.close(); + } + + if (inventoryDb != null) { + inventoryDb.close(); + } + + if (classCatalogDb != null) { + classCatalogDb.close(); + } + } catch(DatabaseException dbe) { + System.err.println("Error closing MyDbs: " + + dbe.toString()); + System.exit(-1); + } + } +} +``` + + + +So far we have identified the data that we want to store in our databases and how we will convert that data in and out of `DatabaseEntry` objects for database storage. We have also updated `MyDbs` to manage our databases for us. Now we write `ExampleDatabaseLoad` to actually put the inventory and vendor data into their respective databases. Because of the work that we have done so far, this application is actually fairly simple to write. + +**Example 8.5 ExampleDatabaseLoad.java** + +First we need the usual series of import statements: + +``` c +// File: ExampleDatabaseLoad.java +package db.GettingStarted; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.serial.SerialBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +``` + +Next comes the class declaration and the private data members that we need for this class. Most of these are setting up default values for the program. + +Note that two `DatabaseEntry` objects are instantiated here. We will reuse these for every database operation that this program performs. Also a `MyDbEnv` object is instantiated here. We can do this because its constructor never throws an exception. See Stored Class Catalog Management with MyDbs for its implementation details. + +Finally, the `inventory.txt` and `vendors.txt` file can be found in the GettingStarted examples directory along with the classes described in this extended example. + +``` c +public class ExampleDatabaseLoad { + + private static String myDbsPath = "./"; + private static File inventoryFile = new File("./inventory.txt"); + private static File vendorsFile = new File("./vendors.txt"); + + // DatabaseEntries used for loading records + private static DatabaseEntry theKey = new DatabaseEntry(); + private static DatabaseEntry theData = new DatabaseEntry(); + + // Encapsulates the databases. + private static MyDbs myDbs = new MyDbs(); +``` + +Next comes the `usage()` and `main()` methods. Notice the exception handling in the `main()` method. This is the only place in the application where we catch exceptions. For this reason, we must catch `DatabaseException` which is thrown by the `com.sleepycat.db.*` classes. + +Also notice the call to `MyDbs.close()` in the `finally` block. This is the only place in the application where `MyDbs.close()` is called. `MyDbs.close()` is responsible for closing all open `Database` handles for you. + +``` c + private static void usage() { + System.out.println("ExampleDatabaseLoad [-h ]"); + System.out.println(" [-s ]"); + System.out.println(" [-v ]"); + System.exit(-1); + } + + public static void main(String args[]) { + ExampleDatabaseLoad edl = new ExampleDatabaseLoad(); + try { + edl.run(args); + } catch (DatabaseException dbe) { + System.err.println("ExampleDatabaseLoad: " + dbe.toString()); + dbe.printStackTrace(); + } catch (Exception e) { + System.out.println("Exception: " + e.toString()); + e.printStackTrace(); + } finally { + myDbs.close(); + } + System.out.println("All done."); + } +``` + +Next we write the `ExampleDatabaseLoad.run()` method. This method is responsible for initializing all objects. Because our environment and databases are all opened using the `MyDbs.setup()` method, `ExampleDatabaseLoad.run()` method is only responsible for calling `MyDbs.setup()` and then calling the `ExampleDatabaseLoad` methods that actually load the databases. + +``` c + private void run(String args[]) throws DatabaseException { + // Parse the arguments list + parseArgs(args); + + myDbs.setup(myDbsPath); // path to the environment home + + System.out.println("loading vendors db."); + loadVendorsDb(); + System.out.println("loading inventory db."); + loadInventoryDb(); + } +``` + +This next method loads the vendor database. This method uses serialization to convert the `Vendor` object to a `DatabaseEntry` object. + +``` c + private void loadVendorsDb() + throws DatabaseException { + + // loadFile opens a flat-text file that contains our data + // and loads it into a list for us to work with. The integer + // parameter represents the number of fields expected in the + // file. + List vendors = loadFile(vendorsFile, 8); + + // Now load the data into the database. The vendor's name is the + // key, and the data is a Vendor class object. + + // Need a serial binding for the data + EntryBinding dataBinding = + new SerialBinding(myDbs.getClassCatalog(), Vendor.class); + + for (int i = 0; i < vendors.size(); i++) { + String[] sArray = (String[])vendors.get(i); + Vendor theVendor = new Vendor(); + theVendor.setVendorName(sArray[0]); + theVendor.setAddress(sArray[1]); + theVendor.setCity(sArray[2]); + theVendor.setState(sArray[3]); + theVendor.setZipcode(sArray[4]); + theVendor.setBusinessPhoneNumber(sArray[5]); + theVendor.setRepName(sArray[6]); + theVendor.setRepPhoneNumber(sArray[7]); + + // The key is the vendor's name. + // ASSUMES THE VENDOR'S NAME IS UNIQUE! + String vendorName = theVendor.getVendorName(); + try { + theKey = new DatabaseEntry(vendorName.getBytes("UTF-8")); + } catch (IOException willNeverOccur) {} + + // Convert the Vendor object to a DatabaseEntry object + // using our SerialBinding + dataBinding.objectToEntry(theVendor, theData); + + // Put it in the database. + myDbs.getVendorDB().put(null, theKey, theData); + } + } +``` + +Now load the inventory database. This method uses our custom tuple binding (see InventoryBinding.java) to convert the `Inventory` object to a `DatabaseEntry` object. + +``` c + private void loadInventoryDb() + throws DatabaseException { + + // loadFile opens a flat-text file that contains our data + // and loads it into a list for us to work with. The integer + // parameter represents the number of fields expected in the + // file. + List inventoryArray = loadFile(inventoryFile, 6); + + // Now load the data into the database. The item's sku is the + // key, and the data is an Inventory class object. + + // Need a tuple binding for the Inventory class. + TupleBinding inventoryBinding = new InventoryBinding(); + + for (int i = 0; i < inventoryArray.size(); i++) { + String[] sArray = (String[])inventoryArray.get(i); + String sku = sArray[1]; + try { + theKey = new DatabaseEntry(sku.getBytes("UTF-8")); + } catch (IOException willNeverOccur) {} + + Inventory theInventory = new Inventory(); + theInventory.setItemName(sArray[0]); + theInventory.setSku(sArray[1]); + Float price = new Float(sArray[2]); + theInventory.setVendorPrice(price.floatValue()); + Integer vInventory = new Integer(sArray[3]); + theInventory.setVendorInventory(vInventory.intValue()); + theInventory.setCategory(sArray[4]); + theInventory.setVendor(sArray[5]); + + // Place the Vendor object on the DatabaseEntry object using + // our the tuple binding we implemented in + // InventoryBinding.java + inventoryBinding.objectToEntry(theInventory, theData); + + // Put it in the database. Note that this causes our + // secondary database to be automatically updated for us. + myDbs.getInventoryDB().put(null, theKey, theData); + } + } +``` + +The remainder of this application provides utility methods to read a flat text file into an array of strings and parse the command line options: + +``` c + private static void parseArgs(String args[]) { + // Implementation omitted for brevity. + } + + private List loadFile(File theFile, int numFields) { + List records = new ArrayList(); + // Implementation omitted for brevity. + return records; + } + + protected ExampleDatabaseLoad() {} +} +``` + +From the perspective of this document, these things are relatively uninteresting. You can see how they are implemented by looking at `ExampleDatabaseLoad.java` in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. diff --git a/docs_src/guides/gsg/java/dpl.md b/docs_src/guides/gsg/java/dpl.md new file mode 100644 index 000000000..7210939fb --- /dev/null +++ b/docs_src/guides/gsg/java/dpl.md @@ -0,0 +1,78 @@ +--- +title: "Part I. Programming with the Direct Persistence Layer" +api-name: "Part I. Programming with the Direct Persistence Layer" +source: docs/gsg/JAVA/dpl.html +--- +# Part I. Programming with the Direct Persistence Layer + +This section discusses how to build an application using the DPL. The DPL is ideally suited for those applications that want a mechanism for storing and managing Java class objects in a DB database. Note that the DPL is best suited for applications that work with classes with a relatively static schema. + +Also, the DPL requires Java 1.5. + +If you want to use Java 1.4 for your DB application, or if you are porting an application from the Berkeley DB API, then you probably want to use the base API instead of the DPL. For information on using the base API, see Programming with the Base API. + +**Table of Contents** + + [3. Direct Persistence Layer First Steps](persist_first.md) + + [Entity Stores](persist_first.md#entitystore) + + [Opening and Closing Environments and Stores](persist_first.md#persist-open) + + [Persistent Objects](persistobject.md) + + [Saving and Retrieving Data](saveret.md) + + [4. Working with Indices](persist_index.md) + + [Accessing Indexes](persist_index.md#dplindexaccess) + + [Accessing Primary Indices](persist_index.md#primaryindexaccess) + + [Accessing Secondary Indices](persist_index.md#secondaryindexaccess) + + [Creating Indexes](dplindexcreate.md) + + [Declaring a Primary Indexes](dplindexcreate.md#dplprimaryidxdecl) + + [Declaring Secondary Indexes](dplindexcreate.md#dplsecondaryidxdecl) + + [Foreign Key Constraints](dplindexcreate.md#foreignkey) + + [5. Saving and Retrieving Objects](persist_access.md) + + [A Simple Entity Class](persist_access.md#simpleentity) + + [SimpleDA.class](simpleda.md) + + [Placing Objects in an Entity Store](simpleput.md) + + [Retrieving Objects from an Entity Store](simpleget.md) + + [Retrieving Multiple Objects](getmultiple.md) + + [Cursor Initialization](getmultiple.md#dpl_cursor_initialize) + + [Working with Duplicate Keys](getmultiple.md#dpl_dups) + + [Key Ranges](getmultiple.md#dpl_cursor_range) + + [Join Cursors](dpl_entityjoin.md) + + [Deleting Entity Objects](dpl_delete.md) + + [Replacing Entity Objects](dpl_replace.md) + + [6. A DPL Example](dpl_example.md) + + [Vendor.java](dpl_example.md#vendorclass) + + [Inventory.java](inventoryclass.md) + + [MyDbEnv](mydbenv-persist.md) + + [DataAccessor.java](dataaccessorclass.md) + + [ExampleDatabasePut.java](dpl_exampledatabaseput.md) + + [ExampleInventoryRead.java](dpl_exampleinventoryread.md) diff --git a/docs_src/guides/gsg/java/dpl_delete.md b/docs_src/guides/gsg/java/dpl_delete.md new file mode 100644 index 000000000..604dc4101 --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_delete.md @@ -0,0 +1,47 @@ +--- +title: "Deleting Entity Objects" +api-name: "Deleting Entity Objects" +source: docs/gsg/JAVA/dpl_delete.html +--- +## Deleting Entity Objects + +The simplest way to remove an object from your entity store is to delete it by its primary index. For example, using the `SimpleDA` class that we created earlier in this document (see SimpleDA.class), you can delete the `SimpleEntityClass` object with a primary key of `keyone` as follows: + +``` c +sda.pIdx.delete("keyone"); +``` + +You can also delete objects by their secondary keys. When you do this, all objects related to the secondary key are deleted, unless the key is a foreign object. + +For example, the following deletes all `SimpleEntityClass` with a secondary key of `skeyone`: + +``` c +sda.sIdx.delete("skeyone"); +``` + +You can delete any single object by positioning a cursor to that object and then calling the cursor's `delete()` method. + +``` c +PrimaryIndex pi = + store.getPrimaryIndex(String.class, SimpleEntityClass.class); + +SecondaryIndex si = + store.getSecondaryIndex(pi, String.class, "sKey"); + +EntityCursor sec_cursor = + si.subIndex("skeyone").entities(); + +try { + SimpleEntityClass sec; + Iterator i = sec_cursor.iterator(); + while (sec = i.nextDup() != null) { + if (sec.getSKey() == "some value") { + i.delete(); + } + } +// Always make sure the cursor is closed when we are done with it. +} finally { + sec_cursor.close(); } +``` + +Finally, if you are indexing by foreign key, then the results of deleting the key is determined by the foreign key constraint that you have set for the index. See Foreign Key Constraints for more information. diff --git a/docs_src/guides/gsg/java/dpl_entityjoin.md b/docs_src/guides/gsg/java/dpl_entityjoin.md new file mode 100644 index 000000000..44ab4e6a8 --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_entityjoin.md @@ -0,0 +1,89 @@ +--- +title: "Join Cursors" +api-name: "Join Cursors" +source: docs/gsg/JAVA/dpl_entityjoin.html +--- +## Join Cursors + +If you have two or more secondary indexes set for an entity object, then you can retrieve sets of objects based on the intersection of multiple secondary index values. You do this using an `EntityJoin` class. + +For example, suppose you had an entity class that represented automobiles. In that case, you might be storing information about automobiles such as color, number of doors, fuel mileage, automobile type, number of passengers, make, model, and year, to name just a few. + +If you created a secondary index based this information, then you could use an `EntityJoin` to return all those objects representing cars with, say, two doors, that were built in 2002, and which are green in color. + +To create a join cursor, you: + +1. Open the primary index for the entity class on which you want to perform the join. + +2. Open the secondary indexes that you want to use for the join. + +3. Instantiate an `EntityJoin` object (you use the primary index to do this). + +4. Use two or more calls to `EntityJoin.addCondition()` to identify the secondary indexes and their values that you want to use for the equality match. + +5. Call `EntityJoin.entities()` to obtain a cursor that you can use to iterate over the join results. + +For example, suppose we had an entity class that included the following features: + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; +import static com.sleepycat.persist.model.Relationship.*; +import com.sleepycat.persist.model.SecondaryKey; + +@Entity +public class Automobiles { + + // Primary key is the vehicle identification number + @PrimaryKey + private String vin; + + // Secondary key is the vehicle's make + @SecondaryKey(relate=MANY_TO_ONE) + private String make; + + // Secondary key is the vehicle's color + @SecondaryKey(relate=MANY_TO_ONE) + private String color; + + ... + + public String getVIN() { + return vin; + } + + public String getMake() { + return make; + } + + public String getColor() { + return color; + } + + ... +``` + +Then we could perform an entity join that searches for all the red automobiles made by Toyota as follows: + +``` c +PrimaryIndex vin_pidx; +SecondaryIndex make_sidx; +SecondaryIndex color_sidx; + +EntityJoin join = new EntityJoin(vin_pidx); +join.addCondition(make_sidx,"Toyota"); +join.addCondition(color_sidx,"Red"); + +// Now iterate over the results of the join operation +ForwardCursor join_cursor = join.entities(); +try { + for (Automobiles autoi : join_cursor) { + // do something with each object "autoi" + } +// Always make sure the cursor is closed when we are done with it. +} finally { + join_cursor.close(); +} +``` diff --git a/docs_src/guides/gsg/java/dpl_example.md b/docs_src/guides/gsg/java/dpl_example.md new file mode 100644 index 000000000..92c5a6749 --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_example.md @@ -0,0 +1,126 @@ +--- +title: "Chapter 6. A DPL Example" +api-name: "Chapter 6. A DPL Example" +source: docs/gsg/JAVA/dpl_example.html +--- +## Chapter 6. A DPL Example + +**Table of Contents** + + [Vendor.java](dpl_example.md#vendorclass) + + [Inventory.java](inventoryclass.md) + + [MyDbEnv](mydbenv-persist.md) + + [DataAccessor.java](dataaccessorclass.md) + + [ExampleDatabasePut.java](dpl_exampledatabaseput.md) + + [ExampleInventoryRead.java](dpl_exampleinventoryread.md) + +In order to illustrate DPL usage, we provide a complete working example in this chapter. This example reads and writes inventory and vendor information for a mythical business. The application consists of the following classes: + +- Several classes used to encapsulate our application's data. See Vendor.java and Inventory.java. + +- A convenience class used to open and close our environment and entity store. See MyDbEnv. + +- A class that loads data into the store. See ExampleDatabasePut.java. + +- Finally, a class that reads data from the store. See ExampleInventoryRead.java. + +## Vendor.java + +The simplest class that our example wants to store contains vendor contact information. This class contains no secondary indices so all we have to do is identify it as an entity class and identify the field in the class used for the primary key. + +In the following example, we identify the `vendor` data member as containing the primary key. This data member is meant to contain a vendor's name. Because of the way we will use our `EntityStore`, the value provided for this data member must be unique within the store or runtime errors will result. + +When used with the DPL, our `Vendor` class appears as follows. Notice that the `@Entity` annotation appears immediately before the class declaration, and the `@PrimaryKey` annotation appears immediately before the `vendor` data member declaration. + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; + +@Entity +public class Vendor { + + private String address; + private String bizPhoneNumber; + private String city; + private String repName; + private String repPhoneNumber; + private String state; + + // Primary key is the vendor's name + // This assumes that the vendor's name is + // unique in the database. + @PrimaryKey + private String vendor; + + private String zipcode; + + public void setRepName(String data) { + repName = data; + } + + public void setAddress(String data) { + address = data; + } + + public void setCity(String data) { + city = data; + } + + public void setState(String data) { + state = data; + } + + public void setZipcode(String data) { + zipcode = data; + } + + public void setBusinessPhoneNumber(String data) { + bizPhoneNumber = data; + } + + public void setRepPhoneNumber(String data) { + repPhoneNumber = data; + } + + public void setVendorName(String data) { + vendor = data; + } + + public String getRepName() { + return repName; + } + + public String getAddress() { + return address; + } + + public String getCity() { + return city; + } + + public String getState() { + return state; + } + + public String getZipcode() { + return zipcode; + } + + public String getBusinessPhoneNumber() { + return bizPhoneNumber; + } + + public String getRepPhoneNumber() { + return repPhoneNumber; + } +} +``` + +For this class, the `vendor` value is set for an individual `Vendor` class object by the `setVendorName()` method. If our example code fails to set this value before storing the object, the data member used to store the primary key is set to a null value. This would result in a runtime error. diff --git a/docs_src/guides/gsg/java/dpl_exampledatabaseput.md b/docs_src/guides/gsg/java/dpl_exampledatabaseput.md new file mode 100644 index 000000000..de4f2a5fd --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_exampledatabaseput.md @@ -0,0 +1,227 @@ +--- +title: "ExampleDatabasePut.java" +api-name: "ExampleDatabasePut.java" +source: docs/gsg/JAVA/dpl_exampledatabaseput.html +--- +## ExampleDatabasePut.java + +Our example reads inventory and vendor information from flat text files, encapsulates this data in objects of the appropriate type, and then writes each object to an `EntityStore`. + +To begin, we import the Java classes that our example needs. Most of the imports are related to reading the raw data from flat text files and breaking them apart for usage with our data classes. We also import classes from the DB package, but we do not actually import any classes from the DPL. The reason why is because we have placed almost all of our DPL work off into other classes, so there is no need for direct usage of those APIs here. + +``` c +package persist.gettingStarted; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import com.sleepycat.db.DatabaseException; +``` + +Now we can begin the class itself. Here we set default paths for the on-disk resources that we require (the environment home, and the location of the text files containing our sample data). We also declare `DataAccessor` and `MyDbEnv` members. We describe these classes and show their implementation in DataAccessor.java and MyDbEnv. + +``` c +public class ExampleDatabasePut { + + private static File myDbEnvPath = new File("/tmp/JEDB"); + private static File inventoryFile = new File("./inventory.txt"); + private static File vendorsFile = new File("./vendors.txt"); + + private DataAccessor da; + + // Encapsulates the environment and data store. + private static MyDbEnv myDbEnv = new MyDbEnv(); +``` + +Next, we provide our `usage()` method. The command line options provided there are necessary only if the default values to the on-disk resources are not sufficient. + +``` c + private static void usage() { + System.out.println("ExampleDatabasePut [-h ]"); + System.out.println(" [-i ]"); + System.out.println(" [-v ]"); + System.exit(-1); + } +``` + +Our `main()` method is also reasonably self-explanatory. We simply instantiate an `ExampleDatabasePut` object there and then call its `run()` method. We also provide a top-level `try` block there for any exceptions that might be thrown during runtime. + +Notice that the `finally` statement in the top-level `try` block calls `MyDbEnv.close()`. This method closes our `EntityStore` and `Environment` objects. By placing it here in the `finally` statement, we can make sure that our store and environment are always cleanly closed. + +``` c + public static void main(String args[]) { + ExampleDatabasePut edp = new ExampleDatabasePut(); + try { + edp.run(args); + } catch (DatabaseException dbe) { + System.err.println("ExampleDatabasePut: " + dbe.toString()); + dbe.printStackTrace(); + } catch (Exception e) { + System.out.println("Exception: " + e.toString()); + e.printStackTrace(); + } finally { + myDbEnv.close(); + } + System.out.println("All done."); + } +``` + +Our `run()` method does four things. It calls `MyDbEnv.setup()`, which opens our `Environment` and `EntityStore`. It then instantiates a `DataAccessor` object, which we will use to write data to the store. It calls `loadVendorsDb()` which loads all of the vendor information. And then it calls `loadInventoryDb()` which loads all of the inventory information. + +Notice that the `MyDbEnv` object is being setup as read-write. This results in the `EntityStore` being opened for transactional support. (See MyDbEnv for implementation details.) + +``` c + private void run(String args[]) + throws DatabaseException { + // Parse the arguments list + parseArgs(args); + + myDbEnv.setup(myDbEnvPath, // Path to the environment home + false); // Environment read-only? + + // Open the data accessor. This is used to store + // persistent objects. + da = new DataAccessor(myDbEnv.getEntityStore()); + + System.out.println("loading vendors db...."); + loadVendorsDb(); + + System.out.println("loading inventory db...."); + loadInventoryDb(); + } +``` + +We can now implement the `loadVendorsDb()` method. This method is responsible for reading the vendor contact information from the appropriate flat-text file, populating `Vendor` class objects with the data and then writing it to the `EntityStore`. As explained above, each individual object is written with transactional support. However, because a transaction handle is not explicitly used, the write is performed using auto-commit. This happens because the `EntityStore` was opened to support transactions. + +To actually write each class to the `EntityStore`, we simply call the `PrimaryIndex.put()` method for the `Vendor` entity instance. We obtain this method from our `DataAccessor` class. + +``` c + private void loadVendorsDb() + throws DatabaseException { + + // loadFile opens a flat-text file that contains our data + // and loads it into a list for us to work with. The integer + // parameter represents the number of fields expected in the + // file. + List vendors = loadFile(vendorsFile, 8); + + // Now load the data into the store. + for (int i = 0; i < vendors.size(); i++) { + String[] sArray = (String[])vendors.get(i); + Vendor theVendor = new Vendor(); + theVendor.setVendorName(sArray[0]); + theVendor.setAddress(sArray[1]); + theVendor.setCity(sArray[2]); + theVendor.setState(sArray[3]); + theVendor.setZipcode(sArray[4]); + theVendor.setBusinessPhoneNumber(sArray[5]); + theVendor.setRepName(sArray[6]); + theVendor.setRepPhoneNumber(sArray[7]); + + // Put it in the store. + da.vendorByName.put(theVendor); + } + } +``` + +Now we can implement our `loadInventoryDb()` method. This does exactly the same thing as the `loadVendorsDb()` method. + +``` c + private void loadInventoryDb() + throws DatabaseException { + + // loadFile opens a flat-text file that contains our data + // and loads it into a list for us to work with. The integer + // parameter represents the number of fields expected in the + // file. + List inventoryArray = loadFile(inventoryFile, 6); + + // Now load the data into the store. The item's sku is the + // key, and the data is an Inventory class object. + + for (int i = 0; i < inventoryArray.size(); i++) { + String[] sArray = (String[])inventoryArray.get(i); + String sku = sArray[1]; + + Inventory theInventory = new Inventory(); + theInventory.setItemName(sArray[0]); + theInventory.setSku(sArray[1]); + theInventory.setVendorPrice( + (new Float(sArray[2])).floatValue()); + theInventory.setVendorInventory( + (new Integer(sArray[3])).intValue()); + theInventory.setCategory(sArray[4]); + theInventory.setVendor(sArray[5]); + + // Put it in the store. Note that this causes our secondary key + // to be automatically updated for us. + da.inventoryBySku.put(theInventory); + } + } +``` + +The remainder of this example simple parses the command line and loads data from a flat-text file. There is nothing here that is of specific interest to the DPL, but we show this part of the example anyway in the interest of completeness. + +``` c + 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': + myDbEnvPath = new File(args[++i]); + break; + case 'i': + inventoryFile = new File(args[++i]); + break; + case 'v': + vendorsFile = new File(args[++i]); + break; + default: + usage(); + } + } + } + } + + private List loadFile(File theFile, int numFields) { + List records = new ArrayList(); + try { + String theLine = null; + FileInputStream fis = new FileInputStream(theFile); + BufferedReader br = + new BufferedReader(new InputStreamReader(fis)); + while((theLine=br.readLine()) != null) { + String[] theLineArray = theLine.split("#"); + if (theLineArray.length != numFields) { + System.out.println("Malformed line found in " + + theFile.getPath()); + System.out.println("Line was: '" + theLine); + System.out.println("length found was: " + + theLineArray.length); + System.exit(-1); + } + records.add(theLineArray); + } + // Close the input stream handle + fis.close(); + } catch (FileNotFoundException e) { + System.err.println(theFile.getPath() + " does not exist."); + e.printStackTrace(); + usage(); + } catch (IOException e) { + System.err.println("IO Exception: " + e.toString()); + e.printStackTrace(); + System.exit(-1); + } + return records; + } + + protected ExampleDatabasePut() {} +} +``` diff --git a/docs_src/guides/gsg/java/dpl_exampleinventoryread.md b/docs_src/guides/gsg/java/dpl_exampleinventoryread.md new file mode 100644 index 000000000..6b69552c3 --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_exampleinventoryread.md @@ -0,0 +1,179 @@ +--- +title: "ExampleInventoryRead.java" +api-name: "ExampleInventoryRead.java" +source: docs/gsg/JAVA/dpl_exampleinventoryread.html +--- +## ExampleInventoryRead.java + +`ExampleInventoryRead` retrieves inventory information from our entity store and displays it. When it displays each inventory item, it also displays the related vendor contact information. + +`ExampleInventoryRead` can do one of two things. If you provide no search criteria, it displays all of the inventory items in the store. If you provide an item name (using the `-s` command line switch), then just those inventory items using that name are displayed. + +The beginning of our example is almost identical to our `ExampleDatabasePut` example program. We repeat that example code here for the sake of completeness. For a complete walk-through of it, see the previous section (ExampleDatabasePut.java). + +``` c +package persist.gettingStarted; + +import java.io.File; +import java.io.IOException; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.persist.EntityCursor; + +public class ExampleInventoryRead { + + private static File myDbEnvPath = + new File("/tmp/JEDB"); + + private DataAccessor da; + + // Encapsulates the database environment. + private static MyDbEnv myDbEnv = new MyDbEnv(); + + // The item to locate if the -s switch is used + private static String locateItem; + + private static void usage() { + System.out.println("ExampleInventoryRead [-h ]" + + "[-s ]"); + System.exit(-1); + } + + public static void main(String args[]) { + ExampleInventoryRead eir = new ExampleInventoryRead(); + try { + eir.run(args); + } catch (DatabaseException dbe) { + System.err.println("ExampleInventoryRead: " + dbe.toString()); + dbe.printStackTrace(); + } finally { + myDbEnv.close(); + } + System.out.println("All done."); + } + + private void run(String args[]) + throws DatabaseException { + // Parse the arguments list + parseArgs(args); + + myDbEnv.setup(myDbEnvPath, // path to the environment home + true); // is this environment read-only? + + // Open the data accessor. This is used to retrieve + // persistent objects. + da = new DataAccessor(myDbEnv.getEntityStore()); + + // If a item to locate is provided on the command line, + // show just the inventory items using the provided name. + // Otherwise, show everything in the inventory. + if (locateItem != null) { + showItem(); + } else { + showAllInventory(); + } + } +``` + +The first method that we provide is used to show inventory items related to a given inventory name. This method is called only if an inventory name is passed to `ExampleInventoryRead` via the `-s` option. Given the sample data that we provide with this example, each matching inventory name will result in the display of three inventory objects. + +To display these objects we use the `Inventory` class' `inventoryByName` secondary index to retrieve an `EntityCursor`, and then we iterate over the resulting objects using the cursor. + +Notice that this method calls `displayInventoryRecord()` to display each individual object. We show this method a little later in the example. + +``` c + // Shows all the inventory items that exist for a given + // inventory name. + private void showItem() throws DatabaseException { + + // Use the inventory name secondary key to retrieve + // these objects. + EntityCursor items = + da.inventoryByName.subIndex(locateItem).entities(); + try { + for (Inventory item : items) { + displayInventoryRecord(item); + } + } finally { + items.close(); + } + } +``` + +Next we implement `showAllInventory()`, which shows all of the `Inventory` objects in the store. To do this, we obtain an `EntityCursor` from the `Inventory` class' primary index and, again, we iterate using that cursor. + +``` c + // Displays all the inventory items in the store + private void showAllInventory() + throws DatabaseException { + + // Get a cursor that will walk every + // inventory object in the store. + EntityCursor items = + da.inventoryBySku.entities(); + + try { + for (Inventory item : items) { + displayInventoryRecord(item); + } + } finally { + items.close(); + } + } +``` + +Now we implement `displayInventoryRecord()`. This uses the getter methods on the `Inventory` class to obtain the information that we want to display. The only thing interesting about this method is that we obtain `Vendor` objects within. The vendor objects are retrieved `Vendor` objects using their primary index. We get the key for the retrieval from the `Inventory` object that we are displaying at the time. + +``` c + private void displayInventoryRecord(Inventory theInventory) + throws DatabaseException { + + System.out.println(theInventory.getSku() + ":"); + System.out.println("\t " + theInventory.getItemName()); + System.out.println("\t " + theInventory.getCategory()); + System.out.println("\t " + theInventory.getVendor()); + System.out.println("\t\tNumber in stock: " + + theInventory.getVendorInventory()); + System.out.println("\t\tPrice per unit: " + + theInventory.getVendorPrice()); + System.out.println("\t\tContact: "); + + Vendor theVendor = + da.vendorByName.get(theInventory.getVendor()); + assert theVendor != null; + + System.out.println("\t\t " + theVendor.getAddress()); + System.out.println("\t\t " + theVendor.getCity() + ", " + + theVendor.getState() + " " + theVendor.getZipcode()); + System.out.println("\t\t Business Phone: " + + theVendor.getBusinessPhoneNumber()); + System.out.println("\t\t Sales Rep: " + + theVendor.getRepName()); + System.out.println("\t\t " + + theVendor.getRepPhoneNumber()); + } +``` + +The last remaining parts of the example are used to parse the command line. This is not very interesting for our purposes here, but we show it anyway for the sake of completeness. + +``` c + protected ExampleInventoryRead() {} + + 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': + myDbEnvPath = new File(args[++i]); + break; + case 's': + locateItem = args[++i]; + break; + default: + usage(); + } + } + } + } +} +``` diff --git a/docs_src/guides/gsg/java/dpl_replace.md b/docs_src/guides/gsg/java/dpl_replace.md new file mode 100644 index 000000000..f421ba88d --- /dev/null +++ b/docs_src/guides/gsg/java/dpl_replace.md @@ -0,0 +1,40 @@ +--- +title: "Replacing Entity Objects" +api-name: "Replacing Entity Objects" +source: docs/gsg/JAVA/dpl_replace.html +--- +## Replacing Entity Objects + +To modify a stored entity object, retrieve it, update it, then put it back to the entity store: + +``` c +SimpleEntityClass sec = sda.pIdx.get("keyone"); +sec.setSKey("skeyoneupdated"); +sda.pIdx.put(sec); +``` + +Note that because we updated a field on the object that is a secondary key, this object will now be accessible by the secondary key of `skeyoneupdated` instead of the previous value, which was `skeyone` + +Be aware that if you modify the object's primary key, the behavior is somewhat different. In this case, you cause a new instance of the object to be created in the store, instead of replacing an existing instance: + +``` c +// Results in two objects in the store. One with a +// primary index of "keyfive" and the other with primary index of +//'keyfivenew'. +SimpleEntityClass sec = sda.pIdx.get("keyfive"); +sec.setPKey("keyfivenew"); +sda.pIdx.put(sec); +``` + +Finally, if you are iterating over a collection of objects using an `EntityCursor`, you can update each object in turn using `EntityCursor.update()`. Note, however, that you must be iterating using a `PrimaryIndex`; this operation is not allowed if you are using a `SecondaryIndex`. + +For example, the following iterates over every `SimpleEntityClass` object in the entity store, and it changes them all so that they have a secondary index of `updatedskey`: + +``` c +EntityCursor sec_pcursor = sda.pIdx.entities(); +for (SimpleEntityClass sec : sec_pcursor) { + sec.setSKey("updatedskey"); + sec_pcursor.update(item); +} +sec_pcursor.close(); +``` diff --git a/docs_src/guides/gsg/java/dplindexcreate.md b/docs_src/guides/gsg/java/dplindexcreate.md new file mode 100644 index 000000000..8c7f90e3a --- /dev/null +++ b/docs_src/guides/gsg/java/dplindexcreate.md @@ -0,0 +1,132 @@ +--- +title: "Creating Indexes" +api-name: "Creating Indexes" +source: docs/gsg/JAVA/dplindexcreate.html +--- +## Creating Indexes + + [Declaring a Primary Indexes](dplindexcreate.md#dplprimaryidxdecl) + + [Declaring Secondary Indexes](dplindexcreate.md#dplsecondaryidxdecl) + + [Foreign Key Constraints](dplindexcreate.md#foreignkey) + +To create an index using the DPL, you use Java annotations to declare which feature on the class is used for the primary index, and which features (if any) are to be used as secondary indexes. + +All entity classes stored in the DPL must have a primary index declared for it. + +Entity classes can have zero or more secondary indexes declared for them. There is no limit on the number of secondary indexes that you can declare. + +### Declaring a Primary Indexes + +You declare a primary key for an entity class by using the `@PrimaryKey` annotation. This annotation must appear immediately before the data member which represents the class's primary key. For example: + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; + +@Entity +public class Vendor { + + private String address; + private String bizPhoneNumber; + private String city; + private String repName; + private String repPhoneNumber; + private String state; + + // Primary key is the vendor's name + // This assumes that the vendor's name is + // unique in the database. + @PrimaryKey + private String vendor; + + ... +``` + +For this class, the `vendor` value is set for an individual `Vendor` class object by the `setVendorName()` method. If our example code fails to set this value before storing the object, the data member used to store the primary key is set to a null value. This would result in a runtime error. + +You can avoid the need to explicitly set a value for a class's primary index by specifying a sequence to be used for the primary key. This results in an unique integer value being used as the primary key for each stored object. + +You declare a sequence is to be used by specifying the `sequence` keyword to the `@PrimaryKey` annotation. You must also provide a name for the sequence. For example: For example: + +``` c +@PrimaryKey(sequence="Sequence_Namespace") +long myPrimaryKey; +``` + +### Declaring Secondary Indexes + +To declare a secondary index, we use the `@SecondaryKey` annotation. Note that when we do this, we must declare what sort of an index it is; that is, what is its relationship to other data in the data store. + +The *kind* of indices that we can declare are: + +- `ONE_TO_ONE` + + This relationship indicates that the secondary key is unique to the object. If an object is stored with a secondary key that already exists in the data store, a run time error is raised. + + For example, a person object might be stored with a primary key of a social security number (in the US), with a secondary key of the person's employee number. Both values are expected to be unique in the data store. + +- `MANY_TO_ONE` + + Indicates that the secondary key may be used for multiple objects in the data store. That is, the key appears more than once, but for each stored object it can be used only once. + + Consider a data store that relates managers to employees. A given manager will have multiple employees, but each employee is assumed to have just one manager. In this case, the manager's employee number might be a secondary key, so that you can quickly locate all the objects related to that manager's employees. + +- `ONE_TO_MANY` + + Indicates that the secondary key might be used more than once for a given object. Index keys themselves are assumed to be unique, but multiple instances of the index can be used per object. + + For example, employees might have multiple unique email addresses. In this case, any given object can be access by one or more email addresses. Each such address is unique in the data store, but each such address will relate to a single employee object. + +- `MANY_TO_MANY` + + There can be multiple keys for any given object, and for any given key there can be many related objects. + + For example, suppose your organization has a shared resource, such as printers. You might want to track which printers a given employee can use (there might be more than one). You might also want to track which employees can use a specific printer. This represents a many-to-many relationship. + +Note that for `ONE_TO_ONE` and `MANY_TO_ONE` relationships, you need a simple data member (not an array or collection) to hold the key. For `ONE_TO_MANY` and `MANY_TO_MANY` relationships, you need an array or collection to hold the keys: + +``` c +@SecondaryKey(relate=ONE_TO_ONE) +private String primaryEmailAddress = new String(); + +@SecondaryKey(relate=ONE_TO_MANY) +private Set emailAddresses = new HashSet(); +``` + +### Foreign Key Constraints + +Sometimes a secondary index is related in some way to another entity class that is also contained in the data store. That is, the secondary key might be the primary key for another entity class. If this is the case, you can declare the foreign key constraint to make data integrity easier to accomplish. + +For example, you might have one class that is used to represent employees. You might have another that is used to represent corporate divisions. When you add or modify an employee record, you might want to ensure that the division to which the employee belongs is known to the data store. You do this by specifying a foreign key constraint. + +When a foreign key constraint is declared: + +- When a new secondary key for the object is stored, it is checked to make sure it exists as a primary key for the related entity object. If it does not, a runtime error occurs. + +- When a related entity is deleted (that is, a corporate division is removed from the data store), some action is automatically taken for the entities that refer to this object (that is, the employee objects). Exactly what that action is, is definable by you. See below. + +When a related entity is deleted from the data store, one of the following actions are taken: + +- `ABORT` + + The delete operation is not allowed. A runtime error is raised as a result of the operation. This is the default behavior. + +- `CASCADE` + + All entities related to this one are deleted as well. For example, if you deleted a `Division` object, then all `Employee` objects that belonged to the division are also deleted. + +- `NULLIFY` + + All entities related to the deleted entity are updated so that the pertinent data member is nullified. That is, if you deleted a division, then all employee objects related to that division would have their division key automatically set to null. + +You declare a foreign key constraint by using the `relatedEntity` keyword. You declare the foreign key constraint deletion policy using the `onRelatedEntityDelete` keyword. For example, the following declares a foreign key constraint to `Division` class objects, and it causes related objects to be deleted if the `Division` class is deleted: + +``` c +@SecondaryKey(relate=ONE_TO_ONE, relatedEntity=Division.class, + onRelatedEntityDelete=CASCADE) +private String division = new String(); +``` diff --git a/docs_src/guides/gsg/java/getmultiple.md b/docs_src/guides/gsg/java/getmultiple.md new file mode 100644 index 000000000..c30ee1434 --- /dev/null +++ b/docs_src/guides/gsg/java/getmultiple.md @@ -0,0 +1,144 @@ +--- +title: "Retrieving Multiple Objects" +api-name: "Retrieving Multiple Objects" +source: docs/gsg/JAVA/getmultiple.html +--- +## Retrieving Multiple Objects + + [Cursor Initialization](getmultiple.md#dpl_cursor_initialize) + + [Working with Duplicate Keys](getmultiple.md#dpl_dups) + + [Key Ranges](getmultiple.md#dpl_cursor_range) + +It is possible to iterate over every object referenced by a specific index. You may want to do this if, for example, you want to examine or modify every object accessible by a specific primary index. + +In addition, some indexes result in the retrieval of multiple objects. For example, `MANY_TO_ONE` secondary indexes can result in more than one object for any given key (also known as *duplicate keys*). When this is the case, you must iterate over the resulting set of objects in order to examine each object in turn. + +There are two ways to iterate over a collection of objects as returned by an index. One is to use a standard Java `Iterator`, which you obtain using an `EntityCursor`, which in turn you can obtain from a `PrimaryIndex`: + +``` c +PrimaryIndex pi = + store.getPrimaryIndex(String.class, SimpleEntityClass.class); +EntityCursor pi_cursor = pi.entities(); +try { + Iterator i = pi_cursor.iterator(); + while (i.hasNext()) { + // Do something here + } +} finally { + // Always close the cursor + pi_cursor.close(); +} +``` + +Alternatively, you can use a Java "foreach" statement to iterate over object set: + +``` c +PrimaryIndex pi = + store.getPrimaryIndex(String.class, SimpleEntityClass.class); +EntityCursor pi_cursor = pi.entities(); +try { + for (SimpleEntityClass seci : pi_cursor) { + // do something with each object "seci" + } +// Always make sure the cursor is closed when we are done with it. +} finally { + pi_cursor.close(); +} +``` + +### Cursor Initialization + +When a cursor is first opened, it is not positioned to any value; that is, it is not *initialized*. Most of the `EntityCursor` methods that move a cursor will initialize it to either the first or last object, depending on whether the operation is moving the cursor forward (all `next...` methods) or backwards (all `prev...`) methods. + +You can also force a cursor, whether it is initialized or not, to return the first object by calling `EntityCursor.first()`. Similarly, you can force a return of the last object using `EntityCursor.last()`. + +Operations that do not move the cursor (such as `EntityCursor.current()` or `EntityCursor.delete()` will throw an `IllegalStateException` when used on an uninitialized cursor. + +### Working with Duplicate Keys + +If you have duplicate secondary keys, you can return an `EntityIndex` class object for them using `SecondaryIndex.subIndex()` Then, use that object's `entities()` method to obtain an `EntityCursor` instance. + +For example: + +``` c +PrimaryIndex pi = + store.getPrimaryIndex(String.class, SimpleEntityClass.class); + +SecondaryIndex si = + store.getSecondaryIndex(pi, String.class, "sKey"); + +EntityCursor sec_cursor = + si.subIndex("skeyone").entities(); + +try { +for (SimpleEntityClass seci : sec_cursor) { + // do something with each object "seci" + } +// Always make sure the cursor is closed when we are done with it. +} finally { + sec_cursor.close(); } +``` + +Note that if you are working with duplicate keys, you can control how cursor iteration works by using the following `EntityCursor` methods: + +- `nextDup()` + + Moves the cursor to the next object with the same key as the cursor is currently referencing. That is, this method returns the next duplicate object. If no such object exists, this method returns `null`. + +- `prevDup()` + + Moves the cursor to the previous object with the same key as the cursor is currently referencing. That is, this method returns the previous duplicate object in the cursor's set of objects. If no such object exists, this method returns `null`. + +- `nextNoDup()` + + Moves the cursor to the next object in the cursor's set that has a key which is different than the key that the cursor is currently referencing. That is, this method skips all duplicate objects and returns the next non-duplicate object in the cursor's set of objects. If no such object exists, this method returns `null`. + +- `prevNoDup()` + + Moves the cursor to the previous object in the cursor's set that has a key which is different than the key that the cursor is currently referencing. That is, this method skips all duplicate objects and returns the previous non-duplicate object in the cursor's set of objects. If no such object exists, this method returns `null`. + +For example: + +``` c +PrimaryIndex pi = + store.getPrimaryIndex(String.class, SimpleEntityClass.class); + +SecondaryIndex si = + store.getSecondaryIndex(pi, String.class, "sKey"); + +EntityCursor sec_cursor = + si.subIndex("skeyone").entities(); + +try { + SimpleEntityClass sec; + Iterator i = sec_cursor.iterator(); + while (sec = i.nextNoDup() != null) { + // Do something here + } +// Always make sure the cursor is closed when we are done with it. +} finally { + sec_cursor.close(); } +``` + +### Key Ranges + +You can restrict the scope of a cursor's movement by specifying a *range* when you create the cursor. The cursor can then never be positioned outside of the specified range. + +When specifying a range, you indicate whether a range bound is *inclusive* or *exclusive* by providing a boolean value for each range. `true` indicates that the provided bound is inclusive, while `false` indicates that it is exclusive. + +You provide this information when you call `PrimaryIndex.entities()` or `SecondaryIndex.entities()`. For example, suppose you had a class indexed by numerical information. Suppose further that you wanted to examine only those objects with indexed values of 100 - 199. Then (assuming the numerical information is the primary index), you can bound your cursor as follows: + +``` c +EntityCursor cursor = + primaryIndex.entities(100, true, 200, false); + +try { + for (SomeEntityClass sec : cursor { + // Do something here to objects ranged from 100 to 199 + } +// Always make sure the cursor is closed when we are done with it. +} finally { + cursor.close(); } +``` diff --git a/docs_src/guides/gsg/java/gettingit.md b/docs_src/guides/gsg/java/gettingit.md new file mode 100644 index 000000000..f91ac2594 --- /dev/null +++ b/docs_src/guides/gsg/java/gettingit.md @@ -0,0 +1,12 @@ +--- +title: "Getting and Using DB" +api-name: "Getting and Using DB" +source: docs/gsg/JAVA/gettingit.html +--- +## Getting and Using DB + +You can obtain DB by visiting the Berkeley DB download page: http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +To install DB, untar or unzip the distribution to the directory of your choice. You will then need to build the product binaries. For information on building DB, see *DB_INSTALL*`/docs/index.html`, where *DB_INSTALL* is the directory where you unpacked DB. On that page, you will find links to platform-specific build instructions. + +That page also contains links to more documentation for DB. In particular, you will find links for the *Berkeley DB Programmer's Reference Guide* as well as the API reference documentation. diff --git a/docs_src/guides/gsg/java/index.md b/docs_src/guides/gsg/java/index.md new file mode 100644 index 000000000..9188f980e --- /dev/null +++ b/docs_src/guides/gsg/java/index.md @@ -0,0 +1,280 @@ +--- +title: "Getting Started with Berkeley DB" +api-name: "Getting Started with Berkeley DB" +source: docs/gsg/JAVA/index.html +--- +# Getting Started with Berkeley DB + +**Language:** [C](../index.md) · [C++](../cxx/index.md) · Java (this page) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the United States and other countries. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + +[Preface](preface.md) + +[Conventions Used in this Book](preface.md#conventions) + +[For More Information](moreinfo.md) + +[Contact Us](moreinfo.md#contact_us) + +[1. Introduction to Berkeley DB](introduction.md) + +[About This Manual](introduction.md#aboutthismanual) + +[Berkeley DB Concepts](javadplconcepts.md) + +[Environments](javadplconcepts.md#dplenvconcepts) + +[Key-Data Pairs](javadplconcepts.md#key-data) + +[Storing Data](javadplconcepts.md#storing-intro) + +[Duplicate Data](javadplconcepts.md#duplicatesintro) + +[Replacing and Deleting Entries](javadplconcepts.md#replacedeleteIntro) + +[Secondary Keys](javadplconcepts.md#secondary) + +[Which API Should You Use?](javadplconcepts.md#whichapi) + +[Access Methods](accessmethods.md) + +[Selecting Access Methods](accessmethods.md#selectAM) + +[Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + +[Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + +[Database Limits and Portability](databaseLimits.md) + +[Exception Handling](coreExceptions.md) + +[Error Returns](returns.md) + +[Getting and Using DB](gettingit.md) + +[2. Database Environments](Env.md) + +[Opening Database Environments](Env.md#EnvOpen) + +[Closing Database Environments](EnvClose.md) + +[Environment Properties](EnvProps.md) + +[The EnvironmentConfig Class](EnvProps.md#envconfig) + +[EnvironmentMutableConfig](EnvProps.md#envhandleconfig) + +[I. Programming with the Direct Persistence Layer](dpl.md) + +[3. Direct Persistence Layer First Steps](persist_first.md) + +[Entity Stores](persist_first.md#entitystore) + +[Opening and Closing Environments and Stores](persist_first.md#persist-open) + +[Persistent Objects](persistobject.md) + +[Saving and Retrieving Data](saveret.md) + +[4. Working with Indices](persist_index.md) + +[Accessing Indexes](persist_index.md#dplindexaccess) + +[Accessing Primary Indices](persist_index.md#primaryindexaccess) + +[Accessing Secondary Indices](persist_index.md#secondaryindexaccess) + +[Creating Indexes](dplindexcreate.md) + +[Declaring a Primary Indexes](dplindexcreate.md#dplprimaryidxdecl) + +[Declaring Secondary Indexes](dplindexcreate.md#dplsecondaryidxdecl) + +[Foreign Key Constraints](dplindexcreate.md#foreignkey) + +[5. Saving and Retrieving Objects](persist_access.md) + +[A Simple Entity Class](persist_access.md#simpleentity) + +[SimpleDA.class](simpleda.md) + +[Placing Objects in an Entity Store](simpleput.md) + +[Retrieving Objects from an Entity Store](simpleget.md) + +[Retrieving Multiple Objects](getmultiple.md) + +[Cursor Initialization](getmultiple.md#dpl_cursor_initialize) + +[Working with Duplicate Keys](getmultiple.md#dpl_dups) + +[Key Ranges](getmultiple.md#dpl_cursor_range) + +[Join Cursors](dpl_entityjoin.md) + +[Deleting Entity Objects](dpl_delete.md) + +[Replacing Entity Objects](dpl_replace.md) + +[6. A DPL Example](dpl_example.md) + +[Vendor.java](dpl_example.md#vendorclass) + +[Inventory.java](inventoryclass.md) + +[MyDbEnv](mydbenv-persist.md) + +[DataAccessor.java](dataaccessorclass.md) + +[ExampleDatabasePut.java](dpl_exampledatabaseput.md) + +[ExampleInventoryRead.java](dpl_exampleinventoryread.md) + +[II. Programming with the Base API](baseapi.md) + +[7. Databases](databases.md) + +[Opening Databases](databases.md#DBOpen) + +[Closing Databases](coredbclose.md) + +[Database Properties](dbprops.md) + +[Administrative Methods](DBAdmin.md) + +[Error Reporting Functions](dbErrorReporting.md) + +[Managing Databases in Environments](CoreEnvUsage.md) + +[Database Example](CoreJavaUsage.md) + +[8. Database Records](DBEntry.md) + +[Using Database Records](DBEntry.md#usingDbEntry) + +[Reading and Writing Database Records](usingDbt.md) + +[Writing Records to the Database](usingDbt.md#databaseWrite) + +[Getting Records from the Database](usingDbt.md#databaseRead) + +[Deleting Records](usingDbt.md#recordDelete) + +[Data Persistence](usingDbt.md#datapersist) + +[Using the BIND APIs](bindAPI.md) + +[Numerical and String Objects](bindAPI.md#bindPrimitive) + +[Serializable Complex Objects](bindAPI.md#object2dbt) + +[Custom Tuple Bindings](bindAPI.md#customTuple) + +[Database Usage Example](dbtJavaUsage.md) + +[9. Using Cursors](Cursors.md) + +[Opening and Closing Cursors](Cursors.md#openCursor) + +[Getting Records Using the Cursor](Positioning.md) + +[Searching for Records](Positioning.md#cursorsearch) + +[Working with Duplicate Records](Positioning.md#getdups) + +[Putting Records Using Cursors](PutEntryWCursor.md) + +[Deleting Records Using Cursors](DeleteEntryWCursor.md) + +[Replacing Records Using Cursors](ReplacingEntryWCursor.md) + +[Cursor Example](cursorJavaUsage.md) + +[10. Secondary Databases](indexes.md) + +[Opening and Closing Secondary Databases](indexes.md#DbAssociate) + +[Implementing Key Creators](keyCreator.md) + +[Working with Multiple Keys](keyCreator.md#multikeys) + +[Secondary Database Properties](secondaryProps.md) + +[Reading Secondary Databases](readSecondary.md) + +[Deleting Secondary Database Records](secondaryDelete.md) + + [Using Secondary Cursors](secondaryCursor.md) + +[Database Joins](joins.md) + +[Using Join Cursors](joins.md#joinUsage) + +[JoinCursor Properties](joins.md#joinconfig) + +[Secondary Database Example](javaindexusage.md) + +[Opening Secondary Databases with MyDbs](javaindexusage.md#secondaryMyDbs) + +[Using Secondary Databases with ExampleDatabaseRead](javaindexusage.md#exampleReadJavaSecondaries) + +[11. Database Configuration](dbconfig.md) + +[Setting the Page Size](dbconfig.md#pagesize) + +[Overflow Pages](dbconfig.md#overflowpages) + +[Locking](dbconfig.md#Locking) + +[IO Efficiency](dbconfig.md#IOEfficiency) + +[Page Sizing Advice](dbconfig.md#pagesizeAdvice) + +[Selecting the Cache Size](cachesize.md) + +[BTree Configuration](btree.md) + +[Allowing Duplicate Records](btree.md#duplicateRecords) + +[Setting Comparison Functions](btree.md#comparators) + +**List of Examples** + +7.1. [MyDbs Class](CoreJavaUsage.md#MyDb) + +8.1. [Inventory.java](dbtJavaUsage.md#inventoryjava) + +8.2. [Vendor.java](dbtJavaUsage.md#vendorjava) + +8.3. [InventoryBinding.java](dbtJavaUsage.md#InventoryJavaBinding) + +8.4. [Stored Class Catalog Management with MyDbs](dbtJavaUsage.md#dbsStoredClass) + +8.5. [ExampleDatabaseLoad.java](dbtJavaUsage.md#EDL) + +9.1. [ExampleDatabaseRead.java](cursorJavaUsage.md#EDR) + +10.1. [ItemNameKeyCreator.java](javaindexusage.md#ItemNameKeyCreator-Java) + +10.2. [SecondaryDatabase Management with MyDbs](javaindexusage.md#mydbsSecondary) + +10.3. [SecondaryDatabase usage with ExampleDatabaseRead](javaindexusage.md#secondaryWithEDR) + +\> diff --git a/docs_src/guides/gsg/java/indexes.md b/docs_src/guides/gsg/java/indexes.md new file mode 100644 index 000000000..4ed6872b2 --- /dev/null +++ b/docs_src/guides/gsg/java/indexes.md @@ -0,0 +1,152 @@ +--- +title: "Chapter 10. Secondary Databases" +api-name: "Chapter 10. Secondary Databases" +source: docs/gsg/JAVA/indexes.html +--- +## Chapter 10. Secondary Databases + +**Table of Contents** + + [Opening and Closing Secondary Databases](indexes.md#DbAssociate) + + [Implementing Key Creators](keyCreator.md) + + [Working with Multiple Keys](keyCreator.md#multikeys) + + [Secondary Database Properties](secondaryProps.md) + + [Reading Secondary Databases](readSecondary.md) + + [Deleting Secondary Database Records](secondaryDelete.md) + + [Using Secondary Cursors](secondaryCursor.md) + + [Database Joins](joins.md) + + [Using Join Cursors](joins.md#joinUsage) + + [JoinCursor Properties](joins.md#joinconfig) + + [Secondary Database Example](javaindexusage.md) + + [Opening Secondary Databases with MyDbs](javaindexusage.md#secondaryMyDbs) + + [Using Secondary Databases with ExampleDatabaseRead](javaindexusage.md#exampleReadJavaSecondaries) + +Usually you find database records by means of the record's key. However, the key that you use for your record will not always contain the information required to provide you with rapid access to the data that you want to retrieve. For example, suppose your `Database` contains records related to users. The key might be a string that is some unique identifier for the person, such as a user ID. Each record's data, however, would likely contain a complex object containing details about people such as names, addresses, phone numbers, and so forth. While your application may frequently want to query a person by user ID (that is, by the information stored in the key), it may also on occasion want to locate people by, say, their name. + +Rather than iterate through all of the records in your database, examining each in turn for a given person's name, you create indexes based on names and then just search that index for the name that you want. You can do this using secondary databases. In DB, the `Database` that contains your data is called a *primary database*. A database that provides an alternative set of keys to access that data is called a *secondary database* In a secondary database, the keys are your alternative (or secondary) index, and the data corresponds to a primary record's key. + +You create a secondary database by using a `SecondaryConfig` class object to identify an implementation of a `SecondaryKeyCreator` class object that is used to create keys based on data found in the primary database. You then pass this `SecondaryConfig` object to the `SecondaryDatabase` constructor. + +Once opened, DB manages secondary databases for you. Adding or deleting records in your primary database causes DB to update the secondary as necessary. Further, changing a record's data in the primary database may cause DB to modify a record in the secondary, depending on whether the change forces a modification of a key in the secondary database. + +Note that you can not write directly to a secondary database. To change the data referenced by a `SecondaryDatabase` record, modify the primary database instead. The exception to this rule is that delete operations are allowed on the `SecondaryDatabase` object. See Deleting Secondary Database Records for more information. + +### Note + +Secondary database records are updated/created by DB only if the `SecondaryKeyCreator.createSecondaryKey()` method returns `true`. If `false` is returned, then DB will not add the key to the secondary database, and in the event of a record update it will remove any existing key. + +See Implementing Key Creators for more information on this interface and method. + +When you read a record from a secondary database, DB automatically returns the data and optionally the key from the corresponding record in the primary database. + +## Opening and Closing Secondary Databases + +You manage secondary database opens and closes using the `SecondaryDatabase` constructor. Just as is the case with primary databases, you must provide the `SecondaryDatabase()` constructor with the database's name and, optionally, other properties such as whether duplicate records are allowed, or whether the secondary database can be created on open. In addition, you must also provide: + +- A handle to the primary database that this secondary database is indexing. Note that this means that secondary databases are maintained only for the specified `Database` handle. If you open the same `Database` multiple times for write (such as might occur when opening a database for read-only and read-write in the same application), then you should open the `SecondaryDatabase` for each such `Database` handle. + +- A `SecondaryConfig` object that provides properties specific to a secondary database. The most important of these is used to identify the key creator for the database. The key creator is responsible for generating keys for the secondary database. See Secondary Database Properties for details. + +### Note + +Primary databases *must not* support duplicate records. Secondary records point to primary records using the primary key, so that key must be unique. + +So to open (create) a secondary database, you: + +1. Open your primary database. + +2. Instantiate your key creator. + +3. Instantiate your `SecondaryConfig` object. + +4. Set your key creator object on your `SecondaryConfig` object. + +5. Open your secondary database, specifying your primary database and your `SecondaryConfig` at that time. + +For example: + +``` c +package db.GettingStarted; +import com.sleepycat.examples.db.GettingStarted.MyTupleBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryConfig; + +import java.io.FileNotFoundException; + +... + +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +myDbConfig.setType(DatabaseType.BTREE); + +SecondaryConfig mySecConfig = new SecondaryConfig(); +mySecConfig.setAllowCreate(true); +mySecConfig.setType(DatabaseType.BTREE); +// Duplicates are frequently required for secondary databases. +mySecConfig.setSortedDuplicates(true); + +// Open the primary +Database myDb = null; +SecondaryDatabase mySecDb = null; +try { + String dbName = "myPrimaryDatabase"; + + myDb = new Database(dbName, null, myDbConfig); + + // A fake tuple binding that is not actually implemented anywhere. + // The tuple binding is dependent on the data in use. + // Tuple bindings are described earlier in this manual. + TupleBinding myTupleBinding = new MyTupleBinding(); + + // Open the secondary. + // Key creators are described in the next section. + FullNameKeyCreator keyCreator = + new FullNameKeyCreator(myTupleBinding); + + // Get a secondary object and set the key creator on it. + mySecConfig.setKeyCreator(keyCreator); + + // Perform the actual open + String secDbName = "mySecondaryDatabase"; + mySecDb = new SecondaryDatabase(secDbName, null, myDb, mySecConfig); +} catch (DatabaseException de) { + // Exception handling goes here ... +} catch (FileNotFoundException fnfe) { + // Exception handling goes here ... +} +``` + +To close a secondary database, call its close() method. Note that for best results, you should close all the secondary databases associated with a primary database before closing the primary. + +For example: + +``` c +try { + if (mySecDb != null) { + mySecDb.close(); + } + + if (myDb != null) { + myDb.close(); + } +} catch (DatabaseException dbe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/introduction.md b/docs_src/guides/gsg/java/introduction.md new file mode 100644 index 000000000..6c94aea5c --- /dev/null +++ b/docs_src/guides/gsg/java/introduction.md @@ -0,0 +1,82 @@ +--- +title: "Chapter 1. Introduction to Berkeley DB" +api-name: "Chapter 1. Introduction to Berkeley DB" +source: docs/gsg/JAVA/introduction.html +--- +## Chapter 1. Introduction to Berkeley DB + +**Table of Contents** + + [About This Manual](introduction.md#aboutthismanual) + + [Berkeley DB Concepts](javadplconcepts.md) + + [Environments](javadplconcepts.md#dplenvconcepts) + + [Key-Data Pairs](javadplconcepts.md#key-data) + + [Storing Data](javadplconcepts.md#storing-intro) + + [Duplicate Data](javadplconcepts.md#duplicatesintro) + + [Replacing and Deleting Entries](javadplconcepts.md#replacedeleteIntro) + + [Secondary Keys](javadplconcepts.md#secondary) + + [Which API Should You Use?](javadplconcepts.md#whichapi) + + [Access Methods](accessmethods.md) + + [Selecting Access Methods](accessmethods.md#selectAM) + + [Choosing between BTree and Hash](accessmethods.md#BTreeVSHash) + + [Choosing between Queue and Recno](accessmethods.md#QueueVSRecno) + + [Database Limits and Portability](databaseLimits.md) + + [Exception Handling](coreExceptions.md) + + [Error Returns](returns.md) + + [Getting and Using DB](gettingit.md) + +Welcome to Berkeley DB (DB). DB is a general-purpose embedded database engine that is capable of providing a wealth of data management services. It is designed from the ground up for high-throughput applications requiring in-process, bullet-proof management of mission-critical data. DB can gracefully scale from managing a few bytes to terabytes of data. For the most part, DB is limited only by your system's available physical resources. + +You use DB through a series of programming APIs which give you the ability to read and write your data, manage your database(s), and perform other more advanced activities such as managing transactions. The Java APIs that you use to interact with DB come in two basic flavors. The first is a high-level API that allows you to make Java classes persistent. The second is a lower-level API which provides additional flexibility when interacting with DB databases. + +### Note + +For long-time users of DB, the lower-level API is the traditional API that you are probably accustomed to using. + +Because DB is an embedded database engine, it is extremely fast. You compile and link it into your application in the same way as you would any third-party library. This means that DB runs in the same process space as does your application, allowing you to avoid the high cost of interprocess communications incurred by stand-alone database servers. + +To further improve performance, DB offers an in-memory cache designed to provide rapid access to your most frequently used data. Once configured, cache usage is transparent. It requires very little attention on the part of the application developer. + +Beyond raw speed, DB is also extremely configurable. It provides several different ways of organizing your data in its databases. Known as *access methods*, each such data organization mechanism provides different characteristics that are appropriate for different data management profiles. (Note that this manual focuses almost entirely on the BTree access method as this is the access method used by the vast majority of DB applications). + +To further improve its configurability, DB offers many different subsystems, each of which can be used to extend DB's capabilities. For example, many applications require write-protection of their data so as to ensure that data is never left in an inconsistent state for any reason (such as software bugs or hardware failures). For those applications, a transaction subsystem can be enabled and used to transactional-protect database writes. + +The list of operating systems on which DB is available is too long to detail here. Suffice to say that it is available on all major commercial operating systems, as well as on many embedded platforms. + +Finally, DB is available in a wealth of programming languages. DB is officially supported in C, C++, and Java, but the library is also available in many other languages, especially scripting languages such as Perl and Python. + +### Note + +Before going any further, it is important to mention that DB is not a relational database (although you could use it to build a relational database). Out of the box, DB does not provide higher-level features such as triggers, or a high-level query language such as SQL. Instead, DB provides just those minimal APIs required to store and retrieve your data as efficiently as possible. + +## About This Manual + +This manual introduces DB. As such, this book does not examine intermediate or advanced features such as threaded library usage or transactional usage. Instead, this manual provides a step-by-step introduction to DB's basic concepts and library usage. + +Specifically, this manual introduces the high-level Java API (the DPL), as well as the "base" Java API that the DPL relies upon. Regardless of the API set that you choose to use, there are a series of concepts and APIs that are common across the product. This manual starts by providing a high-level examination of DB. It then describes the APIs you use regardless of the API set that you choose to use. It then provides information on using the Direct Persistence Layer (DPL) API, followed by information on using the more extensive "base" API. + +Examples are given throughout this book that are designed to illustrate API usage. At the end of each chapter or section in this book, a complete example is given that is designed to reinforce the concepts covered in that chapter or section. In addition to being presented in this book, these final programs are also available in the DB software distribution. You can find them in + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +This book uses the Java programming languages for its examples. Note that versions of this book exist for the C and C++ languages as well. diff --git a/docs_src/guides/gsg/java/inventoryclass.md b/docs_src/guides/gsg/java/inventoryclass.md new file mode 100644 index 000000000..d1bc13532 --- /dev/null +++ b/docs_src/guides/gsg/java/inventoryclass.md @@ -0,0 +1,86 @@ +--- +title: "Inventory.java" +api-name: "Inventory.java" +source: docs/gsg/JAVA/inventoryclass.html +--- +## Inventory.java + +Our example's `Inventory` class is much like our `Vendor` class in that it is simply used to encapsulate data. However, in this case we want to be able to access objects two different ways: by product SKU and by product name. + +In our data set, the product SKU is required to be unique, so we use that as the primary key. The product name, however, is not a unique value so we set this up as a secondary key. + +The class appears as follows in our example: + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; +import static com.sleepycat.persist.model.Relationship.*; +import com.sleepycat.persist.model.SecondaryKey; + +@Entity +public class Inventory { + + // Primary key is sku + @PrimaryKey + private String sku; + + // Secondary key is the itemName + @SecondaryKey(relate=MANY_TO_ONE) + private String itemName; + + private String category; + private String vendor; + private int vendorInventory; + private float vendorPrice; + + public void setSku(String data) { + sku = data; + } + + public void setItemName(String data) { + itemName = data; + } + + public void setCategory(String data) { + category = data; + } + + public void setVendorInventory(int data) { + vendorInventory = data; + } + + public void setVendor(String data) { + vendor = data; + } + + public void setVendorPrice(float data) { + vendorPrice = data; + } + + public String getSku() { + return sku; + } + + public String getItemName() { + return itemName; + } + + public String getCategory() { + return category; + } + + public int getVendorInventory() { + return vendorInventory; + } + + public String getVendor() { + return vendor; + } + + public float getVendorPrice() { + return vendorPrice; + } +} +``` diff --git a/docs_src/guides/gsg/java/javadplconcepts.md b/docs_src/guides/gsg/java/javadplconcepts.md new file mode 100644 index 000000000..1f72f2423 --- /dev/null +++ b/docs_src/guides/gsg/java/javadplconcepts.md @@ -0,0 +1,154 @@ +--- +title: "Berkeley DB Concepts" +api-name: "Berkeley DB Concepts" +source: docs/gsg/JAVA/javadplconcepts.html +--- +## Berkeley DB Concepts + + [Environments](javadplconcepts.md#dplenvconcepts) + + [Key-Data Pairs](javadplconcepts.md#key-data) + + [Storing Data](javadplconcepts.md#storing-intro) + + [Duplicate Data](javadplconcepts.md#duplicatesintro) + + [Replacing and Deleting Entries](javadplconcepts.md#replacedeleteIntro) + + [Secondary Keys](javadplconcepts.md#secondary) + + [Which API Should You Use?](javadplconcepts.md#whichapi) + +Before continuing, it is useful to describe some of the concepts you will encounter when building a DB application. + +The concepts that you will encounter depend upon the actual API that you are using. Some of these concepts are common to both APIs, and so we present those first. Others are only interesting if you use the DPL, while others apply only to the base API. We present each of these in turn. + +### Environments + +Environments are required for applications built using the DPL. They are optional, but very commonly used, for applications built using the base API. Therefore, it is worthwhile to begin with them. + +An *environment* is essentially an encapsulation of one or more databases. You open an environment and then you open databases in that environment. When you do so, the databases are created/located in a location relative to the environment's home directory. + +Environments offer a great many features that a stand-alone DB database cannot offer: + +- Multi-database files. + + It is possible in DB to contain multiple databases in a single physical file on disk. This is desirable for those application that open more than a few handful of databases. However, in order to have more than one database contained in a single physical file, your application *must* use an environment. + +- Multi-thread and multi-process support + + When you use an environment, resources such as the in-memory cache and locks can be shared by all of the databases opened in the environment. The environment allows you to enable subsystems that are designed to allow multiple threads and/or processes to access DB databases. For example, you use an environment to enable the concurrent data store (CDS), the locking subsystem, and/or the shared memory buffer pool. + +- Transactional processing + + DB offers a transactional subsystem that allows for full ACID-protection of your database writes. You use environments to enable the transactional subsystem, and then subsequently to obtain transaction IDs. + +- High availability (replication) support + + DB offers a replication subsystem that enables single-master database replication with multiple read-only copies of the replicated data. You use environments to enable and then manage this subsystem. + +- Logging subsystem + + DB offers write-ahead logging for applications that want to obtain a high-degree of recoverability in the face of an application or system crash. Once enabled, the logging subsystem allows the application to perform two kinds of recovery ("normal" and "catastrophic") through the use of the information contained in the log files. + +For more information on these topics, see the *Berkeley DB Getting Started with Transaction Processing* guide and the *Berkeley DB Getting Started with Replicated Applications* guide. + +### Key-Data Pairs + +DB stores and retrieves data using *key-data pairs*. The *data* portion of this is the data that you have decided to store in DB for future retrieval. The *key* is the information that you want to use to look up your stored data once it has been placed inside a DB database. + +For example, if you were building a database that contained employee information, then the *data* portion is all of the information that you want to store about the employees: name, address, phone numbers, physical location, their manager, and so forth. + +The *key*, however, is the way that you look up any given employee. You can have more than one key if you wish, but every record in your database must have a primary key. If you are using the DPL, then this key must be unique; that is, it must not be used multiple times in the database. However, if you are using the base API, then this requirement is relaxed. See Duplicate Data for more information. + +For example, in the case of an employee database, you would probably use something like the employee identification number as the primary key as this uniquely identifies a given employee. + +You can optionally also have secondary keys that represent indexes into your database. These keys do not have to be unique to a given record; in fact, they often are not. For example, you might set up the employee's manager's name as a secondary key so that it is easy to locate all the employee's that work for a given manager. + +### Storing Data + +How you manage your stored information differs significantly, depending on which API you are using. Both APIs ultimately are doing the same thing, but the DPL hides a lot of the details from you. + +#### Storing Data in the DPL + +The DPL is used to store Java objects in an underlying series of databases. These databases are accessed using an `EntityStore` class object. + +To use the DPL, you must decorate the classes you want to store with Java annotations that identify them as either an *entity class* or a *persistent class*. + +Entity classes are classes that have a primary key, and optionally one or more secondary keys. That is, these are the classes that you will save and retrieve directly using the DPL. You identify an entity class using the `@Entity` java annotation. + +Persistent classes are classes used by entity classes. They do not have primary or secondary indices used for object retrieval. Rather, they are stored or retrieved when an entity class makes direct use of them. You identify an persistent class using the `@Persistent` java annotation. + +The primary key for an object is obtained from one of the class' data members. You identify which data member to use as the primary key using the `@PrimaryKey` java annotation. + +Note that all non-transient instance fields of a persistent class, as well as its superclasses and subclasses, are persistent. Static and transient fields are not persistent. The persistent fields of a class may be private, package-private (default access), protected or public. + +Also, simple Java types, such as `java.lang.String` and `java.util.Date`, are automatically handled as a persistent class when you use them in an entity class; you do not have to do anything special to cause these simple Java objects to be stored in the `EntityStore`. + +#### Storing Data using the Base API + +When you are not using the DPL, both record keys and record data must be byte arrays and are passed to and returned from DB using `DatabaseEntry` instances. `DatabaseEntry` only supports storage of Java byte arrays. Complex objects must be marshaled using either Java serialization, or more efficiently with the bind APIs provided with DB + +Database records and `byte` array conversion are described in Database Records. + +You store records in a `Database` by calling one of the put methods on a `Database` handle. DB automatically determines the record's proper placement in the database's internal B-Tree using whatever key and data comparison functions that are available to it. + +You can also retrieve, or get, records using the `Database` handle. Gets are performed by providing the key (and sometimes also the data) of the record that you want to retrieve. + +You can also use cursors for database puts and gets. Cursors are essentially a mechanism by which you can iterate over the records in the database. Like databases and database environments, cursors must be opened and closed. Cursors are managed using the `Cursor` class. + +Databases are described in Databases. Cursors are described in Using Cursors. + +### Duplicate Data + +If you are using the base API, then at creation time databases can be configured to allow duplicate data. Remember that DB database records consist of a key/data pair. *Duplicate data*, then, occurs when two or more records have identical keys, but different data. By default, a `Database` does not allow duplicate data. + +If your `Database ` contains duplicate data, then a simple database get based only on a key returns just the first record that uses that key. To access all duplicate records for that key, you must use a cursor. + +If you are using the DPL, then you can duplicate date using secondary keys, but not by using the primary key. For more information, see Retrieving Multiple Objects. + +### Replacing and Deleting Entries + +If you are using the DPL, then replacing a stored entity object simply consists of retrieving it, updating it, then storing it again. To delete the object, use the `delete()` method that is available on either its primary or secondary keys. If you use the `delete()` method available on the secondary key, then all objects referenced by that key are also deleted. See Deleting Entity Objects for more information. + +If you are using the base API, then how you replace database records depends on whether duplicate data is allowed in the database. + +If duplicate data is not allowed in the database, then simply calling `Database.put()` with the appropriate key will cause any existing record to be updated with the new data. Similarly, you can delete a record by providing the appropriate key to the `Database.delete()` method. + +If duplicate data is allowed in the database, then you must position a cursor to the record that you want to update, and then perform the put operation using the cursor. + +To delete records using the base API, you can use either `Database.delete()` or `Cursor.delete()`. If duplicate data is not allowed in your database, then these two method behave identically. However, if duplicates are allowed in the database, then `Database.delete()` deletes every record that uses the provided key, while `Cursor.delete()` deletes just the record at which the cursor is currently positioned. + +### Secondary Keys + +Secondary keys provide an alternative way to locate information stored in DB, beyond that which is provided by the primary key. Frequently secondary keys refer to more than one record in the database. In this way, you can find all the cars that are green (if you are maintaining an automotive database) or all the people with brown eyes (if you are maintaining a database about people). In other words, secondary keys represent a index into your data. + +How you create and maintain secondary keys differs significantly, depending on whether you are using the DPL or the base API. + +#### Using Secondaries with the DPL + +Under the DPL, you declare a particular field to be a secondary key by using the `@SecondaryKey` annotation. When you do this, you must declare what kind of an index you are creating. For example, you can declare a secondary key to be part of a `ONE_TO_ONE` index, in which case the key is unique to the object. Or you could declare the key to be `MANY_TO_ONE`, in which case the key can be used for multiple objects in the data store. + +Once you have identified secondary keys for a class, you can access those keys by using the `EntityStore.getSecondaryIndex()` method. + +For more information, see Declaring Secondary Indexes. + +#### Using Secondaries with the Base API. + +When you are using the base API, you create and maintain secondary keys using a special type of a database, called a *secondary database*. When you are using secondary databases, the database that holds the data you are indexing is called the *primary database*. + +You create a secondary database by opening it and associating it with an existing primary database. You must also provide a class that generates the secondary's keys (that is, the index) from primary records. Whenever a record in the primary database is added or changed, DB uses this class to determine what the secondary key should be. + +When a primary record is created, modified, or deleted, DB automatically updates the secondary database(s) for you as is appropriate for the operation performed on the primary. + +You manage secondary databases using the `SecondaryDatabase` class. You identify how to create keys for your secondary databases by supplying an instance of a class that implements the `SecondaryKeyCreator` interface. + +Secondary databases are described in Secondary Databases. + +### Which API Should You Use? + +Of the two APIs that DB makes available to you, we recommend that you use the DPL if all you want to do is make classes with a relatively static schema to be persistent. However, the DPL requires Java 1.5, so if you want to use Java 1.4 then you cannot use the DPL. + +Further, if you are porting an application between the C or C++ versions of DB and the Java version of this API, then you should not use the DPL as the base API is a much closer match to the other languages available for use with DB. + +Additionally, if your application uses a highly dynamic schema, then the DPL is probably a poor choice for your application, although the use of Java annotations can make the DPL work a little better for you in this situation. diff --git a/docs_src/guides/gsg/java/javaindexusage.md b/docs_src/guides/gsg/java/javaindexusage.md new file mode 100644 index 000000000..9fa1259b3 --- /dev/null +++ b/docs_src/guides/gsg/java/javaindexusage.md @@ -0,0 +1,378 @@ +--- +title: "Secondary Database Example" +api-name: "Secondary Database Example" +source: docs/gsg/JAVA/javaindexusage.html +--- +## Secondary Database Example + + [Opening Secondary Databases with MyDbs](javaindexusage.md#secondaryMyDbs) + + [Using Secondary Databases with ExampleDatabaseRead](javaindexusage.md#exampleReadJavaSecondaries) + +In previous chapters in this book, we built applications that load and display several DB databases. In this example, we will extend those examples to use secondary databases. Specifically: + +- In Stored Class Catalog Management with MyDbs we built a class that we can use to open several `Database` objects. In Opening Secondary Databases with MyDbs we will extend that class to also open and manage a `SecondaryDatabase`. + +- In Cursor Example we built an application to display our inventory database (and related vendor information). In Using Secondary Databases with ExampleDatabaseRead we will extend that application to show inventory records based on the index we cause to be loaded using `ExampleDatabaseLoad`. + +Before we can use a secondary database, we must implement a class to extract secondary keys for us. We use `ItemNameKeyCreator` for this purpose. + +**Example 10.1 ItemNameKeyCreator.java** + +This class assumes the primary database uses `Inventory` objects for the record data. The `Inventory` class is described in Inventory.java. + +In our key creator class, we make use of a custom tuple binding called `InventoryBinding`. This class is described in InventoryBinding.java. + +You can find `InventoryBinding.java` in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryKeyCreator; +import com.sleepycat.bind.tuple.TupleBinding; + +import java.io.IOException; + +public class ItemNameKeyCreator implements SecondaryKeyCreator { + + private TupleBinding theBinding; + + // Use the constructor to set the tuple binding + ItemNameKeyCreator(TupleBinding binding) { + theBinding = binding; + } + + // Abstract method that we must implement + public boolean createSecondaryKey(SecondaryDatabase secDb, + DatabaseEntry keyEntry, // From the primary + DatabaseEntry dataEntry, // From the primary + DatabaseEntry resultEntry) // set the key data on this. + throws DatabaseException { + + try { + // Convert dataEntry to an Inventory object + Inventory inventoryItem = + (Inventory) theBinding.entryToObject(dataEntry); + // Get the item name and use that as the key + String theItem = inventoryItem.getItemName(); + resultEntry.setData(theItem.getBytes("UTF-8")); + } catch (IOException willNeverOccur) {} + + return true; + } +} +``` + + + +Now that we have a key creator, we can use it to generate keys for a secondary database. We will now extend `MyDbs` to manage a secondary database, and to use `ItemNameKeyCreator` to generate keys for that secondary database. + +### Opening Secondary Databases with MyDbs + +In Stored Class Catalog Management with MyDbs we built `MyDbs` as an example of a class that encapsulates `Database` opens and closes. We will now extend that class to manage a `SecondaryDatabase`. + +**Example 10.2 SecondaryDatabase Management with MyDbs** + +We start by importing two additional classes needed to support secondary databases. We also add a global variable to use as a handle for our secondary database. + +``` c +// File MyDbs.java +package db.GettingStarted; + +import java.io.FileNotFoundException; + +import com.sleepycat.bind.serial.StoredClassCatalog; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.SecondaryConfig; +import com.sleepycat.db.SecondaryDatabase; + +public class MyDbs { + + // The databases that our application uses + private Database vendorDb = null; + private Database inventoryDb = null; + private Database classCatalogDb = null; + private SecondaryDatabase itemNameIndexDb = null; + + private String vendordb = "VendorDB.db"; + private String inventorydb = "InventoryDB.db"; + private String classcatalogdb = "ClassCatalogDB.db"; + private String itemnameindexdb = "ItemNameIndexDB.db"; + + // Needed for object serialization + private StoredClassCatalog classCatalog; + + // Our constructor does nothing + public MyDbs() {} +``` + +Next we update the `MyDbs.setup()` method to open the secondary database. As a part of this, we have to pass an `ItemNameKeyCreator` object on the call to open the secondary database. Also, in order to instantiate `ItemNameKeyCreator`, we need an `InventoryBinding` object (we described this class in InventoryBinding.java). We do all this work together inside of `MyDbs.setup()`. + +``` c + public void setup(String databasesHome) + throws DatabaseException { + DatabaseConfig myDbConfig = new DatabaseConfig(); + SecondaryConfig mySecConfig = new SecondaryConfig(); + + myDbConfig.setErrorStream(System.err); + mySecConfig.setErrorStream(System.err); + myDbConfig.setErrorPrefix("MyDbs"); + mySecConfig.setErrorPrefix("MyDbs"); + myDbConfig.setType(DatabaseType.BTREE); + mySecConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + mySecConfig.setAllowCreate(true); + + // Now open, or create and open, our databases + // Open the vendors and inventory databases + try { + vendordb = databasesHome + "/" + vendordb; + vendorDb = new Database(vendordb, + null, + myDbConfig); + + inventorydb = databasesHome + "/" + inventorydb; + inventoryDb = new Database(inventorydb, + null, + myDbConfig); + + // Open the class catalog db. This is used to + // optimize class serialization. + classcatalogdb = databasesHome + "/" + classcatalogdb; + classCatalogDb = new Database(classcatalogdb, + null, + myDbConfig); + } catch(FileNotFoundException fnfe) { + System.err.println("MyDbs: " + fnfe.toString()); + System.exit(-1); + } + + // Create our class catalog + classCatalog = new StoredClassCatalog(classCatalogDb); + + // Need a tuple binding for the Inventory class. + // We use the InventoryBinding class + // that we implemented for this purpose. + TupleBinding inventoryBinding = new InventoryBinding(); + + // Open the secondary database. We use this to create a + // secondary index for the inventory database + + // We want to maintain an index for the inventory entries based + // on the item name. So, instantiate the appropriate key creator + // and open a secondary database. + ItemNameKeyCreator keyCreator = + new ItemNameKeyCreator(new InventoryBinding()); + + // Set up additional secondary properties + // Need to allow duplicates for our secondary database + mySecConfig.setSortedDuplicates(true); + mySecConfig.setAllowPopulate(true); // Allow autopopulate + mySecConfig.setKeyCreator(keyCreator); + // Now open it + try { + itemnameindexdb = databasesHome + "/" + itemnameindexdb; + itemNameIndexDb = new SecondaryDatabase(itemnameindexdb, + null, + inventoryDb, + mySecConfig); + } catch(FileNotFoundException fnfe) { + System.err.println("MyDbs: " + fnfe.toString()); + System.exit(-1); + } + } + +``` + +Next we need an additional getter method for returning the secondary database. + +``` c + public SecondaryDatabase getNameIndexDB() { + return itemNameIndexDb; + } +``` + +Finally, we need to update the `MyDbs.close()` method to close the new secondary database. We want to make sure that the secondary is closed before the primaries. While this is not necessary for this example because our closes are single-threaded, it is still a good habit to adopt. + +``` c + public void close() { + try { + if (itemNameIndexDb != null) { + itemNameIndexDb.close(); + } + + if (vendorDb != null) { + vendorDb.close(); + } + + if (inventoryDb != null) { + inventoryDb.close(); + } + + if (classCatalogDb != null) { + classCatalogDb.close(); + } + + } catch(DatabaseException dbe) { + System.err.println("Error closing MyDbs: " + + dbe.toString()); + System.exit(-1); + } + } +} +``` + +That completes our update to `MyDbs`. You can find the complete class implementation in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + + + +### Using Secondary Databases with ExampleDatabaseRead + +Because we performed all our secondary database configuration management in `MyDbs`, we do not need to modify `ExampleDatabaseLoad` at all in order to create our secondary indices. When `ExampleDatabaseLoad` calls `MyDbs.setup()`, all of the necessary work is performed for us. + +However, we still need to take advantage of the new secondary indices. We do this by updating `ExampleDatabaseRead` to allow us to query for an inventory record based on its name. Remember that the primary key for an inventory record is the item's SKU. The item's name is contained in the `Inventory` object that is stored as each record's data in the inventory database. But our new secondary index now allows us to easily query based on the item's name. + +For this update, we modify `ExampleDatabaseRead` to accept a new command line switch, `-s`, whose argument is the name of an inventory item. If the switch is present on the command line call to `ExampleDatabaseRead`, then the application will use the secondary database to look up and display all the inventory records with that item name. Note that we use a `SecondaryCursor` to seek to the item name key and then display all matching records. + +Remember that you can find `ExampleDatabaseRead.java` in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. + +**Example 10.3 SecondaryDatabase usage with ExampleDatabaseRead** + +First we need to import an additional class in order to use the secondary cursor: + +``` c +package db.GettingStarted; + +import java.io.IOException; + +import com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.serial.SerialBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Cursor; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.SecondaryCursor; +``` + +Next we add a single global variable: + +``` c + public class ExampleDatabaseRead { + + private static String myDbsPath = "./"; + + // Encapsulates the database environment and databases. + private static MyDbs myDbs = new MyDbs(); + + private static TupleBinding inventoryBinding; + private static EntryBinding vendorBinding; + + // The item to locate if the -s switch is used + private static String locateItem; +``` + +Next we update `ExampleDatabaseRead.run()` to check to see if the `locateItem` global variable has a value. If it does, then we show just those records related to the item name passed on the `-s` switch. + +``` c + private void run(String args[]) + throws DatabaseException { + // Parse the arguments list + parseArgs(args); + + myDbs.setup(myDbsPath); + + // Setup our bindings. + inventoryBinding = new InventoryBinding(); + vendorBinding = + new SerialBinding(myDbs.getClassCatalog(), + Vendor.class); + + if (locateItem != null) { + showItem(); + } else { + showAllInventory(); + } + } +``` + +Finally, we need to implement `ExampleDatabaseRead.showItem()`. This is a fairly simple method that opens a secondary cursor, and then displays every primary record that is related to the secondary key identified by the `locateItem` global variable. + +``` c + private void showItem() throws DatabaseException { + SecondaryCursor secCursor = null; + try { + // searchKey is the key that we want to find in the + // secondary db. + DatabaseEntry searchKey = + new DatabaseEntry(locateItem.getBytes("UTF-8")); + + // foundKey and foundData are populated from the primary + // entry that is associated with the secondary db key. + DatabaseEntry foundKey = new DatabaseEntry(); + DatabaseEntry foundData = new DatabaseEntry(); + + // open a secondary cursor + secCursor = + myDbs.getNameIndexDB().openSecondaryCursor(null, null); + + // Search for the secondary database entry. + OperationStatus retVal = + secCursor.getSearchKey(searchKey, foundKey, + foundData, LockMode.DEFAULT); + + // Display the entry, if one is found. Repeat until no more + // secondary duplicate entries are found + while(retVal == OperationStatus.SUCCESS) { + Inventory theInventory = + (Inventory)inventoryBinding.entryToObject(foundData); + displayInventoryRecord(foundKey, theInventory); + retVal = secCursor.getNextDup(searchKey, foundKey, + foundData, LockMode.DEFAULT); + } + } catch (Exception e) { + System.err.println("Error on inventory secondary cursor:"); + System.err.println(e.toString()); + e.printStackTrace(); + } finally { + if (secCursor != null) { + secCursor.close(); + } + } + + } +``` + +The only other thing left to do is to update `ExampleDatabaseRead.parseArgs()` to support the `-s` command line switch. To see how this is done, see `ExampleDatabaseRead.java` in: + +``` c +DB_INSTALL/examples_java/db/GettingStarted +``` + +where *`DB_INSTALL`* is the location where you placed your DB distribution. diff --git a/docs_src/guides/gsg/java/joins.md b/docs_src/guides/gsg/java/joins.md new file mode 100644 index 000000000..bc982087b --- /dev/null +++ b/docs_src/guides/gsg/java/joins.md @@ -0,0 +1,174 @@ +--- +title: "Database Joins" +api-name: "Database Joins" +source: docs/gsg/JAVA/joins.html +--- +## Database Joins + + [Using Join Cursors](joins.md#joinUsage) + + [JoinCursor Properties](joins.md#joinconfig) + +If you have two or more secondary databases associated with a primary database, then you can retrieve primary records based on the intersection of multiple secondary entries. You do this using a `JoinCursor`. + +Throughout this document we have presented a class that stores inventory information on grocery That class is fairly simple with a limited number of data members, few of which would be interesting from a query perspective. But suppose, instead, that we were storing information on something with many more characteristics that can be queried, such as an automobile. In that case, you may be storing information such as color, number of doors, fuel mileage, automobile type, number of passengers, make, model, and year, to name just a few. + +In this case, you would still likely be using some unique value to key your primary entries (in the United States, the automobile's VIN would be ideal for this purpose). You would then create a class that identifies all the characteristics of the automobiles in your inventory. You would also have to create some mechanism by which you would move instances of this class in and out of Java `byte` arrays. We described the concepts and mechanisms by which you can perform these activities in Database Records. + +To query this data, you might then create multiple secondary databases, one for each of the characteristics that you want to query. For example, you might create a secondary for color, another for number of doors, another for number of passengers, and so forth. Of course, you will need a unique key creator for each such secondary database. You do all of this using the concepts and techniques described throughout this chapter. + +Once you have created this primary database and all interesting secondaries, what you have is the ability to retrieve automobile records based on a single characteristic. You can, for example, find all the automobiles that are red. Or you can find all the automobiles that have four doors. Or all the automobiles that are minivans. + +The next most natural step, then, is to form compound queries, or joins. For example, you might want to find all the automobiles that are red, and that were built by Toyota, and that are minivans. You can do this using a `JoinCursor` class instance. + +### Using Join Cursors + +To use a join cursor: + +- Open two or more secondary cursors. These cursors for secondary databases that are associated with the same primary database. + +- Position each such cursor to the secondary key value in which you are interested. For example, to build on the previous description, the cursor for the color database is positioned to the `red` records while the cursor for the model database is positioned to the `minivan` records, and the cursor for the make database is positioned to `Toyota`. + +- Create an array of secondary cursors, and place in it each of the cursors that are participating in your join query. + +- Obtain a join cursor. You do this using the `Database.join()` method. You must pass this method the array of secondary cursors that you opened and positioned in the previous steps. + +- Iterate over the set of matching records using `JoinCursor.getNext()` until `OperationStatus` is not `SUCCESS`. + +- Close your join cursor. + +- If you are done with them, close all your secondary cursors. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.JoinCursor; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.SecondaryCursor; +import com.sleepycat.db.SecondaryDatabase; + +... + +// Database and secondary database opens omitted for brevity. +// Assume a primary database handle: +// automotiveDB +// Assume 3 secondary database handles: +// automotiveColorDB -- index based on automobile color +// automotiveTypeDB -- index based on automobile type +// automotiveMakeDB -- index based on the manufacturer +Database automotiveDB = null; +SecondaryDatabase automotiveColorDB = null; +SecondaryDatabase automotiveTypeDB = null; +SecondaryDatabase automotiveMakeDB = null; + +// Query strings: +String theColor = "red"; +String theType = "minivan"; +String theMake = "Toyota"; + +// Secondary cursors used for the query: +SecondaryCursor colorSecCursor = null; +SecondaryCursor typeSecCursor = null; +SecondaryCursor makeSecCursor = null; + +// The join cursor +JoinCursor joinCursor = null; + +// These are needed for our queries +DatabaseEntry foundKey = new DatabaseEntry(); +DatabaseEntry foundData = new DatabaseEntry(); + +// All cursor operations are enclosed in a try block to ensure that they +// get closed in the event of an exception. + +try { + // Database entries used for the query: + DatabaseEntry color = new DatabaseEntry(theColor.getBytes("UTF-8")); + DatabaseEntry type = new DatabaseEntry(theType.getBytes("UTF-8")); + DatabaseEntry make = new DatabaseEntry(theMake.getBytes("UTF-8")); + + colorSecCursor = automotiveColorDB.openSecondaryCursor(null, null); + typeSecCursor = automotiveTypeDB.openSecondaryCursor(null, null); + makeSecCursor = automotiveMakeDB.openSecondaryCursor(null, null); + + // Position all our secondary cursors to our query values. + OperationStatus colorRet = + colorSecCursor.getSearchKey(color, foundData, LockMode.DEFAULT); + OperationStatus typeRet = + typeSecCursor.getSearchKey(type, foundData, LockMode.DEFAULT); + OperationStatus makeRet = + makeSecCursor.getSearchKey(make, foundData, LockMode.DEFAULT); + + // If all our searches returned successfully, we can proceed + if (colorRet == OperationStatus.SUCCESS && + typeRet == OperationStatus.SUCCESS && + makeRet == OperationStatus.SUCCESS) { + + // Get a secondary cursor array and populate it with our + // positioned cursors + SecondaryCursor[] cursorArray = {colorSecCursor, + typeSecCursor, + makeSecCursor}; + + // Create the join cursor + joinCursor = automotiveDB.join(cursorArray, null); + + // Now iterate over the results, handling each in turn + while (joinCursor.getNext(foundKey, foundData, LockMode.DEFAULT) == + OperationStatus.SUCCESS) { + + // Do something with the key and data retrieved in + // foundKey and foundData + } + } +} catch (DatabaseException dbe) { + // Error reporting goes here +} catch (Exception e) { + // Error reporting goes here +} finally { + try { + // Make sure to close out all our cursors + if (colorSecCursor != null) { + colorSecCursor.close(); + } + if (typeSecCursor != null) { + typeSecCursor.close(); + } + if (makeSecCursor != null) { + makeSecCursor.close(); + } + if (joinCursor != null) { + joinCursor.close(); + } + } catch (DatabaseException dbe) { + // Error reporting goes here + } +} +``` + +### JoinCursor Properties + +You can set `JoinCursor` properties using the `JoinConfig` class. Currently there is just one property that you can set: + +- `JoinConfig.setNoSort()` + + Specifies whether automatic sorting of input cursors is disabled. The cursors are sorted from the one that refers to the least number of data items to the one that refers to the most. + + If the data is structured so that cursors with many data items also share many common elements, higher performance will result from listing those cursors before cursors with fewer data items. Turning off sorting permits applications to specify cursors in the proper order given this scenario. + + The default value is `false` (automatic cursor sorting is performed). + + For example: + + ``` c + // All database and environments omitted + JoinConfig config = new JoinConfig(); + config.setNoSort(true); + JoinCursor joinCursor = myDb.join(cursorArray, config); + ``` diff --git a/docs_src/guides/gsg/java/keyCreator.md b/docs_src/guides/gsg/java/keyCreator.md new file mode 100644 index 000000000..48092d500 --- /dev/null +++ b/docs_src/guides/gsg/java/keyCreator.md @@ -0,0 +1,193 @@ +--- +title: "Implementing Key Creators" +api-name: "Implementing Key Creators" +source: docs/gsg/JAVA/keyCreator.html +--- +## Implementing Key Creators + + [Working with Multiple Keys](keyCreator.md#multikeys) + +You must provide every secondary database with a class that creates keys from primary records. You identify this class using the `SecondaryConfig.setKeyCreator()` method. + +You can create keys using whatever data you want. Typically you will base your key on some information found in a record's data, but you can also use information found in the primary record's key. How you build your keys is entirely dependent upon the nature of the index that you want to maintain. + +You implement a key creator by writing a class that implements the `SecondaryKeyCreator` interface. This interface requires you to implement the `SecondaryKeyCreator.createSecondaryKey()` method. + +One thing to remember when implementing this method is that you will need a way to extract the necessary information from the data's `DatabaseEntry` and/or the key's `DatabaseEntry` that are provided on calls to this method. If you are using complex objects, then you are probably using the Bind APIs to perform this conversion. The easiest thing to do is to instantiate the `EntryBinding` or `TupleBinding` that you need to perform the conversion, and then provide this to your key creator's constructor. The Bind APIs are introduced in Using the BIND APIs. + +`SecondaryKeyCreator.createSecondaryKey()` returns a boolean. A return value of `false` indicates that no secondary key exists, and therefore no record should be added to the secondary database for that primary record. If a record already exists in the secondary database, it is deleted. + +For example, suppose your primary database uses the following class for its record data: + +``` c +package db.GettingStarted; + +public class PersonData { + private String userID; + private String surname; + private String familiarName; + + public PersonData(String userID, String surname, + String familiarName) { + this.userID = userID; + this.surname = surname; + this.familiarName = familiarName; + } + + public String getUserID() { + return userID; + } + + public String getSurname() { + return surname; + } + + public String getFamiliarName() { + return familiarName; + } +} +``` + +Also, suppose that you have created a custom tuple binding, `PersonDataBinding`, that you use to convert `PersonData` objects to and from `DatabaseEntry` objects. (Custom tuple bindings are described in Custom Tuple Bindings.) + +Finally, suppose you want a secondary database that is keyed based on the person's full name. + +Then in this case you might create a key creator as follows: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.SecondaryKeyCreator; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.SecondaryDatabase; + +import java.io.IOException; + +public class FullNameKeyCreator implements SecondaryKeyCreator { + + private TupleBinding theBinding; + + public FullNameKeyCreator(TupleBinding theBinding1) { + theBinding = theBinding1; + } + + public boolean createSecondaryKey(SecondaryDatabase secDb, + DatabaseEntry keyEntry, + DatabaseEntry dataEntry, + DatabaseEntry resultEntry) { + + try { + PersonData pd = + (PersonData) theBinding.entryToObject(dataEntry); + String fullName = pd.getFamiliarName() + " " + + pd.getSurname(); + resultEntry.setData(fullName.getBytes("UTF-8")); + } catch (IOException willNeverOccur) {} + return true; + } +} +``` + +Finally, you use this key creator as follows: + +``` c +package db.GettingStarted; +import com.sleepycat.examples.db.GettingStarted.MyTupleBinding; +import com.sleepycat.bind.tuple.TupleBinding; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryConfig; + +import java.io.FileNotFoundException; + +... + +Database myDb = null; +SecondaryDatabase mySecDb = null; +try { + // Primary database open omitted for brevity +... + + TupleBinding myDataBinding = new MyTupleBinding(); + FullNameKeyCreator fnkc = new FullNameKeyCreator(myDataBinding); + + SecondaryConfig mySecConfig = new SecondaryConfig(); + mySecConfig.setKeyCreator(fnkc); + mySecConfig.setType(DatabaseType.BTREE); + + //Perform the actual open + String secDbName = "mySecondaryDatabase"; + mySecDb = new SecondaryDatabase(secDbName, null, myDb, mySecConfig); +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} finally { + try { + if (mySecDb != null) { + mySecDb.close(); + } + + if (myDb != null) { + myDb.close(); + } + } catch (DatabaseException dbe) { + // Exception handling goes here + } +} +``` + +### Working with Multiple Keys + +Until now we have only discussed indexes as if there is a one-to-one relationship between the secondary key and the primary database record. In fact, it is possible to generate multiple keys for any given record, provided that you take appropriate steps in your key creator to do so. + +For example, suppose you had a database that contained information about books. Suppose further that you sometimes want to look up books by author. Because sometimes books have multiple authors, you may want to return multiple secondary keys for every book that you index. + +To do this, you write a key creator that implements `SecondaryMultiKeyCreator` instead of `SecondaryKeyCreator`. The key difference between the two is that `SecondaryKeyCreator` uses a single `DatabaseEntry` object as the result, while `SecondaryMultiKeyCreator` returns a set of `DatabaseEntry` objects (using `java.util.Set`). Also, you assign the `SecondaryMultiKeyCreator` implementation using `SecondaryConfig.setMultiKeyCreator()` instead of `SecondaryConfig.setKeyCreator()`. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryMultiKeyCreator; + +import java.util.HashSet; +import java.util.Set; + +public class MyMultiKeyCreator implements SecondaryMultiKeyCreator { + + // Constructor not implemented. How this is implemented depends on + // how you want to extract the data for your keys. + MyMultiKeyCreator() { + ... + } + + // Abstract method that we must implement + public void createSecondaryKeys(SecondaryDatabase secDb, + DatabaseEntry keyEntry, // From the primary + DatabaseEntry dataEntry, // From the primary + Set results) // Results set + throws DatabaseException { + + try { + // Create your keys, adding each to the set + + // Creation of key 'a' not shown + results.add(a) + + // Creation of key 'b' not shown + results.add(b) + + } catch (IOException willNeverOccur) {} + } +} +``` diff --git a/docs_src/guides/gsg/java/moreinfo.md b/docs_src/guides/gsg/java/moreinfo.md new file mode 100644 index 000000000..5ec13c38a --- /dev/null +++ b/docs_src/guides/gsg/java/moreinfo.md @@ -0,0 +1,34 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg/JAVA/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a DB application: + +- Getting Started with Transaction Processing for Java + +- Berkeley DB Getting Started with Replicated Applications for Java + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB Installation and Build Guide + +- Berkeley DB Getting Started with the SQL APIs + +- Berkeley DB Javadoc + +- Berkeley DB Collections Tutorial + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg/java/mydbenv-persist.md b/docs_src/guides/gsg/java/mydbenv-persist.md new file mode 100644 index 000000000..687544d7a --- /dev/null +++ b/docs_src/guides/gsg/java/mydbenv-persist.md @@ -0,0 +1,97 @@ +--- +title: "MyDbEnv" +api-name: "MyDbEnv" +source: docs/gsg/JAVA/mydbenv-persist.html +--- +## MyDbEnv + +The applications that we are building for our example both must open and close environments and entity stores. One of our applications is writing to the entity store, so this application needs to open the store as read-write. It also wants to be able to create the store if it does not exist. + +Our second application only reads from the store. In this case, the store should be opened as read-only. + +We perform these activities by creating a single class that is responsible for opening and closing our store and environment. This class is shared by both our applications. To use it, callers need to only provide the path to the environment home directory, and to indicate whether the object is meant to be read-only. The class implementation is as follows: + +``` c +package persist.gettingStarted; + +import java.io.File; +import java.io.FileNotFoundException; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +public class MyDbEnv { + + private Environment myEnv; + private EntityStore store; + + // Our constructor does nothing + public MyDbEnv() {} + + // The setup() method opens the environment and store + // for us. + public void setup(File envHome, boolean readOnly) + throws DatabaseException { + + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + StoreConfig storeConfig = new StoreConfig(); + + myEnvConfig.setReadOnly(readOnly); + storeConfig.setReadOnly(readOnly); + + // If the environment is opened for write, then we want to be + // able to create the environment and entity store if + // they do not exist. + myEnvConfig.setAllowCreate(!readOnly); + storeConfig.setAllowCreate(!readOnly); + + try { + // Open the environment and entity store + myEnv = new Environment(envHome, myEnvConfig); + store = new EntityStore(myEnv, "EntityStore", storeConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("setup(): " + fnfe.toString()); + System.exit(-1); + } + + } + + // Return a handle to the entity store + public EntityStore getEntityStore() { + return store; + } + + // Return a handle to the environment + public Environment getEnv() { + return myEnv; + } + + // Close the store and environment. + public void close() { + if (store != null) { + try { + store.close(); + } catch(DatabaseException dbe) { + System.err.println("Error closing store: " + + dbe.toString()); + System.exit(-1); + } + } + + if (myEnv != null) { + try { + // Finally, close the environment. + myEnv.close(); + } catch(DatabaseException dbe) { + System.err.println("Error closing MyDbEnv: " + + dbe.toString()); + System.exit(-1); + } + } + } +} +``` diff --git a/docs_src/guides/gsg/java/persist_access.md b/docs_src/guides/gsg/java/persist_access.md new file mode 100644 index 000000000..8d306f66f --- /dev/null +++ b/docs_src/guides/gsg/java/persist_access.md @@ -0,0 +1,97 @@ +--- +title: "Chapter 5. Saving and Retrieving Objects" +api-name: "Chapter 5. Saving and Retrieving Objects" +source: docs/gsg/JAVA/persist_access.html +--- +## Chapter 5. Saving and Retrieving Objects + +**Table of Contents** + + [A Simple Entity Class](persist_access.md#simpleentity) + + [SimpleDA.class](simpleda.md) + + [Placing Objects in an Entity Store](simpleput.md) + + [Retrieving Objects from an Entity Store](simpleget.md) + + [Retrieving Multiple Objects](getmultiple.md) + + [Cursor Initialization](getmultiple.md#dpl_cursor_initialize) + + [Working with Duplicate Keys](getmultiple.md#dpl_dups) + + [Key Ranges](getmultiple.md#dpl_cursor_range) + + [Join Cursors](dpl_entityjoin.md) + + [Deleting Entity Objects](dpl_delete.md) + + [Replacing Entity Objects](dpl_replace.md) + +To store an object in an `EntityStore` you must annotate the class appropriately and then store it using `PrimaryIndex.put()`. + +To retrieve and object from an `EntityStore` you use the `get()` method from either the `PrimaryIndex` or `SecondaryIndex`, whichever is most appropriate for your application. + +In both cases, it simplifies things greatly if you create a data accessor class to organize your indexes. + +In the next few sections we: + +1. Create an entity class that is ready to be stored in an entity store. This class will have both a primary index (required) declared for it, as well as a secondary index (which is optional). + + See the next section for this implementation. + +2. Create a data accessor class which is used to organize our data. + + See SimpleDA.class for this implementation. + +3. Create a simple class that is used to put objects to our entity store. + + See Placing Objects in an Entity Store for this implementation. + +4. Create another class that retrieves objects from our entity store. + + See Retrieving Objects from an Entity Store for this implementation. + +## A Simple Entity Class + +For clarity's sake, this entity class is a simple a class as we can write. It contains only two data members, both of which are set and retrieved by simple setter and getter methods. Beyond that, by design this class does not do anything or particular interest. + +Its implementation is as follows: + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; +import static com.sleepycat.persist.model.Relationship.*; +import com.sleepycat.persist.model.SecondaryKey; + +@Entity +public class SimpleEntityClass { + + // Primary key is pKey + @PrimaryKey + private String pKey; + + // Secondary key is the sKey + @SecondaryKey(relate=MANY_TO_ONE) + private String sKey; + + public void setPKey(String data) { + pKey = data; + } + + public void setSKey(String data) { + sKey = data; + } + + public String getPKey() { + return pKey; + } + + public String getSKey() { + return sKey; + } +} +``` diff --git a/docs_src/guides/gsg/java/persist_first.md b/docs_src/guides/gsg/java/persist_first.md new file mode 100644 index 000000000..6b7cd5891 --- /dev/null +++ b/docs_src/guides/gsg/java/persist_first.md @@ -0,0 +1,123 @@ +--- +title: "Chapter 3. Direct Persistence Layer First Steps" +api-name: "Chapter 3. Direct Persistence Layer First Steps" +source: docs/gsg/JAVA/persist_first.html +--- +## Chapter 3. Direct Persistence Layer First Steps + +**Table of Contents** + + [Entity Stores](persist_first.md#entitystore) + + [Opening and Closing Environments and Stores](persist_first.md#persist-open) + + [Persistent Objects](persistobject.md) + + [Saving and Retrieving Data](saveret.md) + +This chapter guides you through the first few steps required to use the DPL with your application. These steps include: + +1. Opening your environment as was described in Opening Database Environments. + +2. Opening your entity store. + +3. Identifying the classes that you want to store in DB as either a `persistent` class or an `entity`. + +Once you have done these things, you can write your classes to the DB databases, read them back from the databases, delete them from the databases, and so forth. These activities are described in the chapters that follow in this part of this manual. + +## Entity Stores + + [Opening and Closing Environments and Stores](persist_first.md#persist-open) + +Entity stores are the basic unit of storage that you use with the DPL. That is, it is a unit of encapsulation for the classes that you want to store in DB. Under the hood it actually interacts with DB databases, but the DPL provides a layer of abstraction from the underlying DB APIs. The store, therefore, provides a simplified mechanism by which you read and write your stored classes. By using a store, you have access to your classes that is more simplified than if you were interacting with databases directly, but this simplified access comes at the cost of reduced flexibility. + +Entity stores have configurations in the same way that environments have configurations. You can use a `StoreConfig` object to identify store properties. Among these are methods that allow you to declare whether: + +- the store can be created if it does not exist at the time it is opened. Use the `StoreConfig.setAllowCreate()` method to set this. + +- the store is read-only. Use the `StoreConfig.setReadOnly()` method to set this. + +- the store supports transactions. Use the `StoreConfig.setTransactional()` method to set this. + + Writing DB transactional applications is described in the *Berkeley DB, Java Edition Getting Started with Transaction Processing* guide. + +`EntityStore` objects also provide methods for retrieving information about the store, such as: + +- the store's name. Use the `EntityStore.getStoreName()` method to retrieve this. + +- a handle to the environment in which the store is opened. Use the `EntityStore.getEnvironment` method to retrieve this handle. + +You can also use the `EntityStore` to retrieve all the primary and secondary indexes related to a given type of entity object contained in the store. See Working with Indices for more information. + +### Opening and Closing Environments and Stores + +As described in Database Environments, an *environment* is a unit of encapsulation for DB databases. It also provides a handle by which activities common across the databases can be managed. + +To use an entity store, you must first open an environment and then provide that environment handle to the `EntityStore` constructor. + +For example, the following code fragment configures both the environment and the entity store such that they can be created if they do not exist. Both the environment and the entity store are then opened. + +``` c +package persist.gettingStarted; + +import java.io.File; +import java.io.FileNotFoundException; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +... + +private Environment myEnv; +private EntityStore store; + +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + StoreConfig storeConfig = new StoreConfig(); + + myEnvConfig.setAllowCreate(!readOnly); + storeConfig.setAllowCreate(!readOnly); + + try { + // Open the environment and entity store + myEnv = new Environment(envHome, myEnvConfig); + store = new EntityStore(myEnv, "EntityStore", storeConfig); + } catch (FileNotFoundException fnfe) { + System.err.println(fnfe.toString()); + System.exit(-1); + } +} catch(DatabaseException dbe) { + System.err.println("Error opening environment and store: " + + dbe.toString()); + System.exit(-1); +} +``` + +As always, before you exit your program you should close both your store and your environment. It is recommended that you close your store before you close your environment. + +``` c +if (store != null) { + try { + store.close(); + } catch(DatabaseException dbe) { + System.err.println("Error closing store: " + + dbe.toString()); + System.exit(-1); + } +} + +if (myEnv != null) { + try { + // Finally, close environment. + myEnv.close(); + } catch(DatabaseException dbe) { + System.err.println("Error closing MyDbEnv: " + + dbe.toString()); + System.exit(-1); + } +} +``` diff --git a/docs_src/guides/gsg/java/persist_index.md b/docs_src/guides/gsg/java/persist_index.md new file mode 100644 index 000000000..79a7538c1 --- /dev/null +++ b/docs_src/guides/gsg/java/persist_index.md @@ -0,0 +1,67 @@ +--- +title: "Chapter 4. Working with Indices" +api-name: "Chapter 4. Working with Indices" +source: docs/gsg/JAVA/persist_index.html +--- +## Chapter 4. Working with Indices + +**Table of Contents** + + [Accessing Indexes](persist_index.md#dplindexaccess) + + [Accessing Primary Indices](persist_index.md#primaryindexaccess) + + [Accessing Secondary Indices](persist_index.md#secondaryindexaccess) + + [Creating Indexes](dplindexcreate.md) + + [Declaring a Primary Indexes](dplindexcreate.md#dplprimaryidxdecl) + + [Declaring Secondary Indexes](dplindexcreate.md#dplsecondaryidxdecl) + + [Foreign Key Constraints](dplindexcreate.md#foreignkey) + +All entity classes stored in DB using the DPL must have a primary index, or key, identified for them. All such classes may also have one or more secondary keys declared for them. This chapter describes primary and secondary indexes in detail, and shows how to access the indexes created for a given entity class. + +One way to organize access to your primary and secondary indexes is to create a *data accessor* class. We show an implementation of a data accessor class in SimpleDA.class. + +## Accessing Indexes + + [Accessing Primary Indices](persist_index.md#primaryindexaccess) + + [Accessing Secondary Indices](persist_index.md#secondaryindexaccess) + +In order to retrieve any object from an entity store, you must access at least the primary index for that object. Different entity classes stored in an entity store can have different primary indexes, but all entity classes must have a primary index declared for it. The primary index is just the default index used for the class. (That is, it is the data's primary *key* for the underlying database.) + +Entity classes can optionally have secondary indexes declared for them. In order to access these secondary indexes, you must first access the primary index. + +### Accessing Primary Indices + +You retrieve a primary index using the `EntityStore.getPrimaryIndex()` method. To do this, you indicate the index key type (that is, whether it is a String, Integer, and so forth) and the class of the entities stored in the index. + +For example, the following retrieves the primary index for an `Inventory` class (we provide an implementation of this class in Inventory.java). These index keys are of type `String`. + +``` c +PrimaryIndex inventoryBySku = + store.getPrimaryIndex(String.class, Inventory.class); +``` + +### Accessing Secondary Indices + +You retrieve a secondary index using the `EntityStore.getSecondaryIndex()` method. Because secondary indices actually refer to a primary index somewhere in your data store, to access a secondary index you: + +1. Provide the primary index as returned by `EntityStore.getPrimaryIndex()`. + +2. Identify the key data type used by the secondary index (`String`, `Long`, and so forth). + +3. Identify the name of the secondary key field. When you declare the `SecondaryIndex` object, you identify the entity class to which the secondary index must refer. + +For example, the following first retrieves the primary index, and then uses that to retrieve a secondary index. The secondary key is held by the `itemName` field of the `Inventory` class. + +``` c +PrimaryIndex inventoryBySku = +store.getPrimaryIndex(String.class, Inventory.class); + +SecondaryIndex inventoryByName = + store.getSecondaryIndex(inventoryBySku, String.class, "itemName"); +``` diff --git a/docs_src/guides/gsg/java/persistobject.md b/docs_src/guides/gsg/java/persistobject.md new file mode 100644 index 000000000..d7f5746f8 --- /dev/null +++ b/docs_src/guides/gsg/java/persistobject.md @@ -0,0 +1,46 @@ +--- +title: "Persistent Objects" +api-name: "Persistent Objects" +source: docs/gsg/JAVA/persistobject.html +--- +## Persistent Objects + +When using the DPL, you store data in the underlying DB databases by making objects *persistent*. You do this using Java annotations that both identify the type of persistent object you are declaring, as well as the primary and secondary indices. + +The following are the annotations you will use with your DPL persistent classes: + +| Annotation | Description | +|----|----| +| @Entity | Declares an entity class; that is, a class with a primary index and optionally one or more indices. | +| @Persistent | Declares a persistent class; that is, a class used by an entity class. They do not have indices but instead are are stored or retrieved when an entity class makes direct use of them. | +| @PrimaryKey | Declares a specific data member in an entity class to be the primary key for that object. This annotation must be used one and only one time for every entity class. | +| @SecondaryKey | Declares a specific data member in an entity class to be a secondary key for that object. This annotation is optional, and can be used multiple times for an entity class. | + +For example, the following is declared to be an entity class: + +``` c +package persist.gettingStarted; + +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; + +@Entity +public class ExampleEntity { + + // The primary key must be unique in the database. + @PrimaryKey + private String aPrimaryKey; + + @SecondaryKey(relate=MANY_TO_ONE) + private String aSecondaryKey; + + ... + + // The remainder of the class' implementation is purposefully + // omitted in the interest of brevity. + + ... +} +``` + +We discuss primary and secondary keys in more detail in Working with Indices. diff --git a/docs_src/guides/gsg/java/preface.md b/docs_src/guides/gsg/java/preface.md new file mode 100644 index 000000000..6999c5651 --- /dev/null +++ b/docs_src/guides/gsg/java/preface.md @@ -0,0 +1,56 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg/JAVA/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +Welcome to Berkeley DB (DB). This document introduces Berkeley DB 11*g* Release 2, which provides DB library version 11.2.5.3. + +This document is intended to provide a rapid introduction to the DB API set and related concepts. The goal of this document is to provide you with an efficient mechanism with which you can evaluate DB against your project's technical requirements. As such, this document is intended for Java developers and senior software architects who are looking for an in-process data management solution. No prior experience with Berkeley DB is expected or required. + +## 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: "The `Database()` constructor returns a `Database` class object." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +``` + +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: + +``` c +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +Database myDb = new Database("mydb.db", null, myDbConfig); +``` + +### Note + +Finally, notes of interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg/java/readSecondary.md b/docs_src/guides/gsg/java/readSecondary.md new file mode 100644 index 000000000..af73edaeb --- /dev/null +++ b/docs_src/guides/gsg/java/readSecondary.md @@ -0,0 +1,42 @@ +--- +title: "Reading Secondary Databases" +api-name: "Reading Secondary Databases" +source: docs/gsg/JAVA/readSecondary.html +--- +## Reading Secondary Databases + +Like a primary database, you can read records from your secondary database either by using the `SecondaryDatabase.get()` method, or by using a `SecondaryCursor`. The main difference between reading secondary and primary databases is that when you read a secondary database record, the secondary record's data is not returned to you. Instead, the primary key and data corresponding to the secondary key are returned to you. + +For example, assuming your secondary database contains keys related to a person's full name: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.SecondaryDatabase; + +... +SecondaryDatabase mySecondaryDatabase = null; +try { + // Omitting all database opens + ... + + String searchName = "John Doe"; + DatabaseEntry searchKey = + new DatabaseEntry(searchName.getBytes("UTF-8")); + DatabaseEntry primaryKey = new DatabaseEntry(); + DatabaseEntry primaryData = new DatabaseEntry(); + + // Get the primary key and data for the user 'John Doe'. + OperationStatus retVal = mySecondaryDatabase.get(null, searchKey, + primaryKey, + primaryData, + LockMode.DEFAULT); +} catch (Exception e) { + // Exception handling goes here +} +``` + +Note that, just like `Database.get()`, if your secondary database supports duplicate records then `SecondaryDatabase.get()` only return the first record found in a matching duplicates set. If you want to see all the records related to a specific secondary key, then use a `SecondaryCursor` (described in Using Secondary Cursors ). diff --git a/docs_src/guides/gsg/java/returns.md b/docs_src/guides/gsg/java/returns.md new file mode 100644 index 000000000..5cd6fde7d --- /dev/null +++ b/docs_src/guides/gsg/java/returns.md @@ -0,0 +1,12 @@ +--- +title: "Error Returns" +api-name: "Error Returns" +source: docs/gsg/JAVA/returns.html +--- +## Error Returns + +In addition to exceptions, the DB interfaces always return a value of 0 on success. If the operation does not succeed for any reason, the return value will be non-zero. + +If a system error occurred (for example, DB ran out of disk space, or permission to access a file was denied, or an illegal argument was specified to one of the interfaces), DB returns an `errno` value. All of the possible values of `errno` are greater than 0. + +If the operation did not fail due to a system error, but was not successful either, DB returns a special error value. For example, if you tried to retrieve data from the database and the record for which you are searching does not exist, DB would return `DB_NOTFOUND`, a special error value that means the requested key does not appear in the database. All of the possible special error values are less than 0. diff --git a/docs_src/guides/gsg/java/saveret.md b/docs_src/guides/gsg/java/saveret.md new file mode 100644 index 000000000..0d79f5076 --- /dev/null +++ b/docs_src/guides/gsg/java/saveret.md @@ -0,0 +1,22 @@ +--- +title: "Saving and Retrieving Data" +api-name: "Saving and Retrieving Data" +source: docs/gsg/JAVA/saveret.html +--- +## Saving and Retrieving Data + +All data stored using the DPL has one primary index and zero or more secondary indices associated with it. (Sometimes these are referred to as the primary and secondary *keys*.) So to store data under the DPL, you must: + +1. Declare a class to be an entity class. + +2. Identify the features on the class which represent indexed material. + +3. Retrieve the store's primary index for a given class using the `EntityStore.getPrimaryIndex()` method. + +4. Put class objects to the store using the `PrimaryIndex.put()` method. + +In order to retrieve an object from the store, you use the index that is most convenient for your purpose. This may be the primary index, or it may be some other secondary index that you declared on your entity class. + +You obtain a primary index in the same was as when you put the object to the store: using `EntityStore.getPrimaryIndex()`. You can get a secondary index for the store using the `EntityStore.getSecondaryIndex()` method. Note that `getSecondaryIndex()` requires you to provide a `PrimaryIndex` class instance when you call it, so a class's primary index is always required when retrieving objects from an entity store. + +Usually all of the activity surrounding saving and retrieving data is organized within a class or classes specialized to that purpose. We describe the construction of these data accessor classes in SimpleDA.class. But before you perform any entity store activity, you need to understand indexes. We therefore describe them in the next chapter. diff --git a/docs_src/guides/gsg/java/secondaryCursor.md b/docs_src/guides/gsg/java/secondaryCursor.md new file mode 100644 index 000000000..24890840b --- /dev/null +++ b/docs_src/guides/gsg/java/secondaryCursor.md @@ -0,0 +1,54 @@ +--- +title: "Using Secondary Cursors" +api-name: "Using Secondary Cursors" +source: docs/gsg/JAVA/secondaryCursor.html +--- +## Using Secondary Cursors + +Just like cursors on a primary database, you can use secondary cursors to iterate over the records in a secondary database. Like normal cursors, you can also use secondary cursors to search for specific records in a database, to seek to the first or last record in the database, to get the next duplicate record, and so forth. For a complete description on cursors and their capabilities, see Using Cursors. + +However, when you use secondary cursors: + +- Any data returned is the data contained on the primary database record referenced by the secondary record. + +- `SecondaryCursor.getSearchBoth()` and related methods do not search based on a key/data pair. Instead, you search based on a secondary key and a primary key. The data returned is the primary data that most closely matches the two keys provided for the search. + +For example, suppose you are using the databases, classes, and key creators described in Implementing Key Creators . Then the following searches for a person's name in the secondary database, and deletes all secondary and primary records that use that name. + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryCursor; + +... +try { + SecondaryDatabase mySecondaryDatabase = null; + // Database opens omitted for brevity + ... + + String secondaryName = "John Doe"; + DatabaseEntry secondaryKey = + new DatabaseEntry(secondaryName.getBytes("UTF-8")); + + DatabaseEntry foundData = new DatabaseEntry(); + + SecondaryCursor mySecCursor = + mySecondaryDatabase.openSecondaryCursor(null, null); + + OperationStatus retVal = mySecCursor.getSearchKey(secondaryKey, + foundData, + LockMode.DEFAULT); + while (retVal == OperationStatus.SUCCESS) { + mySecCursor.delete(); + retVal = mySecCursor.getNextDup(secondaryKey, + foundData, + LockMode.DEFAULT); + } +} catch (Exception e) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/secondaryDelete.md b/docs_src/guides/gsg/java/secondaryDelete.md new file mode 100644 index 000000000..f28c91876 --- /dev/null +++ b/docs_src/guides/gsg/java/secondaryDelete.md @@ -0,0 +1,45 @@ +--- +title: "Deleting Secondary Database Records" +api-name: "Deleting Secondary Database Records" +source: docs/gsg/JAVA/secondaryDelete.html +--- +## Deleting Secondary Database Records + +In general, you will not modify a secondary database directly. In order to modify a secondary database, you should modify the primary database and simply allow DB to manage the secondary modifications for you. + +However, as a convenience, you can delete `SecondaryDatabase` records directly. Doing so causes the associated primary key/data pair to be deleted. This in turn causes DB to delete all `SecondaryDatabase` records that reference the primary record. + +You can use the `SecondaryDatabase.delete()` method to delete a secondary database record. Note that if your `SecondaryDatabase` contains duplicate records, then deleting a record from the set of duplicates causes all of the duplicates to be deleted as well. + +### Note + +`SecondaryDatabase.delete()` causes the previously described delete operations to occur only if the primary database is opened for write access. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.SecondaryDatabase; + +... +try { + SecondaryDatabase mySecondaryDatabase = null; + // Omitting all database opens + ... + + String searchName = "John Doe"; + DatabaseEntry searchKey = + new DatabaseEntry(searchName.getBytes("UTF-8")); + + // Delete the first secondary record that uses "John Doe" as + // a key. This causes the primary record referenced by this secondary + // record to be deleted. + OperationStatus retVal = mySecondaryDatabase.delete(null, searchKey); +} catch (Exception e) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg/java/secondaryProps.md b/docs_src/guides/gsg/java/secondaryProps.md new file mode 100644 index 000000000..296263d3f --- /dev/null +++ b/docs_src/guides/gsg/java/secondaryProps.md @@ -0,0 +1,18 @@ +--- +title: "Secondary Database Properties" +api-name: "Secondary Database Properties" +source: docs/gsg/JAVA/secondaryProps.html +--- +## Secondary Database Properties + +Secondary databases accept `SecondaryConfig` objects. `SecondaryConfig` is a subclass of `DatabaseConfig`, so it can manage all of the same properties as does `DatabaseConfig`. See Database Properties for more information. + +In addition to the `DatabaseConfig` properties, `SecondaryConfig` also allows you to manage the following properties: + +- `SecondaryConfig.setAllowPopulate()` + + If true, the secondary database can be auto-populated. This means that on open, if the secondary database is empty then the primary database is read in its entirety and additions/modifications to the secondary's records occur automatically. + +- `SecondaryConfig.setKeyCreator()` + + Identifies the key creator object to be used for secondary key creation. See Implementing Key Creators for more information. diff --git a/docs_src/guides/gsg/java/simpleda.md b/docs_src/guides/gsg/java/simpleda.md new file mode 100644 index 000000000..e80323411 --- /dev/null +++ b/docs_src/guides/gsg/java/simpleda.md @@ -0,0 +1,43 @@ +--- +title: "SimpleDA.class" +api-name: "SimpleDA.class" +source: docs/gsg/JAVA/simpleda.html +--- +## SimpleDA.class + +As mentioned above, we organize our primary and secondary indexes using a specialize data accessor class. The main reason for this class to exist is to provide convenient access to all the indexes in use for our entity class (see the previous section, A Simple Entity Class, for that implementation). + +For a description on retrieving primary and secondary indexes under the DPL, see Working with Indices + +``` c +package persist.gettingStarted; + +import java.io.File; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.PrimaryIndex; +import com.sleepycat.persist.SecondaryIndex; + +public class SimpleDA { + // Open the indices + public SimpleDA(EntityStore store) + throws DatabaseException { + + // Primary key for SimpleEntityClass classes + pIdx = store.getPrimaryIndex( + String.class, SimpleEntityClass.class); + + // Secondary key for SimpleEntityClass classes + // Last field in the getSecondaryIndex() method must be + // the name of a class member; in this case, an + // SimpleEntityClass.class data member. + sIdx = store.getSecondaryIndex( + pIdx, String.class, "sKey"); + } + + // Index Accessors + PrimaryIndex pIdx; + SecondaryIndex sIdx; +} +``` diff --git a/docs_src/guides/gsg/java/simpleget.md b/docs_src/guides/gsg/java/simpleget.md new file mode 100644 index 000000000..55d6920eb --- /dev/null +++ b/docs_src/guides/gsg/java/simpleget.md @@ -0,0 +1,117 @@ +--- +title: "Retrieving Objects from an Entity Store" +api-name: "Retrieving Objects from an Entity Store" +source: docs/gsg/JAVA/simpleget.html +--- +## Retrieving Objects from an Entity Store + +You retrieve objects placed in an entity store by using either the object's primary index, or the appropriate secondary index if it exists. The following application illustrates this by retrieving some of the objects that we placed in an entity store in the previous section. + +To begin, we import the Java classes that our example needs. We also instantiate the private data members that we require. + +``` c +package persist.gettingStarted; + +import java.io.File; +import java.io.FileNotFoundException; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +public class SimpleStoreGet { + + private static File envHome = new File("./JEDB"); + + private Environment envmnt; + private EntityStore store; + private SimpleDA sda; +``` + +Next we create a method that simply opens our database environment and entity store for us. + +``` c + // The setup() method opens the environment and store + // for us. + public void setup() + throws DatabaseException { + + EnvironmentConfig envConfig = new EnvironmentConfig(); + StoreConfig storeConfig = new StoreConfig(); + + envConfig.setAllowCreate(true); + storeConfig.setAllowCreate(true); + + try { + // Open the environment and entity store + envmnt = new Environment(envHome, envConfig); + store = new EntityStore(envmnt, "EntityStore", storeConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("setup(): " + fnfe.toString()); + System.exit(-1); + } + } +``` + +We also need a method to close our environment and store. + +``` c + // Close our environment and store. + public void shutdown() + throws DatabaseException { + + store.close(); + envmnt.close(); + } +``` + +Now we retrieve a few objects. To do this, we instantiate a `SimpleDA` (see SimpleDA.class) class that we use to access our primary and secondary indexes. Then we retrieve objects based on a primary or secondary index value. And finally, we display the retrieved objects. + +``` c + // Retrieve some SimpleEntityClass objects from the store. + private void run() + throws DatabaseException { + + setup(); + + // Open the data accessor. This is used to store + // persistent objects. + sda = new SimpleDA(store); + + // Instantiate and store some entity classes + SimpleEntityClass sec1 = sda.pIdx.get("keyone"); + SimpleEntityClass sec2 = sda.pIdx.get("keytwo"); + + SimpleEntityClass sec4 = sda.sIdx.get("skeythree"); + + System.out.println("sec1: " + sec1.getPKey()); + System.out.println("sec2: " + sec2.getPKey()); + System.out.println("sec4: " + sec4.getPKey()); + + shutdown(); + } +``` + +Finally, to complete our class, we need a `main()` method, which simply calls our `run()` method. + +``` c + // main + public static void main(String args[]) { + SimpleStoreGet ssg = new SimpleStoreGet(); + try { + ssg.run(); + } catch (DatabaseException dbe) { + System.err.println("SimpleStoreGet: " + dbe.toString()); + dbe.printStackTrace(); + } catch (Exception e) { + System.out.println("Exception: " + e.toString()); + e.printStackTrace(); + } + System.out.println("All done."); + } + +} +``` diff --git a/docs_src/guides/gsg/java/simpleput.md b/docs_src/guides/gsg/java/simpleput.md new file mode 100644 index 000000000..ff3e379dd --- /dev/null +++ b/docs_src/guides/gsg/java/simpleput.md @@ -0,0 +1,145 @@ +--- +title: "Placing Objects in an Entity Store" +api-name: "Placing Objects in an Entity Store" +source: docs/gsg/JAVA/simpleput.html +--- +## Placing Objects in an Entity Store + +In order to place an object in a DPL entity store, you must: + +1. Open the environment and store. + +2. Instantiate the object. + +3. Put the object to the store using the `put()` method for the object's primary index. + +The following example uses the `SimpleDA` class that we show in SimpleDA.class to put a `SimpleEntityClass` object (see A Simple Entity Class) to the entity store. + +To begin, we import the Java classes that our example needs. We also instantiate the private data members that we require. + +``` c +package persist.gettingStarted; + +import java.io.File; +import java.io.FileNotFoundException; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +public class SimpleStorePut { + + private static File envHome = new File("./JEDB"); + + private Environment envmnt; + private EntityStore store; + private SimpleDA sda; +``` + +Next we create a method that simply opens our database environment and entity store for us. + +``` c + // The setup() method opens the environment and store + // for us. + public void setup() + throws DatabaseException { + + EnvironmentConfig envConfig = new EnvironmentConfig(); + StoreConfig storeConfig = new StoreConfig(); + + envConfig.setAllowCreate(true); + storeConfig.setAllowCreate(true); + + try { + // Open the environment and entity store + envmnt = new Environment(envHome, envConfig); + store = new EntityStore(envmnt, "EntityStore", storeConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("setup(): " + fnfe.toString()); + System.exit(-1); + } + } +``` + +We also need a method to close our environment and store. + +``` c + // Close our environment and store. + public void shutdown() + throws DatabaseException { + + store.close(); + envmnt.close(); + } +``` + +Now we need to create a method to actually write objects to our store. This method creates a `SimpleDA` object (see SimpleDA.class that we will use to access our indexes. Then we instantiate a series of `SimpleEntityClass` (see A Simple Entity Class) objects that we will place in our store. Finally, we use our primary index (obtained from the `SimpleDA` class instance) to actually place these objects in our store. + +In Retrieving Objects from an Entity Store we show a class that is used to retrieve these objects. + +``` c + // Populate the entity store + private void run() + throws DatabaseException { + + setup(); + + // Open the data accessor. This is used to store + // persistent objects. + sda = new SimpleDA(store); + + // Instantiate and store some entity classes + SimpleEntityClass sec1 = new SimpleEntityClass(); + SimpleEntityClass sec2 = new SimpleEntityClass(); + SimpleEntityClass sec3 = new SimpleEntityClass(); + SimpleEntityClass sec4 = new SimpleEntityClass(); + SimpleEntityClass sec5 = new SimpleEntityClass(); + + sec1.setPKey("keyone"); + sec1.setSKey("skeyone"); + + sec2.setPKey("keytwo"); + sec2.setSKey("skeyone"); + + sec3.setPKey("keythree"); + sec3.setSKey("skeytwo"); + + sec4.setPKey("keyfour"); + sec4.setSKey("skeythree"); + + sec5.setPKey("keyfive"); + sec5.setSKey("skeyfour"); + + sda.pIdx.put(sec1); + sda.pIdx.put(sec2); + sda.pIdx.put(sec3); + sda.pIdx.put(sec4); + sda.pIdx.put(sec5); + + shutdown(); + } +``` + +Finally, to complete our class, we need a `main()` method, which simply calls our `run()` method. + +``` c + // main + public static void main(String args[]) { + SimpleStorePut ssp = new SimpleStorePut(); + try { + ssp.run(); + } catch (DatabaseException dbe) { + System.err.println("SimpleStorePut: " + dbe.toString()); + dbe.printStackTrace(); + } catch (Exception e) { + System.out.println("Exception: " + e.toString()); + e.printStackTrace(); + } + System.out.println("All done."); + } + +} +``` diff --git a/docs_src/guides/gsg/java/usingDbt.md b/docs_src/guides/gsg/java/usingDbt.md new file mode 100644 index 000000000..a32a8dbd6 --- /dev/null +++ b/docs_src/guides/gsg/java/usingDbt.md @@ -0,0 +1,177 @@ +--- +title: "Reading and Writing Database Records" +api-name: "Reading and Writing Database Records" +source: docs/gsg/JAVA/usingDbt.html +--- +## Reading and Writing Database Records + + [Writing Records to the Database](usingDbt.md#databaseWrite) + + [Getting Records from the Database](usingDbt.md#databaseRead) + + [Deleting Records](usingDbt.md#recordDelete) + + [Data Persistence](usingDbt.md#datapersist) + +When reading and writing database records, be aware that there are some slight differences in behavior depending on whether your database supports duplicate records. Two or more database records are considered to be duplicates of one another if they share the same key. The collection of records sharing the same key are called a *duplicates set.* In DB, a given key is stored only once for a single duplicates set. + +By default, DB databases do not support duplicate records. Where duplicate records are supported, cursors (see below) are typically used to access all of the records in the duplicates set. + +DB provides two basic mechanisms for the storage and retrieval of database key/data pairs: + +- The `Database.put()` and `Database.get()` methods provide the easiest access for all non-duplicate records in the database. These methods are described in this section. + +- Cursors provide several methods for putting and getting database records. Cursors and their database access methods are described in Using Cursors. + +### Writing Records to the Database + +Records are stored in the database using whatever organization is required by the access method that you have selected. In some cases (such as BTree), records are stored in a sort order that you may want to define (see Setting Comparison Functions for more information). + +In any case, the mechanics of putting and getting database records do not change once you have selected your access method, configured your sorting routines (if any), and opened your database. From your code's perspective, a simple database put and get is largely the same no matter what access method you are using. + +You can use the following methods to put database records: + +- `Database.put()` + + Puts a database record into the database. If your database does not support duplicate records, and if the provided key already exists in the database, then the currently existing record is replaced with the new data. + +- `Database.putNoOverwrite()` + + Disallows overwriting (replacing) an existing record in the database. If the provided key already exists in the database, then this method returns `OperationStatus.KEYEXIST` even if the database supports duplicates. + +- `Database.putNoDupData()` + + Puts a database record into the database. If the provided key and data already exists in the database (that is, if you are attempting to put a record that compares equally to an existing record), then this returns `OperationStatus.KEYEXIST`. + +When you put database records, you provide both the key and the data as `DatabaseEntry` objects. This means you must convert your key and data into a Java `byte` array. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.Database; + +... + +// Database opens omitted for clarity. +// Databases must NOT be opened read-only. + +String aKey = "myFirstKey"; +String aData = "myFirstData"; + +try { + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(aData.getBytes("UTF-8")); + myDatabase.put(null, theKey, theData); +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Getting Records from the Database + +The `Database` class provides several methods that you can use to retrieve database records. Note that if your database supports duplicate records, then these methods will only ever return the first record in a duplicate set. For this reason, if your database supports duplicates, you should use a cursor to retrieve records from it. Cursors are described in Using Cursors. + +You can use either of the following methods to retrieve records from the database: + +- `Database.get()` + + Retrieves the record whose key matches the key provided to the method. If no records exists that uses the provided key, then `OperationStatus.NOTFOUND` is returned. + +- `Database.getSearchBoth()` + + Retrieve the record whose key matches both the key and the data provided to the method. If no record exists that uses the provided key and data, then `OperationStatus.NOTFOUND` is returned. + +Both the key and data for a database record are returned as byte arrays in `DatabaseEntry` objects. These objects are passed as parameter values to the `Database.get()` method. + +In order to retrieve your data once `Database.get()` has completed, you must retrieve the `byte` array stored in the `DatabaseEntry` and then convert that `byte` array back to the appropriate datatype. For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.Database; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; + +... + +Database myDatabase = null; +// Database opens omitted for clarity. +// Database may be opened read-only. + +String aKey = "myFirstKey"; + +try { + // Create a pair of DatabaseEntry objects. theKey + // is used to perform the search. theData is used + // to store the data returned by the get() operation. + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + DatabaseEntry theData = new DatabaseEntry(); + + // Perform the get. + if (myDatabase.get(null, theKey, theData, LockMode.DEFAULT) == + OperationStatus.SUCCESS) { + + // Recreate the data String. + byte[] retData = theData.getData(); + String foundData = new String(retData, "UTF-8"); + System.out.println("For key: '" + aKey + "' found data: '" + + foundData + "'."); + } else { + System.out.println("No record found for key '" + aKey + "'."); + } +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Deleting Records + +You can use the `Database.delete()` method to delete a record from the database. If your database supports duplicate records, then all records associated with the provided key are deleted. To delete just one record from a list of duplicates, use a cursor. Cursors are described in Using Cursors. + +You can also delete every record in the database by using `Environment.truncateDatabase().` + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.Database; + +... + +Database myDatabase = null; +// Database opens omitted for clarity. +// Database can NOT be opened read-only. + +try { + String aKey = "myFirstKey"; + DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8")); + + // Perform the deletion. All records that use this key are + // deleted. + myDatabase.delete(null, theKey); +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Data Persistence + +When you perform a database modification, your modification is made in the in-memory cache. This means that your data modifications are not necessarily flushed to disk, and so your data may not appear in the database after an application restart. + +Note that as a normal part of closing a database, its cache is written to disk. However, in the event of an application or system failure, there is no guarantee that your databases will close cleanly. In this event, it is possible for you to lose data. Under extremely rare circumstances, it is also possible for you to experience database corruption. + +Therefore, if you care if your data is durable across system failures, and to guard against the rare possibility of database corruption, you should use transactions to protect your database modifications. Every time you commit a transaction, DB ensures that the data will not be lost due to application or system failure. Transaction usage is described in the *Berkeley DB Getting Started with Transaction Processing* guide. + +If you do not want to use transactions, then the assumption is that your data is of a nature that it need not exist the next time your application starts. You may want this if, for example, you are using DB to cache data relevant only to the current application runtime. + +If, however, you are not using transactions for some reason and you still want some guarantee that your database modifications are persistent, then you should periodically run environment syncs. Syncs cause any dirty entries in the in-memory cache and the operating system's file cache to be written to disk. As such, they are quite expensive and you should use them sparingly. + +Remember that by default a sync is performed any time a non-transactional database is closed cleanly. (You can override this behavior by specifying `true` on the call to `Database.close()`.) That said, you can manually run a sync by calling `Database.sync().` + +### Note + +If your application or system crashes and you are not using transactions, then you should either discard and recreate your databases, or verify them. You can verify a database using Database.verify(). If your databases do not verify cleanly, use the **db_dump** command to salvage as much of the database as is possible. Use either the `-R` or `-r` command line options to control how aggressive **db_dump** should be when salvaging your databases. diff --git a/docs_src/guides/gsg_db_rep/cxx/_meta.toml b/docs_src/guides/gsg_db_rep/cxx/_meta.toml new file mode 100644 index 000000000..6257661ee --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/_meta.toml @@ -0,0 +1,32 @@ +# Nav/index metadata for the gsg_db_rep C++ variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Berkeley DB Getting Started with Replicated Applications (C++)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "repadvantage", + "apioverview", + "elections", + "permmessages", + "txnapp", + "simpleprogramlisting", + "repapp", + "rep_init_code", + "repmgr_init_example_c", + "fwrkpermmessage", + "electiontimes", + "fmwrkconnectretry", + "heartbeats", + "fwrkmasterreplica", + "processingloop", + "exampledoloop", + "addfeatures", + "manageblock", + "autoinit", + "rywc", + "c2ctransfer", + "bulk", +] diff --git a/docs_src/guides/gsg_db_rep/cxx/addfeatures.md b/docs_src/guides/gsg_db_rep/cxx/addfeatures.md new file mode 100644 index 000000000..925d96c0b --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/addfeatures.md @@ -0,0 +1,50 @@ +--- +title: "Chapter 5. Additional Features" +api-name: "Chapter 5. Additional Features" +source: docs/gsg_db_rep/CXX/addfeatures.html +--- +## Chapter 5. Additional Features + +**Table of Contents** + + [Delayed Synchronization](addfeatures.md#delayedsync) + + [Managing Blocking Operations](manageblock.md) + + [Stop Auto-Initialization](autoinit.md) + + [Read-Your-Writes Consistency](rywc.md) + + [Client to Client Transfer](c2ctransfer.md) + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + + [Bulk Transfers](bulk.md) + +Beyond the basic functionality that we have discussed so far in this book, there are several replication features that you should understand. These are all optional to use, but provide useful functionality under the right circumstances. + +These additional features are: + +1. Delayed Synchronization + +2. Managing Blocking Operations + +3. Stop Auto-Initialization + +4. Client to Client Transfer + +5. Bulk Transfers + +## Delayed Synchronization + +When a replication group has a new master, all replicas must synchronize with that master. This means they must ensure that the contents of their local database(s) are identical to that contained by the new master. + +This synchronization process can result in quite a lot of network activity. It can also put a large strain on the master server, especially if is part of a large replication group or if there is somehow a large difference between the master's database(s) and the contents of its replicas. + +It is therefore possible to delay synchronization for any replica that discovers it has a new master. You would do this so as to give the master time to synchronize other replicas before proceeding with the delayed replicas. + +To delay synchronization of a replica environment, you specify `DB_REP_CONF_DELAYCLIENT` to `DbEnv::rep_set_config()` and then specify `1` to the `onoff` parameter. (Specify `0` to turn the feature off.) + +If you use delayed synchronization, then you must manually synchronize the replica at some future time. Until you do this, the replica is out of sync with the master, and it will ignore all database changes forwarded to it from the master. + +You synchronize a delayed replica by calling `DbEnv::rep_sync()` on the replica that has been delayed. diff --git a/docs_src/guides/gsg_db_rep/cxx/apioverview.md b/docs_src/guides/gsg_db_rep/cxx/apioverview.md new file mode 100644 index 000000000..a290b89d7 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/apioverview.md @@ -0,0 +1,46 @@ +--- +title: "The Replication APIs" +api-name: "The Replication APIs" +source: docs/gsg_db_rep/CXX/apioverview.html +--- +## The Replication APIs + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + +There are two ways that you can choose to implement replication in your transactional application. The first, and preferred, mechanism is to use the pre-packaged Replication Manager that comes with the DB distribution. This framework should be sufficient for most customers. + +If for some reason the Replication Manager does not meet your application's technical requirements, you will have to use the Replication Base APIs available through the Berkeley DB library to write your own custom replication framework. + +Both of these approaches are described in slightly greater detail in this section. The bulk of the chapters later in this book are dedicated to these two replication implementation mechanisms. + +### Replication Manager Overview + +DB's pre-packaged Replication Manager exists as a layer on top of the DB library. The Replication Manager is a multi-threaded implementation that allows you to easily add replication to your existing transactional application. You access and manage the Replication Manager using methods that are available off the `DbEnv` class. + +The Replication Manager: + +- Provides a multi-threaded communications layer using pthreads (on Unix-style systems and similar derivatives such as Mac OS X), or Windows threads on Microsoft Windows systems. + +- Uses TCP/IP sockets. Network traffic is handled via threads that handle inbound and outbound messages. However, each process uses a single socket that is shared using `select()`. + + Note that for this reason, the Replication Manager is limited to a maximum of 60 replicas (on Windows) and approximately 1000 replicas (on Unix and related systems), depending on how your system is configured. + +- Requires that only one instance of the environment handle be used. + +- Upon application startup, a master can be selected either manually or via elections. After startup time, however, during the course of normal operations it is possible for the replication group to need to locate a new master (due to network or other hardware related problems, for example) and in this scenario elections are always used to select the new master. + +If your application has technical requirements that do not conform to the implementation provided by the Replication Manager, you must write implement replication using the DB Replication Base APIs. See the next section for introductory details. + +### Replication Base API Overview + +The Replication Base API is a series of Berkeley DB library classes and methods that you can use to build your own replication infrastructure. You should use the Base API only if the Replication Manager does not meet your application's technical requirements. + +To make use of the Base API, you must write your own networking code. This frees you from the technical constraints imposed by the Replication Manager. For example, by writing your own framework, you can: + +- Use a threading package other than pthreads (Unix) or Windows threads (Microsoft Windows). This might be interesting to you if you are using a platform whose preferred threading package is something other than (for example) pthreads, such as is the case for Sun Microsystem's Solaris operating systems. + +- Implement your own sockets. The Replication Manager uses TCP/IP sockets. While this should be acceptable for the majority of applications, sometimes UDP or even raw sockets might be desired. + +For information on writing a replicated application using the Berkeley DB Replication Base APIs, see the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/cxx/autoinit.md b/docs_src/guides/gsg_db_rep/cxx/autoinit.md new file mode 100644 index 000000000..ac0ac61d6 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/autoinit.md @@ -0,0 +1,12 @@ +--- +title: "Stop Auto-Initialization" +api-name: "Stop Auto-Initialization" +source: docs/gsg_db_rep/CXX/autoinit.html +--- +## Stop Auto-Initialization + +As stated in the previous section, when a replication replica is synchronizing with its master, it will block DB operations at some points during this process until the synchronization is completed. You can turn off this behavior (see Managing Blocking Operations), but for replicas that have been out of touch from their master for a very long time, this may not be enough. + +If a replica has been out of touch from its master long enough, it may find that it is not possible to perform synchronization. When this happens, by default the master and replica internally decide to completely re-initialize the replica. This re-initialization involves discarding the replica's current database(s) and transferring new ones to it from the master. Depending on the size of the master's databases, this can take a long time, during which time the replica will be completely non-responsive when it comes to performing database operations. + +It is possible that there is a time of the day when it is better to perform a replica re-initialization. Or, you simply might want to decide to bring the replica up to speed by restoring its databases using a hot-backup taken from the master. Either way, you can decide to prevent automatic-initialization of your replica. To do this specify `DB_REP_CONF_AUTOINIT` to `DbEnv::rep_set_config()` and then specify `0` to the `onoff` parameter. diff --git a/docs_src/guides/gsg_db_rep/cxx/bulk.md b/docs_src/guides/gsg_db_rep/cxx/bulk.md new file mode 100644 index 000000000..0fe947d53 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/bulk.md @@ -0,0 +1,32 @@ +--- +title: "Bulk Transfers" +api-name: "Bulk Transfers" +source: docs/gsg_db_rep/CXX/bulk.html +--- +## Bulk Transfers + +By default, messages are sent from the master to replicas as they are generated. This can degrade replication performance because the various participating environments must handle a fair amount of network I/O activity. + +You can alleviate this problem by configuring your master environment for bulk transfers. Bulk transfers simply cause replication messages to accumulate in a buffer until a triggering event occurs. When this event occurs, the entire contents of the buffer is sent to the replica, thereby eliminating excessive network I/O. + +Note that if you are using replica to replica transfers, then you might want any replica that can service replication requests to also be configured for bulk transfers. + +The events that result in a bulk transfer of replication messages to a replica will differ depending on if the transmitting environment is a master or a replica. + +If the servicing environment is a master environment, then bulk transfer occurs when: + +1. Bulk transfers are configured for the master environment, and + +2. the message buffer is full or + +3. a permanent record (for example, a transaction commit or a checkpoint record) is placed in the buffer for the replica. + +If the servicing environment is a replica environment (that is, replica to replica transfers are in use), then a bulk transfer occurs when: + +1. Bulk transfers are configured for the transmitting replica, and + +2. the message buffer is full or + +3. the replica servicing the request is able to completely satisfy the request with the contents of the message buffer. + +To configure bulk transfers, specify `DB_REP_CONF_BULK` to `DbEnv::rep_set_config()` and then specify `1` to the `onoff` parameter. (Specify `0` to turn the feature off.) diff --git a/docs_src/guides/gsg_db_rep/cxx/c2ctransfer.md b/docs_src/guides/gsg_db_rep/cxx/c2ctransfer.md new file mode 100644 index 000000000..557c38465 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/c2ctransfer.md @@ -0,0 +1,26 @@ +--- +title: "Client to Client Transfer" +api-name: "Client to Client Transfer" +source: docs/gsg_db_rep/CXX/c2ctransfer.html +--- +## Client to Client Transfer + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + +It is possible to use a replica instead of a master to synchronize another replica. This serves to take the request load off a master that might otherwise occur if multiple replicas attempted to synchronize with the master at the same time. + +For best results, use this feature combined with the delayed synchronization feature (see Delayed Synchronization). + +For example, suppose your replication group consists of four environments. Upon application startup, all three replicas will immediately attempt to synchronize with the master. But at the same time, the master itself might be busy with a heavy database write load. + +To solve this problem, delay synchronization for two of the three replicas. Allow the third replica to synchronize as normal with the master. Then, start synchronization for each of the delayed replicas (since this is a manual process, you can do them one at a time if that best suits your application). Assuming you have configured replica to replica synchronization correctly, the delayed replicas will synchronize using the up-to-date replica, rather than using the master. + +When you are using the Replication Manager, you configure replica to replica synchronization by declaring an environment to be a peer of another environment. If an environment is a peer, then it can be used for synchronization purposes. + +### Identifying Peers + +You can designate one replica to be a peer of another for replica to replica synchronization. You might want to do this if you have machines that you know are on fast, reliable network connections and so you are willing to accept the overhead of waiting for acknowledgments from those specific machines. + +Note that peers are not required to be a bi-directional. That is, just because machine A declares machine B to be a peer, that does not mean machine B must also declare machine A to be a peer. + +You declare a peer for the current environment when you add that environment to the list of known sites. You do this by specifying the `DB_REPMGR_PEER` flag to `DbEnv::repmgr_add_remote_site()`. diff --git a/docs_src/guides/gsg_db_rep/cxx/elections.md b/docs_src/guides/gsg_db_rep/cxx/elections.md new file mode 100644 index 000000000..f8260df2f --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/elections.md @@ -0,0 +1,56 @@ +--- +title: "Holding Elections" +api-name: "Holding Elections" +source: docs/gsg_db_rep/CXX/elections.html +--- +## Holding Elections + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + +Finding a master environment is one of the fundamental activities that every replication replica must perform. Upon startup, the underlying DB replication code will attempt to locate a master. If a master cannot be found, then the environment should initiate an election. + +### Note + +In some rare situations, it is desireable for the application to manually select its master. For these cases, elections can be turned off. + +Manually selecting a master is an activity that should be performed infrequently, if ever. You turn elections off by using the `DbEnv::rep_set_config()` and `DbEnv::repmgr_start()` methods. + +How elections are held depends upon the API that you use to implement replication. For example, if you are using the Replication Manager elections are held transparently without any input from your application's code. In this case, DB will determine which environment is the master and which are replicas. + +### Influencing Elections + +If you want to control the election process, you can declare a specific environment to be the master. Note that for the Replication Manager, it is only possible to do this at application startup. Should the master become unavailable during run-time for any reason, an election is held. The environment that receives the most number of votes, wins the election and becomes the master. A machine receives a vote because it has the most up-to-date log records. + +Because ties are possible when elections are held, it is possible to influence which environment will win the election. How you do this depends on which API you are using. In particular, if you are writing a custom replication layer, then there are a great many ways to manually influence elections. + +One such mechanism is priorities. When votes are cast during an election, the winner is determined first by the environment with the most up-to-date log records. But if this is a tie, the the environment's priority is considered. So given two environments with log records that are equally recent, votes are cast for the environment with the higher priority. + +Therefore, if you have a machine that you prefer to become a master in the event of an election, assign it a high priority. Assuming that the election is held at a time when the preferred machine has up-to-date log records, that machine will win the election. + +### Winning Elections + +To win an election: + +1. There cannot currently be a master environment. + +2. The environment must have the most recent log records. Part of holding the election is determining which environments have the most recent log records. This process happens automatically; your code does not need to involve itself in this process. + +3. The environment must receive the most number of votes from the replication environments that are participating in the election. + +If you are using the Replication Manager, then in the event of a tie vote the environment with the highest priority wins the election. If two or more environments receive the same number of votes and have the same priority, then the underlying replication code picks one of the environments to be the winner. Which winner will be picked by the replication code is unpredictable from the perspective of your application code. + +### Switching Masters + +To switch masters: + +1. Start up the environment that you want to be master as normal. At this time it is a replica. Make sure this environment has a higher priority than all the other environments. + +2. Allow the new environment to run for a time as a replica. This allows it to obtain the most recent copies of the log files. + +3. Shut down the current master. This should force an election. Because the new environment has the highest priority, it will win the election, provided it has had enough time to obtain all the log records. + +4. Optionally restart the old master environment. Because there is currently a master environment, an election will not be held and the old master will now run as a replica environment. diff --git a/docs_src/guides/gsg_db_rep/cxx/electiontimes.md b/docs_src/guides/gsg_db_rep/cxx/electiontimes.md new file mode 100644 index 000000000..d2b35b484 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/electiontimes.md @@ -0,0 +1,34 @@ +--- +title: "Managing Election Times" +api-name: "Managing Election Times" +source: docs/gsg_db_rep/CXX/electiontimes.html +--- +## Managing Election Times + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + +Where it comes to elections, there are two timeout values with which you should be concerned: election timeouts and election retries. + +### Managing Election Timeouts + +When an environment calls for an election, it will wait some amount of time for the other replicas in the replication group to respond. The amount of time that the environment will wait before declaring the election completed is the *election timeout*. + +If the environment hears from all other known replicas before the election timeout occurs, the election is considered a success and a master is elected. + +If only a subset of replicas respond, then the success or failure of the election is determined by how many replicas have participated in the election. It only takes a simple majority of replicas to elect a master. If there are enough votes for a given environment to meet that standard, then the master has been elected and the election is considered a success. + +However, if not enough replicas have participated in the election when the election timeout value is reached, the election is considered a failure and a master is not elected. At this point, your replication group is operating without a master, which means that, essentially, your replicated application has been placed in read-only mode. + +Note, however, that the Replication Manager will attempt a new election after a given amount of time has passed. See the next section for details. + +You set the election timeout value using `DbEnv::rep_set_timeout()`. To do so, specify the `DB_REP_ELECTION_TIMEOUT` value to the `which` parameter and then a timeout value in microseconds to the `timeout` parameter. + +### Managing Election Retry Times + +In the event that a election fails (see the previous section), an election will not be attempted again until the election retry timeout value has expired. + +You set the retry timeout value using `DbEnv::rep_set_timeout()`. To do so, specify the `DB_REP_ELECTION_RETRY` value to the `which` parameter and then a retry value in microseconds to the `timeout` parameter. + +Note that this flag is only valid when you are using the Replication Manager. If you are using the Base APIs, then this flag is ignored. diff --git a/docs_src/guides/gsg_db_rep/cxx/exampledoloop.md b/docs_src/guides/gsg_db_rep/cxx/exampledoloop.md new file mode 100644 index 000000000..11162180a --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/exampledoloop.md @@ -0,0 +1,373 @@ +--- +title: "Example Processing Loop" +api-name: "Example Processing Loop" +source: docs/gsg_db_rep/CXX/exampledoloop.html +--- +## Example Processing Loop + + [Running It](exampledoloop.md#runningit) + +In this section we take the example processing loop that we presented in the previous section and we flesh it out to provide a more complete example. We do this by updating the `doloop()` function that our original transaction application used (see Method: SimpleTxn::doloop()) to fully support our replicated application. + +In the following example code, code that we add to the original example is presented in **`bold`**. + +To begin, we include a new header file into our application so that we can check for the `ENOENT` return value later in our processing loop. We also define our `APP_DATA` structure, and we define a `sleeptime` value. Finally, we update `RepMgrGSG` to have a new method for our event notification callback, and to add a new data member for our `APP_DATA` data member. + +``` c +#include +#include +#include + +... +// Skipping all the RepHostInfoObj and RepConfigInfo code, which does not +// change. +... + +using std::cout; +using std::cin; +using std::cerr; +using std::endl; +using std::flush; + +#define CACHESIZE (10 * 1024 * 1024) +#define DATABASE "quote.db" +#define SLEEPTIME 3 + +const char *progname = "RepMgrGSG"; + +// Struct used to store information in Db app_private field. +typedef struct { + int is_master; +} APP_DATA; + +class RepMgrGSG +{ +public: + // Constructor. + RepMgrGSG(); + // Initialization method. Creates and opens our environment handle. + int init(RepConfigInfo* config); + // The doloop is where all the work is performed. + int doloop(); + // terminate() provides our shutdown code. + int terminate(); + + // event notification callback + static void + event_callback(DbEnv * dbenv, u_int32_t which, void *info); + +private: + // disable copy constructor. + RepMgrGSG(const RepMgrGSG &); + void operator = (const RepMgrGSG &); + + // internal data members. + APP_DATA app_data; + RepConfigInfo *app_config; + DbEnv dbenv; + + // private methods. + // print_stocks() is used to display the contents of our database. + static int print_stocks(Db *dbp); +}; +``` + +That done, we can skip the `main()` method, because it does not change. Instead, we skip down to our `RepMgrGSG` constructor where we initialize our `APP_DATA is_master` data member: + +``` c +RepMgrGSG::RepMgrGSG() : app_config(0), dbenv(0) +{ + app_data.is_master = 0; // assume I start out as client +} +``` + +That done, we must also update `RepMgrGSG::init()` to do a couple of things. First, we need to register our event callback with the environment handle. We also need to make our `APP_DATA` data member available through our environment handle's `app_private` field. This is a fairly trivial update, and it happens at the top of the method (we skip the rest of the method's listing since it does not change): + +``` c +int RepMgrGSG::init(RepConfigInfo *config) +{ + int ret = 0; + + app_config = config; + + dbenv.set_errfile(stderr); + dbenv.set_errpfx(progname); + dbenv.set_app_private(&app_data); + dbenv.set_event_notify(event_callback); + + ... +``` + +That done, we need to implement our `event_callback()` callback. Note that what we use here is no different from the callback that we described in the previous section. However, for the sake of completeness we provide the implementation here again. + +``` c + + /* + * A callback used to determine whether the local environment is a + * replica or a master. This is called by the Replication Manager + * when the local replication environment changes state. + */ +void RepMgrGSG::event_callback(DbEnv *dbenv, u_int32_t which, void *info) +{ + APP_DATA *app = dbenv->get_app_private(); + + info = NULL; /* Currently unused. */ + + switch (which) { + case DB_EVENT_REP_MASTER: + app->is_master = 1; + break; + + case DB_EVENT_REP_CLIENT: + app->is_master = 0; + break; + + case DB_EVENT_REP_STARTUPDONE: /* fallthrough */ + case DB_EVENT_REP_NEWMASTER: + /* Ignore. */ + break; + + default: + dbenv->errx(dbenv, "ignoring event %d", which); + } +} + + +``` + +That done, we need to update our `doloop()` method. + +We begin by updating our database handle open flags to determine which flags to use, depending on whether the application is running as a master. + +``` c +#define BUFSIZE 1024 +int RepMgrGSG::doloop() +{ + Db *dbp; + Dbt key, data; + char buf[BUFSIZE], *rbuf; + int ret; + + dbp = 0; + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + ret = 0; + + for (;;) { + if (dbp == 0) { + dbp = new Db(&dbenv, 0); + + try { + dbp->open(NULL, DATABASE, NULL, DB_BTREE, + app_data.is_master ? DB_CREATE | DB_AUTO_COMMIT : + DB_AUTO_COMMIT, 0); +``` + +When we open the database, we modify our error handling to account for the case where the database does not yet exist. This can happen if our code is running as a replica and the Replication Manager has not yet had a chance to create the databases for us. Recall that replicas never write to their own databases directly, and so they cannot create databases on their own. + +If we detect that the database does not yet exist, we simply close the database handle, sleep for a short period of time and then continue processing. This gives the Replication Manager a chance to create the database so that our replica can continue operations. + +``` c + } catch(DbException dbe) { + /* It is expected that this condition will be triggered + * when client sites start up. + * It can take a while for the master site to be found + * and synced, and no DB will be available until then. + */ + if (dbe.get_errno() == ENOENT) { + cout << "No stock db available yet - " + << "retrying." << endl; + try { + dbp->close(0); + } catch (DbException dbe2) { + cout << "Unexpected error closing after failed" + << " open, message: " << dbe2.what() << endl; + dbp = NULL; + goto err; + } + dbp = NULL; + sleep(SLEEPTIME); + continue; + } else { + dbenv.err(ret, "DB->open"); + throw dbe; + } + } + } +``` + +Next we modify our prompt, so that if the local process is running as a replica, we can tell from the shell that the prompt is for a read-only process. + +``` c + cout << "QUOTESERVER" ; + if (!app_data.is_master) + cout << "(read-only)"; + cout << "> " << flush; +``` + +When we collect data from the prompt, there is a case that says if no data is entered then show the entire stocks database. This display is performed by our `print_stocks()` method (which has not required a modification since we first introduced it in Method: SimpleTxn::print_stocks() ). + +When we call `print_stocks()`, we check for a dead replication handle. Dead replication handles happen whenever a replication election results in a previously committed transaction becoming invalid. This is an error scenario caused by a new master having a slightly older version of the data than the original master and so all replicas must modify their database(s) to reflect that of the new master. In this situation, some number of previously committed transactions may have to be unrolled. From the replica's perspective, the database handles should all be closed and then opened again. + +``` c + if (fgets(buf, sizeof(buf), stdin) == NULL) + break; + if (strtok(&buf[0], " \t\n") == NULL) { + switch ((ret = print_stocks(dbp))) { + case 0: + continue; + case DB_REP_HANDLE_DEAD: + (void)dbp->close(DB_NOSYNC); + cout << "closing db handle due to rep handle dead" << endl; + dbp = NULL; + continue; + default: + dbp->err(ret, "Error traversing data"); + goto err; + } + } + rbuf = strtok(NULL, " \t\n"); + if (rbuf == NULL || rbuf[0] == '\0') { + if (strncmp(buf, "exit", 4) == 0 || + strncmp(buf, "quit", 4) == 0) + break; + dbenv.errx("Format: TICKER VALUE"); + continue; + } +``` + +That done, we need to add a little error checking to our command prompt to make sure the user is not attempting to modify the database at a replica. Remember, replicas must never modify their local databases on their own. This guards against that happening due to user input at the prompt. + +``` c + if (!app_data.is_master) { + dbenv->errx(dbenv, "Can't update at client"); + continue; + } + + key.set_data(buf); + key.set_size((u_int32_t)strlen(buf)); + + data.set_data(rbuf); + data.set_size((u_int32_t)strlen(rbuf)); + + if ((ret = dbp->put(NULL, &key, &data, 0)) != 0) + { + dbp->err(ret, "DB->put"); + if (ret != DB_KEYEXIST) + goto err; + } + } + +err: if (dbp != 0) + (void)dbp->close(dbp, DB_NOSYNC); + + return (ret); +} +``` + +With that completed, we are all done updating our application for replication. The only remaining method, `print_stocks()`, is unmodified from when we originally introduced it. For details on that function, see Method: SimpleTxn::print_stocks() . + +### Running It + +To run our replicated application, we need to make sure each participating environment has its own unique home directory. We can do this by running each site on a separate networked machine, but that is not strictly necessary; multiple instances of this code can run on the same machine provided the environment home restriction is observed. + +To run a process, make sure the environment home exists and then start the process using the `-h` option to specify that directory. You must also use the `-l` or `-L` option to identify the local host and port that this process will use to listen for replication messages (-L means that this is a group creator), and the `-r` option to identify the other processes in the replication group. Finally, use the `-p` option to specify a priority. The process that you designate to have the highest priority will become the master. + +``` c +> mkdir env1 +> ./RepMgrGSG -h env1 -L localhost:8080 -p 10 +No stock database yet available. +No stock database yet available. +``` + +Now, start another process. This time, change the environment home to something else, use the `-l` flag to at least change the port number the process is listening on, and use the `-r` option to identify the host and port of the other replication process: + +``` c +> mkdir env2 +> ./RepMgrGSG -h env2 -l localhost:8081 -r localhost:8080 -p 20 +``` + +After a short pause, the second process should display the master prompt: + +``` c +QUOTESERVER > +``` + +And the first process should display the read-only prompt: + +``` c +QUOTESERVER (read-only)> +``` + +Now go to the master process and give it a couple of stocks and stock prices: + +``` c +QUOTESERVER> FAKECO 9.87 +QUOTESERVER> NOINC .23 +QUOTESERVER> +``` + +Then, go to the replica and hit **`return`** at the prompt to see the new values: + +``` c +QUOTESERVER (read-only)> + Symbol Price + ====== ===== + FAKECO 9.87 + NOINC .23 +QUOTESERVER (read-only)> +``` + +Doing the same at the master results in the same thing: + +``` c +QUOTESERVER> + Symbol Price + ====== ===== + FAKECO 9.87 + NOINC .23 +QUOTESERVER> +``` + +You can change a stock by simply entering the stock value and new price at the master's prompt: + +``` c +QUOTESERVER> FAKECO 10.01 +QUOTESERVER> +``` + +Then, go to either the master or the replica to see the updated database. On the master: + +``` c +QUOTESERVER> + Symbol Price + ====== ===== + FAKECO 10.01 + NOINC .23 +QUOTESERVER> +``` + +And on the replica: + +``` c +QUOTESERVER (read-only)> + Symbol Price + ====== ===== + FAKECO 10.01 + NOINC .23 +QUOTESERVER (read-only)> +``` + +Finally, to quit the applications, simply type `quit` at both prompts. On the replica: + +``` c +QUOTESERVER (read-only)> quit +> +``` + +And on the master as well: + +``` c +QUOTESERVER> quit +> +``` diff --git a/docs_src/guides/gsg_db_rep/cxx/fmwrkconnectretry.md b/docs_src/guides/gsg_db_rep/cxx/fmwrkconnectretry.md new file mode 100644 index 000000000..ec5b72fb3 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/fmwrkconnectretry.md @@ -0,0 +1,8 @@ +--- +title: "Managing Connection Retries" +api-name: "Managing Connection Retries" +source: docs/gsg_db_rep/CXX/fmwrkconnectretry.html +--- +## Managing Connection Retries + +In the event that a communication failure occurs between two environments in a replication group, the Replication Manager will wait a set amount of time before attempting to re-establish the connection. You can configure this wait value using `DbEnv::rep_set_timeout()`. To do so, specify the `DB_REP_CONNECTION_RETRY` value to the `which` parameter and then a retry value in microseconds to the `timeout` parameter. diff --git a/docs_src/guides/gsg_db_rep/cxx/fwrkmasterreplica.md b/docs_src/guides/gsg_db_rep/cxx/fwrkmasterreplica.md new file mode 100644 index 000000000..c032141da --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/fwrkmasterreplica.md @@ -0,0 +1,184 @@ +--- +title: "Chapter 4. Replica versus Master Processes" +api-name: "Chapter 4. Replica versus Master Processes" +source: docs/gsg_db_rep/CXX/fwrkmasterreplica.html +--- +## Chapter 4. Replica versus Master Processes + +**Table of Contents** + + [Determining State](fwrkmasterreplica.md#determinestate) + + [Processing Loop](processingloop.md) + + [Example Processing Loop](exampledoloop.md) + + [Running It](exampledoloop.md#runningit) + +Every environment participating in a replicated application must know whether it is a *master* or *replica*. The reason for this is because, simply, the master can modify the database while replicas cannot. As a result, not only will you open databases differently depended on whether the environment is running as a master, but the environment will frequently behave quite a bit differently depending on whether it thinks it is operating as the read/write interface for your database. + +Moreover, an environment must also be capable of gracefully switching between master and replica states. This means that the environment must be able to detect when it has switched states. + +Not surprisingly, a large part of your application's code will be tied up in knowing which state a given environment is in and then in the logic of how to behave depending on its state. + +This chapter shows you how to determine your environment's state, and it then shows you some sample code on how an application might behave depending on whether it is a master or a replica in a replicated application. + +## Determining State + +In order to determine whether your code is running as a master or a replica, you implement a callback whose function it is to respond to events that happen within the DB library. Note that these events are raised whenever the state is established. For example, when the current environment becomes a client — including at application startup — the `DB_EVENT_REP_CLIENT` event is raised. Also, when an election is held and a replica is elected to be a master, the `DB_EVENT_REP_MASTER` event is raised on the newly elected master and the `DB_EVENT_REP_NEWMASTER` is raised on the other replicas. + +Note that this callback is usable for events beyond those required for replication purposes. In this section, however, we only discuss the replication-specific events. + +The callback is required to determine which event has been passed to it, and then take action depending on the event. For replication, the events that we care about are: + +Some of the more commonly handled events are described below. For a complete list of events, see the `DbEnv::set_event_notify()` method in the *Berkeley DB C++ API Reference Guide*. + +- `DB_EVENT_REP_CLIENT` + + The local environment is now a replica. + +- `DB_EVENT_REP_CONNECT_BROKEN` + + A previously established connection between two sites in the replication group has been broken. + +- `DB_EVENT_REP_CONNECT_ESTD` + + A connection has been established between two sites in the replication group. + +- `DB_EVENT_REP_CONNECT_RETRY_ESTABLISHED` + + An attempt was made to establish a connection to a known remote site, but the connection attempt failed. + +- `DB_EVENT_REP_DUPMASTER` + + A duplicate master has been discovered in the replication group. + +- `DB_EVENT_REP_ELECTED` + + The local site has just won an election and is now the master. Your code should now reconfigure itself to operation as a master site. + +- `DB_EVENT_REP_ELECTION_FAILED` + + The local site's attempt to initiate or participate in a replication master election failed, due to the lack of timely message response from a sufficient number of remote sites. + +- `DB_EVENT_REP_ELECTION_STARTED` + + Replication Manager has started an election to choose a master site. + +- `DB_EVENT_REP_LOCAL_SITE_REMOVED` + + The local site has been removed from the group. + +- `DB_EVENT_REP_NEWMASTER` + + An election was held and a new environment was made a master. However, the current environment *is not* the master. This event exists so that you can cause your code to take some unique action in the event that the replication groups switches masters. + +- `DB_EVENT_REP_MASTER` + + The local environment is now a master. + +- `DB_EVENT_REP_MASTER_FAILURE` + + The connection to the remote master replication site has failed. + +- `DB_EVENT_REP_PERM_FAILED` + + The Replication Manager did not receive enough acknowledgements to ensure the transaction's durability within the replicationg group. The Replication Manager has therefore flushed the transaction to the master's local disk for storage. + + How the Replication Manager knows whether the acknowledgements it has received is determined by the ack policy you have set for your applicaton. See Identifying Permanent Message Policies for more information. + +- `DB_EVENT_REP_SITE_ADDED` + + A new site has joined the replication group. + +- `DB_EVENT_REP_SITE_REMOVED` + + An existing site has been removed from the replication group. + +- `DB_EVENT_REP_STARTUPDONE` + + The replica has completed startup synchronization and is now processing log records received from the master. + +- `DB_EVENT_WRITE_FAILED` + + A Berkeley DB write to stable storage failed. + +Note that these events are raised whenever the state is established. That is, when the current environment becomes a replica, and that includes at application startup, the event is raised. Also, when an election is held and a replica is elected to be a master, then the event occurs. + +The implementation of this callback is fairly simple. First you pass a structure to the environment handle that you can use to record the environment's state, and then you implement a switch statement within the callback that you use to record the current state, depending on the arriving event. + +For example: + +``` c +#include +/* Forward declaration */ +void *event_callback(DbEnv *, u_int32_t, void *); + +... + +/* The structure we use to track our environment's state */ +typedef struct { + int is_master; +} APP_DATA; + +... + +/* + * Inside our main() function, we declare an APP_DATA variable. + */ +APP_DATA my_app_data; +my_app_data.is_master = 0; /* Assume we start as a replica */ + +... + +/* + * Now we open our environment handle and set the APP_DATA structure + * to it's app_private member. + */ +DbEnv *dbenv = new DbEnv(0); +dbenv->set_app_private(&my_app_data); + +/* Having done that, register the callback with the + * Berkeley DB library + */ +dbenv->set_event_notify(event_callback); +``` + +That done, we still need to implement the callback itself. This implementation can be fairly trivial. + +``` c +/* + * A callback used to determine whether the local environment is a + * replica or a master. This is called by the Replication Manager + * when the local environment changes state. + */ +void * +event_callback(DbEnv *dbenv, u_int32_t which, void *info) +{ + APP_DATA *app = dbenv->get_app_private(); + + info = NULL; /* Currently unused. */ + + switch (which) { + case DB_EVENT_REP_MASTER: + app->is_master = 1; + break; + + case DB_EVENT_REP_CLIENT: + app->is_master = 0; + break; + + case DB_EVENT_REP_STARTUPDONE: /* fallthrough */ + case DB_EVENT_REP_NEWMASTER: + /* Ignore. */ + break; + + default: + dbenv->errx(dbenv, "ignoring event %d", which); + } +} +``` + +Notice how we access the `APP_DATA` information using the environment handle's `app_private` data member. We also ignore the `DB_EVENT_REP_NEWMASTER` and `DB_EVENT_REP_STARTUPDONE` cases since these are not relevant for simple replicated applications. + +Of course, this only gives us the current state of the environment. We still need the code that determines what to do when the environment changes state and how to behave depending on the state (described in the next section). diff --git a/docs_src/guides/gsg_db_rep/cxx/fwrkpermmessage.md b/docs_src/guides/gsg_db_rep/cxx/fwrkpermmessage.md new file mode 100644 index 000000000..8eddf14bb --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/fwrkpermmessage.md @@ -0,0 +1,124 @@ +--- +title: "Permanent Message Handling" +api-name: "Permanent Message Handling" +source: docs/gsg_db_rep/CXX/fwrkpermmessage.html +--- +## Permanent Message Handling + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepMgrGSG](fwrkpermmessage.md#perm2fmwrkexample) + +As described in Permanent Message Handling, messages are marked permanent if they contain database modifications that should be committed at the replica. DB's replication code decides if it must flush its transaction logs to disk depending on whether it receives sufficient permanent message acknowledgments from the participating replicas. More importantly, the thread performing the transaction commit blocks until it either receives enough acknowledgments, or the acknowledgment timeout expires. + +The Replication Manager is fully capable of managing permanent messages for you if your application requires it (most do). Almost all of the details of this are handled by the Replication Manager for you. However, you do have to set some policies that tell the Replication Manager how to handle permanent messages. + +There are two things that you have to do: + +- Determine how many acknowledgments must be received by the master. + +- Identify the amount of time that replicas have to send their acknowledgments. + +### Identifying Permanent Message Policies + +You identify permanent message policies using the Note that you can set permanent message policies at any time during the life of the application. + +The following permanent message policies are available when you use the Replication Manager: + +### Note + +The following list mentions *electable peer* several times. This is simply another environment that can be elected to be a master (that is, it has a priority greater than 0). Do not confuse this with the concept of a peer as used for client to client transfers. See Client to Client Transfer for more information on client to client transfers. + +- `DB_REPMGR_ACKS_NONE` + + No permanent message acknowledgments are required. If this policy is selected, permanent message handling is essentially "turned off." That is, the master will never wait for replica acknowledgments. In this case, transaction log data is either flushed or not strictly depending on the type of commit that is being performed (synchronous or asynchronous). + +- `DB_REPMGR_ACKS_ONE` + + At least one replica must acknowledge the permanent message within the timeout period. + +- `DB_REPMGR_ACKS_ONE_PEER` + + At least one electable peer must acknowledge the permanent message within the timeout period. + +- `DB_REPMGR_ACKS_ALL` + + All replicas must acknowledge the message within the timeout period. This policy should be selected only if your replication group has a small number of replicas, and those replicas are on extremely reliable networks and servers. + +- `DB_REPMGR_ACKS_ALL_AVAILABLE` + + All currently connected replication clients must acknowledge the message. This policy will invoke the `DB_EVENT_REP_PERM_FAILED` event if fewer than a quorum of clients acknowledged during that time. + +- `DB_REPMGR_ACKS_ALL_PEERS` + + All electable peers must acknowledge the message within the timeout period. This policy should be selected only if your replication group is small, and its various environments are on extremely reliable networks and servers. + +- `DB_REPMGR_ACKS_QUORUM` + + A quorum of electable peers must acknowledge the message within the timeout period. A quorum is reached when acknowledgments are received from the minimum number of environments needed to ensure that the record remains durable if an election is held. That is, the master wants to hear from enough electable replicas that they have committed the record so that if an election is held, the master knows the record will exist even if a new master is selected. + +By default, a quorum of electable peers must must acknowledge a permanent message in order for it considered to have been successfully transmitted. + +### Setting the Permanent Message Timeout + +The permanent message timeout represents the maximum amount of time the committing thread will block waiting for message acknowledgments. If sufficient acknowledgments arrive before this timeout has expired, the thread continues operations as normal. However, if this timeout expires, the committing thread flushes its transaction log buffer before continuing with normal operations. + +You set the timeout value using the `DbEnv::rep_set_timeout()` method. When you do this, you provide the `DB_REP_ACK_TIMEOUT` value to the `which` parameter, and the timeout value in microseconds to the `timeout` parameter. + +For example: + +``` c + dbenv->rep_set_timeout(DB_REP_ACK_TIMEOUT, 100); +``` + +This timeout value can be set at anytime during the life of the application. + +### Adding a Permanent Message Policy to RepMgrGSG + +For illustration purposes, we will now update `RepMgrGSG` such that it requires only one acknowledgment from a replica on transactional commits. Also, we will give this acknowledgment a 500 microsecond timeout value. This means that our application's main thread will block for up to 500 microseconds waiting for an acknowledgment. If it does not receive at least one acknowledgment in that amount of time, DB will flush the transaction logs to disk before continuing on. + +This is a very simple update. We can perform the entire thing in `RepMgrGSG::init()` immediately after we set the application's priority and before we open our environment handle. + +``` c +int RepMgrGSG::init(RepConfigInfo *config) +{ + int ret = 0; + + app_config = config; + + dbenv.set_errfile(stderr); + dbenv.set_errpfx(progname); + + DbSite *dbsite; + dbenv.repmgr_site(app_config->this_host.host, + app_config->this_host.port, &dbsite, 0); + dbsite->set_config(DB_LOCAL_SITE, 1); + if (app_config->this_host.creator) + dbsite->set_config(DB_GROUP_CREATOR, 1); + + dbsite->close(); + + int i = 1; + for ( REP_HOST_INFO *cur = app_config->other_hosts; + cur != NULL && i <= app_config->nrsites; + cur = cur->next, i++) { + dbenv.repmgr_site(cur->host, cur->port, &dbsite, 0); + dbsite->set_config(DB_BOOTSTRAP_HELPER, 1); + + dbsite->close(); + } + + dbenv.rep_set_priority(app_config->priority); + + /* Permanent messages require at least one ack */ + dbenv.repmgr_set_ack_policy(DB_REPMGR_ACKS_ONE); + /* Give 500 microseconds to receive the ack */ + dbenv.rep_set_timeout(DB_REP_ACK_TIMEOUT, 500); + + dbenv.set_cachesize(0, CACHESIZE, 0); + dbenv.set_flags(DB_TXN_NOSYNC, 1); + + ... +``` diff --git a/docs_src/guides/gsg_db_rep/cxx/heartbeats.md b/docs_src/guides/gsg_db_rep/cxx/heartbeats.md new file mode 100644 index 000000000..7f83fbd3c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/heartbeats.md @@ -0,0 +1,16 @@ +--- +title: "Managing Heartbeats" +api-name: "Managing Heartbeats" +source: docs/gsg_db_rep/CXX/heartbeats.html +--- +## Managing Heartbeats + +If your replicated application experiences few updates, it is possible for the replication group to lose a master without noticing it. This is because normally a replicated application only knows that a master has gone missing when update activity causes messages to be passed between the master and replicas. + +To guard against this, you can configure a heartbeat. The heartbeat must be configured for both the master and each of the replicas. + +On the master, you configure the application to send a heartbeat on a defined interval when it is otherwise idle. Do this by using the `DB_REP_HEARTBEAT_SEND` value to the `which` parameter of the `DbEnv::rep_set_timeout()` method. You must also provide the method a value representing the period between heartbeats in microseconds. Note that the heartbeat is sent only if the system is idle. + +On the replica, you configure the application to listen for a heartbeat. The time that you configure here is the amount of time the replica will wait for some message from the master (either the heartbeat or some other message) before concluding that the connection is lost. You do this using the `DB_REP_HEARTBEAT_MONITOR` value to the `which` parameter of the `DbEnv::rep_set_timeout()` method and a timeout value in microseconds. + +For best results, configure the heartbeat monitor for a longer time interval than the heartbeat send interval. diff --git a/docs_src/guides/gsg_db_rep/cxx/index.md b/docs_src/guides/gsg_db_rep/cxx/index.md new file mode 100644 index 000000000..4589ff7a6 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/index.md @@ -0,0 +1,144 @@ +--- +title: "Getting Started with Replicated Berkeley DB Applications" +api-name: "Getting Started with Replicated Berkeley DB Applications" +source: docs/gsg_db_rep/CXX/index.html +--- +# Getting Started with Replicated Berkeley DB Applications + +**Language:** [C](../index.md) · C++ (this page) · [Java](../java/index.md) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + + [Preface](preface.md) + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + + [1. Introduction](introduction.md) + + [Overview](introduction.md#overview) + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + + [Replication Benefits](repadvantage.md) + + [The Replication APIs](apioverview.md) + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + + [Holding Elections](elections.md) + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + + [Permanent Message Handling](permmessages.md) + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + + [2. Transactional Application](txnapp.md) + + [Application Overview](txnapp.md#appoverview) + + [Program Listing](simpleprogramlisting.md) + + [Class: RepConfigInfo](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: excxx_repquote_gsg_simple](simpleprogramlisting.md#repmgr_cxx) + + [Function: usage()](simpleprogramlisting.md#usage_cxx) + + [Function: main()](simpleprogramlisting.md#main_cxx) + + [Method: SimpleTxn::init()](simpleprogramlisting.md#repmgr_init_cxx) + + [Method: SimpleTxn::doloop()](simpleprogramlisting.md#doloop_cxx) + + [Method: SimpleTxn::print_stocks()](simpleprogramlisting.md#printstocks_c) + + [3. The DB Replication Manager](repapp.md) + + [The DbSite Handle](repapp.md#repmgr_grpmgmt) + + [Starting and Stopping Replication](rep_init_code.md) + + [Managing Election Policies](rep_init_code.md#election_flags) + + [Selecting the Number of Threads](rep_init_code.md#thread_count) + + [Adding the Replication Manager to RepMgr](repmgr_init_example_c.md) + + [Permanent Message Handling](fwrkpermmessage.md) + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepMgrGSG](fwrkpermmessage.md#perm2fmwrkexample) + + [Managing Election Times](electiontimes.md) + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + + [Managing Connection Retries](fmwrkconnectretry.md) + + [Managing Heartbeats](heartbeats.md) + + [4. Replica versus Master Processes](fwrkmasterreplica.md) + + [Determining State](fwrkmasterreplica.md#determinestate) + + [Processing Loop](processingloop.md) + + [Example Processing Loop](exampledoloop.md) + + [Running It](exampledoloop.md#runningit) + + [5. Additional Features](addfeatures.md) + + [Delayed Synchronization](addfeatures.md#delayedsync) + + [Managing Blocking Operations](manageblock.md) + + [Stop Auto-Initialization](autoinit.md) + + [Read-Your-Writes Consistency](rywc.md) + + [Client to Client Transfer](c2ctransfer.md) + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + + [Bulk Transfers](bulk.md) diff --git a/docs_src/guides/gsg_db_rep/cxx/introduction.md b/docs_src/guides/gsg_db_rep/cxx/introduction.md new file mode 100644 index 000000000..3cd723f0c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/introduction.md @@ -0,0 +1,102 @@ +--- +title: "Chapter 1. Introduction" +api-name: "Chapter 1. Introduction" +source: docs/gsg_db_rep/CXX/introduction.html +--- +## Chapter 1. Introduction + +**Table of Contents** + + [Overview](introduction.md#overview) + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + + [Replication Benefits](repadvantage.md) + + [The Replication APIs](apioverview.md) + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + + [Holding Elections](elections.md) + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + + [Permanent Message Handling](permmessages.md) + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + +This book provides a thorough introduction and discussion on replication as used with Berkeley DB (DB). It begins by offering a general overview to replication and the benefits it provides. It also describes the APIs that you use to implement replication, and it describes architecturally the things that you need to do to your application code in order to use the replication APIs. Finally, it discusses the differences in backup and restore strategies that you might pursue when using replication, especially where it comes to log file removal. + +You should understand the concepts from the *Berkeley DB Getting Started with Transaction Processing* guide before reading this book. + +## Overview + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + +The DB replication APIs allow you to distribute your database write operations (performed on a read-write master) to one or more read-only *replicas*. For this reason, DB's replication implementation is said to be a *single master, multiple replica* replication strategy. + +Note that your database write operations can occur only on the master; any attempt to write to a replica results in an error being raised by the DB API used to perform the write. + +A single replication master and all of its replicas are referred to as a *replication group*. While all members of the replication group can reside on the same machine, usually each replication participant is placed on a separate physical machine somewhere on the network. + +Note that all replication applications must first be transactional applications. The data that the master transmits to its replicas are log records that are generated as records are updated. Upon transactional commit, the master transmits a transaction record which tells the replicas to commit the records they previously received from the master. In order for all of this to work, your replicated application must also be a transactional application. For this reason, it is recommended that you write and debug your DB application as a stand-alone transactional application before introducing the replication layer to your code. + +### Replication Environments + +The most important requirement for a replication participant is that it must use a unique Berkeley DB database environment independent of all other replication participants. So while multiple replication participants can reside on the same physical machine, no two such participants can share the same environment home directory. + +For this reason, technically replication occurs between unique *database environments*. So in the strictest sense, a replication group consists of a *master environment* and one or more *replica environments*. However, the reality is that for production code, each such environment will usually be located on its own unique machine. Consequently, this manual sometimes talks about *replication sites*, meaning the unique combination of environment home directory, host and port that a specific replication application is using. + +There is no DB-specified limit to the number of environments which can participate in a replication group. The only limitation here is one of resources — network bandwidth, for example. + +(Note, however, that the Replication Manager does place a limit on the number of environments you can use. See Replication Manager Overview for details.) + +Also, DB's replication implementation requires all participating environments to be assigned IDs that are locally unique to the given environment. Depending on the replication APIs that you choose to use, you may or may not need to manage this particular detail. + +For detailed information on database environments, see the *Berkeley DB Getting Started with Transaction Processing* guide. For more information on environment IDs, see the *Berkeley DB Programmer's Reference Guide*. + +### Replication Databases + +DB's databases are managed and used in exactly the same way as if you were writing a non-replicated application, with a couple of caveats. First, the databases maintained in a replicated environment must reside either in the `ENV_HOME` directory, or in the directory identified by the `DbEnv::set_data_dir()` method. Unlike non-replication applications, you cannot place your databases in a subdirectory below these locations. You should also not use full path names for your databases or environments as these are likely to break when they are replicated to other machines. + +### Communications Layer + +In order to transmit database writes to the replication replicas, DB requires a communications layer. DB is agnostic as to what this layer should look like. The only requirement is that it be capable of passing two opaque data objects and an environment ID from the master to its replicas without corruption. + +Because replicas are usually placed on different machines on the network, the communications layer is usually some kind of a network-aware implementation. Beyond that, its implementation details are largely up to you. It could use TCP/IP sockets, for example, or it could use raw sockets if they perform better for your particular application. + +Note that you may not have to write your own communications layer. DB provides a Replication Manager that includes a fully-functional TCP/IP-based communications layer. See The Replication APIs for more information. + +See the *Berkeley DB Programmer's Reference Guide* for a description of how to write your own custom replication communications layer. + +### Selecting a Master + +Every replication group is allowed one and only one master environment. Usually masters are selected by holding an *election*, although it is possible to turn elections off and manually select masters (this is not recommended for most replicated applications). + +When elections are being used, they are performed by the underlying Berkeley DB replication code so you have to do very little to implement them. + +When holding an election, replicas "vote" on who should be the master. Among replicas participating in the election, the one with the most up-to-date set of log records will win the election. Note that it's possible for there to be a tie. When this occurs, priorities are used to select the master. See Holding Elections for details. + +For more information on holding and managing elections, see Holding Elections. diff --git a/docs_src/guides/gsg_db_rep/cxx/manageblock.md b/docs_src/guides/gsg_db_rep/cxx/manageblock.md new file mode 100644 index 000000000..28a1562ec --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/manageblock.md @@ -0,0 +1,12 @@ +--- +title: "Managing Blocking Operations" +api-name: "Managing Blocking Operations" +source: docs/gsg_db_rep/CXX/manageblock.html +--- +## Managing Blocking Operations + +When a replica is in the process of synchronizing with its master, DB operations are blocked at some points during this process until the synchronization is completed. For replicas with a heavy read load, these blocked operations may represent an unacceptable loss in throughput. + +You can configure DB so that it will not block when synchronization is in process. Instead, the DB operation will fail, immediately returning a `DB_REP_LOCKOUT` error. When this happens, it is up to your application to determine what action to take (that is, logging the event, making an appropriate user response, retrying the operation, and so forth). + +To turn off blocking on synchronization, specify `DB_REP_CONF_NOWAIT` to `DbEnv::rep_set_config()` and then specify `1` to the `onoff` parameter. (Specify `0` to turn the feature off.) diff --git a/docs_src/guides/gsg_db_rep/cxx/moreinfo.md b/docs_src/guides/gsg_db_rep/cxx/moreinfo.md new file mode 100644 index 000000000..0272448e4 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/moreinfo.md @@ -0,0 +1,28 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg_db_rep/CXX/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a transactional DB application: + +- Getting Started with Transaction Processing for C++ + +- Getting Started with Berkeley DB for C++ + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB C++ API Reference Guide + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg_db_rep/cxx/permmessages.md b/docs_src/guides/gsg_db_rep/cxx/permmessages.md new file mode 100644 index 000000000..1c0de1848 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/permmessages.md @@ -0,0 +1,86 @@ +--- +title: "Permanent Message Handling" +api-name: "Permanent Message Handling" +source: docs/gsg_db_rep/CXX/permmessages.html +--- +## Permanent Message Handling + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + +Messages received by a replica may be marked with special flag that indicates the message is permanent. Custom replicated applications will receive notification of this flag via the `DB_REP_ISPERM` return value from the `DbEnv::rep_process_message()` method. There is no hard requirement that a replication application look for, or respond to, this return code. However, because robust replicated applications typically do manage permanent messages, we introduce the concept here. + +A message is marked as being permanent if the message affects transactional integrity. For example, transaction commit messages are an example of a message that is marked permanent. What the application does about the permanent message is driven by the durability guarantees required by the application. + +For example, consider what the Replication Manager does when it has permanent message handling turned on and a transactional commit record is sent to the replicas. First, the replicas must transactional-commit the data modifications identified by the message. And then, upon a successful commit, the Replication Manager sends the master a message acknowledgment. + +For the master (again, using the Replication Manager), things are a little more complicated than simple message acknowledgment. Usually in a replicated application, the master commits transactions asynchronously; that is, the commit operation does not block waiting for log data to be flushed to disk before returning. So when a master is managing permanent messages, it typically blocks the committing thread immediately before `commit()` returns. The thread then waits for acknowledgments from its replicas. If it receives enough acknowledgments, it continues to operate as normal. + +If the master does not receive message acknowledgments — or, more likely, it does not receive *enough* acknowledgments — the committing thread flushes its log data to disk and then continues operations as normal. The master application can do this because replicas that fail to handle a message, for whatever reason, will eventually catch up to the master. So by flushing the transaction logs to disk, the master is ensuring that the data modifications have made it to stable storage in one location (its own hard drive). + +### When Not to Manage Permanent Messages + +There are two reasons why you might choose to not implement permanent messages. In part, these go to why you are using replication in the first place. + +One class of applications uses replication so that the application can improve transaction through-put. Essentially, the application chooses a reduced transactional durability guarantee so as to avoid the overhead forced by the disk I/O required to flush transaction logs to disk. However, the application can then regain that durability guarantee to a certain degree by replicating the commit to some number of replicas. + +Using replication to improve an application's transactional commit guarantee is called *replicating to the network.* + +In extreme cases where performance is of critical importance to the application, the master might choose to both use asynchronous commits *and* decide not to wait for message acknowledgments. In this case the master is simply broadcasting its commit activities to its replicas without waiting for any sort of a reply. An application like this might also choose to use something other than TCP/IP for its network communications since that protocol involves a fair amount of packet acknowledgment all on its own. Of course, this sort of an application should also be very sure about the reliability of both its network and the machines that are hosting its replicas. + +At the other extreme, there is a class of applications that use replication purely to improve read performance. This sort of application might choose to use synchronous commits on the master because write performance there is not of critical performance. In any case, this kind of an application might not care to know whether its replicas have received and successfully handled permanent messages because the primary storage location is assumed to be on the master, not the replicas. + +### Managing Permanent Messages + +With the exception of a rare breed of replicated applications, most masters need some view as to whether commits are occurring on replicas as expected. At a minimum, this is because masters will not flush their log buffers unless they have reason to expect that permanent messages have not been committed on the replicas. + +That said, it is important to remember that managing permanent messages involves a fair amount of network traffic. The messages must be sent to the replicas and the replicas must acknowledge them. This represents a performance overhead that can be worsened by congested networks or outright outages. + +Therefore, when managing permanent messages, you must first decide on how many of your replicas must send acknowledgments before your master decides that all is well and it can continue normal operations. When making this decision, you could decide that *all* replicas must send acknowledgments. But unless you have only one or two replicas, or you are replicating over a very fast and reliable network, this policy could prove very harmful to your application's performance. + +Therefore, a common strategy is to wait for an acknowledgment from a simple majority of replicas. This ensures that commit activity has occurred on enough machines that you can be reliably certain that data writes are preserved across your network. + +Remember that replicas that do not acknowledge a permanent message are not necessarily unable to perform the commit; it might be that network problems have simply resulted in a delay at the replica. In any case, the underlying DB replication code is written such that a replica that falls behind the master will eventually take action to catch up. + +Depending on your application, it may be possible for you to code your permanent message handling such that acknowledgment must come from only one or two replicas. This is a particularly attractive strategy if you are closely managing which machines are eligible to become masters. Assuming that you have one or two machines designated to be a master in the event that the current master goes down, you may only want to receive acknowledgments from those specific machines. + +Finally, beyond simple message acknowledgment, you also need to implement an acknowledgment timeout for your application. This timeout value is simply meant to ensure that your master does not hang indefinitely waiting for responses that will never come because a machine or router is down. + +### Implementing Permanent Message Handling + +How you implement permanent message handling depends on which API you are using to implement replication. If you are using the Replication Manager, then permanent message handling is configured using policies that you specify to the framework. In this case, you can configure your application to: + +- Ignore permanent messages (the master does not wait for acknowledgments). + +- Require acknowledgments from a quorum. A quorum is reached when acknowledgments are received from the minimum number of electable peers needed to ensure that the record remains durable if an election is held. + + An *electable peer* is any other site that potentially can be elected master. + + The goal here is to be absolutely sure the record is durable. The master wants to hear from enough electable peer that they have committed the record so that if an election is held, the master knows the record will exist even if a new master is selected. + + This is the default policy. + +- Require an acknowledgment from at least one replica. + +- Require acknowledgments from all replicas. + +- Require an acknowledgment from at least one electable peer. + +- Require acknowledgments from all electable peers. + +Note that the Replication Manager simply flushes its transaction logs and moves on if a permanent message is not sufficiently acknowledged. + +For details on permanent message handling with the Replication Manager, see Permanent Message Handling. + +If these policies are not sufficient for your needs, or if you want your application to take more corrective action than simply flushing log buffers in the event of an unsuccessful commit, then you must use implement replication using the Base APIs. + +When using the Base APIs, messages are sent from the master to its replica using a `send()` callback that you implement. Note, however, that DB's replication code automatically sets the permanent flag for you where appropriate. + +If the `send()` callback returns with a non-zero status, DB flushes the transaction log buffers for you. Therefore, you must cause your `send()` callback to block waiting for acknowledgments from your replicas. As a part of implementing the `send()` callback, you implement your permanent message handling policies. This means that you identify how many replicas must acknowledge the message before the callback can return `0`. You must also implement the acknowledgment timeout, if any. + +Further, message acknowledgments are sent from the replicas to the master using a communications channel that you implement (the replication code does not provide a channel for acknowledgments). So implementing permanent messages means that when you write your replication communications channel, you must also write it in such a way as to also handle permanent message acknowledgments. + +For more information on implementing permanent message handling using a custom replication layer, see the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/cxx/preface.md b/docs_src/guides/gsg_db_rep/cxx/preface.md new file mode 100644 index 000000000..9e6a522ba --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/preface.md @@ -0,0 +1,60 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg_db_rep/CXX/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +This document describes how to write replicated applications for Berkeley DB 11*g* Release 2 (library version 11.2.5.3). The APIs used to implement replication in your application are described here. This book describes the concepts surrounding replication, the scenarios under which you might choose to use it, and the architectural requirements that a replication application has over a transactional application. + +This book is aimed at the software engineer responsible for writing a replicated DB application. + +This book assumes that you have already read and understood the concepts contained in the *Berkeley DB Getting Started with Transaction Processing* guide. + +## 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: "`DbEnv::open()` is a `DbEnv` class method." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number +} VENDOR; +``` + +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: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number + char sales_rep[MAXFIELD]; // Name of sales representative + char sales_rep_phone[MAXFIELD]; // Sales rep's phone number +} VENDOR; +``` + +### Note + +Finally, notes of special interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg_db_rep/cxx/processingloop.md b/docs_src/guides/gsg_db_rep/cxx/processingloop.md new file mode 100644 index 000000000..d807d3840 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/processingloop.md @@ -0,0 +1,109 @@ +--- +title: "Processing Loop" +api-name: "Processing Loop" +source: docs/gsg_db_rep/CXX/processingloop.html +--- +## Processing Loop + +Typically the central part of any replication application is some sort of a continuous loop that constantly checks the state of the environment (whether it is a replica or a master), opens and/or closes the databases as is necessary, and performs other useful work. A loop such as this one must of necessity take special care to know whether it is operating on a master or a replica environment because all of its activities are dependent upon that state. + +The flow of activities through the loop will generally be as follows: + +1. Check whether the environment has changed state. If it has, you might want to reopen your database handles, especially if you opened your replica's database handles as read-only. In this case, you might need to reopen them as read-write. However, if you always open your database handles as read-write, then it is not automatically necessary to reopen the databases due to a state change. Instead, you could check for a `DB_REP_HANDLE_DEAD` return code when you use your database handle(s). If you see this, then you need to reopen your database handle(s). + +2. If the databases are closed, create new database handles, configure the handle as is appropriate, and then open the databases. Note that handle configuration will be different, depending on whether the handle is opened as a replica or a master. At a minimum, the master should be opened with database creation privileges, whereas the replica does not need to be. You must also open the master such that its databases are read-write. You *can* open replicas with read-only databases, so long as you are prepared to close and then reopen the handle in the event the client becomes a master. + + Also, note that if the local environment is a replica, then it is possible that databases do not currently exist. In this case, the database open attempts will fail. Your code will have to take this corner case into account (described below). + +3. Once the databases are opened, check to see if the local environment is a master. If it is, do whatever it is a master should do for your application. + + Remember that the code for your master should include some way for you to tell the master to exit gracefully. + +4. If the local environment is not a master, then do whatever it is your replica environments should do. Again, like the code for your master environments, you should provide a way for your replicas to exit the processing loop gracefully. + +The following code fragment illustrates these points (note that we fill out this fragment with a working example next in this chapter): + +``` c +/* loop to manage replication activities */ + +Db *dbp; +int ret; +APP_DATA *app_data; +u_int32_t flags; + +dbp = NULL; +ret = 0; + +/* + * Remember that for this to work, an APP_DATA struct would have first + * had to been set to the environment handle's app_private data + * member. (dbenv is presumably declared and opened in another part of + * the code.) + */ +app_data = dbenv->get_app_private(); + +/* + * Infinite loop. We exit depending on how the master and replica code + * is written. + */ +for (;;) { + /* If dbp is not opened, we need to open it. */ + if (dbp == 0) { + /* + * Create the handle and then configure it. Before you open + * it, you have to decide what open flags to use: + */ + dbp = new Db(&dbenv, 0); + + /* + * Now you can open your database handle. + * + * One thing to watch out for is a case where the databases + * you are trying to open do not yet exist. This can happen + * for replicas where the databases are being opened + * read-only. If this happens, ENOENT is returned by the + * open() call. + */ + try { + dbp->open(NULL, DATABASE, NULL, DB_BTREE, + app_data->is_master ? DB_CREATE | DB_AUTO_COMMIT : + DB_AUTO_COMMIT, 0); + } catch(DbException dbe) { + if (dbe.get_errno() == ENOENT) { + cout << "No stock db available yet - retrying." << endl; + try { + dbp->close(0); + } catch (DbException dbe2) { + cout << "Unexpected error closing after failed" << + " open, message: " << dbe2.what() << endl; + dbp = NULL; + goto err; + } + dbp = NULL; + sleep(SLEEPTIME); + continue; + } else { + dbenv.err(ret, "DB->open"); + throw dbe; + } + } + } + + /* + * Now that the databases have been opened, continue with general + * processing, depending on whether we are a master or a replica. + */ + if (app_data->is_master) { + /* + * Do master stuff here. Don't forget to include a way to + * gracefully exit the loop. */ + */ + } else { + /* + * Do replica stuff here. As is the case with the master + * code, be sure to include a way to gracefully exit the + * loop. + */ + } +} +``` diff --git a/docs_src/guides/gsg_db_rep/cxx/rep_init_code.md b/docs_src/guides/gsg_db_rep/cxx/rep_init_code.md new file mode 100644 index 000000000..bd857033c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/rep_init_code.md @@ -0,0 +1,228 @@ +--- +title: "Starting and Stopping Replication" +api-name: "Starting and Stopping Replication" +source: docs/gsg_db_rep/CXX/rep_init_code.html +--- +## Starting and Stopping Replication + + [Managing Election Policies](rep_init_code.md#election_flags) + + [Selecting the Number of Threads](rep_init_code.md#thread_count) + +As described above, you introduce replication to an application by starting with a transactional application, performing some basic replication configuration, and then starting replication using `DbEnv::repmgr_start()`. + +You stop replication by closing your environment cleanly in the same way you would for any DB application. + +For example, the following code fragment initializes, then stops and starts replication. Note that other replication activities are omitted for brevity. + +``` c +#include + +/* Use a 10mb cache */ +#define CACHESIZE (10 * 1024 * 1024) + +... + + DbEnv *dbenv; /* Environment handle. */ + DbSite *dbsite; /* Replication manager site handle */ + const char *progname; /* Program name. */ + const char *envHome; /* Environment home directory. */ + const char *listen_host; /* A TCP/IP hostname. */ + const char *other_host; /* A TCP/IP hostname. */ + int is_group_creator; /* A flag */ + u_int16 listen_port; /* A TCP/IP port. */ + u_int16 other_port; /* A TCP/IP port. */ + + /* Initialize variables */ + dbenv = NULL; + progname = "example_replication"; + envHome = "ENVIRONMENT_HOME"; + listen_host = "mymachine.sleepycat.com"; + listen_port = 5001; + other_host = "anothermachine.sleepycat.com"; + other_port = 4555; + is_group_creator = 1; /* This is usually set via a command line + argument or some other external + configuration mechanism. */ + + try { + /* Create the environment handle */ + dbenv = new DbEnv(0); + + /* + * Configure the environment handle. Here we configure + * asynchronous transactional commits for performance reasons. + */ + dbenv->set_errfile(stderr); + dbenv->set_errpfx(progname); + (void)dbenv->set_cachesize(0, CACHESIZE, 0); + (void)dbenv->set_flags(DB_TXN_NOSYNC, 1); + + /* + * Configure the local address. This is the local hostname + * and port that this replication environment will use to + * receive incoming replication messages. Note that this can + * be performed only once for the replication environment. + * It is required. + + * First: Create a DB_SITE handle to identify the site's + * host/port network address. + */ + dbenv->repmgr_site(listen_host, listen_port, &dbsite;, 0); + + /* + * Second: Configure this site as the local site within the + * replication group. + */ + dbsite->set_config(DB_LOCAL_SITE, 1); + + /* + * Third: Set DB_GROUP_CREATOR if applicable. This can be done + * only for the local site. It should also only be peformed + * for one and only one site in a replication group, so + * typically this is driven by an externally-supplied + * configuration option. + * + * DB_GROUP_CREATOR only has meaning if you are starting the + * very first site for the very first time in a replication + * group. It is otherwise ignored. + */ + if (is_group_creator) + dbsite->set_config(DB_GROUP_CREATOR, 1); + + /* + * Having configured the local site, we can immediately + * deallocate the DB_SITE handle. + */ + dbsite->close(dbsite); + + /* + * Set this replication environment's priority. This is used + * for elections. + * + * Set this number to a positive integer, or 0 if you do not want + * this site to be able to become a master. + */ + dbenv->rep_set_priority(100); + + /* + * Configure a bootstrap helper. This information is used only + * if the site currently exists, and the local site has never + * been started before. Otherwise, this configuration + * information is ignored. + * + */ + if (!is_group_creator) { + dbenv->repmgr_site(other_host, other_port, &dbsite, 0); + dbsite->set_config(dbsite, DB_BOOTSTRAP_HELPER, 1); + + /* + * Having configured the bootstrap helper site, we can + * immediately deallocate the DB_SITE handle. + */ + dbsite->close(); + } + + /* Open the environment handle. Note that we add DB_THREAD and + * DB_INIT_REP to the list of flags. These are required. + */ + dbenv->open(home, DB_CREATE | DB_RECOVER | + DB_INIT_LOCK | DB_INIT_LOG | + DB_INIT_MPOOL | DB_INIT_TXN | + DB_THREAD | DB_INIT_REP, + 0); + + /* + * Start the replication manager such that it uses 3 + * threads. + */ + dbenv->repmgr_start(3, DB_REP_ELECTION); + + /* Sleep to give ourselves time to find a master */ + sleep(5); + + /* + ********************************************************** + *** All other application code goes here, including ***** + *** database opens ***** + ********************************************************** + */ + + } catch (DbException &de) { + /* Error handling goes here */ + } + + /* Close out the application here. + try { + /* + * Make sure all your database handles are closed + * (omitted from this example). + */ + + /* Close the environment */ + if (dbenv != NULL) + (void)dbenv->close(dbenv, 0); + + } catch (DbException &de) { + /* Error handling goes here */ + } + + /* All done */ +``` + +### Managing Election Policies + +Before continuing, it is worth taking a look at the startup election flags accepted by `DbEnv::repgmr_start()`. These flags control how your replication application will behave when it first starts up. + +In the previous example, we specified `DB_REP_ELECTION` when we started replication. This causes the application to try to find a master upon startup. If it cannot, it calls for an election. In the event an election is held, the environment receiving the most number of votes will become the master. + +There's some important points to make here: + +- This flag only requires that other environments in the replication group participate in the vote. There is no requirement that *all* such environments participate. In other words, if an environment starts up, it can call for an election, and select a master, even if all other environment have not yet joined the replication group. + +- It only requires a simple majority of participating environments to elect a master. This is always true of elections held using the Replication Manager. + +- As always, the environment participating in the election with the most up-to-date log files is selected as master. If an environment with more recent log files has not yet joined the replication group, it may not become the master. + +Any one of these points may be enough to cause a less-than-optimum environment to be selected as master. Therefore, to give you a better degree of control over which environment becomes a master at application startup, the Replication Manager offers the following start-up flags: + + + + + + + + + + + + + + + + + + + + + + +
FlagDescription
DB_REP_MASTER

The application starts up and declares the environment to be a master without calling for an election. It is an error for more than one environment to start up using this flag, or for an environment to use this flag when a master already exists.

+

Note that no replication group should ever operate with more than one master.

+

In the event that a environment attempts to become a master when a master already exists, the replication code will resolve the problem by holding an election. Note, however, that there is always a possibility of data loss in the face of duplicate masters, because once a master is selected, the environment that loses the election will have to roll back any transactions committed until it is in sync with the "real" master.

DB_REP_CLIENT

The application starts up and declares the environment to be a replica without calling for an election. Note that the environment can still become a master if a subsequent application starts up, calls for an election, and this environment is elected master.

DB_REP_ELECTION

As described above, the application starts up, looks for a master, and if one is not found calls for an election.

+ +### Selecting the Number of Threads + +Under the hood, the Replication Manager is threaded and you can control the number of threads used to process messages received from other replicas. The threads that the Replication Manager uses are: + +- Incoming message thread. This thread receives messages from the site's socket and passes those messages to message processing threads (see below) for handling. + +- Outgoing message thread. Outgoing messages are sent from whatever thread performed a write to the database(s). That is, the thread that called, for example, `Db::put()` is the thread that writes replication messages about that fact to the socket. + + Note that if this write activity would cause the thread to be blocked due to some condition on the socket, the Replication Manager will hand the outgoing message to the incoming message thread, and it will then write the message to the socket. This prevents your database write threads from blocking due to abnormal network I/O conditions. + +- Message processing threads are responsible for parsing and then responding to incoming replication messages. Typically, a response will include write activity to your database(s), so these threads can be busy performing disk I/O. + +Of these threads, the only ones that you have any configuration control over are the message processing threads. In this case, you can determine how many of these threads you want to run. + +It is always a bit of an art to decide on a thread count, but the short answer is you probably do not need more than three threads here, and it is likely that one will suffice. That said, the best thing to do is set your thread count to a fairly low number and then increase it if it appears that your application will benefit from the additional threads. diff --git a/docs_src/guides/gsg_db_rep/cxx/repadvantage.md b/docs_src/guides/gsg_db_rep/cxx/repadvantage.md new file mode 100644 index 000000000..810ead7e3 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/repadvantage.md @@ -0,0 +1,40 @@ +--- +title: "Replication Benefits" +api-name: "Replication Benefits" +source: docs/gsg_db_rep/CXX/repadvantage.html +--- +## Replication Benefits + +Replication offers your application a number of benefits that can be a tremendous help. Primarily replication's benefits revolve around performance, but there is also a benefit in terms of data durability guarantees. + +Briefly, the reasons why you might choose to implement replication in your DB application are: + +- Improve application reliability. + + By spreading your data across multiple machines, you can ensure that your application's data continues to be available even in the event of a hardware failure on any given machine in the replication group. + +- Improve read performance. + + By using replication you can spread data reads across multiple machines on your network. Doing so allows you to vastly improve your application's read performance. This strategy might be particularly interesting for applications that have readers on remote network nodes; you can push your data to the network's edges thereby improving application data read responsiveness. + + Additionally, depending on the portion of your data that you read on a given replica, that replica may need to cache part of your data, decreasing cache misses and reducing I/O on the replica. + +- Improve transactional commit performance + + In order to commit a transaction and achieve a transactional durability guarantee, the commit must be made *durable*. That is, the commit must be written to disk (usually, but not always, synchronously) before the application's thread of control can continue operations. + + Replication allows you to avoid this disk I/O and still maintain a degree of durability by *committing to the network*. In other words, you relax your transactional durability guarantees on the master, but by virtue of replicating the data across the network you gain some additional durability guarantees above what is provided locally. + + Usually this strategy is implemented using some form of an asynchronous transactional commit on the master. In this way your data writes will eventually be written to disk, but your application will not have to wait for the disk I/O to complete before continuing with its next operation. + + Note that it is possible to cause DB's replication implementation to wait to hear from one or more replicas as to whether they have successfully saved the write before continuing. However, in this case you might be trading performance for a even higher durability guarantee (see below). + +- Improve data durability guarantee. + + In a traditional transactional application, you commit your transactions such that data modifications are saved to disk. Beyond this, the durability of your data is dependent upon the backup strategy that you choose to implement for your site. + + Replication allows you to increase this durability guarantee by ensuring that data modifications are written to multiple machines. This means that multiple disks, disk controllers, power supplies, and CPUs are used to ensure that your data modification makes it to stable storage. In other words, replication allows you to minimize the problem of a single point of failure by using more hardware to guarantee your data writes. + + If you are using replication for this reason, then you probably will want to configure your application such that it waits to hear about a successful commit from one or more replicas before continuing with the next operation. This will obviously impact your application's write performance to some degree — with the performance penalty being largely dependent upon the speed and stability of the network connecting your replication group. + + For more information, see Permanent Message Handling. diff --git a/docs_src/guides/gsg_db_rep/cxx/repapp.md b/docs_src/guides/gsg_db_rep/cxx/repapp.md new file mode 100644 index 000000000..d38aa1266 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/repapp.md @@ -0,0 +1,86 @@ +--- +title: "Chapter 3. The DB Replication Manager" +api-name: "Chapter 3. The DB Replication Manager" +source: docs/gsg_db_rep/CXX/repapp.html +--- +## Chapter 3. The DB Replication Manager + +**Table of Contents** + + [The DbSite Handle](repapp.md#repmgr_grpmgmt) + + [Starting and Stopping Replication](rep_init_code.md) + + [Managing Election Policies](rep_init_code.md#election_flags) + + [Selecting the Number of Threads](rep_init_code.md#thread_count) + + [Adding the Replication Manager to RepMgr](repmgr_init_example_c.md) + + [Permanent Message Handling](fwrkpermmessage.md) + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepMgrGSG](fwrkpermmessage.md#perm2fmwrkexample) + + [Managing Election Times](electiontimes.md) + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + + [Managing Connection Retries](fmwrkconnectretry.md) + + [Managing Heartbeats](heartbeats.md) + +The easiest way to add replication to your transactional application is to use the Replication Manager. The Replication Manager provides a comprehensive communications layer that enables replication. For a brief listing of the Replication Manager's feature set, see Replication Manager Overview. + +To use the Replication Manager, you make use of a combination of the `DbSite` class and related methods, plus special methods off the `DbEnv` class. That is: + +1. Create an environment handle as normal. + +2. Configure your environment handle as needed (e.g. set the error file and error prefix values, if desired). + +3. Use the Replication Manager replication classes and methods to configure the Replication Manager. Using these classes and methods causes DB to know that you are using the Replication Manager. + + Configuring the Replication Manager entails setting the replication environment's priority, setting the TCP/IP address that this replication environment will use for incoming replication messages, identifying TCP/IP addresses of other replication environments, setting the number of replication environments in the replication group, and so forth. These actions are discussed throughout the remainder of this chapter. + +4. Open your environment handle. When you do this, be sure to specify `DB_INIT_REP` and `DB_THREAD` to your open flags. (This is in addition to the flags that you normally use for a single-threaded transactional application). The first of these causes replication to be initialized for the application. The second causes your environment handle to be free-threaded (thread safe). Both flags are required for Replication Manager usage. + +5. Start replication by calling `DbEnv::repmgr_start()`. + +6. Open your databases as needed. Masters must open their databases for read and write activity. Replicas can open their databases for read-only activity, but doing so means they must re-open the databases if the replica ever becomes a master. Either way, replicas should never attempt to write to the database(s) directly. + +### Note + +The Replication Manager allows you to only use one environment handle per process. + +When you are ready to shut down your application: + +1. Close any open `DbSite` handles that you might have open. + +2. Close your databases + +3. Close your environment. This causes replication to stop as well. + +### Note + +Before you can use the Replication Manager, you may have to enable it in your DB library. This is *not* a requirement for Microsoft Windows systems, or Unix systems that use pthread mutexes by default. Other systems, notably BSD and BSD-derived systems (such as Mac OS X), must enable the Replication Manager when you configure the DB build. + +You do this by *not* disabling replication and by configuring the library with POSIX threads support. In other words, replication must be turned on in the build (it is by default), and POSIX thread support must be enabled if it is not already by default. To do this, use the `--enable-pthread_api` switch on the configure script. + +For example: + +``` c +../dist/configure --enable-pthread-api +``` + +## The DbSite Handle + +Before continuing, it is useful to mention the `DbSite` handle. This class is used to configure important attributes about a site such as its host name and port number, and whether it is the local site. It is also used to indicate whether a site is a *group creator*, which is important when you are starting the very first site in a replication group for the very first time. + +The `DbSite` handle is used whenever you start up a site. It must be closed before you close your `DbEnv` handle. + +The `DbSite` handle is plays an important role in replication group management. This topic is fully described in the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/cxx/repmgr_init_example_c.md b/docs_src/guides/gsg_db_rep/cxx/repmgr_init_example_c.md new file mode 100644 index 000000000..86d07506b --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/repmgr_init_example_c.md @@ -0,0 +1,340 @@ +--- +title: "Adding the Replication Manager to RepMgr" +api-name: "Adding the Replication Manager to RepMgr" +source: docs/gsg_db_rep/CXX/repmgr_init_example_c.html +--- +## Adding the Replication Manager to RepMgr + +We now use the methods described above to add partial support to the RepMgr example that we presented in Transactional Application. That is, in this section we will: + +- Enhance our command line options to accept information of interest to a replicated application. + +- Configure our environment handle to use replication and the Replication Manager. + +- Minimally configure the Replication Manager. + +- Start replication. + +Note that when we are done with this section, we will be only partially ready to run the application. Some critical pieces will be missing; specifically, we will not yet be handling the differences between a master and a replica. (We do that in the next chapter). + +Also, note that in the following code fragments, additions and changes to the code are marked in **`bold`**. + +To begin, we copy the SimpleTxn code to a new file called `RepMgrGSG.cpp`. Having done that, we must make some significant changes to our `RepConfigInfo` class because now we will be using it to maintain a lot more information. + +First, we create a new structure, `RepHostInfoObj`, which we use to store host and port information for all "other" servers identified to the application via the `-o` command line option. This structure is chain-able, which makes cleaning up at program shutdown time easier. + +``` c +#include +#include + +// Chain-able struct used to store host information. +typedef struct RepHostInfoObj{ + char* host; + u_int16_t port; + RepHostInfoObj* next; // used for chaining multiple "other" hosts. + bool creator; // whether this site is the group creator. +} REP_HOST_INFO; +``` + +Next, we update our `RepConfigInfo` class definition to manage a lot more information and a new method. + +``` c +class RepConfigInfo { +public: + RepConfigInfo(); + virtual ~RepConfigInfo(); + + void addOtherHost(char* host, int port); +public: + u_int32_t start_policy; + char* home; + bool got_listen_address; + REP_HOST_INFO this_host; + int nrsites; // number of remote sites + int priority; + // used to store a set of optional other hosts. + REP_HOST_INFO *other_hosts; +}; +``` + +Then, we update our constructor to initialize our new variables. + +``` c +RepConfigInfo::RepConfigInfo() +{ + start_policy = DB_REP_ELECTION; + home = "TESTDIR"; + got_listen_address = false; + nrsites = 0; + priority = 100; + other_hosts = NULL; +} +``` + +Next, we implement our new method, `RepConfigInfo::addOtherHost`, which is used to create `RepHostInfoObj` instances and add them to the chain of "other" hosts. + +``` c +RepConfigInfo::addOtherHost(char* host, int port) +{ + REP_HOST_INFO *newinfo; + newinfo = (REP_HOST_INFO*)malloc(sizeof(REP_HOST_INFO)); + newinfo->host = host; + newinfo->port = port; + if (other_hosts == NULL) { + other_hosts = newinfo; + newinfo->next = NULL; + } else { + newinfo->next = other_hosts; + other_hosts = newinfo; + } + nrsites++; +} +``` + +Having done that, we update our class destructor to release the `RepHostInfoObj` chain of objects at class destruction time. + +``` c +RepConfigInfo::~RepConfigInfo() +{ + // release any other_hosts structs. + if (other_hosts != NULL) { + REP_HOST_INFO *CurItem = other_hosts; + while (CurItem->next != NULL) + { + REP_HOST_INFO *TmpItem = CurItem; + free(CurItem); + CurItem = TmpItem; + } + free(CurItem); + } + other_hosts = NULL; +} +``` + +Having completed our update to the `RepConfigInfo` class, we can now start making changes to the main portion of our program. We begin by changing the program's name. + +``` c +using std::cout; +using std::cin; +using std::cerr; +using std::endl; +using std::flush; + +#define CACHESIZE (10 * 1024 * 1024) +#define DATABASE "quote.db" + +const char *progname = "RepMgrGSG"; +``` + +Next we update our usage function. The application will continue to accept the `-h` parameter so that we can identify the environment home directory used by this application. However, we also add the: + +- `-l` parameter which allows us to identify the host and port used by this application to listen for replication messages. This parameter is required unless the -L parameter is specified. + +- `-L` parameter, which allows us to identify the local site as the group creator. + +- `-r` parameter which allows us to specify other replicas. + +- `-p` option, which is used to identify this replica's priority (recall that the priority is used as a tie breaker for elections) + +``` c +class RepMgrGSG +{ +public: + // Constructor. + RepMgrGSG(); + // Initialization method. Creates and opens our environment handle. + int init(RepConfigInfo* config); + // The doloop is where all the work is performed. + int doloop(); + // terminate() provides our shutdown code. + int terminate(); + +private: + // disable copy constructor. + RepMgrGSG(const RepMgrGSG &); + void operator = (const RepMgrGSG &); + + // internal data members. + RepConfigInfo *app_config; + DbEnv dbenv; + + // private methods. + // print_stocks() is used to display the contents of our database. + static int print_stocks(Db *dbp); +}; + +static void usage() +{ + cerr << "usage: " << progname << endl + << "-h home-l|L host:port [-r host:port]" + << "[-p priority]" << endl; + + cerr << "\t -h home directory" << endl + << "\t -l host:port (required unless -L is specified;" + << "\t l stands for local)" << endl + << "\t -L host:port (optional, L means group " + << " creator)" << endl + << "\t -r host:port (optional; r stands for replica; any " + << "number of these may be specified)" << endl + << "\t -p priority (optional: defaults to 100)" << endl; + + exit(EXIT_FAILURE); +} +``` + +Now we can begin working on our `main()` function. We begin by adding a couple of variables that we will use to collect TCP/IP host and port information. + +``` c +int main(int argc, char **argv) +{ + RepConfigInfo config; + char ch, *portstr, *tmphost; + int tmpport; + int ret; +``` + +Now we collect our command line arguments. As we do so, we will configure host and port information as required, and we will configure the application's election priority if necessary. + +``` c + // Extract the command line parameters + while ((ch = getopt(argc, argv, "h:l:n:p:r:")) != EOF) { + switch (ch) { + case 'h': + config.home = optarg; + break; + case 'L': + config.this_host.creator = true; // FALLTHROUGH + case 'l': + config.this_host.host = strtok(optarg, ":"); + if ((portstr = strtok(NULL, ":")) == NULL) { + cerr << "Bad host specification." << endl; + usage(); + } + config.this_host.port = (unsigned short)atoi(portstr); + config.got_listen_address = true; + break; + case 'p': + config.priority = atoi(optarg); + break; + case 'r': + tmphost = strtok(optarg, ":"); + if ((portstr = strtok(NULL, ":")) == NULL) { + cerr << "Bad host specification." << endl; + usage(); + } + tmpport = (unsigned short)atoi(portstr); + config.addOtherHost(tmphost, tmpport); + break; + case '?': + default: + usage(); + } + } + + // Error check command line. + if ((!config.got_listen_address) || config.home == NULL) + usage(); +``` + +Having done that, the remainder of our `main()` function is left unchanged: + +``` c + RepMgrGSG runner; + try { + if((ret = runner.init(&config)) != 0) + goto err; + if((ret = runner.doloop()) != 0) + goto err; + } catch (DbException dbe) { + cerr << "Caught an exception during initialization or" + << " processing: " << dbe.what() << endl; + } +err: + runner.terminate(); + return 0; +} +``` + +Now we need to update our `RepMgrGSG::init()` method. Our updates are at first related to configuring replication. First, we need to update the method so that we can identify the local site to the environment handle (that is, the site identified by the `-l` command line option): + +``` c +RepMgrGSG::RepMgrGSG() : app_config(0), dbenv(0) +{ +} + +int RepMgrGSG::init(RepConfigInfo *config) +{ + int ret = 0; + + app_config = config; + + dbenv.set_errfile(stderr); + dbenv.set_errpfx(progname); + + DbSite *dbsite; + dbenv.repmgr_site(app_config->this_host.host, + app_config->this_host.port, &dbsite, 0); + dbsite->set_config(DB_LOCAL_SITE, 1); + if (app_config->this_host.creator) + dbsite->set_config(DB_GROUP_CREATOR, 1); + + dbsite->close(); +``` + +And we also add code to allow us to identify "other" sites to the environment handle (that is, the sites that we identify using the `-o` command line option). To do this, we iterate over each of the "other" sites provided to us using the `-o` command line option, and we add each one individually in turn: + +We also add code here to set the environment's priority. + +``` c + int i = 1; + for ( REP_HOST_INFO *cur = app_config->other_hosts; + cur != NULL && i <= app_config->nrsites; + cur = cur->next, i++) { + dbenv.repmgr_site(cur->host, cur->port, &dbsite, 0); + dbsite->set_config(DB_BOOTSTRAP_HELPER, 1); + + dbsite->close(); + } + dbenv.rep_set_priority(app_config->priority); +``` + +We can now open our environment. Note that the flags we use to open the environment are slightly different for a replicated application than they are for a non-replicated application. Namely, replication requires the `DB_INIT_REP` flag. + +Also, because we are using the Replication Manager, we must prepare our environment for threaded usage. For this reason, we also need the `DB_THREAD` flag. + +``` c + dbenv.set_cachesize(0, CACHESIZE, 0); + dbenv.set_flags(DB_TXN_NOSYNC, 1); + + try { + dbenv.open(app_config->home, + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_REP | + DB_INIT_TXN | + DB_RECOVER | + DB_THREAD; + 0); + } catch(DbException dbe) { + cerr << "Caught an exception during DB environment open." << endl + << "Ensure that the home directory is created prior to " + << "starting the application." << endl; + ret = ENOENT; + goto err; + } +``` + +Finally, we start replication before we exit this method. Immediately after exiting this method, our application will go into the `RepMgrGSG::doloop()` method, which is where the bulk of our application's work is performed. We update that method in the next chapter. + +``` c + if ((ret = dbenv.repmgr_start(3, app_config->start_policy)) != 0) + goto err; + +err: + return ret; +} +``` + +This completes our replication updates for the moment. We are not as yet ready to actually run this program; there remains a few critical pieces left to add to it. However, the work that we performed in this section represents a solid foundation for the remainder of our replication work. diff --git a/docs_src/guides/gsg_db_rep/cxx/rywc.md b/docs_src/guides/gsg_db_rep/cxx/rywc.md new file mode 100644 index 000000000..704b0a13a --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/rywc.md @@ -0,0 +1,20 @@ +--- +title: "Read-Your-Writes Consistency" +api-name: "Read-Your-Writes Consistency" +source: docs/gsg_db_rep/CXX/rywc.html +--- +## Read-Your-Writes Consistency + +In a distributed system, the changes made at the master are not always instantaneously available at every replica, although they eventually will be. In general, replicas not directly involved in contributing to the acknowledgement of a transaction commit will lag behind other replicas because they do not synchronize their commits with the master. + +For this reason, you might want to make use of the read-your-writes consistency feature. This feature allows you to ensure that a replica is at least current enough to have the changes made by a specific transaction. Because transactions are applied serially, by ensuring a replica has a specific commit applied to it, you know that all transaction commits occurring prior to the specified transaction have also been applied to the replica. + +You determine whether a transaction has been applied to a replica by generating a *commit token* at the master. You then transfer this commit token to the replica, where it is used to determine whether the replica is consistent enough relative to the master. + +For example, suppose the you have a web application where a replication group is implemented within a load balanced web server group. Each request to the web server consists of an update operation followed by read operations (say, from the same client), The read operations naturally expect to see the data from the updates executed by the same request. However, the read operations might have been routed to a replica that did not execute the update. + +In such a case, the update request would generate a commit token, which would be resubmitted by the browser, along with subsequent read requests. The read request could be directed at any one of the available web servers by a load balancer. The replica which services the read request would use that commit token to determine whether it can service the read operation. If the replica is current enough, it can immediately execute the transaction and satisfy the request. + +What action the replica takes if it is not consistent enough to service the read request is up to you as the application developer. You can do anything from blocking while you wait for the transaction to be applied locally, to rejecting the read request outright. + +For more information, see the `Read your writes consistency` section in the `Berkeley DB Replication` chapter of the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/cxx/simpleprogramlisting.md b/docs_src/guides/gsg_db_rep/cxx/simpleprogramlisting.md new file mode 100644 index 000000000..5735088e2 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/simpleprogramlisting.md @@ -0,0 +1,432 @@ +--- +title: "Program Listing" +api-name: "Program Listing" +source: docs/gsg_db_rep/CXX/simpleprogramlisting.html +--- +## Program Listing + + [Class: RepConfigInfo](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: excxx_repquote_gsg_simple](simpleprogramlisting.md#repmgr_cxx) + + [Function: usage()](simpleprogramlisting.md#usage_cxx) + + [Function: main()](simpleprogramlisting.md#main_cxx) + + [Method: SimpleTxn::init()](simpleprogramlisting.md#repmgr_init_cxx) + + [Method: SimpleTxn::doloop()](simpleprogramlisting.md#doloop_cxx) + + [Method: SimpleTxn::print_stocks()](simpleprogramlisting.md#printstocks_c) + +Our example program is a fairly simple transactional application. At this early stage of its development, the application contains no hint that it must be network-aware so the only command line argument that it takes is one that allows us to specify the environment home directory. (Eventually, we will specify things like host names and ports from the command line). + +Note that the application performs all writes under the protection of a transaction; however, multiple database operations are not performed per transaction. Consequently, we simplify things a bit by using autocommit for our database writes. + +Also, this application is single-threaded. It is possible to write a multi-threaded or multi-process application that performs replication. That said, the concepts described in this book are applicable to both single threaded and multi-threaded applications so nothing is gained by multi-threading this application other than distracting complexity. This manual does, however, identify where care must be taken when performing replication with a non-single threaded application. + +Finally, remember that transaction processing is not described in this manual. Rather, see the *Berkeley DB Getting Started with Transaction Processing* guide for details on that topic. + +### Class: RepConfigInfo + +Before we begin, we present a class that we will use to maintain useful information for us. Under normal circumstances, this class would not be necessary for a simple transactional example such as this. However, this code will grow into a replicated example that needs to track a lot more information for the application, and so we lay the groundwork for it here. + +The class that we create is called `RepConfigInfo` and its only purpose at this time is to track the location of our environment home directory. + +``` c +#include + +class RepConfigInfo { +public: + RepConfigInfo(); + virtual ~RepConfigInfo(); + +public: + char* home; +}; + +RepConfigInfo::RepConfigInfo() +{ + home = "TESTDIR"; +} + +RepConfigInfo::~RepConfigInfo() +{ +} +``` + +### Class: excxx_repquote_gsg_simple + +Our transactional example will instantiate a class, `SimpleTxn`, that performs all our work for us. Before we implement our `main()` function, we show the `SimpleTxn` class declaration. + +First, we provide some declarations and definitions that are needed later in our example: + +``` c +#include +#include +#include "RepConfig.h" + +using std::cout; +using std::cin; +using std::cerr; +using std::endl; +using std::flush; + +#define CACHESIZE (10 * 1024 * 1024) +#define DATABASE "quote.db" + +const char *progname = "excxx_reqquote_gsg_simple"; + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include + +extern "C" { + extern int getopt(int, char * const *, const char *); + extern char *optarg; +} +#else +#include +#endif +``` + +And then we define our `SimpleTxn` class: + +``` c +class SimpleTxn +{ +public: + // Constructor. + SimpleTxn(); + // Initialization method. Creates and opens our environment handle. + int init(RepConfigInfo* config); + // The doloop is where all the work is performed. + int doloop(); + // terminate() provides our shutdown code. + int terminate(); + +private: + // disable copy constructor. + SimpleTxn(const SimpleTxn &); + void operator = (const SimpleTxn &); + + // internal data members. + RepConfigInfo *app_config; + DbEnv dbenv; + + // private methods. + // print_stocks() is used to display the contents of our database. + static int print_stocks(Db *dbp); +}; +``` + +Note that we show the implementation of the various `SimpleTxn` methods later in this section. + +### Function: usage() + +Our `usage()` is at this stage of development trivial because we only have one command line argument to manage. Still, we show it here for the sake of completeness. + +``` c +static void usage() +{ + cerr << "usage: " << progname << endl + << "-h home" << endl; + + exit(EXIT_FAILURE); +} +``` + +### Function: main() + +Now we provide our `main()` function. This is a trivial function whose only job is to collect command line information, then instantiate a `SimpleTxn` object, run it, then terminate it. + +We begin by declaring some useful variables. Of these, note that we instantiate our `RepConfigInfo` object here. Recall that this is used to store information useful to our code. This class becomes more interesting later in this book. + +``` c +int main(int argc, char **argv) +{ + RepConfigInfo config; + char ch; + int ret; +``` + +Then we collect our command line information. Again, this is at this point fairly trivial: + +``` c + // Extract the command line parameters + while ((ch = getopt(argc, argv, "h:")) != EOF) { + switch (ch) { + case 'h': + config.home = optarg; + break; + case '?': + default: + usage(); + } + } + + // Error check command line. + if (config.home == NULL) + usage(); +``` + +Now we instantiate and initialize our `SimpleTxn` class, which is what is responsible for doing all our real work. The `SimpleTxn::init()` method creates and opens our environment handle. + +``` c + SimpleTxn runner; + try { + if((ret = runner.init(&config)) != 0) + goto err; +``` + +Then we call the `SimpleTxn::doloop()` method, which is where the actual transactional work is performed for this application. + +``` c + if((ret = runner.doloop()) != 0) + goto err; +``` + +Finally, catch exceptions and terminate the program: + +``` c + } catch (DbException dbe) { + cerr << "Caught an exception during initialization or" + << " processing: " << dbe.what() << endl; + } +err: + runner.terminate(); + return 0; +} +``` + +### Method: SimpleTxn::init() + +The `SimpleTxn::init()` method is used to create and open our environment handle. For readers familiar with writing transactional DB applications, there should be no surprises here. However, we will be adding to this in later chapters as we roll replication into this example. + +First, we show the class constructor implementation, which is only used to initialize a few variables: + +``` c +SimpleTxn::SimpleTxn() : app_config(0), dbenv(0) +{ +} +``` + +We now provide the `init()` method implementation. The only thing of interest here is that we specify `DB_TXN_NOSYNC` to our environment. This causes our transactional commits to become non-durable, which is something that we are doing only because of the nature of our example. + +``` c +int SimpleTxn::init(RepConfigInfo *config) +{ + int ret = 0; + + app_config = config; + + dbenv.set_errfile(stderr); + dbenv.set_errpfx(progname); + + /* + * We can now open our environment. + */ + dbenv.set_cachesize(0, CACHESIZE, 0); + dbenv.set_flags(DB_TXN_NOSYNC, 1); + + try { + dbenv.open(app_config->home, + DB_CREATE | + DB_RECOVER | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN, + 0); + } catch(DbException dbe) { + cerr << "Caught an exception during DB environment open." << endl + << "Ensure that the home directory is created prior to" + << " starting the application." << endl; + ret = ENOENT; + goto err; + } + +err: + return ret; +} +``` + +Finally, we present the `SimpleTxn::terminate()` method here. All this does is close the environment handle. Again, there should be no surprises here, but we provide the implementation for the sake of completeness anyway. + +``` c +int SimpleTxn::terminate() +{ + try { + dbenv.close(0); + } catch (DbException dbe) { + cerr << "error closing environment: " << dbe.what() << endl; + } + return 0; +} +``` + +### Method: SimpleTxn::doloop() + +Having written our `main()` function and support utility methods, we now implement our application's primary data processing method. This method provides a command prompt at which the user can enter a stock ticker value and a price for that value. This information is then entered to the database. + +To display the database, simply enter `return` at the prompt. + +To begin, we declare a database pointer, several `Dbt` variables, and the usual assortment of variables used for buffers and return codes. We also initialize all of this. + +``` c +#define BUFSIZE 1024 +int SimpleTxn::doloop() +{ + Db *dbp; + Dbt key, data; + char buf[BUFSIZE], *rbuf; + int ret; + + dbp = NULL; + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + ret = 0; +``` + +Next, we begin the loop and we immediately open our database if it has not already been opened. Notice that we specify autocommit when we open the database. In this case, autocommit is important because we will only ever write to our database using it. There is no need for explicit transaction handles and commit/abort code in this application, because we are not combining multiple database operations together under a single transaction. + +Autocommit is described in greater detail in the *Berkeley DB Getting Started with Transaction Processing* guide. + +``` c + for (;;) { + if (dbp == NULL) { + dbp = new Db(&dbenv, 0); + + try { + dbp->open(NULL, DATABASE, NULL, DB_BTREE, + DB_CREATE | DB_AUTO_COMMIT, 0); + } catch(DbException dbe) { + dbenv.err(ret, "DB->open"); + throw dbe; + } + } +``` + +Now we implement our command prompt. This is a simple and not very robust implementation of a command prompt. If the user enters the keywords `exit` or `quit`, the loop is exited and the application ends. If the user enters nothing and instead simply presses `return`, the entire contents of the database is displayed. We use our `print_stocks()` method to display the database. (That implementation is shown next in this chapter.) + +Notice that very little error checking is performed on the data entered at this prompt. If the user fails to enter at least one space in the value string, a simple help message is printed and the prompt is returned to the user. That is the only error checking performed here. In a real-world application, at a minimum the application would probably check to ensure that the price was in fact an integer or float value. However, in order to keep this example code as simple as possible, we refrain from implementing a thorough user interface. + +``` c + cout << "QUOTESERVER" ; + cout << "> " << flush; + + if (fgets(buf, sizeof(buf), stdin) == NULL) + break; + if (strtok(&buf[0], " \t\n") == NULL) { + switch ((ret = print_stocks(dbp))) { + case 0: + continue; + default: + dbp->err(ret, "Error traversing data"); + goto err; + } + } + rbuf = strtok(NULL, " \t\n"); + if (rbuf == NULL || rbuf[0] == '\0') { + if (strncmp(buf, "exit", 4) == 0 || + strncmp(buf, "quit", 4) == 0) + break; + dbenv.errx("Format: TICKER VALUE"); + continue; + } +``` + +Now we assign data to the `Dbt`s that we will use to write the new information to the database. + +``` c + key.set_data(buf); + key.set_size((u_int32_t)strlen(buf)); + + data.set_data(rbuf); + data.set_size((u_int32_t)strlen(rbuf)); +``` + +Having done that, we can write the new information to the database. Remember that this application uses autocommit, so no explicit transaction management is required. Also, the database is not configured for duplicate records, so the data portion of a record is overwritten if the provided key already exists in the database. However, in this case DB returns `DB_KEYEXIST` — which we ignore. + +``` c + if ((ret = dbp->put(NULL, &key, &data, 0)) != 0) + { + dbp->err(ret, "DB->put"); + if (ret != DB_KEYEXIST) + goto err; + } + } +``` + +Finally, we close our database before returning from the method. + +``` c +err: if (dbp != NULL) { + (void)dbp->close(DB_NOSYNC); + cout << "database closed" << endl; + } + + return (ret); +} +``` + +### Method: SimpleTxn::print_stocks() + +The `print_stocks()` method simply takes a database handle, opens a cursor, and uses it to display all the information it finds in a database. This is trivial cursor operation that should hold no surprises for you. We simply provide it here for the sake of completeness. + +If you are unfamiliar with basic cursor operations, please see the *Getting Started with Berkeley DB* guide. + +``` c +int SimpleTxn::print_stocks(Db *dbp) +{ + Dbc *dbc; + Dbt key, data; +#define MAXKEYSIZE 10 +#define MAXDATASIZE 20 + char keybuf[MAXKEYSIZE + 1], databuf[MAXDATASIZE + 1]; + int ret, t_ret; + u_int32_t keysize, datasize; + + if ((ret = dbp->cursor(NULL, &dbc, 0)) != 0) { + dbp->err(ret, "can't open cursor"); + return (ret); + } + + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + + cout << "\tSymbol\tPrice" << endl + << "\t======\t=====" << endl; + + for (ret = dbc->get(&key, &data, DB_FIRST); + ret == 0; + ret = dbc->get(&key, &data, DB_NEXT)) { + keysize = key.get_size() > MAXKEYSIZE ? MAXKEYSIZE : + key.get_size(); + memcpy(keybuf, key.get_data(), keysize); + keybuf[keysize] = '\0'; + + datasize = data.get_size() >= + MAXDATASIZE ? MAXDATASIZE : data.get_size(); + memcpy(databuf, data.get_data(), datasize); + databuf[datasize] = '\0'; + + cout << "\t" << keybuf << "\t" << databuf << endl; + } + cout << endl << flush; + + if ((t_ret = dbc->close()) != 0 && ret == 0) { + cout << "closed cursor" << endl; + ret = t_ret; + } + + switch (ret) { + case 0: + case DB_NOTFOUND: + return (0); + default: + return (ret); + } +} +``` diff --git a/docs_src/guides/gsg_db_rep/cxx/txnapp.md b/docs_src/guides/gsg_db_rep/cxx/txnapp.md new file mode 100644 index 000000000..c307a4cc2 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/cxx/txnapp.md @@ -0,0 +1,62 @@ +--- +title: "Chapter 2. Transactional Application" +api-name: "Chapter 2. Transactional Application" +source: docs/gsg_db_rep/CXX/txnapp.html +--- +## Chapter 2. Transactional Application + +**Table of Contents** + + [Application Overview](txnapp.md#appoverview) + + [Program Listing](simpleprogramlisting.md) + + [Class: RepConfigInfo](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: excxx_repquote_gsg_simple](simpleprogramlisting.md#repmgr_cxx) + + [Function: usage()](simpleprogramlisting.md#usage_cxx) + + [Function: main()](simpleprogramlisting.md#main_cxx) + + [Method: SimpleTxn::init()](simpleprogramlisting.md#repmgr_init_cxx) + + [Method: SimpleTxn::doloop()](simpleprogramlisting.md#doloop_cxx) + + [Method: SimpleTxn::print_stocks()](simpleprogramlisting.md#printstocks_c) + +In this chapter, we build a simple transaction-protected DB application. Throughout the remainder of this book, we will add replication to this example. We do this to underscore the concepts that we are presenting in this book; the first being that you should start with a working transactional program and then add replication to it. + +Note that this book assumes you already know how to write a transaction-protected DB application, so we will not be covering those concepts in this book. To learn how to write a transaction-protected application, see the *Berkeley DB Getting Started with Transaction Processing* guide. + +## Application Overview + +Our application maintains a stock market quotes database. This database contains records whose key is the stock market symbol and whose data is the stock's price. + +The application operates by presenting you with a command line prompt. You then enter the stock symbol and its value, separated by a space. The application takes this information and writes it to the database. + +To see the contents of the database, simply press `return` at the command prompt. + +To quit the application, type 'quit' or 'exit' at the command prompt. + +For example, the following illustrates the application's usage. In it, we use entirely fictitious stock market symbols and price values. + +``` c +> ./excxx_repquote_gsg_simple -h env_home_dir +QUOTESERVER> stock1 88 +QUOTESERVER> stock2 .08 +QUOTESERVER> + Symbol Price + ====== ===== + stock1 88 + +QUOTESERVER> stock1 88.9 +QUOTESERVER> + Symbol Price + ====== ===== + stock1 88.9 + stock2 .08 + +QUOTESERVER> quit +> +``` diff --git a/docs_src/guides/gsg_db_rep/index.md b/docs_src/guides/gsg_db_rep/index.md index 39d39329d..b71d83020 100644 --- a/docs_src/guides/gsg_db_rep/index.md +++ b/docs_src/guides/gsg_db_rep/index.md @@ -5,6 +5,8 @@ source: docs/gsg_db_rep/C/index.html --- # Getting Started with Replicated Berkeley DB Applications +**Language:** C (this page) · [C++](cxx/index.md) · [Java](java/index.md) + **Legal Notice** This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html diff --git a/docs_src/guides/gsg_db_rep/java/_meta.toml b/docs_src/guides/gsg_db_rep/java/_meta.toml new file mode 100644 index 000000000..c842745c9 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/_meta.toml @@ -0,0 +1,31 @@ +# Nav/index metadata for the gsg_db_rep Java variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Berkeley DB Getting Started with Replicated Applications (Java)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "repadvantage", + "apioverview", + "elections", + "permmessages", + "txnapp", + "simpleprogramlisting", + "repapp", + "repmgr_init_example_c", + "fwrkpermmessage", + "electiontimes", + "fmwrkconnectretry", + "heartbeats", + "fwrkmasterreplica", + "processingloop", + "exampledoloop", + "addfeatures", + "manageblock", + "autoinit", + "rywc", + "c2ctransfer", + "bulk", +] diff --git a/docs_src/guides/gsg_db_rep/java/addfeatures.md b/docs_src/guides/gsg_db_rep/java/addfeatures.md new file mode 100644 index 000000000..f578d08b4 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/addfeatures.md @@ -0,0 +1,50 @@ +--- +title: "Chapter 5. Additional Features" +api-name: "Chapter 5. Additional Features" +source: docs/gsg_db_rep/JAVA/addfeatures.html +--- +## Chapter 5. Additional Features + +**Table of Contents** + + [Delayed Synchronization](addfeatures.md#delayedsync) + + [Managing Blocking Operations](manageblock.md) + + [Stop Auto-Initialization](autoinit.md) + + [Read-Your-Writes Consistency](rywc.md) + + [Client to Client Transfer](c2ctransfer.md) + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + + [Bulk Transfers](bulk.md) + +Beyond the basic functionality that we have discussed so far in this book, there are several replication features that you should understand. These are all optional to use, but provide useful functionality under the right circumstances. + +These additional features are: + +1. Delayed Synchronization + +2. Managing Blocking Operations + +3. Stop Auto-Initialization + +4. Client to Client Transfer + +5. Bulk Transfers + +## Delayed Synchronization + +When a replication group has a new master, all replicas must synchronize with that master. This means they must ensure that the contents of their local database(s) are identical to that contained by the new master. + +This synchronization process can result in quite a lot of network activity. It can also put a large strain on the master server, especially if is part of a large replication group or if there is somehow a large difference between the master's database(s) and the contents of its replicas. + +It is therefore possible to delay synchronization for any replica that discovers it has a new master. You would do this so as to give the master time to synchronize other replicas before proceeding with the delayed replicas. + +To delay synchronization of a replica environment, you specify `ReplicationConfig.DELAYCLIENT` and `true` to `Environment.setReplicationConfig()`. To turn off delayed synchronization, specify `false` for the `ReplicationConfig.DELAYCLIENT` field. + +If you use delayed synchronization, then you must manually synchronize the replica at some future time. Until you do this, the replica is out of sync with the master, and it will ignore all database changes forwarded to it from the master. + +You synchronize a delayed replica by calling `Environment.syncReplication()` on the replica that has been delayed. diff --git a/docs_src/guides/gsg_db_rep/java/apioverview.md b/docs_src/guides/gsg_db_rep/java/apioverview.md new file mode 100644 index 000000000..d1a313e4b --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/apioverview.md @@ -0,0 +1,46 @@ +--- +title: "The Replication APIs" +api-name: "The Replication APIs" +source: docs/gsg_db_rep/JAVA/apioverview.html +--- +## The Replication APIs + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + +There are two ways that you can choose to implement replication in your transactional application. The first, and preferred, mechanism is to use the pre-packaged Replication Manager that comes with the DB distribution. This framework should be sufficient for most customers. + +If for some reason the Replication Manager does not meet your application's technical requirements, you will have to use the Replication Base APIs available through the Berkeley DB library to write your own custom replication framework. + +Both of these approaches are described in slightly greater detail in this section. The bulk of the chapters later in this book are dedicated to these two replication implementation mechanisms. + +### Replication Manager Overview + +DB's pre-packaged Replication Manager exists as a layer on top of the DB library. The Replication Manager is a multi-threaded implementation that allows you to easily add replication to your existing transactional application. You access and manage the Replication Manager using special methods and classes designated for its use. Mostly these are centered around the `Environment` and `EnvironmentConfig` classes. + +The Replication Manager: + +- Provides a multi-threaded communications layer using pthreads (on Unix-style systems and similar derivatives such as Mac OS X), or Windows threads on Microsoft Windows systems. + +- Uses TCP/IP sockets. Network traffic is handled via threads that handle inbound and outbound messages. However, each process uses a single socket that is shared using `select()`. + + Note that for this reason, the Replication Manager is limited to a maximum of 60 replicas (on Windows) and approximately 1000 replicas (on Unix and related systems), depending on how your system is configured. + +- Requires that only one instance of the environment handle be used. + +- Upon application startup, a master can be selected either manually or via elections. After startup time, however, during the course of normal operations it is possible for the replication group to need to locate a new master (due to network or other hardware related problems, for example) and in this scenario elections are always used to select the new master. + +If your application has technical requirements that do not conform to the implementation provided by the Replication Manager, you must write implement replication using the DB Replication Base APIs. See the next section for introductory details. + +### Replication Base API Overview + +The Replication Base API is a series of Berkeley DB library classes and methods that you can use to build your own replication infrastructure. You should use the Base API only if the Replication Manager does not meet your application's technical requirements. + +To make use of the Base API, you must write your own networking code. This frees you from the technical constraints imposed by the Replication Manager. For example, by writing your own framework, you can: + +- Use a threading package other than pthreads (Unix) or Windows threads (Microsoft Windows). This might be interesting to you if you are using a platform whose preferred threading package is something other than (for example) pthreads, such as is the case for Sun Microsystem's Solaris operating systems. + +- Implement your own sockets. The Replication Manager uses TCP/IP sockets. While this should be acceptable for the majority of applications, sometimes UDP or even raw sockets might be desired. + +For information on writing a replicated application using the Berkeley DB Replication Base APIs, see the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/java/autoinit.md b/docs_src/guides/gsg_db_rep/java/autoinit.md new file mode 100644 index 000000000..b58b81ef5 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/autoinit.md @@ -0,0 +1,12 @@ +--- +title: "Stop Auto-Initialization" +api-name: "Stop Auto-Initialization" +source: docs/gsg_db_rep/JAVA/autoinit.html +--- +## Stop Auto-Initialization + +As stated in the previous section, when a replication replica is synchronizing with its master, it will block DB operations at some points during this process until the synchronization is completed. You can turn off this behavior (see Managing Blocking Operations), but for replicas that have been out of touch from their master for a very long time, this may not be enough. + +If a replica has been out of touch from its master long enough, it may find that it is not possible to perform synchronization. When this happens, by default the master and replica internally decide to completely re-initialize the replica. This re-initialization involves discarding the replica's current database(s) and transferring new ones to it from the master. Depending on the size of the master's databases, this can take a long time, during which time the replica will be completely non-responsive when it comes to performing database operations. + +It is possible that there is a time of the day when it is better to perform a replica re-initialization. Or, you simply might want to decide to bring the replica up to speed by restoring its databases using a hot-backup taken from the master. Either way, you can decide to prevent automatic-initialization of your replica. To do this specify `ReplicationConfig.AUTOINIT` and `false` to `Environment.setReplicationConfig()`. diff --git a/docs_src/guides/gsg_db_rep/java/bulk.md b/docs_src/guides/gsg_db_rep/java/bulk.md new file mode 100644 index 000000000..deb8f8082 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/bulk.md @@ -0,0 +1,32 @@ +--- +title: "Bulk Transfers" +api-name: "Bulk Transfers" +source: docs/gsg_db_rep/JAVA/bulk.html +--- +## Bulk Transfers + +By default, messages are sent from the master to replicas as they are generated. This can degrade replication performance because the various participating environments must handle a fair amount of network I/O activity. + +You can alleviate this problem by configuring your master environment for bulk transfers. Bulk transfers simply cause replication messages to accumulate in a buffer until a triggering event occurs. When this event occurs, the entire contents of the buffer is sent to the replica, thereby eliminating excessive network I/O. + +Note that if you are using replica to replica transfers, then you might want any replica that can service replication requests to also be configured for bulk transfers. + +The events that result in a bulk transfer of replication messages to a replica will differ depending on if the transmitting environment is a master or a replica. + +If the servicing environment is a master environment, then bulk transfer occurs when: + +1. Bulk transfers are configured for the master environment, and + +2. the message buffer is full or + +3. a permanent record (for example, a transaction commit or a checkpoint record) is placed in the buffer for the replica. + +If the servicing environment is a replica environment (that is, replica to replica transfers are in use), then a bulk transfer occurs when: + +1. Bulk transfers are configured for the transmitting replica, and + +2. the message buffer is full or + +3. the replica servicing the request is able to completely satisfy the request with the contents of the message buffer. + +To configure bulk transfers, specify `ReplicationConfig.BULK` and `true` to `Environment.setReplicationConfig()`. To turn off this feature, specify `false` for the `ReplicationConfig.BULK` field. diff --git a/docs_src/guides/gsg_db_rep/java/c2ctransfer.md b/docs_src/guides/gsg_db_rep/java/c2ctransfer.md new file mode 100644 index 000000000..e4e8fe163 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/c2ctransfer.md @@ -0,0 +1,26 @@ +--- +title: "Client to Client Transfer" +api-name: "Client to Client Transfer" +source: docs/gsg_db_rep/JAVA/c2ctransfer.html +--- +## Client to Client Transfer + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + +It is possible to use a replica instead of a master to synchronize another replica. This serves to take the request load off a master that might otherwise occur if multiple replicas attempted to synchronize with the master at the same time. + +For best results, use this feature combined with the delayed synchronization feature (see Delayed Synchronization). + +For example, suppose your replication group consists of four environments. Upon application startup, all three replicas will immediately attempt to synchronize with the master. But at the same time, the master itself might be busy with a heavy database write load. + +To solve this problem, delay synchronization for two of the three replicas. Allow the third replica to synchronize as normal with the master. Then, start synchronization for each of the delayed replicas (since this is a manual process, you can do them one at a time if that best suits your application). Assuming you have configured replica to replica synchronization correctly, the delayed replicas will synchronize using the up-to-date replica, rather than using the master. + +When you are using the Replication Manager, you configure replica to replica synchronization by declaring an environment to be a peer of another environment. If an environment is a peer, then it can be used for synchronization purposes. + +### Identifying Peers + +You can designate one replica to be a peer of another for replica to replica synchronization. You might want to do this if you have machines that you know are on fast, reliable network connections and so you are willing to accept the overhead of waiting for acknowledgments from those specific machines. + +Note that peers are not required to be a bi-directional. That is, just because machine A declares machine B to be a peer, that does not mean machine B must also declare machine A to be a peer. + +You declare a peer for the current environment when you add that environment to the list of known sites. You do this by constructing a `ReplicationHostAddress` object that specifies `true` for the `isPeer` parameter, and then providing that object to `EnvironmentConfig.replicationManagerAddRemoteSite()` when you add the remote site to the local replication site. diff --git a/docs_src/guides/gsg_db_rep/java/elections.md b/docs_src/guides/gsg_db_rep/java/elections.md new file mode 100644 index 000000000..5364ab71c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/elections.md @@ -0,0 +1,56 @@ +--- +title: "Holding Elections" +api-name: "Holding Elections" +source: docs/gsg_db_rep/JAVA/elections.html +--- +## Holding Elections + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + +Finding a master environment is one of the fundamental activities that every replication replica must perform. Upon startup, the underlying DB replication code will attempt to locate a master. If a master cannot be found, then the environment should initiate an election. + +### Note + +In some rare situations, it is desireable for the application to manually select its master. For these cases, elections can be turned off. + +Manually selecting a master is an activity that should be performed infrequently, if ever. You turn elections off by using the `ReplicationConfig` and `ReplicationManagerStartPolicy` classes. + +How elections are held depends upon the API that you use to implement replication. For example, if you are using the Replication Manager elections are held transparently without any input from your application's code. In this case, DB will determine which environment is the master and which are replicas. + +### Influencing Elections + +If you want to control the election process, you can declare a specific environment to be the master. Note that for the Replication Manager, it is only possible to do this at application startup. Should the master become unavailable during run-time for any reason, an election is held. The environment that receives the most number of votes, wins the election and becomes the master. A machine receives a vote because it has the most up-to-date log records. + +Because ties are possible when elections are held, it is possible to influence which environment will win the election. How you do this depends on which API you are using. In particular, if you are writing a custom replication layer, then there are a great many ways to manually influence elections. + +One such mechanism is priorities. When votes are cast during an election, the winner is determined first by the environment with the most up-to-date log records. But if this is a tie, the the environment's priority is considered. So given two environments with log records that are equally recent, votes are cast for the environment with the higher priority. + +Therefore, if you have a machine that you prefer to become a master in the event of an election, assign it a high priority. Assuming that the election is held at a time when the preferred machine has up-to-date log records, that machine will win the election. + +### Winning Elections + +To win an election: + +1. There cannot currently be a master environment. + +2. The environment must have the most recent log records. Part of holding the election is determining which environments have the most recent log records. This process happens automatically; your code does not need to involve itself in this process. + +3. The environment must receive the most number of votes from the replication environments that are participating in the election. + +If you are using the Replication Manager, then in the event of a tie vote the environment with the highest priority wins the election. If two or more environments receive the same number of votes and have the same priority, then the underlying replication code picks one of the environments to be the winner. Which winner will be picked by the replication code is unpredictable from the perspective of your application code. + +### Switching Masters + +To switch masters: + +1. Start up the environment that you want to be master as normal. At this time it is a replica. Make sure this environment has a higher priority than all the other environments. + +2. Allow the new environment to run for a time as a replica. This allows it to obtain the most recent copies of the log files. + +3. Shut down the current master. This should force an election. Because the new environment has the highest priority, it will win the election, provided it has had enough time to obtain all the log records. + +4. Optionally restart the old master environment. Because there is currently a master environment, an election will not be held and the old master will now run as a replica environment. diff --git a/docs_src/guides/gsg_db_rep/java/electiontimes.md b/docs_src/guides/gsg_db_rep/java/electiontimes.md new file mode 100644 index 000000000..2c78abf42 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/electiontimes.md @@ -0,0 +1,34 @@ +--- +title: "Managing Election Times" +api-name: "Managing Election Times" +source: docs/gsg_db_rep/JAVA/electiontimes.html +--- +## Managing Election Times + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + +Where it comes to elections, there are two timeout values with which you should be concerned: election timeouts and election retries. + +### Managing Election Timeouts + +When an environment calls for an election, it will wait some amount of time for the other replicas in the replication group to respond. The amount of time that the environment will wait before declaring the election completed is the *election timeout*. + +If the environment hears from all other known replicas before the election timeout occurs, the election is considered a success and a master is elected. + +If only a subset of replicas respond, then the success or failure of the election is determined by how many replicas have participated in the election. It only takes a simple majority of replicas to elect a master. If there are enough votes for a given environment to meet that standard, then the master has been elected and the election is considered a success. + +However, if not enough replicas have participated in the election when the election timeout value is reached, the election is considered a failure and a master is not elected. At this point, your replication group is operating without a master, which means that, essentially, your replicated application has been placed in read-only mode. + +Note, however, that the Replication Manager will attempt a new election after a given amount of time has passed. See the next section for details. + +You set the election timeout value using `Environment.setReplicationTimeout()`. You pass this method the `ReplicationTimeoutType.ELECTION_TIMEOUT` constant and a timeout value in microseconds. + +### Managing Election Retry Times + +In the event that a election fails (see the previous section), an election will not be attempted again until the election retry timeout value has expired. + +You set the election timeout value using `Environment.setReplicationTimeout()`. You pass this method the `ReplicationTimeoutType.ELECTION_RETRY` constant and a retry value in microseconds. + +Note that this constant is only valid when you are using the Replication Manager. If you are using the Base APIs, then this constant is ignored. diff --git a/docs_src/guides/gsg_db_rep/java/exampledoloop.md b/docs_src/guides/gsg_db_rep/java/exampledoloop.md new file mode 100644 index 000000000..70b00fc37 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/exampledoloop.md @@ -0,0 +1,481 @@ +--- +title: "Example Processing Loop" +api-name: "Example Processing Loop" +source: docs/gsg_db_rep/JAVA/exampledoloop.html +--- +## Example Processing Loop + + [Running It](exampledoloop.md#runningit) + +In this section we take the example processing loop that we presented in the previous section and we flesh it out to provide a more complete example. We do this by updating the `doloop()` function that our original transaction application used (see Method: SimpleTxn.doloop()) to fully support our replicated application. + +In the following example code, code that we add to the original example is presented in **`bold`**. + +To begin, we must implement a way to track whether our application is running as a master or a client. There are many ways to do this, but in this case what we will do is extend `com.sleepycat.db.Environment` to carry the information. We do this by creating the `RepQuoteEnvironment` class. + +``` c +package db.repquote; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +public class RepQuoteEnvironment extends Environment +{ + private boolean isMaster; + + public RepQuoteEnvironment(final java.io.File host, + EnvironmentConfig config) + throws DatabaseException, java.io.FileNotFoundException + { + super(host, config); + isMaster = false; + } + + boolean getIsMaster() + { + return isMaster; + } + + public void setIsMaster(boolean isMaster) + { + this.isMaster = isMaster; + } +} +``` + +Next, we go to `RepQuoteExampleGSG.java` and we include the `RepQuoteEnvironment` class as well as the `EventHandler` class. We then cause our `RepQuoteExampleGSG` class to implement `EventHandler`. We also change our environment handle to be an instance of `RepQuoteEnvironment` instead of `Environment`. + +Note that we also import the `com.sleepycat.db.ReplicationHandleDeadException` class. We will discuss what that exception is used for a little later in this example. + +``` c +package db.repquote; + +import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.Thread; +import java.lang.InterruptedException; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.EventHandler; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.ReplicationHandleDeadException; +import com.sleepycat.db.ReplicationHostAddress; +import com.sleepycat.db.ReplicationManagerSiteConfig; + +import db.repquote.RepConfig; +import db.repquote.RepQuoteEnvironment + +public class RepQuoteExampleGSG implements EventHandler +{ + private RepConfig repConfig; + private RepQuoteEnvironment dbenv; +``` + +That done, we can skip the `main()` method and our class constructor, because they do not change. Instead, we skip down to our `init()` method where we take care of opening our environment and setting the event handler. + +To update our `init()` method, we only need to do a couple of things. First, we identify the current class as the event handler. Then, when we open our environment, we instantiate a `RepQuoteEnvironment` class instead of an `Environment` class. + +``` c + public int init(RepConfig config) + throws DatabaseException + { + int ret = 0; + repConfig = config; + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setErrorStream(System.err); + envConfig.setErrorPrefix(RepConfig.progname); + + envConfig.addReplicationManagerSite(repConfig.getThisHost()); + for (ReplicationHostAddress host = + repConfig.getFirstOtherHost(); host != null; + host = repConfig.getNextOtherHost()){ + + ReplicationManagerSiteConfig repmgrRemoteSiteConfig = + new ReplicationManagerSiteConfig(host.host, host.port); + repmgrRemoteSiteConfig.setBootstrapHelper(true); + envConfig.addReplicationManagerSite( + repmgrRemoteSiteConfig); + } + + envConfig.setReplicationPriority(repConfig.priority); + + envConfig.setReplicationManagerAckPolicy( + ReplicationManagerAckPolicy.ALL); + envConfig.setCacheSize(RepConfig.CACHESIZE); + envConfig.setTxnNoSync(true); + + envConfig.setEventHandler(this); + + envConfig.setAllowCreate(true); + envConfig.setRunRecovery(true); + envConfig.setThreaded(true); + envConfig.setInitializeReplication(true); + envConfig.setInitializeLocking(true); + envConfig.setInitializeLogging(true); + envConfig.setInitializeCache(true); + envConfig.setTransactional(true); + envConfig.setVerboseReplication(appConfig.verbose); + try { + dbenv = new RepQuoteEnvironment(repConfig.getHome(), + envConfig); + } catch(FileNotFoundException e) { + System.err.println("FileNotFound exception: " + e.toString()); + System.err.println( + "Ensure that the environment directory is pre-created."); + ret = 1; + } + + // start Replication Manager + dbenv.replicationManagerStart(3, repConfig.startPolicy); + return ret; + } +``` + +That done, we need to implement the methods required for responding to replication events. These methods are required because we are now implementing `com.sleepycat.db.EventHandler`. While we are required to provide an implementation for all of these methods, for our simple application we are really only interested in these because they allow us to track whether a replication instance is a master. + +``` c + public void handleRepClientEvent() + { + dbenv.setIsMaster(false); + } + + public void handleRepConnectBrokenEvent() + { + // Ignored for now. + } + + public void handleRepConnectEstablishedEvent() + { + // Ignored for now. + } + + public void handleRepConnectTryFailedEvent() + { + // Ignored for now. + } + + public void handleRepMasterEvent() + { + dbenv.setIsMaster(true); + } + + public void handleRepNewMasterEvent(int envId) + { + // Ignored for now. + } + + public void handleWriteFailedEvent(int errorCode) + { + System.err.println("Write to stable storage failed!" + + "Operating system error code:" + errorCode); + System.err.println("Continuing...."); + } + + public void handleRepStartupDoneEvent() + { + // Ignored for now. + } + + public void handleRepPermFailedEvent() + { + // Ignored for now. + } + + public void handleRepLocalSiteRemovedEvent() + { + // Ignored for now. + } + + public void handleRepSiteAddedEvent() + { + // Ignored for now. + } + + public void handleRepSiteRemovedEvent() + { + // Ignored for now. + } + + public void handleRepElectedEvent() + { + // Safely ignored for Replication Manager applications. + } + + public void handleRepElectionFailedEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handleRepJoinFailureEvent() + { + // Safely ignored since this application did not turn off AUTOINIT. + } + + public void handleRepMasterFailureEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handleRepDupmasterEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handlePanicEvent() + { + System.err.println("Panic encountered!"); + System.err.println("Shutting down."); + System.err.println("You should restart, running recovery."); + try { + terminate(); + } catch (DatabaseException dbe) { + System.err.println("Caught an exception during " + + "termination in handlePanicEvent: " + dbe.toString()); + } + System.exit(-1); + } +``` + +That done, we need to update our `doloop()` method. + +We begin by updating our `DatabaseConfig` instance to determine which options to use, depending on whether the application is running as a master. + +``` c + public int doloop() + throws DatabaseException + { + Database db = null; + + for (;;) + { + if (db == null) { + DatabaseConfig dbconf = new DatabaseConfig(); + dbconf.setType(DatabaseType.BTREE); + if (dbenv.getIsMaster()) { + dbconf.setAllowCreate(true); + } + dbconf.setTransactional(true); +``` + +When we open the database, we modify our error handling to account for the case where the database does not yet exist. This can happen if our code is running as a replica and the Replication Manager has not yet had a chance to create the databases for us. Recall that replicas never write to their own databases directly, and so they cannot create databases on their own. + +If we detect that the database does not yet exist, we simply close the database handle, sleep for a short period of time and then continue processing. This gives the Replication Manager a chance to create the database so that our replica can continue operations. + +``` c + try { + db = dbenv.openDatabase + (null, RepConfig.progname, null, dbconf); + } catch (java.io.FileNotFoundException e) { + System.err.println("no stock database available yet."); + if (db != null) { + db.close(true); + db = null; + } + try { + Thread.sleep(RepConfig.SLEEPTIME); + } catch (InterruptedException ie) {} + continue; + } + } +``` + +Next we modify our prompt, so that if the local process is running as a replica, we can tell from the shell that the prompt is for a read-only process. + +``` c + BufferedReader stdin = + new BufferedReader(new InputStreamReader(System.in)); + + // listen for input, and add it to the database. + System.out.print("QUOTESERVER"); + if (!dbenv.getIsMaster()) + System.out.print("(read-only)"); + System.out.print("> "); + System.out.flush(); + String nextline = null; + try { + nextline = stdin.readLine(); + } catch (IOException ioe) { + System.err.println("Unable to get data from stdin"); + break; + } + String[] words = nextline.split("\\s"); +``` + +When we collect data from the prompt, there is a case that says if no data is entered then show the entire stocks database. This display is performed by our `print_stocks()` method (which has not required a modification since we first introduced it in Method: SimpleTxn.printStocks() ). + +When we call `printStocks()`, we check for a dead replication handle. Dead replication handles happen whenever a replication election results in a previously committed transaction becoming invalid. This is an error scenario caused by a new master having a slightly older version of the data than the original master and so all replicas must modify their database(s) to reflect that of the new master. In this situation, some number of previously committed transactions may have to be unrolled. From the replica's perspective, the database handles should all be closed and then opened again. + +``` c + // A blank line causes the DB to be dumped to stdout. + if (words.length == 0 || + (words.length == 1 && words[0].length() == 0)) { + try { + printStocks(db); + } catch (DeadlockException de) { + continue; + // Dead replication handles are caused by an election + // resulting in a previously committing read becoming + // invalid. Close the db handle and reopen. + } catch (ReplicationHandleDeadException rhde) { + db.close(true); // close no sync. + db = null; + continue; + } catch (DatabaseException e) { + System.err.println("Got db exception reading " + + "replication DB: " + e.toString()); + break; + } + continue; + } + + if (words.length == 1 && + (words[0].compareToIgnoreCase("quit") == 0 || + words[0].compareToIgnoreCase("exit") == 0)) { + break; + } else if (words.length != 2) { + System.err.println("Format: TICKER VALUE"); + continue; + } +``` + +That done, we need to add a little error checking to our command prompt to make sure the user is not attempting to modify the database at a replica. Remember, replicas must never modify their local databases on their own. This guards against that happening due to user input at the prompt. + +``` c + if (!dbenv.getIsMaster()) { + System.err.println("Can't update client."); + continue; + } + + DatabaseEntry key = new DatabaseEntry(words[0].getBytes()); + DatabaseEntry data = new DatabaseEntry(words[1].getBytes()); + + db.put(null, key, data); + } + if (db != null) + db.close(true); + return 0; + } +``` + +With that completed, we are all done updating our application for replication. The only remaining method, `printStocks()`, is unmodified from when we originally introduced it. For details on that function, see Method: SimpleTxn.printStocks() . + +### Running It + +To run our replicated application, we need to make sure each participating environment has its own unique home directory. We can do this by running each site on a separate networked machine, but that is not strictly necessary; multiple instances of this code can run on the same machine provided the environment home restriction is observed. + +To run a process, make sure the environment home exists and then start the process using the `-h` option to specify that directory. You must also use the `-l` or `-L` option to identify the local host and port that this process will use to listen for replication messages (-L means that this is a group creator), and the `-r` option to identify the other processes in the replication group. Finally, use the `-p` option to specify a priority. The process that you designate to have the highest priority will become the master. + +``` c +> mkdir env1 +> java db.repquote_gsg.RepQuoteExampleGSG -h env1 -L localhost:8080 \ +-p 10 +No stock database yet available. +No stock database yet available. +``` + +Now, start another process. This time, change the environment home to something else, use the `-l` flag to at least change the port number the process is listening on, and use the `-r` option to identify the host and port of the other replication process: + +``` c +> mkdir env2 +> java db.repquote_gsg.RepQuoteExampleGSG -h env2 -l localhost:8081 \ +-r localhost:8080 -p 20 +``` + +After a short pause, the second process should display the master prompt: + +``` c +QUOTESERVER > +``` + +And the first process should display the read-only prompt: + +``` c +QUOTESERVER (read-only)> +``` + +Now go to the master process and give it a couple of stocks and stock prices: + +``` c +QUOTESERVER> FAKECO 9.87 +QUOTESERVER> NOINC .23 +QUOTESERVER> +``` + +Then, go to the replica and hit **`return`** at the prompt to see the new values: + +``` c +QUOTESERVER (read-only)> + Symbol Price + ====== ===== + FAKECO 9.87 + NOINC .23 +QUOTESERVER (read-only)> +``` + +Doing the same at the master results in the same thing: + +``` c +QUOTESERVER> + Symbol Price + ====== ===== + FAKECO 9.87 + NOINC .23 +QUOTESERVER> +``` + +You can change a stock by simply entering the stock value and new price at the master's prompt: + +``` c +QUOTESERVER> FAKECO 10.01 +QUOTESERVER> +``` + +Then, go to either the master or the replica to see the updated database. On the master: + +``` c +QUOTESERVER> + Symbol Price + ====== ===== + FAKECO 10.01 + NOINC .23 +QUOTESERVER> +``` + +And on the replica: + +``` c +QUOTESERVER (read-only)> + Symbol Price + ====== ===== + FAKECO 10.01 + NOINC .23 +QUOTESERVER (read-only)> +``` + +Finally, to quit the applications, simply type `quit` at both prompts. On the replica: + +``` c +QUOTESERVER (read-only)> quit +> +``` + +And on the master as well: + +``` c +QUOTESERVER> quit +> +``` diff --git a/docs_src/guides/gsg_db_rep/java/fmwrkconnectretry.md b/docs_src/guides/gsg_db_rep/java/fmwrkconnectretry.md new file mode 100644 index 000000000..e35de13ca --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/fmwrkconnectretry.md @@ -0,0 +1,8 @@ +--- +title: "Managing Connection Retries" +api-name: "Managing Connection Retries" +source: docs/gsg_db_rep/JAVA/fmwrkconnectretry.html +--- +## Managing Connection Retries + +In the event that a communication failure occurs between two environments in a replication group, the Replication Manager will wait a set amount of time before attempting to re-establish the connection. You can configure this wait value using `Environment.setReplicationTimeout()`. You pass this method the `ReplicationTimeoutType.CONNECTION_RETRY` constant and a retry value in microseconds. diff --git a/docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md b/docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md new file mode 100644 index 000000000..9bda281b0 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md @@ -0,0 +1,254 @@ +--- +title: "Chapter 4. Replica versus Master Processes" +api-name: "Chapter 4. Replica versus Master Processes" +source: docs/gsg_db_rep/JAVA/fwrkmasterreplica.html +--- +## Chapter 4. Replica versus Master Processes + +**Table of Contents** + + [Determining State](fwrkmasterreplica.md#determinestate) + + [Processing Loop](processingloop.md) + + [Example Processing Loop](exampledoloop.md) + + [Running It](exampledoloop.md#runningit) + +Every environment participating in a replicated application must know whether it is a *master* or *replica*. The reason for this is because, simply, the master can modify the database while replicas cannot. As a result, not only will you open databases differently depended on whether the environment is running as a master, but the environment will frequently behave quite a bit differently depending on whether it thinks it is operating as the read/write interface for your database. + +Moreover, an environment must also be capable of gracefully switching between master and replica states. This means that the environment must be able to detect when it has switched states. + +Not surprisingly, a large part of your application's code will be tied up in knowing which state a given environment is in and then in the logic of how to behave depending on its state. + +This chapter shows you how to determine your environment's state, and it then shows you some sample code on how an application might behave depending on whether it is a master or a replica in a replicated application. + +## Determining State + +In order to determine whether your code is running as a master or a replica, you must write your application as an implementation of `com.sleepycat.db.EventHandler`. This class gives you a series of methods within which you can detect and respond to various events that occur in your DB code. Some, but not all, of these methods have to do with elections: + +Some of the more commonly handled events are described below. For a complete list of events, see the `com.sleepycat.db.EventHandler` javadoc page. + +- `EventHandler.handlePanicEvent()` + + An error has occured in the Berkeley DB library requiring your application to shut down and then run recovery. + +- `EventHandler.handleRepClientEvent()` + + The local environment is now a replica. + +- `EventHandler.handleRepConnectBrokenEvent()` + + A previously established connection between two sites in the replication group has been broken. + +- `EventHandler.handleRepConnectEstablishedEvent()` + + A connection has been established between two sites in the replication group. + +- `EventHandler.handleRepConnectTryFailedEvent()` + + An attempt was made to establish a connection to a known remote site, but the connection attempt failed. + +- `EventHandler.handleRepDupmasterEvent()` + + A duplicate master has been discovered in the replication group. + +- `EventHandler.handleRepElectedEvent()` + + The local site has just won an election and is now the master. Your code should now reconfigure itself to operation as a master site. + +- `EventHandler.handleRepElectionFailedEvent()` + + The local site's attempt to initiate or participate in a replication master election failed, due to the lack of timely message response from a sufficient number of remote sites. + +- `EventHandler.handleRepJoinFailureEvent()` + + The local site could not synchronize with the master because an internal initialization was required, but internal initialization has been turned off + +- `EventHandler.handleRepLocalSiteRemovedEvent()` + + The local site has been removed from the group. + +- `EventHandler.handleRepNewMasterEvent()` + + An election was held and a new environment was made a master. However, the current environment *is not* the master. This event exists so that you can cause your code to take some unique action in the event that the replication groups switches masters. + +- `EventHandler.handleRepMasterEvent()` + + The local environment is now a master. + +- `EventHandler.handleRepMasterFailureEvent()` + + The connection to the remote master replication site has failed. + +- `EventHandler.handleRepPermFailedEvent()` + + The Replication Manager did not receive enough acknowledgements to ensure the transaction's durability within the replicationg group. The Replication Manager has therefore flushed the transaction to the master's local disk for storage. + + How the Replication Manager knows whether the acknowledgements it has received is determined by the ack policy you have set for your applicaton. See Identifying Permanent Message Policies for more information. + +- `EventHandler.handleRepSiteAddedEvent()` + + A new site has joined the replication group. + +- `EventHandler.handleRepSiteRemovedEvent()` + + An existing site has been removed from the replication group. + +- `EventHandler.handleRepStartupDoneEvent()` + + The replica has completed startup synchronization and is now processing log records received from the master. + +- `EventHandler.handleWriteFailedEvent()` + + A Berkeley DB write to stable storage failed. + +Note that these events are raised whenever the state is established. That is, when the current environment becomes a replica, and that includes at application startup, the event is raised. Also, when an election is held and a replica is elected to be a master, then the event occurs. + +The `EventHandler` implementation is fairly simple. First you detect the event, and then you record the state change in some data member maintained in a location that is convenient to you. + +For example: + +``` c +package db.repquote; + +// We make our main class an EventHandler implementation +... +import com.sleepycat.db.EventHandler; +... + +public class MyReplicationClass implements EventHandler +{ + +... + +// Somewhere we provide a data member that is used to track +// whether we are a master server. This could be in our main +// class, or it could be part of a supporting class. +private boolean isMaster; + +... + +isMaster = false; + +... + +// In the code where we open our environment and start replication, +// we must identify the class that is the event handler. In this +// example, we are performing this from within the class that +// implements com.sleepycat.db.EventHandler so we identify +// "this" class as the event handler +envConfig.setEventHandler(this); +``` + +That done, we still need to implement the methods required for handling replication events. For a simple application like this one, these implementations can be trivial. + +``` c + public void handleRepClientEvent() + { + dbenv.setIsMaster(false); + } + + public void handleRepConnectBrokenEvent() + { + // Ignored for now. + } + + public void handleRepConnectEstablishedEvent() + { + // Ignored for now. + } + + public void handleRepConnectTryFailedEvent() + { + // Ignored for now. + } + + public void handleRepMasterEvent() + { + dbenv.setIsMaster(true); + } + + public void handleRepNewMasterEvent(int envId) + { + // Ignored for now + } + + public void handleWriteFailedEvent(int errorCode) + { + System.err.println("Write to stable storage failed!" + + "Operating system error code:" + errorCode); + System.err.println("Continuing...."); + } + + public void handleRepStartupDoneEvent() + { + System.out.println("Replication startup is completed."); + } + + public void handleRepPermFailedEvent() + { + System.out.println("This application failed to receive enough" + + "acks for a permanent message. The transaction is flushed" + + "to disk on this master host."); + } + + public void handleRepLocalSiteRemovedEvent() + { + // Ignored for now. + } + + public void handleRepSiteAddedEvent() + { + // Ignored for now. + } + + public void handleRepSiteRemovedEvent() + { + // Ignored for now. + } + + public void handleRepElectedEvent() + { + // Safely ignored for Replication Manager applications. + } + + public void handleRepElectionFailedEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handleRepJoinFailureEvent() + { + // Safely ignored since this application did not turn off AUTOINIT. + } + + public void handleRepMasterFailureEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handleRepDupmasterEvent() + { + // Safely ignored for Replication Manager applications that do + // not manage their own master selection. + } + + public void handlePanicEvent() + { + System.err.println("Panic encountered!"); + System.err.println("Shutting down."); + System.err.println("You should restart, running recovery."); + try { + terminate(); + } catch (DatabaseException dbe) { + System.err.println("Caught an exception during " + + "termination in handlePanicEvent: " + dbe.toString()); + } + System.exit(-1); + } +``` + +Of course, this only gives us the current state of the environment. We still need the code that determines what to do when the environment changes state and how to behave depending on the state (described in the next section). diff --git a/docs_src/guides/gsg_db_rep/java/fwrkpermmessage.md b/docs_src/guides/gsg_db_rep/java/fwrkpermmessage.md new file mode 100644 index 000000000..8fb22f29c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/fwrkpermmessage.md @@ -0,0 +1,115 @@ +--- +title: "Permanent Message Handling" +api-name: "Permanent Message Handling" +source: docs/gsg_db_rep/JAVA/fwrkpermmessage.html +--- +## Permanent Message Handling + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepQuoteExampleGSG](fwrkpermmessage.md#perm2fmwrkexample) + +As described in Permanent Message Handling, messages are marked permanent if they contain database modifications that should be committed at the replica. DB's replication code decides if it must flush its transaction logs to disk depending on whether it receives sufficient permanent message acknowledgments from the participating replicas. More importantly, the thread performing the transaction commit blocks until it either receives enough acknowledgments, or the acknowledgment timeout expires. + +The Replication Manager is fully capable of managing permanent messages for you if your application requires it (most do). Almost all of the details of this are handled by the Replication Manager for you. However, you do have to set some policies that tell the Replication Manager how to handle permanent messages. + +There are two things that you have to do: + +- Determine how many acknowledgments must be received by the master. + +- Identify the amount of time that replicas have to send their acknowledgments. + +### Identifying Permanent Message Policies + +You identify permanent message policies using the `ReplicationManagerAckPolicy` class which you pass to the environment using the `EnvironmentConfig.setReplicationManagerAckPolicy` method. Note that you can set permanent message policies at any time during the life of the application. + +The following permanent message policies are available when you use the Replication Manager: + +### Note + +The following list mentions *electable peer* several times. This is simply another environment that can be elected to be a master (that is, it has a priority greater than 0). Do not confuse this with the concept of a peer as used for client to client transfers. See Client to Client Transfer for more information on client to client transfers. + +- `ReplicationManagerAckPolicy.NONE` + + No permanent message acknowledgments are required. If this policy is selected, permanent message handling is essentially "turned off." That is, the master will never wait for replica acknowledgments. In this case, transaction log data is either flushed or not strictly depending on the type of commit that is being performed (synchronous or asynchronous). + +- `ReplicationManagerAckPolicy.ONE` + + At least one replica must acknowledge the permanent message within the timeout period. + +- `ReplicationManagerAckPolicy.ONE_PEER` + + At least one electable peer must acknowledge the permanent message within the timeout period. + +- `ReplicationManagerAckPolicy.ALL` + + All replicas must acknowledge the message within the timeout period. This policy should be selected only if your replication group has a small number of replicas, and those replicas are on extremely reliable networks and servers. + +- `ReplicationManagerAckPolicy.ALL_AVAILABLE` + + All currently connected replication clients must acknowledge the message. This policy will invoke the `DB_EVENT_REP_PERM_FAILED` event if fewer than a quorum of clients acknowledged during that time. + +- `ReplicationManagerAckPolicy.ALL_PEERS` + + All electable peers must acknowledge the message within the timeout period. This policy should be selected only if your replication group is small, and its various environments are on extremely reliable networks and servers. + +- `ReplicationManagerAckPolicy.QUORUM` + + A quorum of electable peers must acknowledge the message within the timeout period. A quorum is reached when acknowledgments are received from the minimum number of environments needed to ensure that the record remains durable if an election is held. That is, the master wants to hear from enough electable replicas that they have committed the record so that if an election is held, the master knows the record will exist even if a new master is selected. + +By default, a quorum of electable peers must must acknowledge a permanent message in order for it considered to have been successfully transmitted. + +### Setting the Permanent Message Timeout + +The permanent message timeout represents the maximum amount of time the committing thread will block waiting for message acknowledgments. If sufficient acknowledgments arrive before this timeout has expired, the thread continues operations as normal. However, if this timeout expires, the committing thread flushes its transaction log buffer before continuing with normal operations. + +You set the timeout value using `Environment.setReplicationTimeout()`. You pass this method the `ReplicationTimeoutType.ACK_TIMEOUT` constant and a timeout value in microseconds. + +For example: + +``` c + dbenv.setReplicationTimeout(ReplicationTimeoutType.ACK_TIMEOUT, 100); +``` + +This timeout value can be set at anytime during the life of the application. + +### Adding a Permanent Message Policy to RepQuoteExampleGSG + +For illustration purposes, we will now update `RepQuoteExampleGSG` such that it requires only one acknowledgment from a replica on transactional commits. Also, we will give this acknowledgment a 500 microsecond timeout value. This means that our application's main thread will block for up to 500 microseconds waiting for an acknowledgment. If it does not receive at least one acknowledgment in that amount of time, DB will flush the transaction logs to disk before continuing on. + +This is a very simple update. We can perform the entire thing in `RepQuoteExampleGSG.init()` immediately after we set the application's priority and before we open our environment handle. + +``` c + public int init(RepConfig config) + throws DatabaseException + { + int ret = 0; + repConfig = config; + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setErrorStream(System.err); + envConfig.setErrorPrefix(RepConfig.progname); + + envConfig.addReplicationManagerSite(repConfig.getThisHost()); + for (ReplicationHostAddress host = + repConfig.getFirstOtherHost(); + host != null; host = repConfig.getNextOtherHost()){ + + ReplicationManagerSiteConfig repmgrRemoteSiteConfig = + new ReplicationManagerSiteConfig(host.host, host.port); + repmgrRemoteSiteConfig.setBootstrapHelper(true); + envConfig.addReplicationManagerSite( + repmgrRemoteSiteConfig); + } + envConfig.setReplicationPriority(appConfig.priority); + + envConfig.setReplicationManagerAckPolicy( + ReplicationManagerAckPolicy.ALL); + envConfig.setReplicationTimeout(ReplicationTimeoutType.ACK_TIMEOUT, + 500); + + envConfig.setCacheSize(RepConfig.CACHESIZE); + envConfig.setTxnNoSync(true); + ... +``` diff --git a/docs_src/guides/gsg_db_rep/java/heartbeats.md b/docs_src/guides/gsg_db_rep/java/heartbeats.md new file mode 100644 index 000000000..4b70b4dbc --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/heartbeats.md @@ -0,0 +1,16 @@ +--- +title: "Managing Heartbeats" +api-name: "Managing Heartbeats" +source: docs/gsg_db_rep/JAVA/heartbeats.html +--- +## Managing Heartbeats + +If your replicated application experiences few updates, it is possible for the replication group to lose a master without noticing it. This is because normally a replicated application only knows that a master has gone missing when update activity causes messages to be passed between the master and replicas. + +To guard against this, you can configure a heartbeat. The heartbeat must be configured for both the master and each of the replicas. + +On the master, you configure the application to send a heartbeat on a defined interval when it is otherwise idle. Do this by using the `Environment.setReplicationTimeout()` method. You pass this method the `ReplicationTimeoutType.HEARTBEAT_SEND` constant. You must also provide the method a value representing the period between heartbeats in microseconds. Note that the heartbeat is sent only if the system is idle. + +On the replica, you configure the application to listen for a heartbeat. The time that you configure here is the amount of time the replica will wait for some message from the master (either the heartbeat or some other message) before concluding that the connection is lost. You do this using the `Environment.setReplicationTimeout()` method. You pass this method the `ReplicationTimeoutType.HEARTBEAT_MONITOR` constant and a timeout value in microseconds. + +For best results, configure the heartbeat monitor for a longer time interval than the heartbeat send interval. diff --git a/docs_src/guides/gsg_db_rep/java/index.md b/docs_src/guides/gsg_db_rep/java/index.md new file mode 100644 index 000000000..cbb570d2d --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/index.md @@ -0,0 +1,142 @@ +--- +title: "Getting Started with Replicated Berkeley DB Applications" +api-name: "Getting Started with Replicated Berkeley DB Applications" +source: docs/gsg_db_rep/JAVA/index.html +--- +# Getting Started with Replicated Berkeley DB Applications + +**Language:** [C](../index.md) · [C++](../cxx/index.md) · Java (this page) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the United States and other countries. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + + [Preface](preface.md) + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + + [1. Introduction](introduction.md) + + [Overview](introduction.md#overview) + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + + [Replication Benefits](repadvantage.md) + + [The Replication APIs](apioverview.md) + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + + [Holding Elections](elections.md) + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + + [Permanent Message Handling](permmessages.md) + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + + [2. Transactional Application](txnapp.md) + + [Application Overview](txnapp.md#appoverview) + + [Program Listing](simpleprogramlisting.md) + + [Class: RepConfig](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: SimpleTxn](simpleprogramlisting.md#simpletxnusage_java) + + [Method: SimpleTxn.main()](simpleprogramlisting.md#simpletxnmain_java) + + [Method: SimpleTxn.init()](simpleprogramlisting.md#simpletxn_init_java) + + [Method: SimpleTxn.doloop()](simpleprogramlisting.md#doloop_java) + + [Method: SimpleTxn.printStocks()](simpleprogramlisting.md#printstocks_c) + + [3. The DB Replication Manager](repapp.md) + + [Starting and Stopping Replication](repapp.md#rep_init_code) + + [Managing Election Policies](repapp.md#election_flags) + + [Selecting the Number of Threads](repapp.md#thread_count) + + [Adding the Replication Manager to SimpleTxn](repmgr_init_example_c.md) + + [Permanent Message Handling](fwrkpermmessage.md) + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepQuoteExampleGSG](fwrkpermmessage.md#perm2fmwrkexample) + + [Managing Election Times](electiontimes.md) + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + + [Managing Connection Retries](fmwrkconnectretry.md) + + [Managing Heartbeats](heartbeats.md) + + [4. Replica versus Master Processes](fwrkmasterreplica.md) + + [Determining State](fwrkmasterreplica.md#determinestate) + + [Processing Loop](processingloop.md) + + [Example Processing Loop](exampledoloop.md) + + [Running It](exampledoloop.md#runningit) + + [5. Additional Features](addfeatures.md) + + [Delayed Synchronization](addfeatures.md#delayedsync) + + [Managing Blocking Operations](manageblock.md) + + [Stop Auto-Initialization](autoinit.md) + + [Read-Your-Writes Consistency](rywc.md) + + [Client to Client Transfer](c2ctransfer.md) + + [Identifying Peers](c2ctransfer.md#fmwrkpeerserver) + + [Bulk Transfers](bulk.md) diff --git a/docs_src/guides/gsg_db_rep/java/introduction.md b/docs_src/guides/gsg_db_rep/java/introduction.md new file mode 100644 index 000000000..25ed4ad95 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/introduction.md @@ -0,0 +1,102 @@ +--- +title: "Chapter 1. Introduction" +api-name: "Chapter 1. Introduction" +source: docs/gsg_db_rep/JAVA/introduction.html +--- +## Chapter 1. Introduction + +**Table of Contents** + + [Overview](introduction.md#overview) + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + + [Replication Benefits](repadvantage.md) + + [The Replication APIs](apioverview.md) + + [Replication Manager Overview](apioverview.md#repframeworkoverview) + + [Replication Base API Overview](apioverview.md#repapioverview) + + [Holding Elections](elections.md) + + [Influencing Elections](elections.md#influencingelections) + + [Winning Elections](elections.md#winningelections) + + [Switching Masters](elections.md#switchingmasters) + + [Permanent Message Handling](permmessages.md) + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + +This book provides a thorough introduction and discussion on replication as used with Berkeley DB (DB). It begins by offering a general overview to replication and the benefits it provides. It also describes the APIs that you use to implement replication, and it describes architecturally the things that you need to do to your application code in order to use the replication APIs. Finally, it discusses the differences in backup and restore strategies that you might pursue when using replication, especially where it comes to log file removal. + +You should understand the concepts from the *Berkeley DB Getting Started with Transaction Processing* guide before reading this book. + +## Overview + + [Replication Environments](introduction.md#repenvirons) + + [Replication Databases](introduction.md#repdbs) + + [Communications Layer](introduction.md#commlayer) + + [Selecting a Master](introduction.md#masterselect) + +The DB replication APIs allow you to distribute your database write operations (performed on a read-write master) to one or more read-only *replicas*. For this reason, DB's replication implementation is said to be a *single master, multiple replica* replication strategy. + +Note that your database write operations can occur only on the master; any attempt to write to a replica results in an error being raised by the DB API used to perform the write. + +A single replication master and all of its replicas are referred to as a *replication group*. While all members of the replication group can reside on the same machine, usually each replication participant is placed on a separate physical machine somewhere on the network. + +Note that all replication applications must first be transactional applications. The data that the master transmits to its replicas are log records that are generated as records are updated. Upon transactional commit, the master transmits a transaction record which tells the replicas to commit the records they previously received from the master. In order for all of this to work, your replicated application must also be a transactional application. For this reason, it is recommended that you write and debug your DB application as a stand-alone transactional application before introducing the replication layer to your code. + +### Replication Environments + +The most important requirement for a replication participant is that it must use a unique Berkeley DB database environment independent of all other replication participants. So while multiple replication participants can reside on the same physical machine, no two such participants can share the same environment home directory. + +For this reason, technically replication occurs between unique *database environments*. So in the strictest sense, a replication group consists of a *master environment* and one or more *replica environments*. However, the reality is that for production code, each such environment will usually be located on its own unique machine. Consequently, this manual sometimes talks about *replication sites*, meaning the unique combination of environment home directory, host and port that a specific replication application is using. + +There is no DB-specified limit to the number of environments which can participate in a replication group. The only limitation here is one of resources — network bandwidth, for example. + +(Note, however, that the Replication Manager does place a limit on the number of environments you can use. See Replication Manager Overview for details.) + +Also, DB's replication implementation requires all participating environments to be assigned IDs that are locally unique to the given environment. Depending on the replication APIs that you choose to use, you may or may not need to manage this particular detail. + +For detailed information on database environments, see the *Berkeley DB Getting Started with Transaction Processing* guide. For more information on environment IDs, see the *Berkeley DB Programmer's Reference Guide*. + +### Replication Databases + +DB's databases are managed and used in exactly the same way as if you were writing a non-replicated application, with a couple of caveats. First, the databases maintained in a replicated environment must reside either in the `ENV_HOME` directory, or in the directory identified by the `EnvironmentConfig.addDataDir()` method. Unlike non-replication applications, you cannot place your databases in a subdirectory below these locations. You should also not use full path names for your databases or environments as these are likely to break when they are replicated to other machines. + +### Communications Layer + +In order to transmit database writes to the replication replicas, DB requires a communications layer. DB is agnostic as to what this layer should look like. The only requirement is that it be capable of passing two opaque data objects and an environment ID from the master to its replicas without corruption. + +Because replicas are usually placed on different machines on the network, the communications layer is usually some kind of a network-aware implementation. Beyond that, its implementation details are largely up to you. It could use TCP/IP sockets, for example, or it could use raw sockets if they perform better for your particular application. + +Note that you may not have to write your own communications layer. DB provides a Replication Manager that includes a fully-functional TCP/IP-based communications layer. See The Replication APIs for more information. + +See the *Berkeley DB Programmer's Reference Guide* for a description of how to write your own custom replication communications layer. + +### Selecting a Master + +Every replication group is allowed one and only one master environment. Usually masters are selected by holding an *election*, although it is possible to turn elections off and manually select masters (this is not recommended for most replicated applications). + +When elections are being used, they are performed by the underlying Berkeley DB replication code so you have to do very little to implement them. + +When holding an election, replicas "vote" on who should be the master. Among replicas participating in the election, the one with the most up-to-date set of log records will win the election. Note that it's possible for there to be a tie. When this occurs, priorities are used to select the master. See Holding Elections for details. + +For more information on holding and managing elections, see Holding Elections. diff --git a/docs_src/guides/gsg_db_rep/java/manageblock.md b/docs_src/guides/gsg_db_rep/java/manageblock.md new file mode 100644 index 000000000..55edaf8fd --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/manageblock.md @@ -0,0 +1,12 @@ +--- +title: "Managing Blocking Operations" +api-name: "Managing Blocking Operations" +source: docs/gsg_db_rep/JAVA/manageblock.html +--- +## Managing Blocking Operations + +When a replica is in the process of synchronizing with its master, DB operations are blocked at some points during this process until the synchronization is completed. For replicas with a heavy read load, these blocked operations may represent an unacceptable loss in throughput. + +You can configure DB so that it will not block when synchronization is in process. Instead, the DB operation will fail, immediately throwing a `com.sleepycat.db.ReplicationLockoutException` exception. When this happens, it is up to your application to determine what action to take (that is, logging the event, making an appropriate user response, retrying the operation, and so forth). + +To turn off blocking on synchronization, specify `ReplicationConfig.NOWAIT` and `true` to `Environment.setReplicationConfig()`. To turn off this feature, specify `false` for the `ReplicationConfig.NOWAIT` field. diff --git a/docs_src/guides/gsg_db_rep/java/moreinfo.md b/docs_src/guides/gsg_db_rep/java/moreinfo.md new file mode 100644 index 000000000..93eb91e9c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/moreinfo.md @@ -0,0 +1,30 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg_db_rep/JAVA/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a transactional DB application: + +- Getting Started with Transaction Processing for Java + +- Getting Started with Berkeley DB for Java + +- Berkeley DB Collections Tutorial + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB Javadoc + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg_db_rep/java/permmessages.md b/docs_src/guides/gsg_db_rep/java/permmessages.md new file mode 100644 index 000000000..1959afa25 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/permmessages.md @@ -0,0 +1,86 @@ +--- +title: "Permanent Message Handling" +api-name: "Permanent Message Handling" +source: docs/gsg_db_rep/JAVA/permmessages.html +--- +## Permanent Message Handling + + [When Not to Manage Permanent Messages](permmessages.md#permmessagenot) + + [Managing Permanent Messages](permmessages.md#permmanage) + + [Implementing Permanent Message Handling](permmessages.md#permimplement) + +Messages received by a replica may be marked with special flag that indicates the message is permanent. Custom replicated applications will receive notification of this flag via the `DB_REP_ISPERM` return value from the method. There is no hard requirement that a replication application look for, or respond to, this return code. However, because robust replicated applications typically do manage permanent messages, we introduce the concept here. + +A message is marked as being permanent if the message affects transactional integrity. For example, transaction commit messages are an example of a message that is marked permanent. What the application does about the permanent message is driven by the durability guarantees required by the application. + +For example, consider what the Replication Manager does when it has permanent message handling turned on and a transactional commit record is sent to the replicas. First, the replicas must transactional-commit the data modifications identified by the message. And then, upon a successful commit, the Replication Manager sends the master a message acknowledgment. + +For the master (again, using the Replication Manager), things are a little more complicated than simple message acknowledgment. Usually in a replicated application, the master commits transactions asynchronously; that is, the commit operation does not block waiting for log data to be flushed to disk before returning. So when a master is managing permanent messages, it typically blocks the committing thread immediately before `commit()` returns. The thread then waits for acknowledgments from its replicas. If it receives enough acknowledgments, it continues to operate as normal. + +If the master does not receive message acknowledgments — or, more likely, it does not receive *enough* acknowledgments — the committing thread flushes its log data to disk and then continues operations as normal. The master application can do this because replicas that fail to handle a message, for whatever reason, will eventually catch up to the master. So by flushing the transaction logs to disk, the master is ensuring that the data modifications have made it to stable storage in one location (its own hard drive). + +### When Not to Manage Permanent Messages + +There are two reasons why you might choose to not implement permanent messages. In part, these go to why you are using replication in the first place. + +One class of applications uses replication so that the application can improve transaction through-put. Essentially, the application chooses a reduced transactional durability guarantee so as to avoid the overhead forced by the disk I/O required to flush transaction logs to disk. However, the application can then regain that durability guarantee to a certain degree by replicating the commit to some number of replicas. + +Using replication to improve an application's transactional commit guarantee is called *replicating to the network.* + +In extreme cases where performance is of critical importance to the application, the master might choose to both use asynchronous commits *and* decide not to wait for message acknowledgments. In this case the master is simply broadcasting its commit activities to its replicas without waiting for any sort of a reply. An application like this might also choose to use something other than TCP/IP for its network communications since that protocol involves a fair amount of packet acknowledgment all on its own. Of course, this sort of an application should also be very sure about the reliability of both its network and the machines that are hosting its replicas. + +At the other extreme, there is a class of applications that use replication purely to improve read performance. This sort of application might choose to use synchronous commits on the master because write performance there is not of critical performance. In any case, this kind of an application might not care to know whether its replicas have received and successfully handled permanent messages because the primary storage location is assumed to be on the master, not the replicas. + +### Managing Permanent Messages + +With the exception of a rare breed of replicated applications, most masters need some view as to whether commits are occurring on replicas as expected. At a minimum, this is because masters will not flush their log buffers unless they have reason to expect that permanent messages have not been committed on the replicas. + +That said, it is important to remember that managing permanent messages involves a fair amount of network traffic. The messages must be sent to the replicas and the replicas must acknowledge them. This represents a performance overhead that can be worsened by congested networks or outright outages. + +Therefore, when managing permanent messages, you must first decide on how many of your replicas must send acknowledgments before your master decides that all is well and it can continue normal operations. When making this decision, you could decide that *all* replicas must send acknowledgments. But unless you have only one or two replicas, or you are replicating over a very fast and reliable network, this policy could prove very harmful to your application's performance. + +Therefore, a common strategy is to wait for an acknowledgment from a simple majority of replicas. This ensures that commit activity has occurred on enough machines that you can be reliably certain that data writes are preserved across your network. + +Remember that replicas that do not acknowledge a permanent message are not necessarily unable to perform the commit; it might be that network problems have simply resulted in a delay at the replica. In any case, the underlying DB replication code is written such that a replica that falls behind the master will eventually take action to catch up. + +Depending on your application, it may be possible for you to code your permanent message handling such that acknowledgment must come from only one or two replicas. This is a particularly attractive strategy if you are closely managing which machines are eligible to become masters. Assuming that you have one or two machines designated to be a master in the event that the current master goes down, you may only want to receive acknowledgments from those specific machines. + +Finally, beyond simple message acknowledgment, you also need to implement an acknowledgment timeout for your application. This timeout value is simply meant to ensure that your master does not hang indefinitely waiting for responses that will never come because a machine or router is down. + +### Implementing Permanent Message Handling + +How you implement permanent message handling depends on which API you are using to implement replication. If you are using the Replication Manager, then permanent message handling is configured using policies that you specify to the framework. In this case, you can configure your application to: + +- Ignore permanent messages (the master does not wait for acknowledgments). + +- Require acknowledgments from a quorum. A quorum is reached when acknowledgments are received from the minimum number of electable peers needed to ensure that the record remains durable if an election is held. + + An *electable peer* is any other site that potentially can be elected master. + + The goal here is to be absolutely sure the record is durable. The master wants to hear from enough electable peer that they have committed the record so that if an election is held, the master knows the record will exist even if a new master is selected. + + This is the default policy. + +- Require an acknowledgment from at least one replica. + +- Require acknowledgments from all replicas. + +- Require an acknowledgment from at least one electable peer. + +- Require acknowledgments from all electable peers. + +Note that the Replication Manager simply flushes its transaction logs and moves on if a permanent message is not sufficiently acknowledged. + +For details on permanent message handling with the Replication Manager, see Permanent Message Handling. + +If these policies are not sufficient for your needs, or if you want your application to take more corrective action than simply flushing log buffers in the event of an unsuccessful commit, then you must use implement replication using the Base APIs. + +When using the Base APIs, messages are sent from the master to its replica using a `send()` callback that you implement. Note, however, that DB's replication code automatically sets the permanent flag for you where appropriate. + +If the `send()` callback returns with a non-zero status, DB flushes the transaction log buffers for you. Therefore, you must cause your `send()` callback to block waiting for acknowledgments from your replicas. As a part of implementing the `send()` callback, you implement your permanent message handling policies. This means that you identify how many replicas must acknowledge the message before the callback can return `0`. You must also implement the acknowledgment timeout, if any. + +Further, message acknowledgments are sent from the replicas to the master using a communications channel that you implement (the replication code does not provide a channel for acknowledgments). So implementing permanent messages means that when you write your replication communications channel, you must also write it in such a way as to also handle permanent message acknowledgments. + +For more information on implementing permanent message handling using a custom replication layer, see the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/java/preface.md b/docs_src/guides/gsg_db_rep/java/preface.md new file mode 100644 index 000000000..bd7693f08 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/preface.md @@ -0,0 +1,58 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg_db_rep/JAVA/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +This document describes how to write replicated applications for Berkeley DB 11*g* Release 2 (library version 11.2.5.3). The APIs used to implement replication in your application are described here. This book describes the concepts surrounding replication, the scenarios under which you might choose to use it, and the architectural requirements that a replication application has over a transactional application. + +This book is aimed at the software engineer responsible for writing a replicated DB application. + +This book assumes that you have already read and understood the concepts contained in the *Berkeley DB Getting Started with Transaction Processing* guide. + +## 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: "The `Environment()` constructor returns an `Environment` class object." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +``` + +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: + +``` c +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +Database myDb = new Database("mydb.db", null, myDbConfig); +``` + +### Note + +Finally, notes of special interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg_db_rep/java/processingloop.md b/docs_src/guides/gsg_db_rep/java/processingloop.md new file mode 100644 index 000000000..32ea2069d --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/processingloop.md @@ -0,0 +1,67 @@ +--- +title: "Processing Loop" +api-name: "Processing Loop" +source: docs/gsg_db_rep/JAVA/processingloop.html +--- +## Processing Loop + +Typically the central part of any replication application is some sort of a continuous loop that constantly checks the state of the environment (whether it is a replica or a master), opens and/or closes the databases as is necessary, and performs other useful work. A loop such as this one must of necessity take special care to know whether it is operating on a master or a replica environment because all of its activities are dependent upon that state. + +The flow of activities through the loop will generally be as follows: + +1. Check whether the environment has changed state. If it has, you might want to reopen your database handles, especially if you opened your replica's database handles as read-only. In this case, you might need to reopen them as read-write. However, if you always open your database handles as read-write, then it is not automatically necessary to reopen the databases due to a state change. Instead, you could check for a `ReplicationHandleDeadException` exception when you use your database handle(s). If you see this, then you need to reopen your database handle(s). + +2. If the databases are closed, create new database handles, configure the handle as is appropriate, and then open the databases. Note that handle configuration will be different, depending on whether the handle is opened as a replica or a master. At a minimum, the master should be opened with database creation privileges, whereas the replica does not need to be. You must also open the master such that its databases are read-write. You *can* open replicas with read-only databases, so long as you are prepared to close and then reopen the handle in the event the client becomes a master. + + Also, note that if the local environment is a replica, then it is possible that databases do not currently exist. In this case, the database open attempts will fail. Your code will have to take this corner case into account (described below). + +3. Once the databases are opened, check to see if the local environment is a master. If it is, do whatever it is a master should do for your application. + + Remember that the code for your master should include some way for you to tell the master to exit gracefully. + +4. If the local environment is not a master, then do whatever it is your replica environments should do. Again, like the code for your master environments, you should provide a way for your replicas to exit the processing loop gracefully. + +The following code fragment illustrates these points (note that we fill out this fragment with a working example next in this chapter): + +``` c +// loop to manage replication activities +public int doloop() +{ + Database db = null; + +// Infinite loop. We exit depending on how the master and replica code +// is written. +for (;;) { + /* If dbp is not opened, we need to open it. */ + if (db == null) { + // Create the handle and then configure it. Before you open + // it, you have to decide what open flags to use: + DatabaseConfig dbconf = new DatabaseConfig(); + dbconf.setType(DatabaseType.BTREE); + if (isMaster) { + dbconf.setAllowCreate(true); + } + + // Now you can open your database handle, passing to it the + // optins selected above. + + try { + db = dbenv.openDatabase + (null, progname, null, dbconf); + } catch(java.io.FileNotFoundException e) { + // Put your error handling code here. + } + } + + // Now that the databases have been opened, continue with general + // processing, depending on whether we are a master or a replica. + if (isMaster) { + // Do master stuff here. Don't forget to include a way to + // gracefully exit the loop. + } else { + // Do replica stuff here. As is the case with the master + // code, be sure to include a way to gracefully exit the + // loop. + } +} +``` diff --git a/docs_src/guides/gsg_db_rep/java/repadvantage.md b/docs_src/guides/gsg_db_rep/java/repadvantage.md new file mode 100644 index 000000000..425633078 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/repadvantage.md @@ -0,0 +1,40 @@ +--- +title: "Replication Benefits" +api-name: "Replication Benefits" +source: docs/gsg_db_rep/JAVA/repadvantage.html +--- +## Replication Benefits + +Replication offers your application a number of benefits that can be a tremendous help. Primarily replication's benefits revolve around performance, but there is also a benefit in terms of data durability guarantees. + +Briefly, the reasons why you might choose to implement replication in your DB application are: + +- Improve application reliability. + + By spreading your data across multiple machines, you can ensure that your application's data continues to be available even in the event of a hardware failure on any given machine in the replication group. + +- Improve read performance. + + By using replication you can spread data reads across multiple machines on your network. Doing so allows you to vastly improve your application's read performance. This strategy might be particularly interesting for applications that have readers on remote network nodes; you can push your data to the network's edges thereby improving application data read responsiveness. + + Additionally, depending on the portion of your data that you read on a given replica, that replica may need to cache part of your data, decreasing cache misses and reducing I/O on the replica. + +- Improve transactional commit performance + + In order to commit a transaction and achieve a transactional durability guarantee, the commit must be made *durable*. That is, the commit must be written to disk (usually, but not always, synchronously) before the application's thread of control can continue operations. + + Replication allows you to avoid this disk I/O and still maintain a degree of durability by *committing to the network*. In other words, you relax your transactional durability guarantees on the master, but by virtue of replicating the data across the network you gain some additional durability guarantees above what is provided locally. + + Usually this strategy is implemented using some form of an asynchronous transactional commit on the master. In this way your data writes will eventually be written to disk, but your application will not have to wait for the disk I/O to complete before continuing with its next operation. + + Note that it is possible to cause DB's replication implementation to wait to hear from one or more replicas as to whether they have successfully saved the write before continuing. However, in this case you might be trading performance for a even higher durability guarantee (see below). + +- Improve data durability guarantee. + + In a traditional transactional application, you commit your transactions such that data modifications are saved to disk. Beyond this, the durability of your data is dependent upon the backup strategy that you choose to implement for your site. + + Replication allows you to increase this durability guarantee by ensuring that data modifications are written to multiple machines. This means that multiple disks, disk controllers, power supplies, and CPUs are used to ensure that your data modification makes it to stable storage. In other words, replication allows you to minimize the problem of a single point of failure by using more hardware to guarantee your data writes. + + If you are using replication for this reason, then you probably will want to configure your application such that it waits to hear about a successful commit from one or more replicas before continuing with the next operation. This will obviously impact your application's write performance to some degree — with the performance penalty being largely dependent upon the speed and stability of the network connecting your replication group. + + For more information, see Permanent Message Handling. diff --git a/docs_src/guides/gsg_db_rep/java/repapp.md b/docs_src/guides/gsg_db_rep/java/repapp.md new file mode 100644 index 000000000..3e086aa3c --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/repapp.md @@ -0,0 +1,246 @@ +--- +title: "Chapter 3. The DB Replication Manager" +api-name: "Chapter 3. The DB Replication Manager" +source: docs/gsg_db_rep/JAVA/repapp.html +--- +## Chapter 3. The DB Replication Manager + +**Table of Contents** + + [Starting and Stopping Replication](repapp.md#rep_init_code) + + [Managing Election Policies](repapp.md#election_flags) + + [Selecting the Number of Threads](repapp.md#thread_count) + + [Adding the Replication Manager to SimpleTxn](repmgr_init_example_c.md) + + [Permanent Message Handling](fwrkpermmessage.md) + + [Identifying Permanent Message Policies](fwrkpermmessage.md#fmwrkpermpolicy) + + [Setting the Permanent Message Timeout](fwrkpermmessage.md#fmwrkpermtimeout) + + [Adding a Permanent Message Policy to RepQuoteExampleGSG](fwrkpermmessage.md#perm2fmwrkexample) + + [Managing Election Times](electiontimes.md) + + [Managing Election Timeouts](electiontimes.md#electiontimeout) + + [Managing Election Retry Times](electiontimes.md#electretrytime) + + [Managing Connection Retries](fmwrkconnectretry.md) + + [Managing Heartbeats](heartbeats.md) + +The easiest way to add replication to your transactional application is to use the Replication Manager. The Replication Manager provides a comprehensive communications layer that enables replication. For a brief listing of the Replication Manager's feature set, see Replication Manager Overview. + +To use the Replication Manager, you make use of special methods off the `Environment` and `EnvironmentConfig` classes, as well as the `ReplicationManagerSiteConfig` class. You also use a series of related classes to perform your implementation. For example, in order to detect whether your code is running as a master or a replica, you must implement `com.sleepycat.db.EventHandler`. (see Determining State). That is: + +1. Create an environment handle as normal. + +2. Configure your environment handle as needed (e.g. set the error file and error prefix values, if desired). + +3. Use the Replication Manager replication classes and methods to configure the Replication Manager. Using these classes and methods causes DB to know that you are using the Replication Manager. + + Configuring the Replication Manager entails setting the replication environment's priority, setting the TCP/IP address that this replication environment will use for incoming replication messages, identifying TCP/IP addresses of other replication environments, setting the number of replication environments in the replication group, and so forth. These actions are discussed throughout the remainder of this chapter. + +4. Open your environment handle. When you do this, be sure to specify `EnvironmentConfig.setInitializeReplication()` when you configure your environment handle. This is in addition to the configuration that you would normally use for a transactional application. This causes replication to be initialized for the application. + +5. Start replication by calling `Environment.replicationManagerStart()`. + +6. Open your databases as needed. Masters must open their databases for read and write activity. Replicas can open their databases for read-only activity, but doing so means they must re-open the databases if the replica ever becomes a master. Either way, replicas should never attempt to write to the database(s) directly. + +### Note + +The Replication Manager allows you to only use one environment handle per process. + +When you are ready to shut down your application: + +1. Close your databases + +2. Close your environment. This causes replication to stop as well. + +### Note + +Before you can use the Replication Manager, you may have to enable it in your DB library. This is *not* a requirement for Microsoft Windows systems, or Unix systems that use pthread mutexes by default. Other systems, notably BSD and BSD-derived systems (such as Mac OS X), must enable the Replication Manager when you configure the DB build. + +You do this by *not* disabling replication and by configuring the library with POSIX threads support. In other words, replication must be turned on in the build (it is by default), and POSIX thread support must be enabled if it is not already by default. To do this, use the `--enable-pthread_api` switch on the configure script. + +For example: + +``` c +../dist/configure --enable-pthread-api +``` + +## Starting and Stopping Replication + + [Managing Election Policies](repapp.md#election_flags) + + [Selecting the Number of Threads](repapp.md#thread_count) + +As described above, you introduce replication to an application by starting with a transactional application, performing some basic replication configuration, and then starting replication using `Environment.replicationManagerStart()`. + +You stop replication by closing your environment cleanly in the same way you would for any DB application. + +For example, the following code fragment initializes, then stops and starts replication. Note that other replication activities are omitted for brevity. + +### Note + +Note that the following code fragment would be part of a larger class that must implement `com.sleepycat.db.EventHandler`. This class is used to track state changes between master and replica. We put off that implementation for the moment, but the point remains that the following code fragment would be contained in a method or two that you would include in your `com.sleepycat.db.EventHandler` implementation. + +``` c +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.ReplicationHostAddress; +import com.sleepycat.db.ReplicationManagerStartPolicy; +import com.sleepycat.db.ReplicationManagerSiteConfig; + +... + String progname = "example_replication"; + String envHome = "TESTDIR"; + int cachesize = 10 * 1024 * 1024; + + Environment dbenv; + String listenHost = "mymachine.sleepycat.com"; + String otherHost = "anothermachine.sleepycat.com"; + int listenPort = 8080; + int otherPort = 8081; + +try { + // Configure the environment handle + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setErrorStream(System.err); + envConfig.setErrorPrefix(progname); + envConfig.setCacheSize(cachesize); + envConfig.setTxnNoSync(true); + + // Identify the local replication site. This is the local hostname + // and port that this replication participant will use to receive + // incoming replication messages. Note that this can be + // performed only once for the application. It is required. + ReplicationManagerSiteConfig thisHostConfig = + new ReplicationManagerSiteConfig(listenHost, listenPort); + thisHostConfig.setGroupCreator(creator); + thisHostConfig.setLocalSite(true) + envConfig.addReplicationManagerSite(thisHostConfig); + + // Set this application's priority. This is used for elections. + // + // Set this number to a positive integer, or 0 if you do not want + // this site to be able to become a master. + envConfig.setReplicationPriority(100); + + // Add a site to the list of replication environments known to + // this application. + ReplicationManagerSiteConfig remoteSiteConfig = + new ReplicationManagerSiteConfig(otherHost, otherPort); + remoteSiteConfig.setBootstrapHelper(true); + envConfig.addReplicationManagerSite(remoteSiteConfig); + + // Configure the environment's subsystems. Note that we initialize + // replication. This is required. + envConfig.setAllowCreate(true); + envConfig.setRunRecovery(true); + envConfig.setThreaded(true); + envConfig.setInitializeReplication(true); + envConfig.setInitializeLocking(true); + envConfig.setInitializeLogging(true); + envConfig.setInitializeCache(true); + envConfig.setTransactional(true); + + // Missing from this is where we set the event handle and the + // acknowledgement policy. We discuss these things later in this + // book. + + // Open our environment handle. + try { + dbenv = new Environment(envHome, envConfig); + } catch(FileNotFoundException e) { + System.err.println("FileNotFound exception: " + e.toString()); + System.err.println( + "Ensure that the environment directory is pre-created."); + } + + // Start the replication manager such that it has three threads. + dbenv.replicationManagerStart(3, + ReplicationManagerStartPolicy.REP_ELECTION); + + ////////////////////////////////////////////////// + // All other application code goes here, including + // database opens. + ////////////////////////////////////////////////// + +} catch (DatabaseException dbe) { + // Error handling goes here +} + +// Close out your application here. +try { + // Make sure all your databases are closed. + + // Closing your environment stops replication. + dbenv.close(); +} catch (DatabaseException dbe) { + // Error handling here. +} + +// All done. +``` + +### Managing Election Policies + +Before continuing, it is worth taking a look at the startup election options that you can set for replication. You set these using the `ReplicationManagerStartPolicy` class that you pass to the `Environment.replicationManagerStart()` method. + +In the previous example, we specified `ReplicationManagerStartPolicy.REP_ELECTION` when we started replication. This causes the application to try to find a master upon startup. If it cannot, it calls for an election. In the event an election is held, the environment receiving the most number of votes will become the master. + +There's some important points to make here: + +- This option only requires that other environments in the replication group participate in the vote. There is no requirement that *all* such environments participate. In other words, if an environment starts up, it can call for an election, and select a master, even if all other environment have not yet joined the replication group. + +- It only requires a simple majority of participating environments to elect a master. This is always true of elections held using the Replication Manager. + +- As always, the environment participating in the election with the most up-to-date log files is selected as master. If an environment with more recent log files has not yet joined the replication group, it may not become the master. + +Any one of these points may be enough to cause a less-than-optimum environment to be selected as master. Therefore, to give you a better degree of control over which environment becomes a master at application startup, the Replication Manager offers the following start-up options: + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
ReplicationManagerStartPolicy. REP_MASTER

The application starts up and declares the environment to be a master without calling for an election. It is an error for more than one environment to start up using this flag, or for an environment to use this flag when a master already exists.

+

Note that no replication group should ever operate with more than one master.

+

In the event that a environment attempts to become a master when a master already exists, the replication code will resolve the problem by holding an election. Note, however, that there is always a possibility of data loss in the face of duplicate masters, because once a master is selected, the environment that loses the election will have to roll back any transactions committed until it is in sync with the "real" master.

ReplicationManagerStartPolicy. REP_CLIENT

The application starts up and declares the environment to be a replica without calling for an election. Note that the environment can still become a master if a subsequent application starts up, calls for an election, and this environment is elected master.

ReplicationManagerStartPolicy. REP_ELECTION

As described above, the application starts up, looks for a master, and if one is not found calls for an election.

+ +### Selecting the Number of Threads + +Under the hood, the Replication Manager is threaded and you can control the number of threads used to process messages received from other replicas. The threads that the Replication Manager uses are: + +- Incoming message thread. This thread receives messages from the site's socket and passes those messages to message processing threads (see below) for handling. + +- Outgoing message thread. Outgoing messages are sent from whatever thread performed a write to the database(s). That is, the thread that called, for example, `Database.put()` is the thread that writes replication messages about that fact to the socket. + + Note that if this write activity would cause the thread to be blocked due to some condition on the socket, the Replication Manager will hand the outgoing message to the incoming message thread, and it will then write the message to the socket. This prevents your database write threads from blocking due to abnormal network I/O conditions. + +- Message processing threads are responsible for parsing and then responding to incoming replication messages. Typically, a response will include write activity to your database(s), so these threads can be busy performing disk I/O. + +Of these threads, the only ones that you have any configuration control over are the message processing threads. In this case, you can determine how many of these threads you want to run. + +It is always a bit of an art to decide on a thread count, but the short answer is you probably do not need more than three threads here, and it is likely that one will suffice. That said, the best thing to do is set your thread count to a fairly low number and then increase it if it appears that your application will benefit from the additional threads. diff --git a/docs_src/guides/gsg_db_rep/java/repmgr_init_example_c.md b/docs_src/guides/gsg_db_rep/java/repmgr_init_example_c.md new file mode 100644 index 000000000..e40501ba1 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/repmgr_init_example_c.md @@ -0,0 +1,380 @@ +--- +title: "Adding the Replication Manager to SimpleTxn" +api-name: "Adding the Replication Manager to SimpleTxn" +source: docs/gsg_db_rep/JAVA/repmgr_init_example_c.html +--- +## Adding the Replication Manager to SimpleTxn + +We now use the methods described above to add partial support to the SimpleTxn example that we presented in Transactional Application. That is, in this section we will: + +- Enhance our command line options to accept information of interest to a replicated application. + +- Configure our environment handle to use replication and the Replication Manager. + +- Minimally configure the Replication Manager. + +- Start replication. + +Note that when we are done with this section, we will be only partially ready to run the application. Some critical pieces will be missing; specifically, we will not yet be handling the differences between a master and a replica. (We do that in the next chapter). + +Also, note that in the following code fragments, additions and changes to the code are marked in **`bold`**. + +To begin, we make some significant changes to our `RepConfig` class because we will be using it to maintain a lot more information that we needed for our simple transactional example. + +We begin by importing a few new classes. `java.util.Vector` is used to organize a list of "other host" definitions (that is, the host and port information for the other replication participants known to this application). We also need a couple of classes used to manage individual host and port information, as well as replication sites and startup policy information. + +``` c +package db.repquote_gsg; + +import java.util.Vector; + +import com.sleepycat.db.ReplicationHostAddress; +import com.sleepycat.db.ReplicationManagerSiteConfig; +import com.sleepycat.db.ReplicationManagerStartPolicy; + +public class RepConfig +{ +``` + +Next we add considerably to the constants and data members used by this class. All of this is used to manage information necessary for replication purposes. We also at this point change the program's name, since we will be doing that to the main class in our application a little later in this description. + +``` c + // Constant values used in the RepQuote application. + public static final String progname = "RepQuoteExampleGSG"; + public static final int CACHESIZE = 10 * 1024 * 1024; + public static final int SLEEPTIME = 5000; + + // member variables containing configuration information + // String specifying the home directory for rep files. + public String home; + // Stores an optional set of "other" hosts. + public Vector otherHosts; + // Priority within the replication group. + public int priority; + public ReplicationManagerStartPolicy startPolicy; + // The host address to listen to. + public ReplicationHostAddress thisHost; + + // member variables used internally. + private int currOtherHost; + private boolean gotListenAddress; +``` + +Now we update our class constructor to initialize all of these new variables: + +``` c + public RepConfig() + { + startPolicy = ReplicationManagerStartPolicy.REP_ELECTION; + home = "TESTDIR"; + gotListenAddress = false; + priority = 100; + currOtherHost = 0; + thisHost = new ReplicationManagerSiteConfig(); + otherHosts = new Vector(); + } +``` + +Finally, we finish updating this class by providing a series of new getter and setter methods. These are used primarily for setting a retrieving host information of interest to our replicated application: + +``` c + public java.io.File getHome() + { + return new java.io.File(home); + } + + public void setThisHost(String host, int port, boolean creator) + { + gotListenAddress = true; + thisHost.setHost(host); + thisPort.setPort(port); + thisHost.setGroupCreator(creator); + } + + public ReplicationManagerSiteConfig getThisHost() + { + if (!gotListenAddress) { + System.err.println("Warning: no host specified."); + System.err.println("Returning default."); + } + return thisHost; + } + + public ReplicationHostAddress getThisHostAddress() + { + if (!gotListenAddress) { + System.err.println("Warning: no host specified."); + System.err.println("Returning default."); + } + return thisHost.getAddress(); + } + + public boolean gotListenAddress() { + return gotListenAddress; + } + + public void addOtherHost(String host, int port) + { + ReplicationHostAddress newInfo = + new ReplicationHostAddress(host, port); + otherHosts.add(newInfo); + } + + public ReplicationHostAddress getFirstOtherHost() + { + currOtherHost = 0; + if (otherHosts.size() == 0) + return null; + return (ReplicationHostAddress)otherHosts.get(currOtherHost); + } + + public ReplicationHostAddress getNextOtherHost() + { + currOtherHost++; + if (currOtherHost >= otherHosts.size()) + return null; + return (ReplicationHostAddress)otherHosts.get(currOtherHost); + } + + public ReplicationHostAddress getOtherHost(int i) + { + if (i >= otherHosts.size()) + return null; + return (ReplicationHostAddress)otherHosts.get(i); + } +} +``` + +Having completed our update to the `RepConfig` class, we can now start making changes to the main portion of our program. We begin by changing the program's name. (This, of course, means that we copy our `SimpleTxn` code to a file named `RepQuoteExampleGSG.java`.) + +``` c +package db.repquote_gsg; + +import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.Thread; +import java.lang.InterruptedException; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.ReplicationHostAddress; +import com.sleepycat.db.ReplicationManagerSiteConfig; + +import db.repquote.RepConfig; + +public class RepQuoteExampleGSG +{ + private RepConfig repConfig; + private Environment dbenv; +``` + +Next we update our usage function. The application will continue to accept the `-h` parameter so that we can identify the environment home directory used by this application. However, we also add the: + +- `-l` parameter which allows us to identify the host and port used by this application to listen for replication messages. This parameter is required unless the -L parameter is specified. + +- `-L` parameter, which allows us to identify the local site as the group creator. + +- `-r` parameter which allows us to specify other replicas. + +- `-p` option, which is used to identify this replica's priority (recall that the priority is used as a tie breaker for elections) + +``` c + public RepQuoteExampleGSG() + throws DatabaseException + { + repConfig = null; + dbenv = null; + } + + public static void usage() + { + System.err.println("usage: " + repConfig.progname); + System.err.println("-h home[-r host:port][-l|-L host:port]" + + "[-r host:port][-p priority]"); + + System.err.println("\t -h home directory (required)\n" + + "\t -l host:port (required, unless -L is specified; " + + "l stands for local)\n" + + "\t -L host:port (optional;L mean group creator)\n" + + "\t -r host:port (optional; r stands for remote; any " + + "number of these may be specified)\n" + + "\t -p priority (optional: defaults to 100)\n"); + + System.exit(1); + } +``` + +Now we can begin working on our `main()` function. We begin by adding a couple of variables that we will use to collect TCP/IP host and port information. + +``` c + public static void main(String[] argv) + throws Exception + { + RepConfig config = new RepConfig(); + String tmpHost; + int tmpPort = 0; +``` + +Now we collect our command line arguments. As we do so, we will configure host and port information as required, and we will configure the application's election priority if necessary. + +``` c + // Extract the command line parameters + for (int i = 0; i < argv.length; i++) + { + if (argv[i].compareTo("-h") == 0) { + // home is a string arg. + i++; + config.home = argv[i]; + } else if (argv[i].compareTo("-l") == 0) || + argv[i].compareTo("-L") == 0) { + if (i == argv.length - 1) + usage(); + if (argv[i].compareTo("-L") == 0) + isCreator = true; + // "local" should be host:port. + i++; + String[] words = argv[i].split(":"); + if (words.length != 2) { + System.err.println( + "Invalid host specification host:port needed."); + usage(); + } + try { + tmpPort = Integer.parseInt(words[1]); + } catch (NumberFormatException nfe) { + System.err.println("Invalid host specification, " + + "could not parse port number."); + usage(); + } + config.setThisHost(words[0], tmpPort, isCreator); + } else if (argv[i].compareTo("-p") == 0) { + i++; + config.priority = Integer.parseInt(argv[i]); + } else if (argv[i].compareTo("-r") == 0) { + i++; + String[] words = argv[i].split(":"); + if (words.length != 2) { + System.err.println( + "Invalid host specification host:port needed."); + usage(); + } + try { + tmpPort = Integer.parseInt(words[1]); + } catch (NumberFormatException nfe) { + System.err.println("Invalid host specification, " + + "could not parse port number."); + usage(); + } + config.addOtherHost(words[0], tmpPort); + } else { + System.err.println("Unrecognized option: " + argv[i]); + usage(); + } + } + + // Error check command line. + if ((!config.gotListenAddress()) || config.home.length() == 0) + usage(); +``` + +Having done that, the remainder of our `main()` function is left unchanged, with the exception of a few name changes required by the new class name: + +``` c + RepQuoteExampleGSG runner = null; + try { + runner = new RepQuoteExampleGSG(); + runner.init(config); + + runner.doloop(); + runner.terminate(); + } catch (DatabaseException dbe) { + System.err.println("Caught an exception during " + + "initialization or processing: " + dbe.toString()); + if (runner != null) + runner.terminate(); + } + System.exit(0); + } // end main +``` + +Now we need to update our `RepQuoteExampleGSG.init()` method. Our updates are at first related to configuring replication. First, we need to update the method so that we can identify the local site to the environment handle (that is, the site identified by the `-l` command line option): + +``` c + public int init(RepConfig config) + throws DatabaseException + { + int ret = 0; + repConfig = config; + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setErrorStream(System.err); + envConfig.setErrorPrefix(RepConfig.progname); + + envConfig.addReplicationManagerSite(repConfig.getThisHost()); +``` + +And we also add code to allow us to identify "other" sites to the environment handle (that is, the sites that we identify using the `-o` command line option). To do this, we iterate over each of the "other" sites provided to us using the `-o` command line option, and we add each one individually in turn: + +We also add code here to set the environment's priority. + +``` c + + for (ReplicationHostAddress host = + repConfig.getFirstOtherHost(); host != null; + host = repConfig.getNextOtherHost()) { + + ReplicationManagerSiteConfig repmgrRemoteSiteConfig = + new ReplicationManagerSiteConfig(host.host, host.port); + repmgrRemoteSiteConfig.setBootstrapHelper(true); + envConfig.addReplicationManagerSite( + repmgrRemoteSiteConfig); + } + + envConfig.addReplicationPriority(repConfig.priority); + +``` + +We can now open our environment. Note that the options we use to open the environment are slightly different for a replicated application than they are for a non-replicated application. Namely, replication requires the `EnvironmentConfig.setInitializeReplication()` option. + +Also, because we are using the Replication Manager, we must prepare our environment for threaded usage. For this reason, we also need the `DB_THREAD` flag. + +``` c + envConfig.setCacheSize(RepConfig.CACHESIZE); + envConfig.setTxnNoSync(true); + + envConfig.setAllowCreate(true); + envConfig.setRunRecovery(true); + envConfig.setInitializeReplication(true); + envConfig.setInitializeLocking(true); + envConfig.setInitializeLogging(true); + envConfig.setInitializeCache(true); + envConfig.setTransactional(true); + try { + dbenv = new Environment(appConfig.getHome(), envConfig); + } catch(FileNotFoundException e) { + System.err.println("FileNotFound exception: " + e.toString()); + System.err.println( + "Ensure that the environment directory is pre-created."); + ret = 1; + } +``` + +Finally, we start replication before we exit this method. Immediately after exiting this method, our application will go into the `RepQuoteExampleGSG.doloop()` method, which is where the bulk of our application's work is performed. We update that method in the next chapter. + +``` c + // start Replication Manager + dbenv.replicationManagerStart(3, appConfig.startPolicy); + return ret; + } +``` + +This completes our replication updates for the moment. We are not as yet ready to actually run this program; there remains a few critical pieces left to add to it. However, the work that we performed in this section represents a solid foundation for the remainder of our replication work. diff --git a/docs_src/guides/gsg_db_rep/java/rywc.md b/docs_src/guides/gsg_db_rep/java/rywc.md new file mode 100644 index 000000000..2a3c7f5f8 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/rywc.md @@ -0,0 +1,20 @@ +--- +title: "Read-Your-Writes Consistency" +api-name: "Read-Your-Writes Consistency" +source: docs/gsg_db_rep/JAVA/rywc.html +--- +## Read-Your-Writes Consistency + +In a distributed system, the changes made at the master are not always instantaneously available at every replica, although they eventually will be. In general, replicas not directly involved in contributing to the acknowledgement of a transaction commit will lag behind other replicas because they do not synchronize their commits with the master. + +For this reason, you might want to make use of the read-your-writes consistency feature. This feature allows you to ensure that a replica is at least current enough to have the changes made by a specific transaction. Because transactions are applied serially, by ensuring a replica has a specific commit applied to it, you know that all transaction commits occurring prior to the specified transaction have also been applied to the replica. + +You determine whether a transaction has been applied to a replica by generating a *commit token* at the master. You then transfer this commit token to the replica, where it is used to determine whether the replica is consistent enough relative to the master. + +For example, suppose the you have a web application where a replication group is implemented within a load balanced web server group. Each request to the web server consists of an update operation followed by read operations (say, from the same client), The read operations naturally expect to see the data from the updates executed by the same request. However, the read operations might have been routed to a replica that did not execute the update. + +In such a case, the update request would generate a commit token, which would be resubmitted by the browser, along with subsequent read requests. The read request could be directed at any one of the available web servers by a load balancer. The replica which services the read request would use that commit token to determine whether it can service the read operation. If the replica is current enough, it can immediately execute the transaction and satisfy the request. + +What action the replica takes if it is not consistent enough to service the read request is up to you as the application developer. You can do anything from blocking while you wait for the transaction to be applied locally, to rejecting the read request outright. + +For more information, see the `Read your writes consistency` section in the `Berkeley DB Replication` chapter of the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_db_rep/java/simpleprogramlisting.md b/docs_src/guides/gsg_db_rep/java/simpleprogramlisting.md new file mode 100644 index 000000000..664fb17b4 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/simpleprogramlisting.md @@ -0,0 +1,379 @@ +--- +title: "Program Listing" +api-name: "Program Listing" +source: docs/gsg_db_rep/JAVA/simpleprogramlisting.html +--- +## Program Listing + + [Class: RepConfig](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: SimpleTxn](simpleprogramlisting.md#simpletxnusage_java) + + [Method: SimpleTxn.main()](simpleprogramlisting.md#simpletxnmain_java) + + [Method: SimpleTxn.init()](simpleprogramlisting.md#simpletxn_init_java) + + [Method: SimpleTxn.doloop()](simpleprogramlisting.md#doloop_java) + + [Method: SimpleTxn.printStocks()](simpleprogramlisting.md#printstocks_c) + +Our example program is a fairly simple transactional application. At this early stage of its development, the application contains no hint that it must be network-aware so the only command line argument that it takes is one that allows us to specify the environment home directory. (Eventually, we will specify things like host names and ports from the command line). + +Note that the application performs all writes under the protection of a transaction; however, multiple database operations are not performed per transaction. Consequently, we simplify things a bit by using autocommit for our database writes. + +Also, this application is single-threaded. It is possible to write a multi-threaded or multi-process application that performs replication. That said, the concepts described in this book are applicable to both single threaded and multi-threaded applications so nothing is gained by multi-threading this application other than distracting complexity. This manual does, however, identify where care must be taken when performing replication with a non-single threaded application. + +Finally, remember that transaction processing is not described in this manual. Rather, see the *Berkeley DB Getting Started with Transaction Processing* guide for details on that topic. + +### Class: RepConfig + +Before we begin, we present a class that we will use to maintain useful information for us. Under normal circumstances, this class would not be necessary for a simple transactional example such as this. However, this code will grow into a replicated example that needs to track a lot more information for the application, and so we lay the groundwork for it here. + +The class that we create is called `RepConfig` and its only purpose at this time is to track the location of our environment home directory. + +``` c +package db.repquote_gsg; + +public class RepConfig +{ + // Constant values used in the RepQuote application. + public static final String progname = "SimpleTxn"; + public static final int CACHESIZE = 10 * 1024 * 1024; + + // member variables containing configuration information + public String home; // String specifying the home directory for + // rep files. + + public RepConfig() + { + home = "TESTDIR"; + } + + public java.io.File getHome() + { + return new java.io.File(home); + } + +} +``` + +### Class: SimpleTxn + +Our transactional example will consist of a class, `SimpleTxn`, that performs all our work for us. + +First, we provide the package declaration and then a few import statements that the class needs. + +``` c +package db.repquote_gsg; + +import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import db.repquote_gsg.RepConfig; + +public class SimpleTxn +{ + private RepConfig repConfig; + private Environment dbenv; +``` + +Next, we provide our class constructor. This simply initializes our class data members. + +``` c + public SimpleTxn() + throws DatabaseException + { + repConfig = null; + dbenv = null; + } +``` + +And then we provide our `usage()` method. At this point, this method has very little to report: + +``` c + public static void usage() + { + System.err.println("usage: " + repConfig.progname); + System.err.println("-h home"); + + System.err.println("\t -h home directory\n"); + + System.exit(1); + } +``` + +### Method: SimpleTxn.main() + +Having implemented our `usage()` method, we can jump directly into our `main()` method. This method begins by instantiating a `RepConfig` object, and then collecting the command line arguments so that it can populate the object with the appropriate data (just the environment home directory, at this time): + +``` c + public static void main(String[] argv) + throws Exception + { + RepConfig config = new RepConfig(); + // Extract the command line parameters + for (int i = 0; i < argv.length; i++) + { + if (argv[i].compareTo("-h") == 0) { + // home - a string arg. + i++; + config.home = argv[i]; + } else { + System.err.println("Unrecognized option: " + argv[i]); + usage(); + } + } +``` + +And then perform a little sanity checking on the command line input: + +``` c + // Error check command line. + if (config.home.length() == 0) + usage(); +``` + +Now we perform the class' work. To begin, we initialize the object. The `init()` method actually opens our environment for us (shown in the next section). + +``` c + SimpleTxn runner = null; + try { + runner = new SimpleTxn(); + runner.init(config); +``` + +And then we call our `doloop()` method. This method is where we perform all our database activity. See Method: SimpleTxn.doloop() for it's details. + +``` c + runner.doloop(); +``` + +And then, finally terminate the application (which closes our environment handle) and end the method. + +``` c + runner.terminate(); + } catch (DatabaseException dbe) { + System.err.println("Caught an exception during " + + "initialization or processing: " + dbe.toString()); + if (runner != null) + runner.terminate(); + } + System.exit(0); + } // end main +``` + +### Method: SimpleTxn.init() + +The `SimpleTxn.init()` method is used to open our environment handle. For readers familiar with writing transactional DB applications, there should be no surprises here. However, we will be adding to this in later chapters as we roll replication into this example. + +The only thing worth noting in this method here is that we relax our transactional durability guarantee for this application. We do this because the application will eventually be replicated and so we don't need a high durability guarantee. + +``` c + public int init(RepConfig config) + throws DatabaseException + { + int ret = 0; + repConfig = config; + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setErrorStream(System.err); + envConfig.setErrorPrefix(RepConfig.progname); + + envConfig.setCacheSize(RepConfig.CACHESIZE); + envConfig.setTxnNoSync(true); + + envConfig.setAllowCreate(true); + envConfig.setRunRecovery(true); + envConfig.setInitializeLocking(true); + envConfig.setInitializeLogging(true); + envConfig.setInitializeCache(true); + envConfig.setTransactional(true); + try { + dbenv = new Environment(repConfig.getHome(), envConfig); + } catch(FileNotFoundException e) { + System.err.println("FileNotFound exception: " + e.toString()); + System.err.println( + "Ensure that the environment directory is pre-created."); + ret = 1; + } + + return ret; + } +``` + +Finally, we present the `SimpleTxn.terminate()` method here. All this does is close the environment handle. Again, there should be no surprises here, but we provide the implementation for the sake of completeness anyway. + +``` c + public void terminate() + throws DatabaseException + { + dbenv.close(); + } +``` + +### Method: SimpleTxn.doloop() + +We now implement our application's primary data processing method. This method provides a command prompt at which the user can enter a stock ticker value and a price for that value. This information is then entered to the database. + +To display the database, simply enter `return` at the prompt. + +To begin, we declare a database pointer: + +``` c + public int doloop() + throws DatabaseException, , UnsupportedEncodingException + { + Database db = null; +``` + +Next, we begin the loop and we immediately open our database if it has not already been opened. + +``` c + for (;;) + { + if (db == null) { + DatabaseConfig dbconf = new DatabaseConfig(); + dbconf.setType(DatabaseType.BTREE); + dbconf.setAllowCreate(true); + dbconf.setTransactional(true); + + try { + db = dbenv.openDatabase(null, // Txn handle + RepConfig.progname, // db filename + null, // db name + dbconf); + } catch (FileNotFoundException fnfe) { + System.err.println("File not found exception" + + fnfe.toString()); + // Get here only if the environment home directory + // somehow does not exist. + } + } +``` + +Now we implement our command prompt. This is a simple and not very robust implementation of a command prompt. If the user enters the keywords `exit` or `quit`, the loop is exited and the application ends. If the user enters nothing and instead simply presses `return`, the entire contents of the database is displayed. We use our `printStocks()` method to display the database. (That implementation is shown next in this chapter.) + +Notice that very little error checking is performed on the data entered at this prompt. If the user fails to enter at least one space in the value string, a simple help message is printed and the prompt is returned to the user. That is the only error checking performed here. In a real-world application, at a minimum the application would probably check to ensure that the price was in fact an integer or float value. However, in order to keep this example code as simple as possible, we refrain from implementing a thorough user interface. + +``` c + BufferedReader stdin = + new BufferedReader(new InputStreamReader(System.in)); + + // listen for input, and add it to the database. + System.out.print("QUOTESERVER> "); + System.out.flush(); + String nextline = null; + try { + nextline = stdin.readLine(); + } catch (IOException ioe) { + System.err.println("Unable to get data from stdin"); + break; + } + String[] words = nextline.split("\\s"); + + // A blank line causes the DB to be dumped to stdout. + if (words.length == 0 || + (words.length == 1 && words[0].length() == 0)) { + try { + printStocks(db); + } catch (DatabaseException e) { + System.err.println("Got db exception reading " + + "DB: " + e.toString()); + break; + } + continue; + } + + if (words.length == 1 && + (words[0].compareToIgnoreCase("quit") == 0 || + words[0].compareToIgnoreCase("exit") == 0)) { + break; + } else if (words.length != 2) { + System.err.println("Format: TICKER VALUE"); + continue; + } +``` + +Now we assign data to the `DatabaseEntry` classes that we will use to write the new information to the database. + +``` c + DatabaseEntry key = + new DatabaseEntry(words[0].getBytes("UTF-8")); + DatabaseEntry data = + new DatabaseEntry(words[1].getBytes("UTF-8")); +``` + +Having done that, we can write the new information to the database. Remember that because a transaction handle is not explicitly used, but we did open the database such that it supports transactions, then autocommit is automatically used for this database write. + +Autocommit is described in the *Berkeley DB Getting Started with Transaction Processing* guide. + +Also, the database is not configured for duplicate records, so the data portion of a record is overwritten if the provided key already exists in the database. However, in this case DB returns `OperationStatus.KEYEXIST` — which we ignore. + +``` c + db.put(null, key, data); +``` + +Finally, we close our database before returning from the method. + +``` c + } + if (db != null) + db.close(true); + return 0; + } +``` + +### Method: SimpleTxn.printStocks() + +The `printStocks()` method simply takes a database handle, opens a cursor, and uses it to display all the information it finds in a database. This is trivial cursor operation that should hold no surprises for you. We simply provide it here for the sake of completeness. + +If you are unfamiliar with basic cursor operations, please see the *Getting Started with Berkeley DB* guide. + +``` c + public void terminate() + throws DatabaseException + { + dbenv.close(); + } + + /* + * void return type since error conditions are propagated + * via exceptions. + */ + private void printStocks(Database db) + throws DatabaseException + { + Cursor dbc = db.openCursor(null, null); + + System.out.println("\tSymbol\tPrice"); + System.out.println("\t======\t====="); + + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry data = new DatabaseEntry(); + OperationStatus ret; + for (ret = dbc.getFirst(key, data, LockMode.DEFAULT); + ret == OperationStatus.SUCCESS; + ret = dbc.getNext(key, data, LockMode.DEFAULT)) { + String keystr = new String + (key.getData(), key.getOffset(), key.getSize()); + String datastr = new String + (data.getData(), data.getOffset(), data.getSize()); + System.out.println("\t"+keystr+"\t"+datastr); + + } + dbc.close(); + } +} // end class +``` diff --git a/docs_src/guides/gsg_db_rep/java/txnapp.md b/docs_src/guides/gsg_db_rep/java/txnapp.md new file mode 100644 index 000000000..5443d54c9 --- /dev/null +++ b/docs_src/guides/gsg_db_rep/java/txnapp.md @@ -0,0 +1,60 @@ +--- +title: "Chapter 2. Transactional Application" +api-name: "Chapter 2. Transactional Application" +source: docs/gsg_db_rep/JAVA/txnapp.html +--- +## Chapter 2. Transactional Application + +**Table of Contents** + + [Application Overview](txnapp.md#appoverview) + + [Program Listing](simpleprogramlisting.md) + + [Class: RepConfig](simpleprogramlisting.md#repconfiginfo_cxx) + + [Class: SimpleTxn](simpleprogramlisting.md#simpletxnusage_java) + + [Method: SimpleTxn.main()](simpleprogramlisting.md#simpletxnmain_java) + + [Method: SimpleTxn.init()](simpleprogramlisting.md#simpletxn_init_java) + + [Method: SimpleTxn.doloop()](simpleprogramlisting.md#doloop_java) + + [Method: SimpleTxn.printStocks()](simpleprogramlisting.md#printstocks_c) + +In this chapter, we build a simple transaction-protected DB application. Throughout the remainder of this book, we will add replication to this example. We do this to underscore the concepts that we are presenting in this book; the first being that you should start with a working transactional program and then add replication to it. + +Note that this book assumes you already know how to write a transaction-protected DB application, so we will not be covering those concepts in this book. To learn how to write a transaction-protected application, see the *Berkeley DB Getting Started with Transaction Processing* guide. + +## Application Overview + +Our application maintains a stock market quotes database. This database contains records whose key is the stock market symbol and whose data is the stock's price. + +The application operates by presenting you with a command line prompt. You then enter the stock symbol and its value, separated by a space. The application takes this information and writes it to the database. + +To see the contents of the database, simply press `return` at the command prompt. + +To quit the application, type 'quit' or 'exit' at the command prompt. + +For example, the following illustrates the application's usage. In it, we use entirely fictitious stock market symbols and price values. + +``` c +> java db.repquote_gsg.SimpleTxn -h env_home_dir +QUOTESERVER> stock1 88 +QUOTESERVER> stock2 .08 +QUOTESERVER> + Symbol Price + ====== ===== + stock1 88 + +QUOTESERVER> stock1 88.9 +QUOTESERVER> + Symbol Price + ====== ===== + stock1 88.9 + stock2 .08 + +QUOTESERVER> quit +> +``` diff --git a/docs_src/guides/gsg_txn/cxx/_meta.toml b/docs_src/guides/gsg_txn/cxx/_meta.toml new file mode 100644 index 000000000..ffcdcd455 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/_meta.toml @@ -0,0 +1,44 @@ +# Nav/index metadata for the gsg_txn C++ variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Getting Started with Berkeley DB Transaction Processing (C++)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "sysfailure", + "apireq", + "multithread-intro", + "recovery-intro", + "perftune-intro", + "enabletxn", + "envopen", + "usingtxns", + "nodurabletxn", + "abortresults", + "autocommit", + "nestedtxn", + "txncursor", + "txnindices", + "maxtxns", + "txnconcurrency", + "blocking_deadlocks", + "lockingsubsystem", + "isolation", + "txn_ccursor", + "exclusivelock", + "readmodifywrite", + "txnnowait", + "reversesplit", + "filemanagement", + "backuprestore", + "recovery", + "architectrecovery", + "hotfailover", + "logfileremoval", + "logconfig", + "wrapup", + "txnexample_c", + "inmem_txnexample_c", +] diff --git a/docs_src/guides/gsg_txn/cxx/abortresults.md b/docs_src/guides/gsg_txn/cxx/abortresults.md new file mode 100644 index 000000000..f7e7b4174 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/abortresults.md @@ -0,0 +1,12 @@ +--- +title: "Aborting a Transaction" +api-name: "Aborting a Transaction" +source: docs/gsg_txn/CXX/abortresults.html +--- +## Aborting a Transaction + +When you abort a transaction, all database modifications performed under the protection of the transaction are discarded, and all locks currently held by the transaction are released. In this event, your data is simply left in the state that it was in before the transaction began performing data modifications. + +Once you have aborted a transaction, the transaction handle that you used for the transaction is no longer valid. To perform database activities under the control of a new transaction, you must obtain a fresh transactional handle. + +To abort a transaction, call `DbTxn::abort()`. diff --git a/docs_src/guides/gsg_txn/cxx/apireq.md b/docs_src/guides/gsg_txn/cxx/apireq.md new file mode 100644 index 000000000..323bf7cd1 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/apireq.md @@ -0,0 +1,38 @@ +--- +title: "Application Requirements" +api-name: "Application Requirements" +source: docs/gsg_txn/CXX/apireq.html +--- +## Application Requirements + +In order to use transactions, your application has certain requirements beyond what is required of non-transactional protected applications. They are: + +- Environments. + + Environments are optional for non-transactional applications, but they are required for transactional applications. + + Environment usage is described in detail in Transaction Basics. + +- Transaction subsystem. + + In order to use transactions, you must explicitly enable the transactional subsystem for your application, and this must be done at the time that your environment is first created. + +- Logging subsystem. + + The logging subsystem is required for recovery purposes, but its usage also means your application may require a little more administrative effort than it does when logging is not in use. See Managing DB Files for more information. + +- DbTxn handles. + + 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 must use transaction handles. These handles are obtained from your DbEnv objects. They should normally be short-lived, and their usage is reasonably simple. To complete a transaction and save the work it performed, you call its `commit()` method. To complete 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 a single write operation. Auto commit allows a transaction to be used without obtaining an explicit transaction handle. See Auto Commit for information on how to use auto commit. + +- Database open requirements. + + In addition to using environments and initializing the correct subsystems, your application must transaction protect the database opens, and any secondary index associations, if subsequent operations on the databases are to be transaction protected. The database open and secondary index association are commonly transaction protected using auto commit. + +- Deadlock detection. + + Typically transactional applications use multiple threads of control when accessing the database. Any time multiple threads are used on a single resource, the potential for lock contention arises. In turn, lock contention can lead to deadlocks. See Locks, Blocks, and Deadlocks for more information. + + Therefore, transactional applications must frequently include code for detecting and responding to deadlocks. Note that this requirement is not *specific* to transactions – you can certainly write concurrent non-transactional DB applications. Further, not every transactional application uses concurrency and so not every transactional application must manage deadlocks. Still, deadlock management is so frequently a characteristic of transactional applications that we discuss it in this book. See Concurrency for more information. diff --git a/docs_src/guides/gsg_txn/cxx/architectrecovery.md b/docs_src/guides/gsg_txn/cxx/architectrecovery.md new file mode 100644 index 000000000..7af543ab6 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/architectrecovery.md @@ -0,0 +1,108 @@ +--- +title: "Designing Your Application for Recovery" +api-name: "Designing Your Application for Recovery" +source: docs/gsg_txn/CXX/architectrecovery.html +--- +## Designing Your Application for Recovery + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + +When building your DB application, you should consider how you will run recovery. If you are building a single threaded, single process application, it is fairly simple to run recovery when your application first opens its environment. In this case, you need only decide if you want to run recovery every time you open your application (recommended) or only some of the time, presumably triggered by a start up option controlled by your application's user. + +However, for multi-threaded and multi-process applications, you need to carefully consider how you will design your application's startup code so as to run recovery only when it makes sense to do so. + +### Recovery for Multi-Threaded Applications + +If your application uses only one environment handle, then handling recovery for a multi-threaded application is no more difficult than for a single threaded application. You simply open the environment in the application's main thread, and then pass that handle to each of the threads that will be performing DB operations. We illustrate this with our final example in this book (see Transaction Example for more information). + +Alternatively, you can have each worker thread open its own environment handle. However, in this case, designing for recovery is a bit more complicated. + +Generally, when a thread performing database operations fails or hangs, it is frequently best to simply restart the application and run recovery upon application startup as normal. However, not all applications can afford to restart because a single thread has misbehaved. + +If you are attempting to continue operations in the face of a misbehaving thread, then at a minimum recovery must be run if a thread performing database operations fails or hangs. + +Remember that recovery clears the environment of all outstanding locks, including any that might be outstanding from an aborted thread. If these locks are not cleared, other threads performing database operations can back up behind the locks obtained but never cleared by the failed thread. The result will be an application that hangs indefinitely. + +To run recovery under these circumstances: + +1. Suspend or shutdown all other threads performing database operations. + +2. Discarding any open environment handles. Note that attempting to gracefully close these handles may be asking for trouble; the close can fail if the environment is already in need of recovery. For this reason, it is best and easiest to simply discard the handle. + +3. Open new handles, running recovery as you open them. See Normal Recovery for more information. + +4. Restart all your database threads. + +A traditional way to handle this activity is to spawn a watcher thread that is responsible for making sure all is well with your threads, and performing the above actions if not. + +However, in the case where each worker thread opens and maintains its own environment handle, recovery is complicated for two reasons: + +1. For some applications and workloads, it might be worthwhile to give your database threads the ability to gracefully finalize any on-going transactions. If this is the case, your code must be capable of signaling each thread to halt DB activities and close its environment. If you simply run recovery against the environment, your database threads will detect this and fail in the midst of performing their database operations. + +2. Your code must be capable of ensuring only one thread runs recovery before allowing all other threads to open their respective environment handles. Recovery should be single threaded because when recovery is run against an environment, it is deleted and then recreated. This will cause all other processes and threads to "fail" when they attempt operations against the newly recovered environment. If all threads run recovery when they start up, then it is likely that some threads will fail because the environment that they are using has been recovered. This will cause the thread to have to re-execute its own recovery path. At best, this is inefficient and at worst it could cause your application to fall into an endless recovery pattern. + +### Recovery in Multi-Process Applications + +Frequently, DB applications use multiple processes to interact with the databases. For example, you may have a long-running process, such as some kind of server, and then a series of administrative tools that you use to inspect and administer the underlying databases. Or, in some web-based architectures, different services are run as independent processes that are managed by the server. + +In any case, recovery for a multi-process environment is complicated for two reasons: + +1. In the event that recovery must be run, you might want to notify processes interacting with the environment that recovery is about to occur and give them a chance to gracefully terminate. Whether it is worthwhile for you to do this is entirely dependent upon the nature of your application. Some long-running applications with multiple processes performing meaningful work might want to do this. Other applications with processes performing database operations that are likely to be harmed by error conditions in other processes will likely find it to be not worth the effort. For this latter group, the chances of performing a graceful shutdown may be low anyway. + +2. Unlike single process scenarios, it can quickly become wasteful for every process interacting with the databases to run recovery when it starts up. This is partly because recovery *does* take some amount of time to run, but mostly you want to avoid a situation where your server must reopen all its environment handles just because you fire up a command line database administrative utility that always runs recovery. + +DB offers you two methods by which you can manage recovery for multi-process DB applications. Each has different strengths and weaknesses, and they are described in the next sections. + +#### Effects of Multi-Process Recovery + +Before continuing, it is worth noting that the following sections describe recovery processes than can result in one process running recovery while other processes are currently actively performing database operations. + +When this happens, the current database operation will abnormally fail, indicating a DB_RUNRECOVERY condition. This means that your application should immediately abandon any database operations that it may have on-going, discard any environment handles it has opened, and obtain and open new handles. + +The net effect of this is that any writes performed by unresolved transactions will be lost. For persistent applications (servers, for example), the services it provides will also be unavailable for the amount of time that it takes to complete a recovery and for all participating processes to reopen their environment handles. + +#### Process Registration + +One way to handle multi-process recovery is for every process to "register" its environment. In doing so, the process gains the ability to see if any other applications are using the environment and, if so, whether they have suffered an abnormal termination. If an abnormal termination is detected, the process runs recovery; otherwise, it does not. + +Note that using process registration also ensures that recovery is serialized across applications. That is, only one process at a time has a chance to run recovery. Generally this means that the first process to start up will run recovery, and all other processes will silently not run recovery because it is not needed. + +To cause your application to register its environment, you specify the `DB_REGISTER` flag when you open your environment. You may also specify `DB_RECOVER`. However, it is an error to specify `DB_RECOVER_FATAL` when using the `DB_REGISTER` flag. If during the open, DB determines that recovery must be run, it will automatically run the correct type of recovery for you, so long as you specify normal recovery on your environment open. If you do not specify normal recovery, and you register your environment, then no recovery is run if the registration process identifies a need for it. In this case, the environment open simply fails by returning `DB_RUNRECOVERY`. + +### Note + +If you do not specify normal recovery when you open your first registered environment in the application, then that application will fail the environment open by returning `DB_RUNRECOVERY`. This is because the first process to register must create an internal registration file, and recovery is forced when that file is created. To avoid an abnormal termination of the environment open, specify recovery on the environment open for at least the first process starting in your application. + +In addition, if you specify `DB_ENV_FAILCHK` when you register your environment, then a fail check is performed on environment open (fail checks are described in the next section). If, during the fail check process, an abnormal termination is detected for any of the processes involved in the application, DB releases any read locks held by the dead process and performs transaction aborts as necessary. This is done in an attempt to clean up the environment. + +In this situation, if a general cleanup of the environment is not possible and normal recovery is not specified on environment open, then the open will abort, returning `DB_RUNRECOVERY`. However, if this situation occurs and recovery was specified, then the appropriate type of recovery (normal or fatal) is run so as to bring the environment back to a healthy state. + +Be aware that there are some limitations/requirements if you want your various processes to coordinate recovery using registration: + +1. There can be only one environment handle per environment per process. In the case of multi-threaded processes, the environment handle must be shared across threads. + +2. All processes sharing the environment must use registration. If registration is not uniformly used across all participating processes, then you can see inconsistent results in terms of your application's ability to recognize that recovery must be run. + +#### Failure Checking + +For very large and robust multi-process applications, the most common way to ensure all the processes are working as intended is to make use of a watchdog process. To assist a watchdog process, DB offers a failure checking mechanism. + +When a thread of control fails with open environment handles, the result is that there may be resources left locked or corrupted. Other threads of control may encountered these unavailable resources quickly or not at all, depending on data access patterns. + +In any case, the DB failure checking mechanism allows a watchdog to detect if an environment is unusable as a result of a thread of control failure. It should be called periodically (for example, once a minute) from the watchdog process. If the environment is deemed unusable, then the watchdog process is notified that recovery should be run. It is then up to the watchdog to actually run recovery. It is also the watchdog's responsibility to decide what to do about currently running processes before running recovery. The watchdog could, for example, attempt to gracefully shutdown or kill all relevant processes before running recovery. + +Note that failure checking need not be run from a separate process, although conceptually that is how the mechanism is meant to be used. This same mechanism could be used in a multi-threaded application that wants to have a watchdog thread. + +To use failure checking you must: + +1. Provide an `is_alive()` call back using the `Dbenv::set_isalive()` method. DB uses this method to determine whether a specified process and thread is alive when the failure checking is performed. + +2. Possibly provide a `thread_id` callback that uniquely identifies a process and thread of control. This callback is only necessary if the standard process and thread identification functions for your platform are not sufficient to for use by failure checking. This is rarely necessary and is usually because the thread and/or process ids used by your system cannot fit into an unsigned integer. + + You provide this callback using the `DbEnv::set_thread_id()` method. See the API reference for this method for more information on when setting a thread id callback might be necessary. + +3. Call the `DbEnv::failchk()` method periodically. You can do this either periodically (once per minute, for example), or whenever a thread of control exits for your application. + + If this method determines that a thread of control exited holding read locks, those locks are automatically released. If the thread of control exited with an unresolved transaction, that transaction is aborted. If any other problems exist beyond these such that the environment must be recovered, the method will return `DB_RUNRECOVERY`. diff --git a/docs_src/guides/gsg_txn/cxx/autocommit.md b/docs_src/guides/gsg_txn/cxx/autocommit.md new file mode 100644 index 000000000..8924f70f1 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/autocommit.md @@ -0,0 +1,94 @@ +--- +title: "Auto Commit" +api-name: "Auto Commit" +source: docs/gsg_txn/CXX/autocommit.html +--- +## Auto Commit + +While transactions are frequently used to provide atomicity to multiple database operations, it is sometimes necessary to perform a single database operation under the control of a transaction. Rather than force you to obtain a transaction, perform the single write operation, and then either commit or abort the transaction, you can automatically group this sequence of events using *auto commit*. + +To use auto commit: + +1. Open your environment and your databases so that they support transactions. See Enabling Transactions for details. + + Note that frequently auto commit is used for the environment or database open. To use auto commit for either your environment or database open, specify `DB_AUTO_COMMIT` to the `DbEnv::set_flags()` or `Db::open()` method. If you specify auto commit for the environment open, then you do not need to also specify auto commit for the database open. + +2. Do not provide a transactional handle to the method that is performing the database write operation. + +Note that auto commit is not available for cursors. You must always open your cursor using a transaction if you want the cursor's operations to be transactional protected. See Transactional Cursors for details on using transactional cursors. + +### Note + +Never have more than one active transaction in your thread at a time. This is especially a problem if you mix an explicit transaction with another operation that uses auto commit. Doing so can result in undetectable deadlocks. + +For example, the following uses auto commit to perform the database write operation: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + const char *keystr ="thekey"; + const char *datastr = "thedata"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + + // Open the database. Note that we are using auto commit for + // the open, so the database is able to support transactions. + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name */ + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + Dbt key, data; + key.set_data(keystr); + key.set_size((strlen(keystr) + 1) * sizeof(char)); + key.set_data(datastr); + key.set_size((strlen(datastr) + 1) * sizeof(char)); + + // Perform the write. Because the database was opened to support + // auto commit, this write is performed using auto commit. + db->put(NULL, &key, &data, 0); + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + try { + if (dbp != NULL) + dbp->close(0); + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` diff --git a/docs_src/guides/gsg_txn/cxx/backuprestore.md b/docs_src/guides/gsg_txn/cxx/backuprestore.md new file mode 100644 index 000000000..db3c1836f --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/backuprestore.md @@ -0,0 +1,98 @@ +--- +title: "Backup Procedures" +api-name: "Backup Procedures" +source: docs/gsg_txn/CXX/backuprestore.html +--- +## Backup Procedures + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + +*Durability* is an important part of your transactional guarantees. It means that once a transaction has been successfully committed, your application will always see the results of that transaction. + +Of course, no software algorithm can guarantee durability in the face of physical data loss. Hard drives can fail, and if you have not copied your data to locations other than your primary disk drives, then you will lose data when those drives fail. Therefore, in order to truly obtain a durability guarantee, you need to ensure that any data stored on disk is backed up to secondary or alternative storage, such as secondary disk drives, or offline tapes. + +There are three different types of backups that you can perform with DB databases and log files. They are: + +- Offline backups + + This type of backup is perhaps the easiest to perform as it involves simply copying database and log files to an offline storage area. It also gives you a snapshot of the database at a fixed, known point in time. However, you cannot perform this type of a backup while you are performing writes to the database. + +- Hot backups + + This type of backup gives you a snapshot of your database. Since your application can be writing to the database at the time that the snapshot is being taken, you do not necessarily know what the exact state of the database is for that given snapshot. + +- Incremental backups + + This type of backup refreshes a previously performed backup. + +Once you have performed a backup, you can perform *catastrophic recovery* to restore your databases from the backup. See Catastrophic Recovery for more information. + +Note that you can also maintain a hot failover. See Using Hot Failovers for more information. + +### About Unix Copy Utilities + +If you are copying database files you must copy databases atomically, in multiples of the database page size. In other words, the reads made by the copy program must not be interleaved with writes by other threads of control, and the copy program must read the databases in multiples of the underlying database page size. Generally, this is not a problem because operating systems already make this guarantee and system utilities normally read in power-of-2 sized chunks, which are larger than the largest possible Berkeley DB database page size. + +On some platforms (most notably, some releases of Solaris), the copy utility (`cp`) was implemented using the `mmap()` system call rather than the `read()` system call. Because `mmap()` did not make the same guarantee of read atomicity as did `read()`, the `cp` utility could create corrupted copies of the databases. + +Also, some platforms have implementations of the `tar` utility that performs 10KB block reads by default. Even when an output block size is specified, the utility will still not read the underlying databases in multiples of the specified block size. Again, the result can be a corrupted backup. + +To fix these problems, use the `dd` utility instead of `cp` or `tar`. When you use `dd`, make sure you specify a block size that is equal to, or an even multiple of, your database page size. Finally, if you plan to use a system utility to copy database files, you may want to use a system call trace utility (for example, `ktrace` or `truss`) to make sure you are not using a I/O size that is smaller than your database page size. You can also use these utilities to make sure the system utility is not using a system call other than `read()`. + +### Offline Backups + +To create an offline backup: + +1. Commit or abort all on-going transactions. + +2. Pause all database writes. + +3. Force a checkpoint. See Checkpoints for details. + +4. Copy all your database files to the backup location. Note that you can simply copy all of the database files, or you can determine which database files have been written during the lifetime of the current logs. To do this, use either the `DbEnv::log_archive()` method with the `DB_ARCH_DATA` option, or use the **db_archive** command with the `-s` option. + + However, be aware that backing up just the modified databases only works if you have all of your log files. If you have been removing log files for any reason then using `log_archive()` can result in an unrecoverable backup because you might not be notified of a database file that was modified. + +5. Copy the *last* log file to your backup location. Your log files are named `log.`*`xxxxxxxxxx`*, where *xxxxxxxxxx* is a sequential number. The last log file is the file with the highest number. + +### Hot Backup + +To create a hot backup, you do not have to stop database operations. Transactions may be on-going and you can be writing to your database at the time of the backup. However, this means that you do not know exactly what the state of your database is at the time of the backup. + +You can use the **db_hotbackup** command line utility to create a hot backup. This program optionally runs a checkpoint, and then copies all necessary files to a target directory. + +You can also create your own hot backup facility using the `DbEnv::backup()` method. + +Alternatively, you can manually create a hot backup as follows: + +1. Set the `DB_HOTBACKUP_IN_PROGRESS` flag in your environment. For more information, see the DbEnv::set_flags() API reference page. + +2. Copy all your database files to the backup location. Note that you can simply copy all of the database files, or you can determine which database files have been written during the lifetime of the current logs. To do this, use either the `DbEnv::log_archive()` with the `DB_ARCH_DATA` option, or use the **db_archive** command with the `-s` option. + +3. Copy all logs to your backup location. + +4. Reset the `DB_HOTBACKUP_IN_PROGRESS` flag. + +### Note + +It is important to copy your database files *and then* your logs. In this way, you can complete or roll back any database operations that were only partially completed when you copied the databases. + +### Incremental Backups + +Once you have created a full backup (that is, either a offline or hot backup), you can create incremental backups. To do this, simply copy all of your currently existing log files to your backup location. + +Incremental backups do not require you to run a checkpoint or to cease database write operations. + +If your application uses the transactional bulk insert optimization, it is important to know that a database copy taken prior to a bulk loading event can no longer be used as the target of an incremental backup. This is true because bulk loading omits logging of some record insertions, so recovery cannot roll forward these insertions. It is recommended that a full backup be scheduled following a bulk loading event. + +For more information, see the description of the `DB_TXN_BULK` flag in the DbEnv::txn_begin() API reference page. + +When you are working with incremental backups, remember that the greater the number of log files contained in your backup, the longer recovery will take. You should run full backups on some interval, and then do incremental backups on a shorter interval. How frequently you need to run a full backup is determined by the rate at which your databases change and how sensitive your application is to lengthy recoveries (should one be required). + +You can also shorten recovery time by running recovery against the backup as you take each incremental backup. Running recovery as you go means that there will be less work for DB to do if you should ever need to restore your environment from the backup. diff --git a/docs_src/guides/gsg_txn/cxx/blocking_deadlocks.md b/docs_src/guides/gsg_txn/cxx/blocking_deadlocks.md new file mode 100644 index 000000000..faa0cf965 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/blocking_deadlocks.md @@ -0,0 +1,160 @@ +--- +title: "Locks, Blocks, and Deadlocks" +api-name: "Locks, Blocks, and Deadlocks" +source: docs/gsg_txn/CXX/blocking_deadlocks.html +--- +## Locks, Blocks, and Deadlocks + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + +It is important to understand how locking works in a concurrent application before continuing with a description of the concurrency mechanisms DB makes available to you. Blocking and deadlocking have important performance implications for your application. Consequently, this section provides a fundamental description of these concepts, and how they affect DB operations. + +### Locks + +When one thread of control wants to obtain access to an object, it requests a *lock* for that object. This lock is what allows DB to provide your application with its transactional isolation guarantees by ensuring that: + +- no other thread of control can read that object (in the case of an exclusive lock), and + +- no other thread of control can modify that object (in the case of an exclusive or non-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 a transaction handle. For non-transactional operations, the locker is a cursor or a Db handle. + +2. The lock. + + This is the actual data structure that locks the object. In DB, a locked object structure in 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 DB application, the locked object is usually a database page, which in turn contains multiple database entries (key and data). However, for Queue databases, individual database records are locked. + +You can configure how many total lockers, locks, and locked objects your application is allowed to support. See Configuring the Locking Subsystem for details. + +The following figure shows a transaction handle, `Txn A`, that is holding a lock on database page `002`. In this graphic, `Txn A` is the locker, and the locked object is page `002`. Only a single lock is in use in this operation. + +![](simplelock.jpg) + +#### Types of Locks + +DB applications support both exclusive and non-exclusive locks. *Exclusive locks* are granted when a locker wants to write to an object. For this reason, exclusive locks are also sometimes 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 exclusively locked object until the locker is done writing to that object. + +*Non-exclusive locks* are granted for read-only access. For this reason, non-exclusive locks are also sometimes called *read locks*. Since multiple lockers can simultaneously hold read locks 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 while the locker is still reading the object. This is how transactional cursors are able to achieve repeatable reads; by default, the cursor's transaction holds a read lock on any object that the cursor has examined until such a time as the transaction is committed or aborted. You can avoid these read locks by using snapshot isolation. See Using Snapshot Isolation for details. + +In the following figure, `Txn A` and `Txn B` are both holding read locks on page `002`, while `Txn C` is holding a write lock on page `003`: + +![](rwlocks1.jpg) + +#### Lock Lifetime + +A locker holds its locks until such a time as it does not need the lock any more. What this means is: + +1. A transaction holds any locks that it obtains until the transaction is committed or aborted. + +2. All non-transaction operations hold locks until such a time as the operation is completed. For cursor operations, the lock is held until the cursor is moved to a new position or closed. + +### Blocks + +Simply put, a thread of control is blocked when it attempts to obtain a lock, but that attempt is denied because some other thread of control holds a conflicting lock. Once blocked, the thread of control is temporarily unable to make any forward progress until the 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 is attempting 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, the entire 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 object 002, then if `Txn B` tries to obtain a read *or* write lock on that object, the thread of control in which `Txn B` is running is blocked: + +![](writeblock.jpg) + +However, if `Txn A` only holds a read lock (a shared lock) on object `002`, then only those handles that attempt to obtain a write lock on that object will block. + +![](readblock.jpg) + +### Note + +The previous description describes DB's default behavior when it cannot obtain a lock. It is possible to configure DB transactions so that they will not block. Instead, if a lock is unavailable, the application is immediately notified of a deadlock situation. See No Wait on Blocks for more information. + +#### Blocking and Application Performance + +Multi-threaded and multi-process applications typically perform better than simple single-threaded applications because the application can perform one part of its workload (updating a database record, for example) while it is waiting for some other lengthy operation to complete (performing disk or network I/O, for example). This performance improvement is particularly noticeable if you use hardware that offers multiple CPUs, because the threads and processes can run simultaneously. + +That said, concurrent applications can see reduced workload throughput if their threads of control are seeing a large amount of lock contention. That is, if threads are blocking on 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 already holding read locks on the requested object. In this case, the thread in which `Txn C` is running will pause until such a time as `Txn C` either obtains its write lock, or the operation that is requesting the lock is abandoned. The fact that `Txn C`'s thread has temporarily halted 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 will also block until such a time as `Txn C` has obtained and subsequently released its write lock. + +#### Avoiding Blocks + +Reducing lock contention is an important part of performance tuning your concurrent DB application. Applications that have multiple threads of control obtaining exclusive (write) locks are prone to contention issues. Moreover, as you increase the numbers of lockers and as you increase the time that a lock is held, you increase the chances of your application seeing lock contention. + +As you are designing your application, try to do the following in order to reduce lock contention: + +- Reduce the length of time your application holds locks. + + Shorter lived transactions will result in shorter lock lifetimes, which will in turn help to reduce lock contention. + + In addition, by default transactional cursors hold read locks until such a time as the transaction is completed. For this reason, try to minimize the time you keep transactional cursors opened, or reduce your isolation levels – see below. + +- If possible, access heavily accessed (read or write) items toward the end of the transaction. This reduces the amount of time that a heavily used page is locked by the transaction. + +- Reduce your application's isolation guarantees. + + By reducing your isolation guarantees, you reduce the situations in which a lock can block another lock. Try using uncommitted reads for your read operations in order to prevent a read lock being blocked by a write lock. + + In addition, for cursors you can use degree 2 (read committed) isolation, which causes the cursor to release its read locks as soon as it is done reading the record (as opposed to holding its read locks until the transaction ends). + + Be aware that reducing your isolation guarantees can have adverse consequences for your application. Before deciding to reduce your isolation, take care to examine your application's isolation requirements. For information on isolation levels, see Isolation. + +- Use snapshot isolation for read-only threads. + + Snapshot isolation causes the transaction to make a copy of the page on which it is holding a lock. When a reader makes a copy of a page, write locks can still be obtained for the original page. This eliminates entirely read-write contention. + + Snapshot isolation is described in Using Snapshot Isolation. + +- Consider your data access patterns. + + Depending on the nature of your application, this may be something that you can not do anything about. However, if it is possible to create your threads such that they operate only on non-overlapping portions of your database, then you can reduce lock contention because your threads will rarely (if ever) block on one another's locks. + +### Note + +It is possible to configure DB's transactions so that they never wait on blocked lock requests. Instead, if they are blocked on a lock request, they will notify the application of a deadlock (see the next section). + +You configure this behavior on a transaction by transaction basis. See No Wait on Blocks for more information. + +### Deadlocks + +A deadlock occurs when two or more threads of control are blocked, each waiting on a resource held by the other thread. When this happens, there is no possibility of the threads ever making forward progress unless some outside agent takes action to break the deadlock. + +For example, if `Txn A` is blocked by `Txn B` at the same time `Txn B` is blocked by `Txn A` then the threads of control containing `Txn A` and `Txn B` are deadlocked; neither thread can make any forward progress because neither thread will ever release the lock that is blocking the other thread. + +![](deadlock.jpg) + +When two threads of control deadlock, the only solution is to have a mechanism external to the two threads capable of recognizing the deadlock and notifying at least one thread that it is in a deadlock situation. Once notified, a thread of control must abandon the attempted operation in order to resolve the deadlock. DB's locking subsystem offers a deadlock notification mechanism. See Configuring Deadlock Detection for more information. + +Note that when one locker in a thread of control is blocked waiting on a lock held by another locker in that same thread of the control, the thread is said to be *self-deadlocked*. + +#### Deadlock Avoidance + +The things that you do to avoid lock contention also help to reduce deadlocks (see Avoiding Blocks). Beyond that, you can also do the following in order to avoid deadlocks: + +- Never have more than one active transaction at a time in a thread. A common cause of this is for a thread to be using auto-commit for one operation while an explicit transaction is in use in that thread at the same time. + +- Make sure all threads access data in the same order as all other threads. So long as threads lock database pages 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 (indexes), it is not possible to obtain locks in a consistent order because you cannot predict the order in which locks are obtained in secondary databases. If you are writing a concurrent application and you are using secondary databases, you must be prepared to handle deadlocks. + +- If you are using BTrees in which you are constantly adding and then deleting data, turn Btree reverse split off. See Reverse BTree Splits for more information. + +- Declare a read/modify/write lock for those situations where you are reading a record in preparation of modifying and then writing the record. Doing this causes DB to give your read operation a write lock. This means that no other thread of control can share a read lock (which might cause contention), but it also means that the writer thread will not have to wait to obtain a write lock when it is ready to write the modified data back to the database. + + For information on declaring read/modify/write locks, see Read/Modify/Write. diff --git a/docs_src/guides/gsg_txn/cxx/enabletxn.md b/docs_src/guides/gsg_txn/cxx/enabletxn.md new file mode 100644 index 000000000..b54daf2ef --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/enabletxn.md @@ -0,0 +1,231 @@ +--- +title: "Chapter 2. Enabling Transactions" +api-name: "Chapter 2. Enabling Transactions" +source: docs/gsg_txn/CXX/enabletxn.html +--- +## Chapter 2. Enabling Transactions + +**Table of Contents** + + [Environments](enabletxn.md#environments) + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + + [Opening a Transactional Environment and Database](envopen.md) + +In order to use transactions with your application, you must turn them on. To do this you must: + +- Use an environment (see Environments for details). + +- Turn on transactions for your environment. You do this by providing the `DB_INIT_TXN` flag to the `DbEnv::open()` method. Note that initializing the transactional subsystem implies that the logging subsystem is also initialized. Also, note that if you do not initialize transactions when you first create your environment, then you cannot use transactions for that environment after that. This is because DB allocates certain structures needed for transactional locking that are not available if the environment is created without transactional support. + +- Initialize the in-memory cache by passing the `DB_INIT_MPOOL` flag to the `DbEnv::open()` method. + +- Initialize the locking subsystem. This is what provides locking for concurrent applications. It also is used to perform deadlock detection. See Concurrency for more information. + + You initialize the locking subsystem by passing the `DB_INIT_LOCK` flag to the `DbEnv::open()` method. + +- Initialize the logging subsystem. While this is enabled by default for transactional applications, we suggest that you explicitly initialize it anyway for the purposes of code readability. The logging subsystem is what provides your transactional application its durability guarantee, and it is required for recoverability purposes. See Managing DB Files for more information. + + You initialize the logging subsystem by passing the `DB_INIT_LOG` flag to the `DbEnv::open()` method. + +- Transaction-enable your databases. If you are using the base API, transaction-enable your databases. You do this by encapsulating the database open in a transaction. Note that the common practice is for auto commit to be used to transaction-protect the database open. To use auto-commit, you must still enable transactions as described here, but you do not have to explicitly use a transaction when you open your database. An example of this is given in the next section. + +## Environments + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + +For simple DB applications, environments are optional. However, in order to transaction protect your database operations, you must use an environment. + +An *environment*, represents an encapsulation of one or more databases and any associated log and region files. They are used to support multi-threaded and multi-process applications by allowing different threads of control to share the in-memory cache, the locking tables, the logging subsystem, and the file namespace. By sharing these things, your concurrent application is more efficient than if each thread of control had to manage these resources on its own. + +By default all DB databases are backed by files on disk. In addition to these files, transactional DB applications create logs that are also by default stored on disk (they can optionally be backed using shared memory). Finally, transactional DB applications also create and use shared-memory regions that are also typically backed by the filesystem. But like databases and logs, the regions can be maintained strictly in-memory if your application requires it. For an example of an application that manages all environment files in-memory, see In-Memory Transaction Example. + +### Warning + +Using environments with some journaling filesystems might result in log file corruption. This can occur if the operating system experiences an unclean shutdown when a log file is being created. Please see Using Recovery on Journaling Filesystems in the *Berkeley DB Programmer's Reference Guide* for more information. + +### File Naming + +In order to operate, your DB application must be able to locate its database files, log files, and region files. If these are stored in the filesystem, then you must tell DB where they are located (a number of mechanisms exist that allow you to identify the location of these files – see below). Otherwise, by default they are located in the current working directory. + +#### Specifying the Environment Home Directory + +The environment home directory is used to determine where DB files are located. Its location is identified using one of the following mechanisms, in the following order of priority: + +- If no information is given as to where to put the environment home, then the current working directory is used. + +- If a home directory is specified on the `DbEnv::open()` method, then that location is always used for the environment home. + +- If a home directory is not supplied to `DbEnv::open()`, then the directory identified by the `DB_HOME` environment variable is used *if* you specify either the `DB_USE_ENVIRON` or `DB_USE_ENVIRON_ROOT` flags to the `DbEnv::open()` method. Both flags allow you to identify the path to the environment's home directory using the `DB_HOME` environment variable. However, `DB_USE_ENVIRON_ROOT` is honored only if the process is run with root or administrative privileges. + +#### Specifying File Locations + +By default, all DB files are created relative to the environment home directory. For example, suppose your environment home is in `/export/myAppHome`. Also suppose you name your database `data/myDatabase.db`. Then in this case, the database is placed in: `/export/myAppHome/data/myDatabase.db`. + +That said, DB always defers to absolute pathnames. This means that if you provide an absolute filename when you name your database, then that file is *not* placed relative to the environment home directory. Instead, it is placed in the exact location that you specified for the filename. + +On UNIX systems, an absolute pathname is a name that begins with a forward slash ('/'). On Windows systems, an absolute pathname is a name that begins with one of the following: + +- A backslash ('\\). + +- Any alphabetic letter, followed by a colon (':'), followed by a backslash ('\\). + +### Note + +Try not to use absolute path names for your environment's files. Under certain recovery scenarios, absolute path names can render your environment unrecoverable. This occurs if you are attempting to recover your environment on a system that does not support the absolute path name that you used. + +#### Identifying Specific File Locations + +As described in the previous sections, DB will place all its files in or relative to the environment home directory. You can also cause a specific database file to be placed in a particular location by using an absolute path name for its name. In this situation, the environment's home directory is not considered when naming the file. + +It is frequently desirable to place database, log, and region files on separate disk drives. By spreading I/O across multiple drives, you can increase parallelism and improve throughput. Additionally, by placing log files and database files on separate drives, you improve your application's reliability by providing your application with a greater chance of surviving a disk failure. + +You can cause DB's files to be placed in specific locations using the following mechanisms: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File TypeTo Override
database files

You can cause database files to be created in a directory other than the environment home by using the DbEnv::add_data_dir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

This method modifies the directory used for database files created and managed by a single environment handle; it does not configure the entire environment. This method may not be called after the environment has been opened.

+

You can also set a default data location that is used by the entire environment by using the add_data_dir parameter in the environment's DB_CONFIG file. Note that the add_data_dir parameter overrides any value set by the DbEnv::set_data_dir() method.

Log files

You can cause log files to be created in a directory other than the environment home directory by using the DbEnv::set_lg_dir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

This method modifies the directory used for database files created and managed by a single environment handle; it does not configure the entire environment. This method may not be called after the environment has been opened.

+

You can also set a default log file location that is used by the entire environment by using the set_lg_dir parameter in the environment's DB_CONFIG file. Note that the set_lg_dir parameter overrides any value set by the DbEnv::set_lg_dir() method.

Temporary files

You can cause temporary files required by the environment to be created in a directory other than the environment home directory by using the DbEnv::set_tmp_dir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

You can also set a temporary file location by using the set_tmp_dir parameter in the environment's DB_CONFIG file. Note that the set_tmp_dir parameter overrides any value set by the DbEnv::set_tmp_dir() method.

Metadata files

You can cause persistent metadata files required by the replicated applications to be created in a directory other than the environment home directory by using the DbEnv::set_metadata_dir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

You can also set a metadata directory location by using the set_metadata_dir parameter in the environment's DB_CONFIG file. Note that the set_metadata_dir parameter overrides any value set by the DbEnv::set_metadata_dir() method.

Region filesIf backed by the filesystem, region files are always placed in the environment home directory.
+ +Note that the `DB_CONFIG` must reside in the environment home directory. Parameters are specified in it one parameter to a line. Each parameter is followed by a space, which is followed by the parameter value. For example: + +``` c + add_data_dir /export1/db/env_data_files +``` + +### Error Support + +To simplify error handling and to aid in application debugging, environments offer several useful methods. Note that many of these methods are identical to the error handling methods available for the Db class. They are: + +- `set_error_stream()` + + Sets the C++ `ostream` to be used for displaying error messages issued by the DB library. + +- `set_errcall()` + + Defines the function that is called when an error message is issued by DB. The error prefix and message are passed to this callback. It is up to the application to display this information correctly. + + This is the recommended way to get error messages from DB. + +- `set_errfile()` + + Sets the C library `FILE *` to be used for displaying error messages issued by the DB library. + +- `set_errpfx()` + + Sets the prefix used to for any error messages issued by the DB library. + +- `err()` + + Issues an error message based upon a DB error code a message text that you supply. The error message is sent to the callback function as defined by `set_errcall()`. If that method has not been used, then the error message is sent to the file defined by `set_errfile()` or `set_error_stream()`. If none of these methods have been used, then the error message is sent to standard error. + + The error message consists of the prefix string (as defined by `set_errprefix()`), an optional `printf`-style formatted message, the DB error message associated with the supplied error code, and a trailing newline. + +- `errx()` + + Behaves identically to `err()` except that you do not provide the DB error code and so the DB message text is not displayed. + +In addition, you can use the `db_strerror()` function to directly return the error string that corresponds to a particular error number. For more information on the `db_strerror()` function, see the `Error Returns` section of the *Getting Started with Berkeley DB* guide. + +### Shared Memory Regions + +The subsystems that you enable for an environment (in our case, transaction, logging, locking, and the memory pool) are described by one or more regions. The regions contain all of the state information that needs to be shared among threads and/or processes using the environment. + +Regions may be backed by the file system, by heap memory, or by system shared memory. + +#### Regions Backed by Files + +By default, shared memory regions are created as files in the environment's home directory (*not* the environment's data directory). If it is available, the POSIX `mmap` interface is used to map these files into your application's address space. If `mmap` is not available, then the UNIX `shmget` interfaces are used instead (again, if they are available). + +In this default case, the region files are named `__db.###` (for example, `__db.001`, `__db.002`, and so on). + +#### Regions Backed by Heap Memory + +If heap memory is used to back your shared memory regions, then you can only open a single handle for the environment. This means that the environment cannot be accessed by multiple processes. In this case, the regions are managed only in memory, and they are not written to the filesystem. You indicate that heap memory is to be used for the region files by specifying `DB_PRIVATE` to the `DbEnv::open()` method. + +Note that you can also set this flag by using the `set_open_flags` parameter in the `DB_CONFIG` file. See the *Berkeley DB C API Reference Guide* for more information. + +(For an example of an entirely in-memory transactional application, see In-Memory Transaction Example.) + +#### Regions Backed by System Memory + +Finally, you can cause system memory to be used for your regions instead of memory-mapped files. You do this by providing `DB_SYSTEM_MEM` to the `DbEnv::open()` method. + +When region files are backed by system memory, DB creates a single file in the environment's home directory. This file contains information necessary to identify the system shared memory in use by the environment. By creating this file, DB enables multiple processes to share the environment. + +The system memory that is used is architecture-dependent. For example, on systems supporting X/Open-style shared memory interfaces, such as UNIX systems, the `shmget(2)` and related System V IPC interfaces are used. Additionally, VxWorks systems use system memory. In these cases, an initial segment ID must be specified by the application to ensure that applications do not overwrite each other's environments, so that the number of segments created does not grow without bounds. See the `DbEnv::set_shm_key()` method for more information. + +On Windows platforms, the use of system memory for the region files is problematic because the operating system uses reference counting to clean up shared objects in the paging file automatically. In addition, the default access permissions for shared objects are different from files, which may cause problems when an environment is accessed by multiple processes running as different users. See Windows notes or more information. + +### Security Considerations + +When using environments, there are some security considerations to keep in mind: + +- Database environment permissions + + The directory used for the environment should have its permissions set to ensure that files in the environment are not accessible to users without appropriate permissions. Applications that add to the user's permissions (for example, UNIX `setuid` or `setgid` applications), must be carefully checked to not permit illegal use of those permissions such as general file access in the environment directory. + +- Environment variables + + Setting the `DB_USE_ENVIRON` or `DB_USE_ENVIRON_ROOT` flags so that environment variables can be used during file naming can be dangerous. Setting those flags in DB applications with additional permissions (for example, UNIX `setuid` or `setgid` applications) could potentially allow users to read and write databases to which they would not normally have access. + + For example, suppose you write a DB application that runs `setuid`. This means that when the application runs, it does so under a userid different than that of the application's caller. This is especially problematic if the application is granting stronger privileges to a user than the user might ordinarily have. + + Now, if the `DB_USE_ENVIRON` or `DB_USE_ENVIRON_ROOT` flags are set for the environment, then the environment that the application is using is modifiable using the `DB_HOME` environment variable. In this scenario, if the uid used by the application has sufficiently broad privileges, then the application's caller can read and/or write databases owned by another user simply by setting his `DB_HOME` environment variable to the environment used by that other user. + + Note that this scenario need not be malicious; the wrong environment could be used by the application simply by inadvertently specifying the wrong path to `DB_HOME`. + + As always, you should use `setuid` sparingly, if at all. But if you do use `setuid`, then you should refrain from specifying the `DB_USE_ENVIRON` or `DB_USE_ENVIRON_ROOT` flags for the environment open. And, of course, if you must use `setuid`, then make sure you use the weakest uid possible – preferably one that is used only by the application itself. + +- File permissions + + By default, DB always creates database and log files readable and writable by the owner and the group (that is, `S_IRUSR`, `S_IWUSR`, `S_IRGRP` and `S_IWGRP`; or octal mode 0660 on historic UNIX systems). The group ownership of created files is based on the system and directory defaults, and is not further specified by DB. + +- Temporary backing files + + If an unnamed database is created and the cache is too small to hold the database in memory, Berkeley DB will create a temporary physical file to enable it to page the database to disk as needed. In this case, environment variables such as `TMPDIR` may be used to specify the location of that temporary file. Although temporary backing files are created readable and writable by the owner only (`S_IRUSR` and `S_IWUSR`, or octal mode 0600 on historic UNIX systems), some filesystems may not sufficiently protect temporary files created in random directories from improper access. To be absolutely safe, applications storing sensitive data in unnamed databases should use the `DbEnv::set_tmp_dir()` method to specify a temporary directory with known permissions. diff --git a/docs_src/guides/gsg_txn/cxx/envopen.md b/docs_src/guides/gsg_txn/cxx/envopen.md new file mode 100644 index 000000000..304b8f286 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/envopen.md @@ -0,0 +1,109 @@ +--- +title: "Opening a Transactional Environment and Database" +api-name: "Opening a Transactional Environment and Database" +source: docs/gsg_txn/CXX/envopen.html +--- +## Opening a Transactional Environment and Database + +To enable transactions for your environment, you must initialize the transactional subsystem. Note that doing this also initializes the logging subsystem. In addition, you must initialize the memory pool (in-memory cache). You must also initialize the locking subsystem. For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + + } catch(DbException &e) { + std::cerr << "Error opening database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + try { + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +You then create and open your database(s) as you would for a non-transactional system. The only difference is that you must pass the environment handle to the `DbEnv::open()` method, and you must open the database within a transaction. Typically auto commit is used for this purpose. To do so, pass `DB_AUTO_COMMIT` to the database open command. It is recommended that you close all your databases before you close your environment. For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + try { + dbp->close(0); + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +### Note + +Never close a database that has active transactions. Make sure all transactions are resolved (either committed or aborted) before closing the database. diff --git a/docs_src/guides/gsg_txn/cxx/exclusivelock.md b/docs_src/guides/gsg_txn/cxx/exclusivelock.md new file mode 100644 index 000000000..4b119ecb3 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/exclusivelock.md @@ -0,0 +1,18 @@ +--- +title: "Exclusive Database Handles" +api-name: "Exclusive Database Handles" +source: docs/gsg_txn/CXX/exclusivelock.html +--- +## Exclusive Database Handles + +In some cases, concurrent applications can benefit from occasionally granting exclusive access to the entire database to a single database handle. This is desirable when a thread will perform an operation that touches all or most of the pages in a database. + +To configure a handle to have exclusive access to a database, you give it a single write lock to the entire database. This causes all other threads to block when they attempt to gain a read or write lock to any part of that database. + +The exclusive lock allows for improved throughput because the handle will not attempt to acquire any further locks once it has the exclusive write lock. It will also never be blocked waiting for a lock, and there is no possibility of a deadlock/retry cycle. + +Note that an exclusive database handle can only have one transaction active for it at a time. + +To configure a database handle with an exclusive lock, you use the `Db::set_lk_exclusive()` method before you open the database handle. Setting a value of `0` to this method means that the handle open operation will block until it can obtain the exclusive lock. A non-zero value means that if the method cannot obtain the exclusive lock immediately when the handle is opened, the open operation will exit with a `DB_LOCK_NOTGRANTED` error return. + +Once configured and opened, a handled configured with an exclusive database lock will hold that lock until the handle is closed. diff --git a/docs_src/guides/gsg_txn/cxx/filemanagement.md b/docs_src/guides/gsg_txn/cxx/filemanagement.md new file mode 100644 index 000000000..609f8052f --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/filemanagement.md @@ -0,0 +1,149 @@ +--- +title: "Chapter 5. Managing DB Files" +api-name: "Chapter 5. Managing DB Files" +source: docs/gsg_txn/CXX/filemanagement.html +--- +## Chapter 5. Managing DB Files + +**Table of Contents** + + [Checkpoints](filemanagement.md#checkpoints) + + [Backup Procedures](backuprestore.md) + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + + [Recovery Procedures](recovery.md) + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + + [Designing Your Application for Recovery](architectrecovery.md) + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + + [Using Hot Failovers](hotfailover.md) + + [Removing Log Files](logfileremoval.md) + + [Configuring the Logging Subsystem](logconfig.md) + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + +DB is capable of storing several types of files on disk: + +- Data files, which contain the actual data in your database. + +- Log files, which contain information required to recover your database in the event of a system or application failure. + +- Region files, which contain information necessary for the overall operation of your application. + +- Temporary files, which are created only under certain special circumstances. These files never need to be backed up or otherwise managed and so they are not a consideration for the topics described in this chapter. See Security Considerations for more information on temporary files. + +Of these, you must manage your data and log files by ensuring that they are backed up. You should also pay attention to the amount of disk space your log files are consuming, and periodically remove any unneeded files. Finally, you can optionally tune your logging subsystem to best suit your application's needs and requirements. These topics are discussed in this chapter. + +## Checkpoints + +Before we can discuss DB file management, we need to describe checkpoints. When databases are modified (that is, a transaction is committed), the modifications are recorded in DB's logs, but they are *not* necessarily reflected in the actual database files on disk. + +This means that as time goes on, increasingly more data is contained in your log files that is not contained in your data files. As a result, you must keep more log files around than you might actually need. Also, any recovery run from your log files will take increasingly longer amounts of time, because there is more data in the log files that must be reflected back into the data files during the recovery process. + +You can reduce these problems by periodically running a checkpoint against your environment. The checkpoint: + +- Flushes dirty pages from the in-memory cache. This means that data modifications found in your in-memory cache are written to the database files on disk. Note that a checkpoint also causes data dirtied by an uncommitted transaction to also be written to your database files on disk. In this latter case, DB's normal recovery is used to remove any such modifications that were subsequently abandoned by your application using a transaction abort. + + Normal recovery is describe in Recovery Procedures. + +- Writes a checkpoint record. + +- Flushes the log. This causes all log data that has not yet been written to disk to be written. + +- Writes a list of open databases. + +There are several ways to run a checkpoint. One way is to use the **db_checkpoint** command line utility. (Note, however, that this command line utility cannot be used if your environment was opened using `DB_PRIVATE`.) + +You can also run a thread that periodically checkpoints your environment for you by calling the `DbEnv::txn_checkpoint()` method. + +Note that you can prevent a checkpoint from occurring unless more than a specified amount of log data has been written since the last checkpoint. You can also prevent the checkpoint from running unless more than a specified amount of time has occurred since the last checkpoint. These conditions are particularly interesting if you have multiple threads or processes running checkpoints. + +For configuration information, see the DbEnv::txn_checkpoint() API reference page. + +Note that running checkpoints can be quite expensive. DB must flush every dirty page to the backing database files. On the other hand, if you do not run checkpoints often enough, your recovery time can be unnecessarily long and you may be using more disk space than you really need. Also, you cannot remove log files until a checkpoint is run. Therefore, deciding how frequently to run a checkpoint is one of the most common tuning activity for DB applications. + +For example, to run a checkpoint from a separate thread of control: + +``` c +#include +#include "db_cxx.h" + +... + +void *checkpoint_thread(void *); + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + + // Start a checkpoint thread. + pthread_t ptid; + int ret; + if ((ret = pthread_create( + &ptid, NULL, checkpoint_thread, (void *)&myEnv)) != 0) { + fprintf(stderr, + "txnapp: failed spawning checkpoint thread: %s\n", + strerror(errno)); + myEnv.close(0); + exit (1); + } + + // All other threads and application shutdown code + // omitted for brevity. + + ... +} + +void * +checkpoint_thread(void *arg) { + DbEnv *dbenv = arg; + + // Checkpoint once a minute. + for (;; sleep(60)) { + try { + dbenv->txn_checkpoint(0, 0, 0)); + } catch(DbException &e) { + dbenv->err(e.get_errno(), "checkpoint thread"); + exit (e.get_errno()); + } + } + + // NOTREACHED +} +``` diff --git a/docs_src/guides/gsg_txn/cxx/hotfailover.md b/docs_src/guides/gsg_txn/cxx/hotfailover.md new file mode 100644 index 000000000..cf44f1be0 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/hotfailover.md @@ -0,0 +1,50 @@ +--- +title: "Using Hot Failovers" +api-name: "Using Hot Failovers" +source: docs/gsg_txn/CXX/hotfailover.html +--- +## Using Hot Failovers + +You can maintain a backup that can be used for failover purposes. Hot failovers differ from the backup and restore procedures described previously in this chapter in that data used for traditional backups is typically copied to offline storage. Recovery time for a traditional backup is determined by: + +- How quickly you can retrieve that storage media. Typically storage media for critical backups is moved to a safe facility in a remote location, so this step can take a relatively long time. + +- How fast you can read the backup from the storage media to a local disk drive. If you have very large backups, or if your storage media is very slow, this can be a lengthy process. + +- How long it takes you to run catastrophic recovery against the newly restored backup. As described earlier in this chapter, this process can be lengthy because every log file must be examined during the recovery process. + +When you use a hot failover, the backup is maintained at a location that is reasonably fast to access. Usually, this is a second disk drive local to the machine. In this situation, recovery time is very quick because you only have to reopen your environment and database, using the failover environment for the environment open. + +Hot failovers obviously do not protect you from truly catastrophic disasters (such as a fire in your machine room) because the backup is still local to the machine. However, you can guard against more mundane problems (such as a broken disk drive) by keeping the backup on a second drive that is managed by an alternate disk controller. + +To maintain a hot failover: + +1. Copy all the active database files to the failover directory. Use the **db_archive** command line utility with the `-s` option to identify all the active database files. + +2. Identify all the inactive log files in your production environment and *move* these to the failover directory. Use the **db_archive** command with no command line options to obtain a list of these log files. + +3. Identify the active log files in your production environment, and *copy* these to the failover directory. Use the **db_archive** command with the `-l` option to obtain a list of these log files. + +4. Run catastrophic recovery against the failover directory. Use the **db_recover** command with the `-c` option to do this. + +5. Optionally copy the backup to an archival location. + +Once you have performed this procedure, you can maintain an active hot backup by repeating steps 2 - 5 as often as is required by your application. + +### Note + +If you perform step 1, steps 2-5 must follow in order to ensure consistency of your hot backup. + +### Note + +Rather than use the previous procedure, you can use the **db_hotbackup** command line utility to do the same thing. This utility will (optionally) run a checkpoint and then copy all necessary files to a target directory for you. + +To actually perform a failover, simply: + +1. Shut down all processes which are running against the original environment. + +2. If you have an archival copy of the backup environment, you can optionally try copying the remaining log files from the original environment and running catastrophic recovery against that backup environment. Do this *only* if you have a an archival copy of the backup environment. + + This step can allow you to recover data created or modified in the original environment, but which did not have a chance to be reflected in the hot backup environment. + +3. Reopen your environment and databases as normal, but use the backup environment instead of the production environment. diff --git a/docs_src/guides/gsg_txn/cxx/img/deadlock.jpg b/docs_src/guides/gsg_txn/cxx/img/deadlock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0995a84d82583bfccce034cc69e3b661fe0cb264 GIT binary patch literal 12599 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/img/readblock.jpg b/docs_src/guides/gsg_txn/cxx/img/readblock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16a511feb2a1d42e66eb303054f9616b60060390 GIT binary patch literal 10504 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 JQ4#lx{{>E>0o4Ei literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/img/rwlocks1-pdf.jpg b/docs_src/guides/gsg_txn/cxx/img/rwlocks1-pdf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..11346c0bba5795b50cf4abad3342d0ba94d4d70b GIT binary patch literal 192136 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 Mz6Ad164=51A3IEtI{*Lx literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/img/rwlocks1.jpg b/docs_src/guides/gsg_txn/cxx/img/rwlocks1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fc88fd31022397e8b5cc6292c4f28000051d7ec GIT binary patch literal 7428 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/img/simplelock-pdf.jpg b/docs_src/guides/gsg_txn/cxx/img/simplelock-pdf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78f8321d92c359985f7368bf7ef70f542b18b39f GIT binary patch literal 129293 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/img/simplelock.jpg b/docs_src/guides/gsg_txn/cxx/img/simplelock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8dca4ad820d049e31ac7c6a7abd682093f72b2c3 GIT binary patch literal 4453 zcma))yT3K6iiXo};m&Spf8!p^+f~ z$N~U3FbCjh4FEHE^uR3u00e*m0Dultq67Gl4qhlnfWt?o0z8@ogag>XU@$9~jg^&+ zlZ|BVAP5FLY6avm(+08vfy_Mr0~-s7m7N0&WQw4_ z>&%(3fWS}y5X?M-SUI>LoPRvR0sw-*tS~kyes;KkAo7;|6KNk+{bSEZ%sCRxQ0Syn z`IW~t@^CMQF!*8GornS_Aq(M4w;!k*IQWLk!k9sTAP^YzpWw_GFgQr+7I@Za)4e@W~&(NTvRY zVKJb890?aWjGaR%i;mK9EO=+S7@M|M1*uHTFRBfx*|u);q_j<$)Bc%}QU>?cOOE-P zPO5STwl+}ZS$K;rT{cR5&Xww*WgsjnGg>sjz2wlJTJK*VuP~mu=R4b5-c+$eJzzNqjqw)NhNwt8)x%@X<=JNKvGKQ9yt@0AmV{Fcl$U{EZ^ zf13tm*Wk6jXNn+M#-J~soY6VkrcRcTOZgpk@9V)JKQuXf{rF2&gqmM)1J+mA8iVpQJn|7BR0hDkO zQ7g91lu4qoR;II0C0te(Q?52H-$fDfPC%VXs4TS^A1AA0lJR;9jVeK!npgLO2o%Kq z8W*<9B-HO)7vQNH{zV0RB{vU3Euv7DQ#tIjpk5Wb69I!|~?9 zijXZI=>CB5g9I6Z10n5|28zW~X>Gj-A^Dc+1^)!tBGA)1_Mw}{R9%bQN)P7+3ysBR zSw11M_4W-z7kq)c&bfP2_|sK8qXPmoZySDxtxL$r7 zyDxt}(+W-;0X%7eS=p_Zh*4+5-yTa!tu6MC!9Y2#T_l@nn8?Ww1O}nUP}%T_7S~&VZB7o=fOWqe-XF1cjG8;{CxCj|m>nG4;sgxCDr!i4!rA>-UQa$9+N%C+?)TqM znB3U?l5Fvn%895ktl3P%RBR>(h?89y>^p33WhgdI{Xs}*^aCiKQaG#eHBe!Z@Y_7YdK`{GNT_$OSStZ;kCm)dvLuu#7K zJQWj_RLA1QZNK{~@G}!`Kgb@+T*(Dw{Yi|+-IMEGL#9<@ROLJ># z5#{u$gN}t-S3cCRq09ZDWsQA~z6q?y*V?QZL$8?aupeloO#1?ST>A+|+)43~>2xnA zJg#J7qWXDVaY?q2XhHfNpFYQPsTObV*#Fr)tg1$0J}F58V7op;hqdPB-lE#t9W}`( z^MLP*s0M1e%;Xgj`Jnf!87`mQ`AQfmxWk~bBfx8+p90+{z>k3#|^`ct2DFAojT!$CI&QZ7rMcIV*x7-R#LIgsBJ82)RYksBM!Dt zl>5ESBo)79C`ItQjvSWKXW7p2={6iJ1mEwk+vgL&SLB&sCmy!fMXop+pb%=fjJPtlRr~_&v{= zUWJotg<)4V!jvcX3PA0TOqYK`7HY%PbN&0?{*Y)otoB+=fryOgz1r0yt{)EJb_QBK zuk8gd-b1izmoIQtWA4Vj@Q9A}&lK-AavKh@nJL4MGQ9!M`+Op;I3oUp_yW%>4SZ6* zO>dky!IMl?_(8^9TE1uBPgJ|XSjTSfJLd3I+m!m_9-R$Ydhax&y`%a_Y0)7G)Yb`0 z!Ssk>IIj(cx}-w?XR_@0Q>z9T?0>dQPM0A8g3 z+vX*X(ZYRAw;GrM&A?3FYDi1hHaeHFv1IzC50bJv>7gX&PRKXy%I@C6Gph8LujP-K+$LseDH#GDfHRAMFJr0CE;7M|sHH5RK)*(RaX{gst{4YvP^JU*0%2OBvF2gq2 z+6=@F=9)X`Kn7==Iw5FFcn^n8MSro1tUX7!QEqd{k5jyf?vRkIOCXU}LrT{51dlE4 zcxo4j^$sB+9`p%9!m%6n|mRlM!8+A7hykcs{#HVt=Mw$X{Il#bZKVd`QD zxW&nGA~p-!p<+ABAsCKqg8Agf8pu)_svIcz2fNu#(|fj zM9k1|;-|#rRyg_zD9E6>UyA#@w}%a@R_q(hDNCheg`W(AASP*(r`WSwRBEnBNQ!En z)mk+}$6z!CCEy6#cEiD&ZqExB5MR03w0pYMj5X@&$4Koy+%-YU)4~j7q?DvQzX!*C zedl#&2a#ZNYRwf;dJDXEsOK`b;CS>q`iKnWBx#B4IMo;-q4ww>{#ujt1Wfn9YKcXS(VQN6ZzZH1evt)bSsB6<*W34jVHSepjQE1 z%^LWk4~?r~y-zBNh{Q1rtT_ykP~Ll8zZuGYLB6{QeF!Pu=H`E}A=LV))b^gr zHQNW74>18beU)4v#qpxRQOs?GlXKgKP zmUF?av{XqSsGtY06zJA&B?$_xUo5JX)Obnf$>${Lo#HJPUYeDi%CoBMy5BqLS~@(I zK`%JFKaDO%p3W^*k8|*@NE;&@RP1ocea^tU9zrh`e?KXApO3bIN%dX&nt5&~pmK9% z0=IDYxYFkuolC{OD3PTw6~R32Q2H$-rStFq_)_(^V%`kR>R(t9uvKAZHP>bD?oYGBnz^ z;;`vwNA&>>GRQ{^OVb-;?lXdMR`0yCc=FN;vDFeC=h+kP7GrZtsP*EKj`ft$E0qLn}KvWDz zN}50PN8MD^G(a{074T-hiqd8Ji1E^?#wCr@pIp_r;uoHGdS0Bk;9Ij0Kz^QPZ z3t=xD^6F$5DE@^h@+H(vWLET=V{n32g*P`m$AO*d=v8VOdf?Ateu0V|LM=!GI}TjA zig*66QkTb6xtK zman-6+l_GAwEMR$NP)mp?)oBeU9;j)@%IrKB+|a@7o}_g0o=XQh3-aqeqw9Kt=m;9 zZr}~vwOXcE5w5*r>nM#UtT*7u^Kv(K#*QJqTy8~(1?#NRxQmfjmSG7G|0_aTP4$p^ zraTvTaPb^lINah=u*aP%1^$SSPl#^k@8*=b=;@QfbqZ}RGtuT^UesI322X;$_!F_g zQj-^*FN49PBqK?V9@qJ8@pCc|8y~HoQc0H=>P>IRm$xqbl$!Y**{?XzNWmm zyF#b33K-sQ?B2m!EvsHcnZ#2BDvvz%7rrZAt7fL;)k-^aGzyU)%PO}i7POjl^q^NH zHiqAk^3T+yzb?9N(0DNiqh5<(1S6;vp0maMh-Ad6_=809NFV9$Im4yeqM}Z!Occ-t z+A&&+(@W~lOM)`CHI-GqwvPKNKen+z`Yy(s28Z1X5J)M$d=sk}t{<6V6*+r>AvLU$ z-DxPzoF9sdRju-it3H##D#m<5?z+FRRj!cwt+j=8>zY^Sk46E?0-epDkhSC@?s_X) z`?=sjDqL#4Ue;v97Osxffuh#LGCAS1CoS43|Ke_sk^GF}#w#^dr5MuS<)iuLdABX9 zE3O>^xZ`PGQ~d<3>%Dy+(o{vGte#9h=x;#Em56}`ubzEB`4Vet(=Cv~-LfCs`mvT) z**L z{MXkML$Ob0-pGG-fY@$IV}Z!}Qe&nWDRyTOCY$QUt@`xtGO8^sYI*d|yfwO=#jW4_%r%SQ zF<65^F$C)Z;v0IF<>1C^R9tLa+T)S)$u1pW@nR^{!ov2`)V&f{ck>3i88!n^Te zrg!IdlO%=h1J18}7<3_a>WXhA?Y1g^PvT%eHjx1%}z1=vc2wpHY)_ALuE+0hYp5g+M6CR~V2JqVk2oVT+@ zq3*@hOzG6$+u!&s^8knD2{fyHM7vF^f;a?tE^-d{_@^v~kwxY{Ez4`6n1iUULOd4y zu2@}RJAzrA-3C8+`w$SV7d#+>=;hHBDC@j8ZEM&sD?6^DqT#R1a@Gdd&5L1EjfY0- zwh}c(^EeoPKv$~k1U{XR=%l8y5d_djXlq(Nm!o((I&PiKL$VA40BlrtRE~C>Zak^D z)KvOg!}%-etUwQ7>?IK^nr3Z>S!1o-*jbapohxX8e7NK&wr(r>B&Ayi`M_D%g_BxP zFs!@564=qPKA2`aa#f;7is=yW$~RiPpmwp${B&pk6&mFoe+7Y!xstB|hN@O+wU?Yx z57scG#qu_JGi{EGa1l91UL;K!1A{`*Man3dT=b#u7Z-jPw2T_^6BAIl^B|NNt2wG@ z{ey(hbSD}`g-&2coJP7CcXAe83p7AB*}>d`u^?gY?1&pe5j>$l+DdkI_fALmc~~E< z#v8&FHwGyCA;1z#F(*K=o90EpYVg6CK7rl4GGsEjf1psqRYhRuDC~#arV!C)rAiTq zbJORNri)C!lQV6IBKFAmif3-oiDB!qONx6EA`ceK4+cIR+`_II(5EkEjqUOGq7Ixj z*D}m@HA1%Cr}`vLd%X#AS_zVw*nUmZf8T_Bz&eKulF#<-FWAp&*~5CwKjWLwVy~5& zTT8u1KC#&Q>Fcb6Yex5v%0piPJ=ebaN*^ zW@FGE_Yc_7GlF&_z$f_c3cpsMlLF8(_$ob$>CYX!; zOZ9I$?&-stn%mY3V{9NpQNjzDc3{Pd@#E1v+z4mCs<~ zi{?ix%azGSF772tUt*eRg07pDKxlxkNppZK;q!nKq4ktvz9W;27=~!9{B^2u^A#-G5K#ygtb?q~Ksmpp+^z_J;w*DD zdh8WEly}=pWh8+N!`yJXP_R z3h&-4!**edZ%|lJ6@N8-Q+FNIY=f(cwfxt?9@Tp{*WS#;f$a z{LMmhjjA#|EH;cCO9?@HJe<F zS&sPDVy7L47Vy|8jNWGdQnOk0(??~ZOyoaC^;-MZXl%1}qeR_X(Kl7A^-_W~}fsg71D!7BX z;h{N~2gg{m0ee59fsuX1q92eU`%@f!Aj z@{9UWksWus_P&1QAwby;+}87c_{R%adEK3lIlT^otxKM<;Oh)I1#yuzfZ4zn1}zIa zWZ<;>05iCO?(0!Xz?Vtnj;52d%Dmb2A&;w0?gOcNH}p~!Qhdb`mhW+}Hu{6iHrRaG z9_OFxRDKS82xx{7`VIkdAZF5T3(NGx1!>_=rweQzPCps8g8SSVzVX`DhdwIlUPo^6 z+XzvutrrSZ@Cn0(9E6JXP{y9OyVln|i3>p#u3e|~<&`gD3Jc4ESUE20YWvWdNAW*& zdi6$xxPmBr!E&7IrFG0z^M$@1=lfWtTKX1N70)K;aq)AZSu*OfDSxf|M-3PU&cg&V zUYoWt72aW>;1t>r5_=pH^x5IGIQj?y*1IHN|46G=BK8j1%i#4Xyo=Abul?~U;R3O0 zH&P?!p3&QI4O@E$&T@#COC}koesy^(>w+q|RiXm13hAm3Q9}oEx5xC0*~!vv8Rc&( zGpR03bG4fA5w)IF7#|2=?r?#K)?`+iAv+KZ4d9@LYF=(9Ql{#~t1$x5?i5*FJlm`;^C_4;V{B|}9kt|rdURZo?4Xc)YCn0TqwaiO z>Qwt|s9x3t8YcZPgpgIQK2z)O*T~a1o+KtQYQI$?y&C;mc*n<3cO3R+&}4F~_LR3% z>2;}I`*eg!9KKU$Q|b~limI!h2E5rNZAfP4&RxN6HGUuZFBJiGV{C0Ms zFk|#9B5wmPlSx=ici0AMh+dOYE85-{U*K2#vXE^6^~^-dgh>a^&x7b>e+o&dSmALl zaRGUA@z+0SA`J|&AXa=cWgQzD&5UJrn!4wAraFirAG`h+;Uoj}7vB;`y*0n{qL2!- zJxR*4ZBHMUb;V4CQe`zvVMm=T-IqGK(B9T`0or@sooA)_Fb(+|y0#9QH(VUVf?aq{ z6)jmMWXiEY&=gO>0Lw!FqmS6gva0#F9m%h2bB$1EUk~Bf+L$K$u+dELeFurW2wZ#E z`u)N%(JHTNt4YN%2}#P>#SF{blAK6< z{H;UD#}@|T&q)Pz5<;iH3kC4dSD#br?66%O?UYq`aq|=*a6n9fIp}4`1uFVPzwL`1 z?ysjZuf9_mvdE<0)U@Niy@NQ;>>YA&CPO< zG@4H>`B`zo8H8Ca(Cdownx=(lSsyj1?}c1k4m;9|;T8<#K0QP$1594z#SATfBx3h( z&(|kIjr%>$rsnPCtJVEl`+gT8uil!0?F{o`io0l~@27XXl4u##8$Cab;1Y8RGI&zl zD_<;sRk`3DCa+3Ktox*|%j0cxtI|0yRd7K}DcOLgOoX=xR0yO=G*!`p6baheD7;)?4iSuNxq^4V@IX<@{TY8NJ1wYh0tfV-cVmSn8?j+xAxkL*p z(IE}D*t64b=1O38Bp{75et8FxJJ(FYHFskcvv$P4UQ_3xSyFrx^d;-H`40yUu-BH) zB=#+h<4tKJv7nx(-MrsxcG;t~;U#O4aULbvx17FUhBTxOOnT>5T)(ZlcJxgj3}Z$< zj=}8y3ZXEXw2C^Xno)wNXFiY#1x@mCh!qn@eGaCQmApgV+7nR2eDhB<%ynalDMC$O zsCa%*wvoxcwA@UIb_A!%tD=ycmHxFVqJ%0HESmSZLh$4|Z3_Klg(MynhDLuM(0?a1 z#2GtTaNkkrKOZywOACtLTFJ(YoOD@H=^9N;YaJ!bEPK*(&R1M8ILgj4APTI*ierUHbr9%&o z!dSP;-VIdW(0KOdfe`&9{<8X*?SW~*N(8?@pY-mXkZps!R)GMrcLgPhb?!_S=+S?9 z{6gCY^^`sdEtch0ed*8^Uc2~wZEcerP=$UWu8#2{HH(-nK*yn#x_{Pq2MR^uaCIOn zg~gJz27&2s-Ea=Jq}t4uVv!=lKE8ke=d>nE+8Pykr=Vl-3^&JJ97pJb+|)qT2fOvM ziO1xFXOl11!;!Y!bLGu4f)7<{B2T9qx6ba!Rq=TBNm}zQUi)MEzeRJb;>VbrUES&F z;GSRP4K99v2*}?)OL$-}&vk-N6qPb@?2alNsVNCBnwJsOVIk-UmT*MWLREImuen>k zc77=it+MT#r0%(q&?zR!cdjAirv8l#BH8(yA!%I4q-CtIqUk=e6I1m zx1LlqIy$X<$OvMDf-3G3`bG(a9x`v-Z>#Eu&h3qSpdO^VRIo+ayGMP>ajc&BEubO5Sc&q z^%Z!ghYW|>M7%jX!JWZ}PCban4>Q`OMB(cd^D7<`DLEmoGd3(KWitP!uZqQoRNBg|iS z*18fc<~cCouih^-f5ulDSd3}4XiH;MxuDO$SRYf;5O{i)CvBM4^+NFX^9F*YToP7v zWrgiFI_nCP(9cc*%-NrpR$+K&w+SUwcb`KxPkGYh=MTB8e0Ogr@>)U4)L+f@%s_`~ zB1!8G+zlo>n1nrir!WlhS6utE0W&2|*(hKr{83LB&-{uv$FODxIkQcW)ri4=9rOJ8 wll_)`3%4eB3agveat{IJA^%QlM60t#iWCp-d+fG`tZ)CH9oIh!`oppR0mMaI^Z)<= literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/cxx/index.md b/docs_src/guides/gsg_txn/cxx/index.md new file mode 100644 index 000000000..18e4789c2 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/index.md @@ -0,0 +1,170 @@ +--- +title: "Getting Started with Berkeley DB Transaction Processing" +api-name: "Getting Started with Berkeley DB Transaction Processing" +source: docs/gsg_txn/CXX/index.html +--- +# Getting Started with Berkeley DB Transaction Processing + +**Language:** [C](../index.md) · C++ (this page) · [Java](../java/index.md) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + + [Preface](preface.md) + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + + [1. Introduction](introduction.md) + + [Transaction Benefits](introduction.md#txnintro) + + [A Note on System Failure](sysfailure.md) + + [Application Requirements](apireq.md) + + [Multi-threaded and Multi-process Applications](multithread-intro.md) + + [Recoverability](recovery-intro.md) + + [Performance Tuning](perftune-intro.md) + + [2. Enabling Transactions](enabletxn.md) + + [Environments](enabletxn.md#environments) + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + + [Opening a Transactional Environment and Database](envopen.md) + + [3. Transaction Basics](usingtxns.md) + + [Committing a Transaction](usingtxns.md#commitresults) + + [Non-Durable Transactions](nodurabletxn.md) + + [Aborting a Transaction](abortresults.md) + + [Auto Commit](autocommit.md) + + [Nested Transactions](nestedtxn.md) + + [Transactional Cursors](txncursor.md) + + [Secondary Indices with Transaction Applications](txnindices.md) + + [Configuring the Transaction Subsystem](maxtxns.md) + + [4. Concurrency](txnconcurrency.md) + + [Which DB Handles are Free-Threaded](txnconcurrency.md#concurrenthandles) + + [Locks, Blocks, and Deadlocks](blocking_deadlocks.md) + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + + [The Locking Subsystem](lockingsubsystem.md) + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + + [Isolation](isolation.md) + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + + [Transactional Cursors and Concurrent Applications](txn_ccursor.md) + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + + [Exclusive Database Handles](exclusivelock.md) + + [Read/Modify/Write](readmodifywrite.md) + + [No Wait on Blocks](txnnowait.md) + + [Reverse BTree Splits](reversesplit.md) + + [5. Managing DB Files](filemanagement.md) + + [Checkpoints](filemanagement.md#checkpoints) + + [Backup Procedures](backuprestore.md) + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + + [Recovery Procedures](recovery.md) + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + + [Designing Your Application for Recovery](architectrecovery.md) + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + + [Using Hot Failovers](hotfailover.md) + + [Removing Log Files](logfileremoval.md) + + [Configuring the Logging Subsystem](logconfig.md) + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + + [6. Summary and Examples](wrapup.md) + + [Anatomy of a Transactional Application](wrapup.md#anatomy) + + [Transaction Example](txnexample_c.md) + + [In-Memory Transaction Example](inmem_txnexample_c.md) diff --git a/docs_src/guides/gsg_txn/cxx/inmem_txnexample_c.md b/docs_src/guides/gsg_txn/cxx/inmem_txnexample_c.md new file mode 100644 index 000000000..7acfe3d20 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/inmem_txnexample_c.md @@ -0,0 +1,396 @@ +--- +title: "In-Memory Transaction Example" +api-name: "In-Memory Transaction Example" +source: docs/gsg_txn/CXX/inmem_txnexample_c.html +--- +## In-Memory Transaction Example + +DB is sometimes used for applications that simply need to cache data retrieved from some other location (such as a remote database server). DB is also often used in embedded systems. + +In both cases, applications may want to use transactions for atomicity, consistency, and isolation guarantees, but they may also want to forgo the durability guarantee entirely. In doing so, they can keep their DB environment and databases entirely in-memory so as to avoid the performance impact of unneeded disk I/O. + +To do this: + +- Refrain from specifying a home directory when you open your environment. The exception to this is if you are using the `DB_CONFIG` configuration file — in that case you must identify the environment's home directory so that the configuration file can be found. + +- Configure your environment to back your regions from system memory instead of the filesystem. + +- Configure your logging subsystem such that log files are kept entirely in-memory. + +- Increase the size of your in-memory log buffer so that it is large enough to hold the largest set of concurrent write operations. + +- Increase the size of your in-memory cache so that it can hold your entire data set. You do not want your cache to page to disk. + +- Do not specify a file name when you open your database(s). + +As an example, this section takes the transaction example provided in Transaction Example and it updates that example so that the environment, database, log files, and regions are all kept entirely in-memory. + +For illustration purposes, we also modify this example so that uncommitted reads are no longer used to enable the `countRecords()` function. Instead, we simply provide a transaction handle to `countRecords()` so as to avoid the self-deadlock. Be aware that using a transaction handle here rather than uncommitted reads will work just as well as if we had continued to use uncommitted reads. However, the usage of the transaction handle here will probably cause more deadlocks than using read-uncommitted does, because more locking is being performed in this case. + +To begin, we simplify the beginning of our example a bit. Because we no longer need an environment home directory, we can remove all the code that we used to determine path delimiters and include the `getopt` function. We can also remove our `usage()` function because we no longer require any command line arguments. + +``` c +// File TxnGuideInMemory.cpp + +// We assume an ANSI-compatible compiler +#include +#include +#include + +// Run 5 writers threads at a time. +#define NUMWRITERS 5 + +// Printing of pthread_t is implementation-specific, so we +// create our own thread IDs for reporting purposes. +int global_thread_num; +pthread_mutex_t thread_num_lock; + +// Forward declarations +int countRecords(Db *, DbTxn *); +int openDb(Db **, const char *, const char *, DbEnv *, u_int32_t); +int usage(void); +void *writerThread(void *); +``` + +Next, in our `main()`, we also eliminate some variables that this example no longer needs. In particular, we are able to remove the `dbHomeDir` and `fileName` variables. We also remove all our `getopt` code. + +``` c +int +main(void) +{ + // Initialize our handles + Db *dbp = NULL; + DbEnv *envp = NULL; + + pthread_t writerThreads[NUMWRITERS]; + int i; + u_int32_t envFlags; + + // Application name + const char *progName = "TxnGuideInMemory"; +``` + +Next we create our environment as always. However, we add `DB_PRIVATE` to our environment open flags. This flag causes our environment to back regions using our application's heap memory rather than by using the filesystem. This is the first important step to keeping our DB data entirely in-memory. + +We also remove the `DB_RECOVER` flag from the environment open flags. Because our databases, logs, and regions are maintained in-memory, there will never be anything to recover. + +Note that we show the additional code here in **`bold.`** + +``` c + // Env open flags + envFlags = + DB_CREATE | // Create the environment if it does not exist + DB_INIT_LOCK | // Initialize the locking subsystem + DB_INIT_LOG | // Initialize the logging subsystem + DB_INIT_TXN | // Initialize the transactional subsystem. This + // also turns on logging. + DB_INIT_MPOOL | // Initialize the memory pool (in-memory cache) + DB_PRIVATE | // Region files are not backed by the filesystem. + // Instead, they are backed by heap memory. + DB_THREAD; // Cause the environment to be free-threaded + + try { + // Create the environment + envp = new DbEnv(0); +``` + +Now we configure our environment to keep the log files in memory, increase the log buffer size to 10 MB, and increase our in-memory cache to 10 MB. These values should be more than enough for our application's workload. + +``` c + + // Specify in-memory logging + envp->log_set_config(DB_LOG_IN_MEMORY, 1); + + // Specify the size of the in-memory log buffer. + envp->set_lg_bsize(10 * 1024 * 1024); + + // Specify the size of the in-memory cache + envp->set_cachesize(0, 10 * 1024 * 1024, 1); + + +``` + +Next, we open the environment and setup our lock detection. This is identical to how the example previously worked, except that we do not provide a location for the environment's home directory. + +``` c + // Indicate that we want db to internally perform deadlock + // detection. Also indicate that the transaction with + // the fewest number of write locks will receive the + // deadlock notification in the event of a deadlock. + envp->set_lk_detect(DB_LOCK_MINWRITE); + + // Open the environment + envp->open(NULL, envFlags, 0); +``` + +When we call `openDb()`, which is what we use to open our database, we no not provide a database filename for the third parameter. When the filename is `NULL`, the database is not backed by the filesystem. + +``` c + // If we had utility threads (for running checkpoints or + // deadlock detection, for example) we would spawn those + // here. However, for a simple example such as this, + // that is not required. + + // Open the database + openDb(&dbp, progName, NULL, + envp, DB_DUPSORT); + +``` + +After that, our `main()` function is unchanged, except that when we check for exceptions on the database open, we change the error message string so as to not reference the database filename. + +``` c + // Initialize a pthread mutex. Used to help provide thread ids. + (void)pthread_mutex_init(&thread_num_lock, NULL); + + // Start the writer threads. + for (i = 0; i < NUMWRITERS; i++) + (void)pthread_create( + &writerThreads[i], NULL, + writerThread, + (void *)dbp); + + // Join the writers + for (i = 0; i < NUMWRITERS; i++) + (void)pthread_join(writerThreads[i], NULL); + + } catch(DbException &e) { + std::cerr << "Error opening database environment: " + << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + try { + // Close our database handle if it was opened. + if (dbp != NULL) + dbp->close(0); + + // Close our environment if it was opened. + if (envp != NULL) + envp->close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment." + << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + // Final status message and return. + + std::cout << "I'm all done." << std::endl; + return (EXIT_SUCCESS); +} +``` + +That completes `main()`. The bulk of our `writerThread()` function implementation is unchanged from the initial transaction example, except that we now pass `countRecords` a transaction handle, rather than configuring our application to perform uncommitted reads. Both mechanisms work well-enough for preventing a self-deadlock. However, the individual count in this example will tend to be lower than the counts seen in the previous transaction example, because `countRecords()` can no longer see records created but not yet committed by other threads. + +``` c +// A function that performs a series of writes to a +// Berkeley DB database. The information written +// to the database is largely nonsensical, but the +// mechanism of transactional commit/abort and +// deadlock detection is illustrated here. +void * +writerThread(void *args) +{ + Db *dbp = (Db *)args; + DbEnv *envp = dbp->get_env(dbp); + + int j, thread_num; + int max_retries = 20; // Max retry on a deadlock + char *key_strings[] = {"key 1", "key 2", "key 3", "key 4", + "key 5", "key 6", "key 7", "key 8", + "key 9", "key 10"}; + + // Get the thread number + (void)pthread_mutex_lock(&thread_num_lock); + global_thread_num++; + thread_num = global_thread_num; + (void)pthread_mutex_unlock(&thread_num_lock); + + // Initialize the random number generator + srand((u_int)pthread_self()); + + // Perform 50 transactions + for (int i=0; i<50; i++) { + DbTxn *txn; + bool retry = true; + int retry_count = 0; + // while loop is used for deadlock retries + while (retry) { + // try block used for deadlock detection and + // general db exception handling + try { + + // Begin our transaction. We group multiple writes in + // this thread under a single transaction so as to + // (1) show that you can atomically perform multiple + // writes at a time, and (2) to increase the chances + // of a deadlock occurring so that we can observe our + // deadlock detection at work. + + // Normally we would want to avoid the potential for + // deadlocks, so for this workload the correct thing + // would be to perform our puts with auto commit. But + // that would excessively simplify our example, so we + // do the "wrong" thing here instead. + txn = NULL; + envp->txn_begin(NULL, &txn, 0); + // Perform the database write for this transaction. + for (j = 0; j < 10; j++) { + Dbt key, value; + key.set_data(key_strings[j]); + key.set_size((strlen(key_strings[j]) + 1) * + sizeof(char)); + + int payload = rand() + i; + value.set_data(&payload); + value.set_size(sizeof(int)); + + // Perform the database put + dbp->put(txn, &key, &value, 0); + } + + // countRecords runs a cursor over the entire database. + // We do this to illustrate issues of deadlocking + std::cout << thread_num << " : Found " + << countRecords(dbp, txn) + << " records in the database." << std::endl; + + std::cout << thread_num << " : committing txn : " << i + << std::endl; + + // commit + try { + txn->commit(0); + retry = false; + txn = NULL; + } catch (DbException &e) { + std::cout << "Error on txn commit: " + << e.what() << std::endl; + } + } catch (DbDeadlockException &de) { + // First thing we MUST do is abort the transaction. + if (txn != NULL) + (void)txn->abort(); + + // Now we decide if we want to retry the operation. + // If we have retried less than max_retries, + // increment the retry count and goto retry. + if (retry_count < max_retries) { + std::cout << "############### Writer " << thread_num + << ": Got DB_LOCK_DEADLOCK.\n" + << "Retrying write operation." + << std::endl; + retry_count++; + retry = true; + } else { + // Otherwise, just give up. + std::cerr << "Writer " << thread_num + << ": Got DeadLockException and out of " + << "retries. Giving up." << std::endl; + retry = false; + } + } catch (DbException &e) { + std::cerr << "db put failed" << std::endl; + std::cerr << e.what() << std::endl; + if (txn != NULL) + txn->abort(); + retry = false; + } catch (std::exception &ee) { + std::cerr << "Unknown exception: " << ee.what() << std::endl; + return (0); + } + } + } + return (0); +} +``` + +Next we update `countRecords()`. The only difference here is that we no longer specify `DB_READ_UNCOMMITTED` when we open our cursor. Note that even this minor change is not required. If we do not configure our database to support uncommitted reads, `DB_READ_UNCOMMITTED` on the cursor open will be silently ignored. However, we remove the flag anyway from the cursor open so as to avoid confusion. + +``` c +int +countRecords(Db *dbp, DbTxn *txn) +{ + + Dbc *cursorp = NULL; + int count = 0; + + try { + // Get the cursor + dbp->cursor(txn, &cursorp, 0); + + Dbt key, value; + while (cursorp->get(&key, &value, DB_NEXT) == 0) { + count++; + } + } catch (DbDeadlockException &de) { + std::cerr << "countRecords: got deadlock" << std::endl; + cursorp->close(); + throw de; + } catch (DbException &e) { + std::cerr << "countRecords error:" << std::endl; + std::cerr << e.what() << std::endl; + } + + if (cursorp != NULL) { + try { + cursorp->close(); + } catch (DbException &e) { + std::cerr << "countRecords: cursor close failed:" << std::endl; + std::cerr << e.what() << std::endl; + } + } + + return (count); +} +``` + +Finally, we update `openDb()`. This involves removing `DB_READ_UNCOMMITTED` from the open flags. + +``` c +// Open a Berkeley DB database +int +openDb(Db **dbpp, const char *progname, const char *fileName, + DbEnv *envp, u_int32_t extraFlags) +{ + int ret; + u_int32_t openFlags; + + try { + Db *dbp = new Db(envp, 0); + + // Point to the new'd Db + *dbpp = dbp; + + if (extraFlags != 0) + ret = dbp->set_flags(extraFlags); + + // Now open the database + openFlags = DB_CREATE | // Allow database creation + DB_THREAD | + DB_AUTO_COMMIT; // Allow auto commit + + dbp->open(NULL, // Txn pointer + fileName, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + openFlags, // Open flags + 0); // File mode. Using defaults + } catch (DbException &e) { + std::cerr << progname << ": openDb: db open failed:" << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +This completes our in-memory transactional example. If you would like to experiment with this code, you can find the example in the following location in your DB distribution: + +``` c +DB_INSTALL/examples_cxx/txn_guide +``` diff --git a/docs_src/guides/gsg_txn/cxx/introduction.md b/docs_src/guides/gsg_txn/cxx/introduction.md new file mode 100644 index 000000000..fa5feeaca --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/introduction.md @@ -0,0 +1,50 @@ +--- +title: "Chapter 1. Introduction" +api-name: "Chapter 1. Introduction" +source: docs/gsg_txn/CXX/introduction.html +--- +## Chapter 1. Introduction + +**Table of Contents** + + [Transaction Benefits](introduction.md#txnintro) + + [A Note on System Failure](sysfailure.md) + + [Application Requirements](apireq.md) + + [Multi-threaded and Multi-process Applications](multithread-intro.md) + + [Recoverability](recovery-intro.md) + + [Performance Tuning](perftune-intro.md) + +This book provides a thorough introduction and discussion on transactions as used with Berkeley DB (DB). It begins by offering a general overview to transactions, the guarantees they provide, and the general application infrastructure required to obtain full transactional protection for your data. + +This book also provides detailed examples on how to write a transactional application. Both single threaded and multi-threaded (as well as multi-process applications) are discussed. A detailed description of various backup and recovery strategies is included in this manual, as is a discussion on performance considerations for your transactional application. + +You should understand the concepts from the *Getting Started with Berkeley DB* guide before reading this book. + +## Transaction Benefits + +Transactions offer your application's data protection from application or system failures. That is, DB transactions offer your application full ACID support: + +- **A**tomicity + + 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 to your databases. Further, in the event that you abort a transaction, all write operations performed during the transaction are discarded. In this event, your database is left in the state it was in before the transaction began, regardless of the number or type of write operations you may have performed during the course of the transaction. + + Note that DB transactions can span one or more database handles. + +- **C**onsistency + + Your databases will never see a partially completed transaction. This is true even if your application fails while there are in-progress transactions. If the application or system fails, then either all of the database changes appear when the application next runs, or none of them appear. + + In other words, whatever consistency requirements your application has will never be violated by DB. If, for example, your application requires every record to include an employee ID, and your code faithfully adds that ID to its database records, then DB will never violate that consistency requirement. The ID will remain in the database records until such a time as your application chooses to delete it. + +- **I**solation + + While a transaction is in progress, your databases will appear to the transaction as if there are no other operations occurring outside of the transaction. That is, operations wrapped inside a transaction will always have a clean and consistent view of your databases. They never have to see updates currently in progress under the protection of another transaction. Note, however, that isolation guarantees can be relaxed from the default setting. See Isolation for more information. + +- **D**urability + + Once committed to your databases, your modifications will persist even in the event of an application or system failure. Note that like isolation, your durability guarantee can be relaxed. See Non-Durable Transactions for more information. diff --git a/docs_src/guides/gsg_txn/cxx/isolation.md b/docs_src/guides/gsg_txn/cxx/isolation.md new file mode 100644 index 000000000..179f94f4c --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/isolation.md @@ -0,0 +1,294 @@ +--- +title: "Isolation" +api-name: "Isolation" +source: docs/gsg_txn/CXX/isolation.html +--- +## Isolation + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + +Isolation guarantees are an important aspect of transactional protection. Transactions ensure the data your transaction is working with will not be changed by some other transaction. Moreover, the modifications made by a transaction will never be viewable outside of that transaction until the changes have been committed. + +That said, there are different degrees of isolation, and you can choose to relax your isolation guarantees to one degree or another depending on your application's requirements. The primary reason why you might want to do this is because of performance; the more isolation you ask your transactions to provide, the more locking that 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 relaxing your isolation guarantees, you can *potentially* improve your application's throughput. Whether you actually see any improvement depends, of course, on the nature of your application's data and transactions. + +### Supported Degrees of Isolation + +DB supports the following levels of isolation: + + + + + + + + + + + + + + + + + + + + + + + + + + +
DegreeANSI TermDefinition
1READ UNCOMMITTEDUncommitted reads means that one transaction will never overwrite another transaction's dirty data. Dirty data is data that a transaction has modified but not yet committed to the underlying data store. However, uncommitted reads allows a transaction to see data dirtied by another transaction. In addition, a transaction may read data dirtied by another transaction, but which subsequently is aborted by that other transaction. In this latter case, the reading transaction may be reading data that never really existed in the database.
2READ COMMITTED

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

+

In addition, this isolation level guarantees that data will never change so long as it is addressed by the cursor, but the data may change before the reading cursor is closed. In the case of a transaction, data at the current cursor position will not change, but once the cursor moves, the previous referenced data can change. This means that readers release read locks before the cursor is closed, and therefore, before the transaction completes. Note that this level of isolation causes the cursor to operate in exactly the same way as it does in the absence of a transaction.

3SERIALIZABLE

Committed read is observed, plus the data read by a transaction, T, will never be dirtied by another transaction before T completes. This means that both read and write locks are not released until the transaction completes.

+

In addition, no transactions will see phantoms. Phantoms are records returned as a result of a search, but which were not seen by the same transaction when the identical search criteria was previously used.

+

This is DB's default isolation guarantee.

+ +By default, DB transactions and transactional cursors offer serializable isolation. You can optionally reduce your isolation level by configuring DB to use uncommitted read isolation. See Reading Uncommitted Data for more information. You can also configure DB to use committed read isolation. See Committed Reads for more information. + +Finally, in addition to DB's normal degrees of isolation, you can also use *snapshot isolation*. This allows you to avoid the read locks that serializable isolation requires. See Using Snapshot Isolation for details. + +### Reading Uncommitted Data + +Berkeley DB allows you to configure your application to read data that has been modified but not yet committed by another transaction; that is, dirty data. When you do this, you may see a performance benefit by allowing your application to not have to block waiting for write locks. On the other hand, the data that your application is reading may change before the transaction has completed. + +When used with transactions, uncommitted reads means that one transaction can see data modified but not yet committed by another transaction. When used with transactional cursors, uncommitted reads means that any database reader can see data modified by the cursor before the cursor's transaction has committed. + +Because of this, uncommitted reads allow a transaction to read data that may subsequently be aborted by another transaction. In this case, the reading transaction will have read data that never really existed in the database. + +To configure your application to read uncommitted data: + +1. Open your database such that it will allow uncommitted reads. You do this by specifying `DB_READ_UNCOMMITTED` when you open your database. + +2. Specify `DB_READ_UNCOMMITTED` when you create the transaction, open the cursor, or read a record from the database. + +For example, the following opens the database such that it supports uncommitted reads, and then creates a transaction that causes all reads performed within it to use uncommitted reads. Remember that simply opening the database to support uncommitted reads is not enough; you must also declare your read operations to be performed using uncommitted reads. + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | // Create the db if it does + // not exist + DB_AUTO_COMMIT | // Enable auto commit + DB_READ_UNCOMMITTED; // Enable uncommitted reads + + Db *dbp = NULL; + const char *file_name = "mydb.db"; + const char *keystr ="thekey"; + const char *datastr = "thedata"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + DbTxn *txn = NULL; + myEnv.txn_begin(NULL, &txn, DB_READ_UNCOMMITTED); + + // 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). + + ... +``` + +### Committed Reads + +You can configure your transaction so that the data being read by a transactional cursor is consistent so long as it is being addressed by the cursor. However, once the cursor is done reading the record (that is, reading records from the page that it currently has locked), the cursor releases its lock on that record or page. This means that the data the cursor has read and released may change before the cursor's transaction has completed. + +For example, suppose you have two transactions, `Ta` and `Tb`. Suppose further that `Ta` has a cursor that reads `record R`, but does not modify it. Normally, `Tb` would then be unable to write `record R` because `Ta` would be holding a read lock on it. But when you configure your transaction for committed reads, `Tb` *can* modify `record R` before `Ta` completes, so long as the reading cursor is no longer addressing the record or page. + +When you configure your application for this level of isolation, you may see better performance throughput because there are fewer read locks being held by your transactions. Read committed isolation is most useful when you have a cursor that is reading and/or writing records in a single direction, and that does not ever have to go back to re-read those same records. In this case, you can allow DB to release read locks as 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: + +- Create your transaction such that it allows committed reads. You do this by specifying `DB_READ_COMMITTED` when you open the transaction. + +- Specify `DB_READ_COMMITTED` when you open the cursor. + +For example, the following creates a transaction that allows committed reads: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + // Notice that we do not have to specify any flags to the database to + // allow committed reads (this is as opposed to uncommitted reads + // where we DO have to specify a flag on the database open. + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + DbTxn *txn = NULL; + + // Open the transaction and enable committed reads. All cursors + // open with this transaction handle will use read committed + // isolation. + myEnv.txn_begin(NULL, &txn, DB_READ_COMMITTED); + + // From here, you perform your database reads and writes as + // normal, committing and aborting the transactions as is + // necessary, testing for deadlock exceptions as normal + // (omitted for brevity). + + // Using transactional cursors with concurrent applications is + // described in more detail in the following section. + + ... +``` + +### Using Snapshot Isolation + +By default DB uses serializable isolation. An important side effect of this isolation level is that read operations obtain read locks on database pages, and then hold those locks until the read operation is completed. When you are using transactional cursors, this means that read locks are held until the transaction commits or aborts. In that case, over time a transactional cursor can gradually block all other transactions from writing to the database. + +You can avoid this by using snapshot isolation. Snapshot isolation uses *multiversion concurrency control* to guarantee repeatable reads. What this means is that every time a writer would take a read lock on a page, instead a copy of the page is made and the writer operates on that page copy. This frees other writers from blocking due to a read lock held on the page. + +### Note + +Snapshot isolation is strongly recommended for read-only threads when writer threads are also running, as this will eliminate read-write contention and greatly improve transaction throughput for your writer threads. However, in order for snapshot isolation to work for your reader-only threads, you must of course use transactions for your DB reads. + +#### Snapshot Isolation Cost + +Snapshot isolation does not come without a cost. Because pages are being duplicated before being operated upon, the cache will fill up faster. This means that you might need a larger cache in order to hold the entire working set in memory. + +If the cache becomes full of page copies before old copies can be discarded, additional I/O will occur as pages are written to temporary "freezer" files on disk. This can substantially reduce throughput, and should be avoided if possible by configuring a large cache and keeping snapshot isolation transactions short. + +You can estimate how large your cache should be by taking a checkpoint, followed by a call to the `DbEnv::log_archive()` method. The amount of cache required is approximately double the size of the remaining log files (that is, the log files that cannot be archived). + +#### Snapshot Isolation Transactional Requirements + +In addition to an increased cache size, you may also need to increase the number of transactions that your application supports. (See Configuring the Transaction Subsystem for details on how to set this.) In the worst case scenario, you might need to configure your application for one more transaction for every page in the cache. This is because transactions are retained until the last page they created is evicted from the cache. + +#### When to Use Snapshot Isolation + +Snapshot isolation is best used when all or most of the following conditions are true: + +- You can have a large cache relative to your working data set size. + +- You require repeatable reads. + +- You will be using transactions that routinely work on the entire database, or more commonly, there is data in your database that will be very frequently written by more than one transaction. + +- Read/write contention is limiting your application's throughput, or the application is all or mostly read-only and contention for the lock manager mutex is limiting throughput. + +#### How to use Snapshot Isolation + +You use snapshot isolation by: + +- Opening the database with multiversion support. You can configure this either when you open your environment or when you open your database. Use the `DB_MULTIVERSION` flag to configure this support. + +- Configure your cursor or transaction to use snapshot isolation. + + To do this, pass the `DB_TXN_SNAPSHOT` flag when you open the cursor or create the transaction. If configured for the transaction, then this flag is not required when the cursor is opened. + +The simplest way to take advantage of snapshot isolation is for queries: keep update transactions using full read/write locking and use snapshot isolation on read-only transactions or cursors. This should minimize blocking of snapshot isolation transactions and will avoid deadlock errors. + +If the application has update transactions which read many items and only update a small set (for example, scanning until a desired record is found, then modifying it), throughput may be improved by running some updates at snapshot isolation as well. But doing this means that you must manage deadlock errors. See Resolving Deadlocks for details. + +The following code fragment turns on snapshot isolation for a transaction: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + // Note that no special flags are required here for snapshot isolation. + // This is because it will be enabled at the environment level. + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + // Support snapshot isolation + myEnv.set_flags(DB_MULTIVERSION, 1); + dbp = new Db(&myEnv, 0); + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + .... + + envp->txn_begin(NULL, txn, DB_TXN_SNAPSHOT); + + // Remainder of program omitted for brevity. + + +``` diff --git a/docs_src/guides/gsg_txn/cxx/lockingsubsystem.md b/docs_src/guides/gsg_txn/cxx/lockingsubsystem.md new file mode 100644 index 000000000..8bc276c58 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/lockingsubsystem.md @@ -0,0 +1,280 @@ +--- +title: "The Locking Subsystem" +api-name: "The Locking Subsystem" +source: docs/gsg_txn/CXX/lockingsubsystem.html +--- +## The Locking Subsystem + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + +In order to allow concurrent operations, DB provides the locking subsystem. This subsystem provides inter- and intra- process concurrency mechanisms. It is extensively used by DB concurrent applications, but it can also be generally used for non-DB resources. + +This section describes the locking subsystem as it is used to protect DB resources. In particular, issues on configuration are examined here. For information on using the locking subsystem to manage non-DB resources, see the *Berkeley DB Programmer's Reference Guide*. + +### Configuring the Locking Subsystem + +You initialize the locking subsystem by specifying `DB_INIT_LOCK` to the `DbEnv::open()` method. + +Before opening your environment, you can configure various values for your locking subsystem. Note that these limits can only be configured before the environment is opened. Also, these methods configure the entire environment, not just a specific environment handle. + +Finally, each bullet below identifies the `DB_CONFIG` file parameter that can be used to specify the specific locking limit. If used, these `DB_CONFIG` file parameters override any value that you might specify using the environment handle. + +The limits that you can configure are as follows: + +- The number of lockers supported by the environment. This value is used by the environment when it is opened to estimate the amount of space that it should allocate for various internal data structures. By default, 1,000 lockers are supported. + + To configure this value, use the `DbEnv::set_memory_init()` method to configure the `DB_MEM_LOCKER` structure. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_lockers` parameter. + +- The number of locks supported by the environment. By default, 1,000 locks are supported. + + To configure this value, use the `DbEnv::set_memory_init()` method to configure the `DB_MEM_LOCK` structure. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_locks` parameter. + +- The number of locked objects supported by the environment. By default, 1,000 objects can be locked. + + To configure this value, use the `DbEnv::set_memory_init()` method to configure the `DB_MEM_LOCKOBJECT` structure. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_objects` parameter. + +For a definition of lockers, locks, and locked objects, see Lock Resources. + +For example, to configure the number of locks that your environment can use: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle. + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + // Configure max locks + myEnv.set_lk_max_locks(5000); + myEnv.set_memory_init(DB_MEM_LOCK, 5000); + + myEnv.open(envHome.c_str(), env_flags, 0); + + } catch(DbException &e) { + std::cerr << "Error opening database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + try { + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database environment: " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +### Configuring Deadlock Detection + +In order for DB to know that a deadlock has occurred, some mechanism must be used to perform deadlock detection. There are three ways that deadlock detection can occur: + +1. Allow DB to internally detect deadlocks as they occur. + + To do this, you use `DbEnv::set_lk_detect()`. This method causes DB to walk its internal lock table looking for a deadlock whenever a lock request is blocked. This method also identifies how DB decides which lock requests are rejected when deadlocks are detected. For example, DB can decide to reject the lock request for the transaction that has the most number of locks, the least number of locks, holds the oldest lock, holds the most number of write locks, and so forth (see the API reference documentation for a complete list of the lock detection policies). + + You can call this method at any time during your application's lifetime, but typically it is used before you open your environment. + + Note that how you want DB to decide which thread of control should break a deadlock is extremely dependent on the nature of your application. It is not unusual for some performance testing to be required in order to make this determination. That said, a transaction that is holding the most number of locks is usually indicative of the transaction that has performed the most amount of work. Frequently you will not want a transaction that has performed a lot of work to abandon its efforts and start all over again. It is not therefore uncommon for application developers to initially select the transaction with the *minimum* number of write locks to break the deadlock. + + Using this mechanism for deadlock detection means that your application will never have to wait on a lock before discovering that a deadlock has occurred. However, walking the lock table every time a lock request is blocked can be expensive from a performance perspective. + +2. Use a dedicated thread or external process to perform deadlock detection. Note that this thread must be performing no other database operations beyond deadlock detection. + + To externally perform lock detection, you can use either the `DbEnv::lock_detect()` method, or use the **db_deadlock** command line utility. This method (or command) causes DB to walk the lock table looking for deadlocks. + + Note that like `DbEnv::set_lk_detect()`, you also use this method (or command line utility) to identify which lock requests are rejected in the event that a deadlock is detected. + + Applications that perform deadlock detection in this way typically run deadlock detection between every few seconds and a minute. This means that your application may have to wait to be notified of a deadlock, but you also save the overhead of walking the lock table every time a lock request is blocked. + +3. Lock timeouts. + + You can configure your locking subsystem such that it times out any lock that is not released within a specified amount of time. To do this, use the `DbEnv::set_timeout()` method. Note that lock timeouts are only checked when a lock request is blocked or when deadlock detection is otherwise performed. Therefore, a lock can have timed out and still be held for some length of time until DB has a reason to examine its locking tables. + + Be aware that extremely long-lived transactions, or operations that hold locks for a long time, may be inappropriately timed out before the transaction or operation has a chance to complete. You should therefore use this mechanism only if you know your application will hold locks for very short periods of time. + +For example, to configure your application such that DB checks the lock table for deadlocks every time a lock request is blocked: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + // Configure db to perform deadlock detection internally, and to + // choose the transaction that has performed the least amount + // of writing to break the deadlock in the event that one + // is detected. + myEnv.set_lk_detect(DB_LOCK_MINWRITE); + myEnv.open(envHome.c_str(), env_flags, 0); + + // From here, you open your databases, proceed with your + // database operations, and respond to deadlocks as + // is normal (omitted for brevity). + + + ... +``` + +Finally, the following command line call causes deadlock detection to be run against the environment contained in `/export/dbenv`. The transaction with the youngest lock is chosen to break the deadlock: + +``` c +> /usr/local/db_install/bin/db_deadlock -h /export/dbenv -a y +``` + +For more information, see the db_deadlock reference documentation. + +### Resolving Deadlocks + +When DB determines that a deadlock has occurred, it will select a thread of control to resolve the deadlock and then throws `DbDeadlockException` in that thread. If a deadlock is detected, the thread must: + +1. Cease all read and write operations. + +2. Close all open cursors. + +3. Abort the transaction. + +4. Optionally retry the operation. If your application retries deadlocked operations, the new attempt must be made using a new transaction. + +### Note + +If a thread has deadlocked, it may not make any additional database calls using the handle that has deadlocked. + +For example: + +``` c +// 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 DBT handles. Their usage is not shown here. +while (retry_count < MAX_DEADLOCK_RETRIES) { + try { + envp->txn_begin(NULL, txn, 0); + dbp->put(txn, &key, &data, 0); + txn->commit(0); + return (EXIT_SUCCESS); + } catch (DbDeadlockException &de) { + try { + // Abort the transaction and increment the + // retry counter + txn->abort(); + retry_count++; + // If we've retried too many times, log it and exit + if (retry_count >= MAX_DEADLOCK_RETRIES) { + envp->errx("Exceeded retry limit. Giving up."); + return (EXIT_FAILURE); + } + } catch (DbException &ae) { + envp->err(ae.get_errno(), "txn abort failed."); + return (EXIT_FAILURE); + } + } catch (DbException &e) { + try { + // For a generic error, log it and abort. + envp->err(e.get_errno(), "Error putting data."); + txn->abort(); + } catch (DbException &ae) { + envp->err(ae.get_errno(), "txn abort failed."); + return (EXIT_FAILURE); + } + } +} +``` + +### Setting Transaction Priorities + +Normally when a thread of control must be selected to resolve a deadlock, DB decides which thread will perform the resolution; you have no way of knowing in advance which thread will be selected to resolve the deadlock. + +However, there may be situations where you know it is better for one thread to resolve a deadlock over another thread. As an example, if you have a background thread running data management activities, and another thread responding to user requests, you might want deadlock resolution to occur in the background thread because you can better afford the throughput costs there. Under these circumstances, you can identify which thread of control will be selected for resolved deadlocks by setting a transaction priorities. + +When two transactions are deadlocked, DB will abort the transaction with the lowest priority. By default, every transaction is given a priority of 100. However, you can set a different priority on a transaction-by-transaction basis by using the `DbTxn::set_priority()` method. + +When two or more transactions are tied for the lowest priority, the tie is broken based on the policy provided to the `DbEnv::lock_detect()` method's `atype` parameter. + +A transaction's priority can be changed at any time after the transaction handle has been created and before the transaction has been resolved (committed or aborted). For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + ... + + try { + + ... + // Database and environment open omitted for brevity. + ... + DbTxn *txn = NULL; + myEnv.txn_begin(NULL, &txn, 0); + txn->set_priority(200); + + try { + db->put(txn, &key, &data, 0); + txn->commit(0); + } catch (DbException &e) { + std::cerr << "Error in transaction: " + << e.what() << std::endl; + txn->abort(); + } + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + ... + +} +``` diff --git a/docs_src/guides/gsg_txn/cxx/logconfig.md b/docs_src/guides/gsg_txn/cxx/logconfig.md new file mode 100644 index 000000000..74bff1054 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/logconfig.md @@ -0,0 +1,140 @@ +--- +title: "Configuring the Logging Subsystem" +api-name: "Configuring the Logging Subsystem" +source: docs/gsg_txn/CXX/logconfig.html +--- +## Configuring the Logging Subsystem + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + +You can configure the following aspects of the logging subsystem: + +- Size of the log files. + +- Size of the logging subsystem's region. See Configuring the Logging Region Size. + +- Maintain logs entirely in-memory. See Configuring In-Memory Logging for more information. + +- Size of the log buffer in memory. See Setting the In-Memory Log Buffer Size. + +- On-disk location of your log files. See Identifying Specific File Locations. + +### Setting the Log File Size + +Whenever a pre-defined amount of data is written to a log file (10 MB by default), DB stops using the current log file and starts writing to a new file. You can change the maximum amount of data contained in each log file by using the `DbEnv::set_lg_max()` method. Note that this method can be used at any time during an application's lifetime. + +Setting the log file size to something larger than its default value is largely a matter of convenience and a reflection of the application's preference in backup media and frequency. However, if you set the log file size too low relative to your application's traffic patterns, you can cause yourself trouble. + +From a performance perspective, setting the log file size to a low value can cause your active transactions to pause their writing activities more frequently than would occur with larger log file sizes. Whenever a transaction completes the log buffer is flushed to disk. Normally other transactions can continue to write to the log buffer while this flush is in progress. However, when one log file is being closed and another created, all transactions must cease writing to the log buffer until the switch over is completed. + +Beyond performance concerns, using smaller log files can cause you to use more physical files on disk. As a result, your application could run out of log sequence numbers, depending on how busy your application is. + +Every log file is identified with a 10 digit number. Moreover, the maximum number of log files that your application is allowed to create in its lifetime is 2,000,000,000. + +For example, if your application performs 6,000 transactions per second for 24 hours a day, and you are logging 500 bytes of data per transaction into 10 MB log files, then you will run out of log files in around 221 years: + +``` c + (10 * 2^20 * 2000000000) / (6000 * 500 * 365 * 60 *60 * 24) = 221 +``` + +However, if you were writing 2000 bytes of data per transaction, and using 1 MB log files, then the same formula shows you running out of log files in 5 years time. + +All of these time frames are quite long, to be sure, but if you do run out of log files after, say, 5 years of continuous operations, then you must reset your log sequence numbers. To do so: + +1. Backup your databases as if to prepare for catastrophic failure. See Backup Procedures for more information. + +2. Reset the log file's sequence number using the **db_load** utility's `-r` option. + +3. Remove all of the log files from your environment. Note that this is the only situation in which all of the log files are removed from an environment; in all other cases, at least a single log file is retained. + +4. Restart your application. + +### Configuring the Logging Region Size + +The logging subsystem's default region size is 60 KB. The logging region is used to store filenames, and so you may need to increase its size if a large number of files (that is, if you have a very large number of databases) will be opened and registered with DB's log manager. + +You can set the size of your logging region by using the `DbEnv::set_lg_region()` method. Note that this method can only be called before the first environment handle for your application is opened. + +### Configuring In-Memory Logging + +It is possible to configure your logging subsystem such that logs are maintained entirely in memory. When you do this, you give up your transactional durability guarantee. Without log files, you have no way to run recovery so any system or software failures that you might experience can corrupt your databases. + +However, by giving up your durability guarantees, you can greatly improve your application's throughput by avoiding the disk I/O necessary to write logging information to disk. In this case, you still retain your transactional atomicity, consistency, and isolation guarantees. + +To configure your logging subsystem to maintain your logs entirely in-memory: + +- Make sure your log buffer is capable of holding all log information that can accumulate during the longest running transaction. See Setting the In-Memory Log Buffer Size for details. + +- Do not run normal recovery when you open your environment. In this configuration, there are no log files available against which you can run recovery. As a result, if you specify recovery when you open your environment, it is ignored. + +- Specify `DB_LOG_IN_MEMORY` to the `DbEnv::log_set_config()` method. Note that you must specify this before your application opens its first environment handle. + +For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + // Set the normal flags for a transactional subsystem. Note that + // we DO NOT specify DB_RECOVER. + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + // Indicate that logging is to be performed only in memory. + // Doing this means that we give up our transactional durability + // guarantee. + myEnv.log_set_config(DB_LOG_IN_MEMORY, 1); + + // Configure the size of our log memory buffer. This must be + // large enough to hold all the logging information likely + // to be created for our longest running transaction. The + // default size for the logging buffer is 1 MB when logging + // is performed in-memory. For this example, we arbitrarily + // set the logging buffer to 5 MB. + myEnv.set_lg_bsize(5 * 1024 * 1024); + + // Open the environment as normal. + myEnv.open(envHome.c_str(), env_flags, 0); + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + // From here, you open databases, create transactions and + // perform database operations exactly as you would if you + // were logging to disk. This part is omitted for brevity. +``` + +### Setting the In-Memory Log Buffer Size + +When your application is configured for on-disk logging (the default behavior for transactional applications), log information is stored in-memory until the storage space fills up, or a transaction commit forces the log information to be flushed to disk. + +It is possible to increase the amount of memory available to your file log buffer. Doing so improves throughput for long-running transactions, or for transactions that produce a large amount of data. + +When you have your logging subsystem configured to maintain your log entirely in memory (see Configuring In-Memory Logging), it is very important to configure your log buffer size because the log buffer must be capable of holding all log information that can accumulate during the longest running transaction. You must make sure that the in-memory log buffer size is large enough that no transaction will ever span the entire buffer. You must also avoid a state where the in-memory buffer is full and no space can be freed because a transaction that started the first log "file" is still active. + +When your logging subsystem is configured for on-disk logging, the default log buffer space is 32 KB. When in-memory logging is configured, the default log buffer space is 1 MB. + +You can increase your log buffer space using the `DbEnv::set_lg_bsize()` method. Note that this method can only be called before the first environment handle for your application is opened. diff --git a/docs_src/guides/gsg_txn/cxx/logfileremoval.md b/docs_src/guides/gsg_txn/cxx/logfileremoval.md new file mode 100644 index 000000000..b9576bbc1 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/logfileremoval.md @@ -0,0 +1,42 @@ +--- +title: "Removing Log Files" +api-name: "Removing Log Files" +source: docs/gsg_txn/CXX/logfileremoval.html +--- +## Removing Log Files + +By default DB does not delete log files for you. For this reason, DB's log files will eventually grow to consume an unnecessarily large amount of disk space. To guard against this, you should periodically take administrative action to remove log files that are no longer in use by your application. + +You can remove a log file if all of the following are true: + +- the log file is not involved in an active transaction. + +- a checkpoint has been performed *after* the log file was created. + +- the log file is not the only log file in the environment. + +- the log file that you want to remove has already been included in an offline or hot backup. Failure to observe this last condition can cause your backups to be unusable. + +DB provides several mechanisms to remove log files that meet all but the last criteria (DB has no way to know which log files have already been included in a backup). The following mechanisms make it easy to remove unneeded log files, but can result in an unusable backup if the log files are not first saved to your archive location. All of the following mechanisms automatically delete unneeded log files for you: + +- Run the **db_archive** command line utility with the `-d` option. + +- From within your application, call the `DbEnv::log_archive()` method with the `DB_ARCH_REMOVE` flag. + +- Call `DbEnv::log_set_config()` method with the `DB_LOG_AUTO_REMOVE` flag. Note that this flag can be set at any point in the lifetime of your application. Setting this parameter affects all environment handles opened against the environment; not just the handle used to set the flag. + + Note that unlike the other log removal mechanisms identified here, this method actually causes log files to be removed on an on-going basis as they become unnecessary. This is extremely desirable behavior if what you want is to use the absolute minimum amount of disk space possible for your application. This mechanism *will* leave you with the log files that are required to run normal recovery. However, it is highly likely that this mechanism will prevent you from running catastrophic recovery. + + Do NOT use this mechanism if you want to be able to perform catastrophic recovery, or if you want to be able to maintain a hot backup. + +In order to safely remove log files and still be able to perform catastrophic recovery, use the **db_archive** command line utility as follows: + +1. Run either a normal or hot backup as described in Backup Procedures. Make sure that all of this data is safely stored to your backup media before continuing. + +2. If you have not already done so, perform a checkpoint. See Checkpoints for more information. + +3. If you are maintaining a hot backup, perform the hot backup procedure as described in Using Hot Failovers. + +4. Run the **db_archive** command line utility with the `-d` option against your production environment. + +5. Run the **db_archive** command line utility with the `-d` option against your failover environment, if you are maintaining one. diff --git a/docs_src/guides/gsg_txn/cxx/maxtxns.md b/docs_src/guides/gsg_txn/cxx/maxtxns.md new file mode 100644 index 000000000..a5b2f831e --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/maxtxns.md @@ -0,0 +1,70 @@ +--- +title: "Configuring the Transaction Subsystem" +api-name: "Configuring the Transaction Subsystem" +source: docs/gsg_txn/CXX/maxtxns.html +--- +## Configuring the Transaction Subsystem + +Most of the configuration activities that you need to perform for your transactional DB application will involve the locking and logging subsystems. See Concurrency and Managing DB Files for details. + +However, there are a couple of things that you can do to configure your transaction subsystem directly. These things are: + +- + + Configure the maximum number of simultaneous transactions needed by your application. In general, you should not need to do this unless you use deeply nested transactions or you have many threads all of which have active transactions. In addition, you may need to configure a higher maximum number of transactions if you are using snapshot isolation. See Snapshot Isolation Transactional Requirements for details. + + By default, your application can support 20 active transactions. + + You can set the maximum number of simultaneous transactions supported by your application using the `DbEnv::set_tx_max()` method. Note that this method must be called before the environment has been opened. + + If your application has exceeded this maximum value, then any attempt to begin a new transaction will fail. + + This value can also be set using the `DB_CONFIG` file's `set_tx_max` parameter. Remember that the `DB_CONFIG` must reside in your environment home directory. + +- + + Configure the timeout value for your transactions. This value represents the longest period of time a transaction can be active. Note, however, that transaction timeouts are checked only when DB examines its lock tables for blocked locks (see Locks, Blocks, and Deadlocks for more information). Therefore, a transaction's timeout can have expired, but the application will not be notified until DB has a reason to examine its lock tables. + + Be aware that some transactions may be inappropriately timed out before the transaction has a chance to complete. You should therefore use this mechanism only if you know your application might have unacceptably long transactions and you want to make sure your application will not stall during their execution. (This might happen if, for example, your transaction blocks or requests too much data.) + + Note that by default transaction timeouts are set to 0 seconds, which means that they never time out. + + To set the maximum timeout value for your transactions, use the `DbEnv::set_timeout()` method. This method configures the entire environment; not just the handle used to set the configuration. Further, this value may be set at any time during the application's lifetime. + + This value can also be set using the `DB_CONFIG` file's `set_txn_timeout` parameter. + +For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + // Configure a maximum transaction timeout of 1 second. + myEnv.set_timeout(1000000, DB_SET_TXN_TIMEOUT); + // Configure 40 maximum transactions. + myEnv.set_tx_max(40); + myEnv.open(envHome.c_str(), env_flags, 0); + + // From here, you open your databases, proceed with your + // database operations, and respond to deadlocks as + // is normal (omitted for brevity). + + + ... +``` diff --git a/docs_src/guides/gsg_txn/cxx/moreinfo.md b/docs_src/guides/gsg_txn/cxx/moreinfo.md new file mode 100644 index 000000000..a4947bfc0 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/moreinfo.md @@ -0,0 +1,28 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg_txn/CXX/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a transactional DB application: + +- Getting Started with Berkeley DB for C++ + +- Berkeley DB Getting Started with Replicated Applications for C++ + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB C++ API Reference Guide + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg_txn/cxx/multithread-intro.md b/docs_src/guides/gsg_txn/cxx/multithread-intro.md new file mode 100644 index 000000000..025c10ed4 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/multithread-intro.md @@ -0,0 +1,14 @@ +--- +title: "Multi-threaded and Multi-process Applications" +api-name: "Multi-threaded and Multi-process Applications" +source: docs/gsg_txn/CXX/multithread-intro.html +--- +## Multi-threaded and Multi-process Applications + +DB is designed to support multi-threaded and multi-process applications, but their usage means you must pay careful attention to issues of concurrency. Transactions help your application's concurrency by providing various levels of isolation for your threads of control. In addition, DB provides mechanisms that allow you to detect and respond to deadlocks. + +*Isolation* means that database modifications made by one transaction will not normally be seen by readers from another transaction until the first commits its changes. Different threads use different transaction handles, so this mechanism is normally used to provide isolation between database operations performed by different threads. + +Note that DB supports different isolation levels. For example, you can configure your application to see uncommitted reads, which means that one transaction can see data that has been modified but not yet committed by another transaction. Doing this might mean your transaction reads data "dirtied" by another transaction, but which subsequently might change before that other transaction commits its changes. On the other hand, lowering your isolation requirements means that your application can experience improved throughput due to reduced lock contention. + +For more information on concurrency, on managing isolation levels, and on deadlock detection, see Concurrency. diff --git a/docs_src/guides/gsg_txn/cxx/nestedtxn.md b/docs_src/guides/gsg_txn/cxx/nestedtxn.md new file mode 100644 index 000000000..b188d62a1 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/nestedtxn.md @@ -0,0 +1,34 @@ +--- +title: "Nested Transactions" +api-name: "Nested Transactions" +source: docs/gsg_txn/CXX/nestedtxn.html +--- +## Nested Transactions + +A *nested transaction* is used to provide a transactional guarantee for a subset of operations performed within the scope of a larger transaction. Doing this allows you to commit and abort the subset of operations independently of the larger transaction. + +The rules to the usage of a nested transaction are as follows: + +- While the nested (child) transaction is active, the parent transaction may not perform any operations other than to commit or abort, or to create more child transactions. + +- Committing a nested transaction has no effect on the state of the parent transaction. The parent transaction is still uncommitted. However, the parent transaction can now see any modifications made by the child transaction. Those modifications, of course, are still hidden to all other transactions until the parent also commits. + +- Likewise, aborting the nested transaction has no effect on the state of the parent transaction. The only result of the abort is that neither the parent nor any other transactions will see any of the database modifications performed under the protection of the nested transaction. + +- If the parent transaction commits or aborts while it has active children, the child transactions are resolved in the same way as the parent. That is, if the parent aborts, then the child transactions abort as well. If the parent commits, then whatever modifications have been performed by the child transactions are also committed. + +- The locks held by a nested transaction are not released when that transaction commits. Rather, they are now held by the parent transaction until such a time as that parent commits. + +- Any database modifications performed by the nested transaction are not visible outside of the larger encompassing transaction until such a time as that parent transaction is committed. + +- The depth of the nesting that you can achieve with nested transaction is limited only by memory. + +To create a nested transaction, simply pass the parent transaction's handle when you created the nested transaction's handle. For example: + +``` c + // parent transaction + DbTxn *parentTxn, *childTxn; + ret = myEnv.txn_begin(NULL, &parentTxn, 0); + // child transaction + ret = myEnv.txn_begin(parent_txn, &childTxn, 0); +``` diff --git a/docs_src/guides/gsg_txn/cxx/nodurabletxn.md b/docs_src/guides/gsg_txn/cxx/nodurabletxn.md new file mode 100644 index 000000000..d79cd3826 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/nodurabletxn.md @@ -0,0 +1,28 @@ +--- +title: "Non-Durable Transactions" +api-name: "Non-Durable Transactions" +source: docs/gsg_txn/CXX/nodurabletxn.html +--- +## Non-Durable Transactions + +As previously noted, by default transaction commits are durable because they cause the modifications performed under the transaction to be synchronously recorded in your on-disk log files. However, it is possible to use non-durable transactions. + +You may want non-durable transactions for performance reasons. For example, you might be using transactions simply for the isolation guarantee. In this case, you might not want a durability guarantee and so you may want to prevent the disk I/O that normally accompanies a transaction commit. + +There are several ways to remove the durability guarantee for your transactions: + +- Specify `DB_TXN_NOSYNC` using the `DbEnv::set_flags()` method. This causes DB to not synchronously force any log data to disk upon transaction commit. That is, the modifications are held entirely in the in-memory cache and the logging information is not forced to the filesystem for long-term storage. Note, however, that the logging data will eventually make it to the filesystem (assuming no application or OS crashes) as a part of DB's management of its logging buffers and/or cache. + + This form of a commit provides a weak durability guarantee because data loss can occur due to an application or OS crash. + + This behavior is specified on a per-environment handle basis. In order for your application to exhibit consistent behavior, you need to specify this flag for all of the environment handles used in your application. + + You can achieve this behavior on a transaction by transaction basis by specifying `DB_TXN_NOSYNC` to the `DbTxn::commit()` method. + +- Specify `DB_TXN_WRITE_NOSYNC` using the `DbEnv::set_flags()` method. This causes logging data to 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 system chooses to schedule the activity; the transaction commit can complete successfully before this disk I/O is performed by the OS. + + This form of commit protects you against application crashes, but not against OS crashes. This method offers less room for the possibility of data loss than does `DB_TXN_NOSYNC`. + + This behavior is specified on a per-environment handle basis. In order for your application to exhibit consistent behavior, you need to specify this flag for all of the environment handles used in your application. + +- Maintain your logs entirely in-memory. In this case, your logs are never written to disk. The result is that you lose all durability guarantees. See Configuring In-Memory Logging for more information. diff --git a/docs_src/guides/gsg_txn/cxx/perftune-intro.md b/docs_src/guides/gsg_txn/cxx/perftune-intro.md new file mode 100644 index 000000000..4ca02d2d0 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/perftune-intro.md @@ -0,0 +1,10 @@ +--- +title: "Performance Tuning" +api-name: "Performance Tuning" +source: docs/gsg_txn/CXX/perftune-intro.html +--- +## Performance Tuning + +From a performance perspective, the use of transactions is not free. Depending on how you configure them, transaction commits usually require your application to perform disk I/O that a non-transactional application does not perform. Also, for multi-threaded and multi-process applications, the use of transactions can result in increased lock contention due to extra locking requirements driven by transactional isolation guarantees. + +There is therefore a performance tuning component to transactional applications that is not applicable for non-transactional applications (although some tuning considerations do exist whether or not your application uses transactions). Where appropriate, these tuning considerations are introduced in the following chapters. However, for a more complete description of them, see the Transaction tuning and Transaction throughput sections of the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_txn/cxx/preface.md b/docs_src/guides/gsg_txn/cxx/preface.md new file mode 100644 index 000000000..6651bc6fc --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/preface.md @@ -0,0 +1,62 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg_txn/CXX/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +This document describes how to use transactions with your Berkeley DB applications. 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 environment infrastructure and administrative tasks required by a transactional application. This book also describes multi-threaded and multi-process DB applications and the requirements they have for deadlock detection. + +This book describes Berkeley DB 11*g* Release 2, which provides library version 11.2.5.3. + +This book is aimed at the software engineer responsible for writing a transactional DB application. + +This book assumes that you have already read and understood the concepts contained in the *Getting Started with Berkeley DB* guide. + +## 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: "`DbEnv::open()` is a `DbEnv` class method." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number +} VENDOR; +``` + +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: + +``` c +typedef struct vendor { + char name[MAXFIELD]; // Vendor name + char street[MAXFIELD]; // Street name and number + char city[MAXFIELD]; // City + char state[3]; // Two-digit US state code + char zipcode[6]; // US zipcode + char phone_number[13]; // Vendor phone number + char sales_rep[MAXFIELD]; // Name of sales representative + char sales_rep_phone[MAXFIELD]; // Sales rep's phone number +} VENDOR; +``` + +### Note + +Finally, notes of special interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg_txn/cxx/readmodifywrite.md b/docs_src/guides/gsg_txn/cxx/readmodifywrite.md new file mode 100644 index 000000000..c4f8c851f --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/readmodifywrite.md @@ -0,0 +1,44 @@ +--- +title: "Read/Modify/Write" +api-name: "Read/Modify/Write" +source: docs/gsg_txn/CXX/readmodifywrite.html +--- +## Read/Modify/Write + +If you are retrieving a record from the database for the purpose of modifying or deleting it, you should declare a read-modify-write cycle at the time that you read the record. Doing so causes DB 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 read lock 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 blocking that your application sees, because readers immediately obtain write locks and write locks cannot be shared. For this reason, you should use read-modify-write cycles only if you 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, pass the `DB_RMW` flag to the database or cursor get method. + +For example: + +``` c +// Begin the deadlock retry loop as is normal. +while (retry_count < MAX_DEADLOCK_RETRIES) { + try { + envp->txn_begin(NULL, txn, 0); + + ... + // key and data are DBTs. Their usage is omitted for brevity. + ... + + // Read the data. Declare the read/modify/write cycle here + dbp->get(txn, &key, &data, DB_RMW); + + ... + // Modify the data as is required. (not shown here) + ... + + // 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. + dbp->put(txn, &key, &data, 0); + txn->commit(0); + return (EXIT_SUCCESS); + } catch (DbDeadlockException &de) { + // Deadlock detection and exception handling omitted + // for brevity + ... +``` diff --git a/docs_src/guides/gsg_txn/cxx/recovery-intro.md b/docs_src/guides/gsg_txn/cxx/recovery-intro.md new file mode 100644 index 000000000..ea56b4743 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/recovery-intro.md @@ -0,0 +1,18 @@ +--- +title: "Recoverability" +api-name: "Recoverability" +source: docs/gsg_txn/CXX/recovery-intro.html +--- +## Recoverability + +An important part of DB's transactional guarantees is durability. *Durability* means that once a transaction has been committed, the database modifications performed under its protection will not be lost due to system failure. + +In order to provide the transactional durability guarantee, DB uses a write-ahead logging system. Every operation performed on your databases is described in a log before it is performed on your databases. This is done in order to ensure that an operation can be recovered in the event of an untimely application or system failure. + +Beyond logging, another important aspect of durability is recoverability. That is, backup and restore. DB supports a normal recovery that runs against a subset of your log files. This is a routine procedure used whenever your environment is first opened upon application startup, and it is intended to ensure that your database is in a consistent state. DB also supports archival backup and recovery in the case of catastrophic failure, such as the loss of a physical disk drive. + +This book describes several different backup procedures you can use to protect your on-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 fatal hardware failure. + +This book also describes the recovery procedures you should use for each of the backup strategies that you might employ. + +For a detailed description of backup and restore procedures, see Managing DB Files. diff --git a/docs_src/guides/gsg_txn/cxx/recovery.md b/docs_src/guides/gsg_txn/cxx/recovery.md new file mode 100644 index 000000000..5af5ac38a --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/recovery.md @@ -0,0 +1,132 @@ +--- +title: "Recovery Procedures" +api-name: "Recovery Procedures" +source: docs/gsg_txn/CXX/recovery.html +--- +## Recovery Procedures + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + +DB supports two types of recovery: + +- Normal recovery, which is run when your environment is opened upon application startup, examines only those log records needed to bring the databases to a consistent state since the last checkpoint. Normal recovery starts with any logs used by any transactions active at the time of the last checkpoint, and examines all logs from then to the current logs. + +- Catastrophic recovery, which is performed in the same way that normal recovery is except that it examines all available log files. You use catastrophic recovery to restore your databases from a previously created backup. + +Of these two, normal recovery should be considered a routine matter; in fact you should run normal recovery whenever you start up your application. + +Catastrophic recovery is run whenever you have lost or corrupted your database files and you want to restore from a backup. You also run catastrophic recovery when you create a hot backup (see Using Hot Failovers for more information). + +### Normal Recovery + +Normal recovery examines the contents of your environment's log files, and uses this information to ensure that your database files are consistent relative to the information contained in the log files. + +Normal recovery also recreates your environment's region files. This has the desired effect of clearing any unreleased locks that your application may have held at the time of an unclean application shutdown. + +Normal recovery is run only against those log files created since the time of your last checkpoint. For this reason, your recovery time is dependent on how much data has been written since the last checkpoint, and therefore on how much log file information there is to examine. If you run checkpoints infrequently, then normal recovery can take a relatively long time. + +### Note + +You should run normal recovery every time you perform application startup. + +To run normal recovery: + +- Make sure all your environment handles are closed. + +- Normal recovery *must be* single-threaded. + +- Provide the `DB_RECOVER` flag when you open your environment. + +You can also run recovery by pausing or shutting down your application and using the **db_recover** command line utility. + +For example: + +``` c +#include "db_cxx.h" + +... + +void *checkpoint_thread(void *); + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN | // Initialize transactions + DB_THREAD | // Free-thread the env handle + DB_RECOVER; // Run normal recovery + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + + ... + + // All other operations are identical from here. Notice, however, + // that we have not created any other threads of control before + // recovery is complete. You want to run recovery for + // the first thread in your application that opens an environment, + // but not for any subsequent threads. +``` + +### Catastrophic Recovery + +Use catastrophic recovery when you are recovering your databases from a previously created backup. Note that to restore your databases from a previous backup, you should copy the backup to a new environment directory, and then run catastrophic recovery. Failure to do so can lead to the internal database structures being out of sync with your log files. + +Catastrophic recovery must be run single-threaded. + +To run catastrophic recovery: + +- Shutdown all database operations. + +- Restore the backup to an empty directory. + +- Provide the `DB_RECOVER_FATAL` flag when you open your environment. This environment open must be single-threaded. + +You can also run recovery by pausing or shutting down your application and using the **db_recover** command line utility with the the `-c` option. + +Note that catastrophic recovery examines every available log file — not just those log files created since the last checkpoint as is the case for normal recovery. For this reason, catastrophic recovery is likely to take longer than does normal recovery. + +For example: + +``` c +#include "db_cxx.h" + +... + +void *checkpoint_thread(void *); + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN | // Initialize transactions + DB_THREAD | // Free-thread the env handle + DB_RECOVER_FATAL; // Run catastrophic recovery + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + + ... + + // All other operations are identical from here. Notice, however, + // that we have not created any other threads of control before + // recovery is complete. You want to run recovery for + // the first thread in your application that opens an environment, + // but not for any subsequent threads. +``` diff --git a/docs_src/guides/gsg_txn/cxx/reversesplit.md b/docs_src/guides/gsg_txn/cxx/reversesplit.md new file mode 100644 index 000000000..57f37047a --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/reversesplit.md @@ -0,0 +1,76 @@ +--- +title: "Reverse BTree Splits" +api-name: "Reverse BTree Splits" +source: docs/gsg_txn/CXX/reversesplit.html +--- +## Reverse BTree Splits + +If your application is using the Btree access method, and your application is repeatedly deleting then adding records to your database, then you might be able to reduce lock contention by turning off reverse Btree splits. + +As pages are emptied in a database, DB attempts to delete empty pages in order to keep the database as small as possible and minimize search time. Moreover, when a page in the database fills up, DB, of course, adds additional pages to make room for more data. + +Adding and deleting pages in the database requires that the writing thread lock the parent page. Consequently, as the number of pages in your database diminishes, your application will see increasingly more lock contention; the maximum level of concurrency in a database of two pages is far smaller than that in a database of 100 pages, because there are fewer pages that can be locked. + +Therefore, if you prevent the database from being reduced to a minimum number of pages, you can improve your application's concurrency throughput. Note, however, that you should do so only if your application tends to delete and then add the same data. If this is not the case, then preventing reverse Btree splits can harm your database search time. + +To turn off reverse Btree splits, provide the `DB_REVSPLITOFF` flag to the `Db::set_flags()` method. + +For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize locking + DB_INIT_MPOOL | // Initialize the cache + DB_THREAD | // Free-thread the env handle + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + + // Turn off BTree reverse split. + dbp=>set_flags(DB_REVSPLITOFF); + + dbp->open(dbp, // Pointer to the database + NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + try { + dbp->close(dbp, 0); + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment: " + << file_name << ", " << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` diff --git a/docs_src/guides/gsg_txn/cxx/sysfailure.md b/docs_src/guides/gsg_txn/cxx/sysfailure.md new file mode 100644 index 000000000..4148b052d --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/sysfailure.md @@ -0,0 +1,18 @@ +--- +title: "A Note on System Failure" +api-name: "A Note on System Failure" +source: docs/gsg_txn/CXX/sysfailure.html +--- +## 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 failures are created equal and no data protection mechanism can protect you against every conceivable way a computing system can find to die. + +Generally, when this book talks about protection against failures, it means that transactions offer protection against the likeliest culprits for system and application crashes. So long as your data modifications have been committed to disk, those modifications should persist even if your application or OS subsequently fails. And, even if the application or OS fails in the middle of a transaction commit (or abort), the data on disk should be either in a consistent state, or there should be enough data available to bring your databases into a consistent state (via a recovery procedure, for example). You may, however, lose whatever data you were committing at the time of the failure, but your databases will be otherwise unaffected. + +### Note + +Be aware that many disks have a disk write cache and on some systems it is enabled by default. This means that a transaction can have committed, and to your application the data may appear to reside on disk, but the data may in fact reside only in the write cache at that time. This means that if the disk write cache is enabled and there is no battery backup for it, data can be lost after an OS crash even when maximum durability mode is in use. For maximum durability, disable the disk write cache or use a disk write cache with a battery backup. + +Of course, if your *disk* fails, then the transactional benefits described in this book are only as good as the backups you have taken. By spreading your data and log files across separate disks, you can minimize the risk of data loss due to a disk failure, but even in this case it is possible to conjure a scenario where even this protection is insufficient (a fire in the machine room, for example) and you must go to your backups for protection. + +Finally, by following the programming examples shown in this book, you can write your code so as to protect your data in the event that your code crashes. However, no programming API can protect you against logic failures in your own code; transactions cannot protect you from simply writing the wrong thing to your databases. diff --git a/docs_src/guides/gsg_txn/cxx/txn_ccursor.md b/docs_src/guides/gsg_txn/cxx/txn_ccursor.md new file mode 100644 index 000000000..3bb3c94b9 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txn_ccursor.md @@ -0,0 +1,79 @@ +--- +title: "Transactional Cursors and Concurrent Applications" +api-name: "Transactional Cursors and Concurrent Applications" +source: docs/gsg_txn/CXX/txn_ccursor.html +--- +## Transactional Cursors and Concurrent Applications + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + +When you use transactional cursors with a concurrent application, remember that in the event of a deadlock you must make sure that you close your cursor before you abort and retry your transaction. + +Also, remember that when you are using the default isolation level, every time your cursor reads a record it locks that record until the encompassing transaction is resolved. This means that walking your database with a transactional cursor increases the chance of lock contention. + +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 above, it is possible to relax your transaction's isolation level such that it can read data modified but not yet committed by 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 use uncommitted 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 uncommitted reads. + +Either way, you must first configure your database handle to support uncommitted reads before you can configure your transactions or your cursors to use them. + +The following example shows how to configure an individual cursor handle to read uncommitted data from within a serializable (full isolation) transaction. For an example of configuring a transaction to perform uncommitted reads in general, see Reading Uncommitted Data. + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | // Create the db if it does + // not exist + DB_AUTO_COMMIT | // Enable auto commit + DB_READ_UNCOMMITTED; // Enable uncommitted reads + + Db *dbp = NULL; + const char *file_name = "mydb.db"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + Dbc *cursorp = NULL; + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + DbTxn *txn = NULL; + myEnv.txn_begin(NULL, &txn, 0); + try { + // Get our cursor. Note that we pass the transaction + // handle here. Note also that we pass the + // DB_READ_UNCOMMITTED flag here so as to cause the + // cursor to perform uncommitted reads. + db.cursor(txn, &cursorp, DB_READ_UNCOMMITTED); + + // 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). + + ... +``` diff --git a/docs_src/guides/gsg_txn/cxx/txnconcurrency.md b/docs_src/guides/gsg_txn/cxx/txnconcurrency.md new file mode 100644 index 000000000..72f581be8 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txnconcurrency.md @@ -0,0 +1,104 @@ +--- +title: "Chapter 4. Concurrency" +api-name: "Chapter 4. Concurrency" +source: docs/gsg_txn/CXX/txnconcurrency.html +--- +## Chapter 4. Concurrency + +**Table of Contents** + + [Which DB Handles are Free-Threaded](txnconcurrency.md#concurrenthandles) + + [Locks, Blocks, and Deadlocks](blocking_deadlocks.md) + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + + [The Locking Subsystem](lockingsubsystem.md) + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + + [Isolation](isolation.md) + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + + [Transactional Cursors and Concurrent Applications](txn_ccursor.md) + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + + [Exclusive Database Handles](exclusivelock.md) + + [Read/Modify/Write](readmodifywrite.md) + + [No Wait on Blocks](txnnowait.md) + + [Reverse BTree Splits](reversesplit.md) + +DB offers a great deal of support for multi-threaded and multi-process applications even when transactions are not in use. Many of DB's handles are thread-safe, or can be made thread-safe by providing the appropriate flag at handle creation time, and DB provides a flexible locking subsystem for managing databases in a concurrent application. Further, DB provides a robust mechanism for detecting and responding to deadlocks . All of these concepts are explored in this chapter. + +Before continuing, it is useful to define a few terms that will appear throughout this chapter: + +- *Thread of control* + + Refers to a thread that is performing work in your application. Typically, in this book that thread will be performing DB operations. + + Note that this term can also be taken to mean a separate process that is performing work — DB supports multi-process operations on your databases. + + Also, DB is agnostic with regard to the type or style of threads in use in your application. So if you are using multiple threads (as opposed to multiple processes) to perform concurrent database access, you are free to use whatever thread package is best for your platform and application. That said, this manual will use pthreads for its threading examples because those have the best chance of being supported across a large range of platforms. + +- *Locking* + + When a thread of control obtains access to a shared resource, it is said to be *locking* that resource. Note that DB supports both exclusive and non-exclusive locks. See Locks for more information. + +- *Free-threaded* + + Data structures and objects are free-threaded if they can be shared across threads of control without any explicit locking on the part of the application. Some books, libraries, and programming languages may use the term *thread-safe* for data structures or objects that have this characteristic. The two terms mean the same thing. + + For a description of free-threaded DB objects, see Which DB Handles are Free-Threaded. + +- *Blocked* + + When a thread cannot obtain a lock because some other thread already holds a lock on that object, the lock attempt is said to be *blocked*. See Blocks for more information. + +- *Deadlock* + + Occurs when two or more threads of control attempt to access conflicting resource in such a way as none of the threads can any longer make further progress. + + For example, if Thread A is blocked waiting for a resource held by Thread B, while at the same time Thread B is blocked waiting for a resource held by Thread A, then neither thread can make any forward progress. In this situation, Thread A and Thread B are said to be *deadlocked.* + + For more information, see Deadlocks. + +## Which DB Handles are Free-Threaded + +The following describes to what extent and under what conditions individual handles are free-threaded. + +- `DbEnv` + + Free-threaded so long as the `DB_THREAD` flag is provided to the environment `open()` method. + +- `Db` + + Free-threaded so long as the `DB_THREAD` flag is provided to the database `open()` method, or if the database is opened using a free-threaded environment handle. + +- `Dbc` + + Cursors are not free-threaded. However, they can be used by multiple threads of control so long as the application serializes access to the handle. + +- `DbTxn` + + Access must be serialized by the application across threads of control. diff --git a/docs_src/guides/gsg_txn/cxx/txncursor.md b/docs_src/guides/gsg_txn/cxx/txncursor.md new file mode 100644 index 000000000..17f894218 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txncursor.md @@ -0,0 +1,69 @@ +--- +title: "Transactional Cursors" +api-name: "Transactional Cursors" +source: docs/gsg_txn/CXX/txncursor.html +--- +## Transactional Cursors + +You can transaction-protect your cursor operations by specifying a transaction handle at the time that you create your cursor. Beyond that, you do not ever provide a transaction handle directly to a cursor method. + +Note that if you transaction-protect a cursor, then you must make sure that the cursor is closed before you either commit or abort the transaction. For example: + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + // Environment and database opens omitted + ... + + DbTxn *txn = NULL; + Dbc *cursorp = NULL; + + try { + + Dbt key, data; + key.set_data(keystr); + key.set_size((strlen(keystr) + 1) * sizeof(char)); + key.set_data(datastr); + key.set_size((strlen(datastr) + 1) * sizeof(char)); + + DbTxn *txn = NULL; + myEnv.txn_begin(NULL, &txn, 0); + try { + // Get our cursor. Note that we pass the transaction handle + // here. + db.cursor(txn, &cursorp, 0); + + // Perform our operations. Note that we do not pass a + // transaction handle here. + char *replacementString = "new string"; + while (cursor->get(&key, &data, DB_NEXT) == 0) { + data.set_data(void *)replacementString); + data.set_size((strlen(replacementString) + 1) * + sizeof(char)); + cursor->put(&key, &data, DB_CURRENT); + } + + // We're done. Commit the transaction. + cursor->close(); + txn->commit(0); + } catch (DbException &e) { + std::cerr << "Error in transaction: " + << e.what() << std::endl; + cursor->close(); + txn->abort(); + } + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + return (EXIT_SUCCESS); +} +``` diff --git a/docs_src/guides/gsg_txn/cxx/txnexample_c.md b/docs_src/guides/gsg_txn/cxx/txnexample_c.md new file mode 100644 index 000000000..57c410dfd --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txnexample_c.md @@ -0,0 +1,474 @@ +--- +title: "Transaction Example" +api-name: "Transaction Example" +source: docs/gsg_txn/CXX/txnexample_c.html +--- +## Transaction Example + +The following code provides a fully functional example of a multi-threaded transactional DB application. For improved portability across platforms, this examples uses pthreads to provide threading support. + +The example opens an environment and database and then creates 5 threads, each of which writes 500 records to the database. The keys used for these writes are pre-determined strings, while the data is a random value. 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 stay out of the way of the main points of this example. + +Each thread writes 10 records under a single transaction before committing and writing another 10 (this is repeated 50 times). At the end of each transaction, but before committing, each thread calls a function that uses a cursor to read every record in the database. We do this in order to make some points about database reads in a transactional environment. + +Of course, each writer thread performs deadlock detection as described in this manual. In addition, normal recovery is performed when the environment is opened. + +We start with our normal `include` directives: + +``` c +// File TxnGuide.cpp + +// We assume an ANSI-compatible compiler +#include +#include +#include + +#ifdef _WIN32 +extern int getopt(int, char * const *, const char *); +#else +#include +#endif +``` + +We also need a directive that we use to identify how many threads we want our program to create: + +``` c +// Run 5 writers threads at a time. +#define NUMWRITERS 5 +``` + +Next we declare a couple of global variables (used by our threads), and we provide our forward declarations for the functions used by this example. + +``` c +// Printing of pthread_t is implementation-specific, so we +// create our own thread IDs for reporting purposes. +int global_thread_num; +pthread_mutex_t thread_num_lock; + +// Forward declarations +int countRecords(Db *, DbTxn *); +int openDb(Db **, const char *, const char *, DbEnv *, u_int32_t); +int usage(void); +void *writerThread(void *); +``` + +We now implement our usage function, which identifies our only command line parameter: + +``` c +// Usage function +int +usage() +{ + std::cerr << " [-h ]" << std::endl; + return (EXIT_FAILURE); +} +``` + +With that, we have finished up our program's housekeeping, and we can now move on to the main part of our program. As usual, we begin with `main()`. First we declare all our variables, and then we initialize our DB handles. + +``` c +int +main(int argc, char *argv[]) +{ + // Initialize our handles + Db *dbp = NULL; + DbEnv *envp = NULL; + + pthread_t writerThreads[NUMWRITERS]; + int ch, i; + u_int32_t envFlags; + char *dbHomeDir; + + // Application name + const char *progName = "TxnGuide"; + + // Database file name + const char *fileName = "mydb.db"; +``` + +Now we need to parse our command line. In this case, all we want is to know where our environment directory is. If the `-h` option is not provided when this example is run, the current working directory is used instead. + +``` c + // Parse the command line arguments +#ifdef _WIN32 + dbHomeDir = ".\\"; +#else + dbHomeDir = "./"; +#endif + while ((ch = getopt(argc, argv, "h:")) != EOF) + switch (ch) { + case 'h': + dbHomeDir = optarg; + break; + case '?': + default: + return (usage()); + } +``` + +Next we create our database handle, and we define our environment open flags. There are a few things to notice here: + +- We specify `DB_RECOVER`, which means that normal recovery is run every time we start the application. This is highly desirable and recommended for most applications. + +- We also specify `DB_THREAD`, which means our environment handle will be free-threaded. This is very important because we will be sharing the environment handle across threads. + +``` c + // Env open flags + envFlags = + DB_CREATE | // Create the environment if it does not exist + DB_RECOVER | // Run normal recovery. + DB_INIT_LOCK | // Initialize the locking subsystem + DB_INIT_LOG | // Initialize the logging subsystem + DB_INIT_TXN | // Initialize the transactional subsystem. This + // also turns on logging. + DB_INIT_MPOOL | // Initialize the memory pool (in-memory cache) + DB_THREAD; // Cause the environment to be free-threaded + + try { + // Create and open the environment + envp = new DbEnv(0); +``` + +Now we configure how we want deadlock detection performed. In our case, we will cause DB to perform deadlock detection by walking its internal lock tables looking for a block every time a lock is requested. Further, in the event of a deadlock, the thread that holds the youngest lock will receive the deadlock notification. + +``` c + // Indicate that we want db to internally perform deadlock + // detection. Also indicate that the transaction with + // the fewest number of write locks will receive the + // deadlock notification in the event of a deadlock. + envp->set_lk_detect(DB_LOCK_MINWRITE); +``` + +Now we open our environment. + +``` c + // If we had utility threads (for running checkpoints or + // deadlock detection, for example) we would spawn those + // here. However, for a simple example such as this, + // that is not required. + + envp->open(dbHomeDir, envFlags, 0); +``` + +Now we call the function that will open our database for us. This is not very interesting, except that you will notice that we are specifying `DB_DUPSORT`. This is required purely by the data that we are writing to the database, and it is only necessary if you run the application more than once without first deleting the environment. + +The implementation of `open_db()` is described later in this section. + +``` c + // Open the database + openDb(&dbp, progName, fileName, envp, DB_DUPSORT); +``` + +Now we create our threads. In this example we are using pthreads for our threading package. A description of threading (beyond how it impacts DB usage) is beyond the scope of this manual. However, the things that we are doing here should be familiar to anyone who has prior experience with any threading package. We are simply initializing a mutex, creating our threads, and then joining our threads, which causes our program to wait until the joined threads have completed before continuing operations in the main thread. + +``` c + // Initialize a pthread mutex. Used to help provide thread ids. + (void)pthread_mutex_init(&thread_num_lock, NULL); + + // Start the writer threads. + for (i = 0; i < NUMWRITERS; i++) + (void)pthread_create(&writerThreads[i], NULL, + writerThread, (void *)dbp); + + // Join the writers + for (i = 0; i < NUMWRITERS; i++) + (void)pthread_join(writerThreads[i], NULL); + + } catch(DbException &e) { + std::cerr << "Error opening database environment: " + << dbHomeDir << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } +``` + +Finally, to wrap up `main()`, we close out our database and environment handle, as is normal for any DB application. Notice that this is where our `err` label is placed in our application. If any database operation prior to this point in the program returns an error status, the program simply jumps to this point and closes our handles if necessary before exiting the application completely. + +``` c + try { + // Close our database handle if it was opened. + if (dbp != NULL) + dbp->close(0); + + // Close our environment if it was opened. + if (envp != NULL) + envp->close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment." + << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + // Final status message and return. + + std::cout << "I'm all done." << std::endl; + return (EXIT_SUCCESS); +} +``` + +Now that we have completed `main()`, we need to implement the function that our writer threads will actually run. This is where the bulk of our transactional code resides. + +We start as usual with variable declarations and initialization. + +``` c +// A function that performs a series of writes to a +// Berkeley DB database. The information written +// to the database is largely nonsensical, but the +// mechanisms of transactional commit/abort and +// deadlock detection are illustrated here. +void * +writerThread(void *args) +{ + int j, thread_num; + int max_retries = 20; // Max retry on a deadlock + char *key_strings[] = {"key 1", "key 2", "key 3", "key 4", + "key 5", "key 6", "key 7", "key 8", + "key 9", "key 10"}; + + Db *dbp = (Db *)args; + DbEnv *envp = dbp->get_env(); +``` + +Now we want a thread number for reporting purposes. It is possible to use the `pthread_t` value directly for this purpose, but how that is done unfortunately differs depending on the pthread implementation you are using. So instead we use a mutex-protected global variable to obtain a simple integer for our reporting purposes. + +Note that we are also use this thread id for initializing a random number generator, which we do here. We use this random number generator for data generation. + +``` c + // Get the thread number + (void)pthread_mutex_lock(&thread_num_lock); + global_thread_num++; + thread_num = global_thread_num; + (void)pthread_mutex_unlock(&thread_num_lock); + + // Initialize the random number generator + srand((u_int)pthread_self()); +``` + +Now we begin the loop that we use to write data to the database. Notice that in this top loop, we begin a new transaction. We will actually use 50 transactions per writer thread, although we will only ever have one active transaction per thread at a time. Within each transaction, we will perform 10 database writes. + +By combining multiple writes together under a single transaction, we increase the likelihood that a deadlock will occur. Normally, you want to reduce the potential for a deadlock and in this case the way to do that is to perform a single write per transaction. To avoid deadlocks, we could be using auto commit to write to our database for this workload. + +However, we want to show deadlock handling and by performing multiple writes per transaction we can actually observe deadlocks occurring. We also want to underscore the idea that you can combing multiple database operations together in a single atomic unit of work in order to improve the efficiency of your writes. + +``` c + // Perform 50 transactions + for (int i=0; i<50; i++) { + DbTxn *txn; + bool retry = true; + int retry_count = 0; + // while loop is used for deadlock retries + while (retry) { + // try block used for deadlock detection and + // general db exception handling + try { + + // Begin our transaction. We group multiple writes in + // this thread under a single transaction so as to + // (1) show that you can atomically perform multiple + // writes at a time, and (2) to increase the chances + // of a deadlock occurring so that we can observe our + // deadlock detection at work. + + // Normally we would want to avoid the potential for + // deadlocks, so for this workload the correct thing + // would be to perform our puts with auto commit. But + // that would excessively simplify our example, so we + // do the "wrong" thing here instead. + txn = NULL; + envp->txn_begin(NULL, &txn, 0); +``` + +Now we begin the inner loop that we use to actually perform the write. + +``` c + // Perform the database write for this transaction. + for (j = 0; j < 10; j++) { + Dbt key, value; + key.set_data(key_strings[j]); + key.set_size((strlen(key_strings[j]) + 1) * + sizeof(char)); + + int payload = rand() + i; + value.set_data(&payload); + value.set_size(sizeof(int)); + + // Perform the database put + dbp->put(txn, &key, &value, 0); + } +``` + +Having completed the inner database write loop, we could simply commit the transaction and continue on to the next block of 10 writes. However, we want to first illustrate a few points about transactional processing so instead we call our `countRecords()` function before calling the transaction commit. `countRecords()` uses a cursor to read every record in the database and return a count of the number of records that it found. + +``` c + // countRecords runs a cursor over the entire database. + // We do this to illustrate issues of deadlocking + std::cout << thread_num << " : Found " + << countRecords(dbp, NULL) + << " records in the database." << std::endl; + + std::cout << thread_num << " : committing txn : " << i + << std::endl; + + // commit + try { + txn->commit(0); + retry = false; + txn = NULL; + } catch (DbException &e) { + std::cout << "Error on txn commit: " + << e.what() << std::endl; + } +``` + +Finally, we finish our try block. Notice how we examine the exceptions to determine whether we need to abort (or abort/retry in the case of a deadlock) our current transaction. + +``` c + } catch (DbDeadlockException &de) { + // First thing we MUST do is abort the transaction. + if (txn != NULL) + (void)txn->abort(); + + // Now we decide if we want to retry the operation. + // If we have retried less than max_retries, + // increment the retry count and goto retry. + if (retry_count < max_retries) { + std::cout << "############### Writer " << thread_num + << ": Got DB_LOCK_DEADLOCK.\n" + << "Retrying write operation." + << std::endl; + retry_count++; + retry = true; + } else { + // Otherwise, just give up. + std::cerr << "Writer " << thread_num + << ": Got DeadLockException and out of " + << "retries. Giving up." << std::endl; + retry = false; + } + } catch (DbException &e) { + std::cerr << "db put failed" << std::endl; + std::cerr << e.what() << std::endl; + if (txn != NULL) + txn->abort(); + retry = false; + } catch (std::exception &ee) { + std::cerr << "Unknown exception: " << ee.what() << std::endl; + return (0); + } + } + } + return (0); +} +``` + +We want to back up for a moment and take a look at the call to `countRecords()`. If you look at the `countRecords()` function prototype at the beginning of this example, you will see that the function's second parameter takes a transaction handle. However, our usage of the function here does not pass a transaction handle through to the function. + +Because `countRecords()` reads every record in the database, if used incorrectly the thread will self-deadlock. The writer thread has just written 500 records to the database, but because the transaction used for that write has not yet been committed, each of those 500 records are still locked by the thread's transaction. If we then simply run a non-transactional cursor over the database from within the same thread that has locked those 500 records, the cursor will block when it tries to read one of those transactional protected records. The thread immediately stops operation at that point while the cursor waits 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 for the the 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 has committed. + +2. We can allow `countRecords()` to operate under the same transaction as all of the writes were performed (this is what the transaction parameter for the function is for). + +3. We can reduce our isolation guarantee for the application by allowing uncommitted reads. + +For this example, we choose to use option 3 (uncommitted reads) to avoid the deadlock. This means that we have to open our database such that it supports uncommitted reads, and we have to open our cursor handle so that it knows to perform uncommitted reads. + +Note that in In-Memory Transaction Example, we simply perform the cursor operation using the same transaction as is used for the thread's writes. + +The following is the `countRecords()` implementation. There is not anything particularly interesting about this function other than specifying uncommitted reads when we open the cursor handle, but we include the function here anyway for the sake of completeness. + +``` c +// This simply counts the number of records contained in the +// database and returns the result. +// +// 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. +int +countRecords(Db *dbp, DbTxn *txn) +{ + + Dbc *cursorp = NULL; + int count = 0; + + try { + // Get the cursor + dbp->cursor(txn, &cursorp, DB_READ_UNCOMMITTED); + + Dbt key, value; + while (cursorp->get(&key, &value, DB_NEXT) == 0) { + count++; + } + } catch (DbDeadlockException &de) { + std::cerr << "countRecords: got deadlock" << std::endl; + cursorp->close(); + throw de; + } catch (DbException &e) { + std::cerr << "countRecords error:" << std::endl; + std::cerr << e.what() << std::endl; + } + + if (cursorp != NULL) { + try { + cursorp->close(); + } catch (DbException &e) { + std::cerr << "countRecords: cursor close failed:" << std::endl; + std::cerr << e.what() << std::endl; + } + } + + return (count); +} +``` + +Finally, we provide the implementation of our `openDb()` function. This function should hold no surprises for you. Note, however, that we do specify uncommitted reads when we open the database. If we did not do this, then our `countRecords()` function would cause our thread to self-deadlock because the cursor could not be opened to support uncommitted reads (that flag on the cursor open would, in fact, be silently ignored by DB). + +``` c +// Open a Berkeley DB database +int +openDb(Db **dbpp, const char *progname, const char *fileName, + DbEnv *envp, u_int32_t extraFlags) +{ + int ret; + u_int32_t openFlags; + + try { + Db *dbp = new Db(envp, 0); + + // Point to the new'd Db + *dbpp = dbp; + + if (extraFlags != 0) + ret = dbp->set_flags(extraFlags); + + // Now open the database + openFlags = DB_CREATE | // Allow database creation + DB_READ_UNCOMMITTED | // Allow uncommitted reads + DB_AUTO_COMMIT | /* Allow auto commit */ + DB_THREAD; /* Cause the database to + be free-threaded */ + + dbp->open(NULL, // Txn pointer + fileName, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + openFlags, // Open flags + 0); // File mode. Using defaults + } catch (DbException &e) { + std::cerr << progname << "open_db: db open failed:" << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +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 DB distribution: + +``` c +DB_INSTALL/examples_cxx/txn_guide +``` diff --git a/docs_src/guides/gsg_txn/cxx/txnindices.md b/docs_src/guides/gsg_txn/cxx/txnindices.md new file mode 100644 index 000000000..3ef3c9c7b --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txnindices.md @@ -0,0 +1,41 @@ +--- +title: "Secondary Indices with Transaction Applications" +api-name: "Secondary Indices with Transaction Applications" +source: docs/gsg_txn/CXX/txnindices.html +--- +## Secondary Indices with Transaction Applications + +You can use transactions with your secondary indices so long as you open the secondary index so that it supports transactions (that is, you wrap the database open in a transaction, or use auto commit, in the same way as when you open a primary transactional database). In addition, you must make sure that when you associate the secondary index with the primary database, the association is performed using a transaction. The easiest thing to do here is to simply specify `DB_AUTO_COMMIT` when you perform the association. + +All other aspects of using secondary indices with transactions are identical to using secondary indices without transactions. In addition, transaction-protecting cursors opened against secondary indices is performed in exactly the same way as when you use transactional cursors against a primary database. See Transactional Cursors for details. + +Note that when you use transactions to protect your database writes, your secondary indices are protected from corruption because updates to the primary and the secondaries are performed in a single atomic transaction. + +For example: + +``` c +#include + +... + +// Environment and primary database open omitted +... + +Db my_index(&envp, 0); // Secondary + +// Open the secondary +my_index.open(NULL, // Transaction pointer + "my_secondary.db", // On-disk file that holds the database. + NULL, // Optional logical database name + DB_BTREE, // Database access method + DB_AUTO_COMMIT, // Open flags. + 0); // File mode (using defaults) + +// Now associate the primary and the secondary +my_database.associate(NULL, // Txn id + &my_index, // Associated secondary database + get_sales_rep, // Callback used for key + // extraction. This is described + // in the Getting Started guide. + DB_AUTO_COMMIT); // Flags +``` diff --git a/docs_src/guides/gsg_txn/cxx/txnnowait.md b/docs_src/guides/gsg_txn/cxx/txnnowait.md new file mode 100644 index 000000000..77af2017b --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/txnnowait.md @@ -0,0 +1,24 @@ +--- +title: "No Wait on Blocks" +api-name: "No Wait on Blocks" +source: docs/gsg_txn/CXX/txnnowait.html +--- +## No Wait on Blocks + +Normally when a DB transaction is blocked on a lock request, it must wait until the requested lock becomes available before its thread-of-control can proceed. However, it is possible to configure a transaction handle such that it will report a deadlock rather than wait for the block to clear. + +You do this on a transaction by transaction basis by specifying `DB_TXN_NOWAIT` to the `DbEnv::txn_begin()` method. + +For example: + +``` c + DbTxn *txn = NULL; + try { + envp->txn_begin(NULL, &txn, DB_TXN_NOWAIT); + + ... + } catch (DbException &de) { + // Deadlock detection and exception handling omitted + // for brevity + ... +``` diff --git a/docs_src/guides/gsg_txn/cxx/usingtxns.md b/docs_src/guides/gsg_txn/cxx/usingtxns.md new file mode 100644 index 000000000..9f1d29dec --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/usingtxns.md @@ -0,0 +1,149 @@ +--- +title: "Chapter 3. Transaction Basics" +api-name: "Chapter 3. Transaction Basics" +source: docs/gsg_txn/CXX/usingtxns.html +--- +## Chapter 3. Transaction Basics + +**Table of Contents** + + [Committing a Transaction](usingtxns.md#commitresults) + + [Non-Durable Transactions](nodurabletxn.md) + + [Aborting a Transaction](abortresults.md) + + [Auto Commit](autocommit.md) + + [Nested Transactions](nestedtxn.md) + + [Transactional Cursors](txncursor.md) + + [Secondary Indices with Transaction Applications](txnindices.md) + + [Configuring the Transaction Subsystem](maxtxns.md) + +Once you have enabled transactions for your environment and your databases, you can use them to protect your database operations. You do this by acquiring a transaction handle and then using that handle for any database operation that you want to participate in that transaction. + +You obtain a transaction handle using the `DbEnv::txn_begin()` method. + +Once you have completed all of the operations that you want to include in the transaction, you must commit the transaction using the `DbTxn::commit()` method. + +If, for any reason, you want to abandon the transaction, you abort it using `DbTxn::abort()`. + +Any transaction handle that has been committed or aborted can no longer be used by your application. + +Finally, you must make sure that all transaction handles are either committed or aborted before closing your databases and environment. + +### Note + +If you only want to transaction protect a single database write operation, you can use auto commit to perform the transaction administration. When you use auto commit, you do not need an explicit transaction handle. See Auto Commit for more information. + +For example, the following example opens a transactional-enabled environment and database, obtains a transaction handle, and then performs a write operation under its protection. In the event of any failure in the write operation, the transaction is aborted and the database is left in a state as if no operations had ever been attempted in the first place. + +``` c +#include "db_cxx.h" + +... + +int main(void) +{ + u_int32_t env_flags = DB_CREATE | // If the environment does not + // exist, create it. + DB_INIT_LOCK | // Initialize locking + DB_INIT_LOG | // Initialize logging + DB_INIT_MPOOL | // Initialize the cache + DB_INIT_TXN; // Initialize transactions + + u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT; + Db *dbp = NULL; + const char *file_name = "mydb.db"; + const char *keystr ="thekey"; + const char *datastr = "thedata"; + + std::string envHome("/export1/testEnv"); + DbEnv myEnv(0); + + try { + + myEnv.open(envHome.c_str(), env_flags, 0); + dbp = new Db(&myEnv, 0); + + // Open the database. Note that we are using auto commit for + // the open, so the database is able to support transactions. + dbp->open(NULL, // Txn pointer + file_name, // File name + NULL, // Logical db name + DB_BTREE, // Database type (using btree) + db_flags, // Open flags + 0); // File mode. Using defaults + + Dbt key, data; + key.set_data(keystr); + key.set_size((strlen(keystr) + 1) * sizeof(char)); + key.set_data(datastr); + key.set_size((strlen(datastr) + 1) * sizeof(char)); + + DbTxn *txn = NULL; + myEnv.txn_begin(NULL, &txn, 0); + try { + db->put(txn, &key, &data, 0); + txn->commit(0); + } catch (DbException &e) { + std::cerr << "Error in transaction: " + << e.what() << std::endl; + txn->abort(); + } + + } catch(DbException &e) { + std::cerr << "Error opening database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + } + + try { + if (dbp != NULL) + dbp->close(0); + myEnv.close(0); + } catch(DbException &e) { + std::cerr << "Error closing database and environment: " + << file_name << ", " + << envHome << std::endl; + std::cerr << e.what() << std::endl; + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} +``` + +## Committing a Transaction + +In order to fully understand what is happening when you commit a transaction, you must first understand a little about what DB is doing with the logging subsystem. Logging causes all database write operations to be identified in logs, and by default these logs are backed by files on disk. These logs are used to restore your databases in the event of a system or application failure, so by performing logging, DB ensures the integrity of your data. + +Moreover, DB performs *write-ahead* logging. This means that information is written to the logs *before* the actual database is changed. This means that all write activity performed under the protection of the transaction is noted in the log before the transaction is committed. Be aware, however, that database maintains logs in-memory. If you are backing your logs on disk, the log information will eventually be written to the log files, but while the transaction is on-going the log data may be held only in memory. + +When you commit a transaction, the following occurs: + +- A commit record is written to the log. This indicates that the modifications made by the transaction are now permanent. By default, this write is performed synchronously to 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. Note that this requirement can be relaxed, depending on the type of commit you perform. See Non-Durable Transactions for more information. Also, if you are maintaining your logs entirely in-memory, then this step will of course not be taken. To configure your logging system for in-memory usage, see Configuring In-Memory Logging. + +- All locks held by the transaction are released. This means that read operations performed by other transactions or threads of control can now see the modifications without resorting to uncommitted reads (see Reading Uncommitted Data for more information). + +To commit a transaction, you simply call `DbTxn::commit()`. + +Notice that committing a transaction does not necessarily cause data modified in your memory cache to be written to the files backing your databases on disk. Dirtied database pages are written for a number of reasons, but a transactional commit is not one of them. The following are the things that can cause a dirtied database page to be written to the backing database file: + +- Checkpoints. + + Checkpoints cause all dirtied pages currently existing in the cache to be written to disk, and a checkpoint record is then written to the logs. You can run checkpoints explicitly. For more information on checkpoints, see Checkpoints. + +- Cache is full. + + If the in-memory cache fills up, then dirtied pages might be written to disk in order to free up space for other pages that your application needs to use. Note that if dirtied pages are written to the database files, then any log records that describe how those pages were dirtied are written to disk before the database pages are written. + +Be aware that because your transaction commit caused database modifications recorded in your logs to be forced to disk, your modifications are by default "persistent" in that they can be recovered in the event of an application or system failure. However, recovery time is gated by how much data has been modified since the last checkpoint, so for applications that perform a lot of writes, you may want to run a checkpoint with some frequency. + +Note that once you have committed a transaction, the transaction handle that you used for the transaction is no longer valid. To perform database activities under the control of a new transaction, you must obtain a fresh transaction handle. diff --git a/docs_src/guides/gsg_txn/cxx/wrapup.md b/docs_src/guides/gsg_txn/cxx/wrapup.md new file mode 100644 index 000000000..798fd9858 --- /dev/null +++ b/docs_src/guides/gsg_txn/cxx/wrapup.md @@ -0,0 +1,74 @@ +--- +title: "Chapter 6. Summary and Examples" +api-name: "Chapter 6. Summary and Examples" +source: docs/gsg_txn/CXX/wrapup.html +--- +## Chapter 6. Summary and Examples + +**Table of Contents** + + [Anatomy of a Transactional Application](wrapup.md#anatomy) + + [Transaction Example](txnexample_c.md) + + [In-Memory Transaction Example](inmem_txnexample_c.md) + +Throughout this manual we have presented the concepts and mechanisms that you need to provide transactional protection for your application. In this chapter, we summarize these mechanisms, and we provide a complete example of a multi-threaded transactional DB 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 following subsystems be used: + + - Transactional Subsystem (this also initializes the logging subsystem). + + - Memory pool (the in-memory cache). + + - Logging subsystem. + + - Locking subsystem (if your application is multi-process or multi-threaded). + + It is also highly recommended that you run normal recovery upon first environment open. Normal recovery examines only those logs required to ensure your database files are consistent relative to the information found in your log files. + +3. Optionally spawn off any utility threads that you might need. Utility threads can be used to run checkpoints periodically, or to periodically run a deadlock detector if you do not want to use DB's built-in deadlock detector. + +4. Open whatever database handles that you need. + +5. Spawn off worker threads. How many of these you need and how they split their DB workload is entirely up to your application's requirements. However, any worker threads that perform write operations will do the following: + + 1. Begin a transaction. + + 2. Perform one or more read and write operations. + + 3. Commit the transaction if all goes well. + + 4. Abort and retry the operation if a deadlock is detected. + + 5. Abort the transaction for most other errors. + +6. On application shutdown: + + 1. Make sure there are no opened cursors. + + 2. Make sure there are no active transactions. Either abort or commit all transactions before shutting down. + + 3. Close your databases. + + 4. Close your environment. + +### Note + +Robust DB applications should monitor their worker threads to make sure they have not 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 to do this). This is the only way to clear any resources (such as a lock or a mutex) that the abnormally exiting worker thread might have been holding at the time that it died. + +Failure to perform this recovery can cause your still-functioning worker threads to eventually block forever while waiting for a lock that will never be released. + +In addition to these activities, which are all entirely handled by code within your application, there are some administrative activities that you should perform: + +- Periodically checkpoint your application. Checkpoints will reduce the time to run recovery in the event that one is required. See Checkpoints for details. + +- Periodically back up your database and log files. This is required in order to fully obtain the durability guarantee made by DB's transaction ACID support. See Backup Procedures for more information. + +- You may want to maintain a hot failover if 24x7 processing with rapid restart in the face of a disk hit is important to you. See Using Hot Failovers for more information. diff --git a/docs_src/guides/gsg_txn/index.md b/docs_src/guides/gsg_txn/index.md index b190bb259..76d905d14 100644 --- a/docs_src/guides/gsg_txn/index.md +++ b/docs_src/guides/gsg_txn/index.md @@ -5,6 +5,8 @@ source: docs/gsg_txn/C/index.html --- # Getting Started with Berkeley DB Transaction Processing +**Language:** C (this page) · [C++](cxx/index.md) · [Java](java/index.md) + **Legal Notice** This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html diff --git a/docs_src/guides/gsg_txn/java/_meta.toml b/docs_src/guides/gsg_txn/java/_meta.toml new file mode 100644 index 000000000..dd11a8d2c --- /dev/null +++ b/docs_src/guides/gsg_txn/java/_meta.toml @@ -0,0 +1,45 @@ +# Nav/index metadata for the gsg_txn Java variant (order derived from the +# source index.html TOC chain). See the C variant _meta.toml. + +title = "Getting Started with Berkeley DB Transaction Processing (Java)" +landing = "index.md" +order = [ + "preface", + "moreinfo", + "introduction", + "sysfailure", + "apireq", + "multithread-intro", + "recovery-intro", + "perftune-intro", + "enabletxn", + "envopen", + "usingtxns", + "nodurabletxn", + "abortresults", + "autocommit", + "nestedtxn", + "txncursor", + "txnindices", + "maxtxns", + "txnconcurrency", + "blocking_deadlocks", + "lockingsubsystem", + "isolation", + "txn_ccursor", + "exclusivelock", + "readmodifywrite", + "txnnowait", + "reversesplit", + "filemanagement", + "backuprestore", + "recovery", + "architectrecovery", + "hotfailover", + "logfileremoval", + "logconfig", + "wrapup", + "txnexample_java", + "txnexample_dpl", + "inmem_txnexample_java", +] diff --git a/docs_src/guides/gsg_txn/java/abortresults.md b/docs_src/guides/gsg_txn/java/abortresults.md new file mode 100644 index 000000000..5c2fd2265 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/abortresults.md @@ -0,0 +1,12 @@ +--- +title: "Aborting a Transaction" +api-name: "Aborting a Transaction" +source: docs/gsg_txn/JAVA/abortresults.html +--- +## Aborting a Transaction + +When you abort a transaction, all database or store modifications performed under the protection of the transaction are discarded, and all locks currently held by the transaction are released. In this event, your data is simply left in the state that it was in before the transaction began performing data modifications. + +Once you have aborted a transaction, the transaction handle that you used for the transaction is no longer valid. To perform database activities under the control of a new transaction, you must obtain a fresh transactional handle. + +To abort a transaction, call `Transaction.abort()`. diff --git a/docs_src/guides/gsg_txn/java/apireq.md b/docs_src/guides/gsg_txn/java/apireq.md new file mode 100644 index 000000000..389c8a24c --- /dev/null +++ b/docs_src/guides/gsg_txn/java/apireq.md @@ -0,0 +1,44 @@ +--- +title: "Application Requirements" +api-name: "Application Requirements" +source: docs/gsg_txn/JAVA/apireq.html +--- +## Application Requirements + +In order to use transactions, your application has certain requirements beyond what is required of non-transactional protected applications. They are: + +- Environments. + + Environments are optional for non-transactional applications that use the base API, but they are required for transactional applications. (Of course, applications that use the DPL always require the DPL.) + + Environment usage is described in detail in Transaction Basics. + +- Transaction subsystem. + + In order to use transactions, you must explicitly enable the transactional subsystem for your application, and this must be done at the time that your environment is first created. + +- Logging subsystem. + + The logging subsystem is required for recovery purposes, but its usage also means your application may require a little more administrative effort than it does when logging is not in use. See Managing DB Files for more information. + +- Transaction handles. + + 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 must use transaction handles. These handles are obtained from your Environment objects. They should normally be short-lived, and their usage is reasonably simple. To complete a transaction and save the work it performed, you call its `commit()` method. To complete 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 a single write operation. Auto commit allows a transaction to be used without obtaining an explicit transaction handle. See Auto Commit for information on how to use auto commit. + +- Entity Store + + If you are using the DPL, then you must configure your entity stores for transactional support before opening them (that is, before obtaining a primary index from them for the first time). + +- Database open requirements. + + In addition to using environments and initializing the correct subsystems, your application must transaction protect the database opens, and any secondary index associations, if subsequent operations on the databases are to be transaction protected. The database open and secondary index association are commonly transaction protected using auto commit. + + Note that if you are using the DPL, you do not have to explicitly do anything to the underlying databases unless you want to modify their default behavior — such as the isolation level that they use, for example. + +- Deadlock detection. + + Typically transactional applications use multiple threads of control when accessing the database. Any time multiple threads are used on a single resource, the potential for lock contention arises. In turn, lock contention can lead to deadlocks. See Locks, Blocks, and Deadlocks for more information. + + Therefore, transactional applications must frequently include code for detecting and responding to deadlocks. Note that this requirement is not *specific* to transactions – you can certainly write concurrent non-transactional DB applications. Further, not every transactional application uses concurrency and so not every transactional application must manage deadlocks. Still, deadlock management is so frequently a characteristic of transactional applications that we discuss it in this book. See Concurrency for more information. diff --git a/docs_src/guides/gsg_txn/java/architectrecovery.md b/docs_src/guides/gsg_txn/java/architectrecovery.md new file mode 100644 index 000000000..a4cdda786 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/architectrecovery.md @@ -0,0 +1,82 @@ +--- +title: "Designing Your Application for Recovery" +api-name: "Designing Your Application for Recovery" +source: docs/gsg_txn/JAVA/architectrecovery.html +--- +## Designing Your Application for Recovery + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + +When building your DB application, you should consider how you will run recovery. If you are building a single threaded, single process application, it is fairly simple to run recovery when your application first opens its environment. In this case, you need only decide if you want to run recovery every time you open your application (recommended) or only some of the time, presumably triggered by a start up option controlled by your application's user. + +However, for multi-threaded and multi-process applications, you need to carefully consider how you will design your application's startup code so as to run recovery only when it makes sense to do so. + +### Recovery for Multi-Threaded Applications + +If your application uses only one environment handle, then handling recovery for a multi-threaded application is no more difficult than for a single threaded application. You simply open the environment in the application's main thread, and then pass that handle to each of the threads that will be performing DB operations. We illustrate this with our final example in this book (see Base API Transaction Example for more information). + +Alternatively, you can have each worker thread open its own environment handle. However, in this case, designing for recovery is a bit more complicated. + +Generally, when a thread performing database operations fails or hangs, it is frequently best to simply restart the application and run recovery upon application startup as normal. However, not all applications can afford to restart because a single thread has misbehaved. + +If you are attempting to continue operations in the face of a misbehaving thread, then at a minimum recovery must be run if a thread performing database operations fails or hangs. + +Remember that recovery clears the environment of all outstanding locks, including any that might be outstanding from an aborted thread. If these locks are not cleared, other threads performing database operations can back up behind the locks obtained but never cleared by the failed thread. The result will be an application that hangs indefinitely. + +To run recovery under these circumstances: + +1. Suspend or shutdown all other threads performing database operations. + +2. Discarding any open environment handles. Note that attempting to gracefully close these handles may be asking for trouble; the close can fail if the environment is already in need of recovery. For this reason, it is best and easiest to simply discard the handle. + +3. Open new handles, running recovery as you open them. See Normal Recovery for more information. + +4. Restart all your database threads. + +A traditional way to handle this activity is to spawn a watcher thread that is responsible for making sure all is well with your threads, and performing the above actions if not. + +However, in the case where each worker thread opens and maintains its own environment handle, recovery is complicated for two reasons: + +1. For some applications and workloads, it might be worthwhile to give your database threads the ability to gracefully finalize any on-going transactions. If this is the case, your code must be capable of signaling each thread to halt DB activities and close its environment. If you simply run recovery against the environment, your database threads will detect this and fail in the midst of performing their database operations. + +2. Your code must be capable of ensuring only one thread runs recovery before allowing all other threads to open their respective environment handles. Recovery should be single threaded because when recovery is run against an environment, it is deleted and then recreated. This will cause all other processes and threads to "fail" when they attempt operations against the newly recovered environment. If all threads run recovery when they start up, then it is likely that some threads will fail because the environment that they are using has been recovered. This will cause the thread to have to re-execute its own recovery path. At best, this is inefficient and at worst it could cause your application to fall into an endless recovery pattern. + +### Recovery in Multi-Process Applications + +Frequently, DB applications use multiple processes to interact with the databases. For example, you may have a long-running process, such as some kind of server, and then a series of administrative tools that you use to inspect and administer the underlying databases. Or, in some web-based architectures, different services are run as independent processes that are managed by the server. + +In any case, recovery for a multi-process environment is complicated for two reasons: + +1. In the event that recovery must be run, you might want to notify processes interacting with the environment that recovery is about to occur and give them a chance to gracefully terminate. Whether it is worthwhile for you to do this is entirely dependent upon the nature of your application. Some long-running applications with multiple processes performing meaningful work might want to do this. Other applications with processes performing database operations that are likely to be harmed by error conditions in other processes will likely find it to be not worth the effort. For this latter group, the chances of performing a graceful shutdown may be low anyway. + +2. Unlike single process scenarios, it can quickly become wasteful for every process interacting with the databases to run recovery when it starts up. This is partly because recovery *does* take some amount of time to run, but mostly you want to avoid a situation where your server must reopen all its environment handles just because you fire up a command line database administrative utility that always runs recovery. + +The following sections describe a mechanism that you can use to determine if and when you should run recovery in a multi-process application. + +#### Effects of Multi-Process Recovery + +Before continuing, it is worth noting that the following sections describe recovery processes than can result in one process running recovery while other processes are currently actively performing database operations. + +When this happens, the current database operation will abnormally fail, indicating a DB_RUNRECOVERY condition. This means that your application should immediately abandon any database operations that it may have on-going, discard any environment handles it has opened, and obtain and open new handles. + +The net effect of this is that any writes performed by unresolved transactions will be lost. For persistent applications (servers, for example), the services it provides will also be unavailable for the amount of time that it takes to complete a recovery and for all participating processes to reopen their environment handles. + +#### Process Registration + +One way to handle multi-process recovery is for every process to "register" its environment. In doing so, the process gains the ability to see if any other applications are using the environment and, if so, whether they have suffered an abnormal termination. If an abnormal termination is detected, the process runs recovery; otherwise, it does not. + +Note that using process registration also ensures that recovery is serialized across applications. That is, only one process at a time has a chance to run recovery. Generally this means that the first process to start up will run recovery, and all other processes will silently not run recovery because it is not needed. + +To cause your application to register its environment, you specify `true` to the `EnvironmentConfig.setRegister()` method when you open your environment. You may also specify `true` to `EnvironmentConfig.setRunRecovery()`. However, it is an error to specify `true` to `EnvironmentConfig.setRunFatalRecovery()` when you are also registering your environment with the `setRegister()` method. If during the open, DB determines that recovery must be run, it will automatically run the correct type of recovery for you, so long as you specify normal recovery on your environment open. If you do not specify normal recovery, and you register your environment, then no recovery is run if the registration process identifies a need for it. In this case, the environment open simply fails by throwing `RunRecoveryException`. + +### Note + +If you do not specify normal recovery when you open your first registered environment in the application, then that application will fail the environment open by throwing `RunRecoveryException`. This is because the first process to register must create an internal registration file, and recovery is forced when that file is created. To avoid an abnormal termination of the environment open, specify recovery on the environment open for at least the first process starting in your application. + +Be aware that there are some limitations/requirements if you want your various processes to coordinate recovery using registration: + +1. There can be only one environment handle per environment per process. In the case of multi-threaded processes, the environment handle must be shared across threads. + +2. All processes sharing the environment must use registration. If registration is not uniformly used across all participating processes, then you can see inconsistent results in terms of your application's ability to recognize that recovery must be run. diff --git a/docs_src/guides/gsg_txn/java/autocommit.md b/docs_src/guides/gsg_txn/java/autocommit.md new file mode 100644 index 000000000..35384d757 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/autocommit.md @@ -0,0 +1,72 @@ +--- +title: "Auto Commit" +api-name: "Auto Commit" +source: docs/gsg_txn/JAVA/autocommit.html +--- +## Auto Commit + +While transactions are frequently used to provide atomicity to multiple database or store operations, it is sometimes necessary to perform a single database or store operation under the control of a transaction. Rather than force you to obtain a transaction, perform the single write operation, and then either commit or abort the transaction, you can automatically group this sequence of events using *auto commit*. + +To use auto commit: + +1. Open your environment and your databases or store so that they support transactions. See Enabling Transactions for details. + +2. Do not provide a transactional handle to the method that is performing the database or store write operation. + +Note that auto commit is not available for cursors. You must always open your cursor using a transaction if you want the cursor's operations to be transactional protected. See Transactional Cursors for details on using transactional cursors. + +### Note + +Never have more than one active transaction in your thread at a time. This is especially a problem if you mix an explicit transaction with another operation that uses auto commit. Doing so can result in undetectable deadlocks. + +For example, the following uses auto commit to perform the database write operation: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + 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 +} +``` diff --git a/docs_src/guides/gsg_txn/java/backuprestore.md b/docs_src/guides/gsg_txn/java/backuprestore.md new file mode 100644 index 000000000..fa1b0363a --- /dev/null +++ b/docs_src/guides/gsg_txn/java/backuprestore.md @@ -0,0 +1,98 @@ +--- +title: "Backup Procedures" +api-name: "Backup Procedures" +source: docs/gsg_txn/JAVA/backuprestore.html +--- +## Backup Procedures + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + +*Durability* is an important part of your transactional guarantees. It means that once a transaction has been successfully committed, your application will always see the results of that transaction. + +Of course, no software algorithm can guarantee durability in the face of physical data loss. Hard drives can fail, and if you have not copied your data to locations other than your primary disk drives, then you will lose data when those drives fail. Therefore, in order to truly obtain a durability guarantee, you need to ensure that any data stored on disk is backed up to secondary or alternative storage, such as secondary disk drives, or offline tapes. + +There are three different types of backups that you can perform with DB databases and log files. They are: + +- Offline backups + + This type of backup is perhaps the easiest to perform as it involves simply copying database and log files to an offline storage area. It also gives you a snapshot of the database at a fixed, known point in time. However, you cannot perform this type of a backup while you are performing writes to the database. + +- Hot backups + + This type of backup gives you a snapshot of your database. Since your application can be writing to the database at the time that the snapshot is being taken, you do not necessarily know what the exact state of the database is for that given snapshot. + +- Incremental backups + + This type of backup refreshes a previously performed backup. + +Once you have performed a backup, you can perform *catastrophic recovery* to restore your databases from the backup. See Catastrophic Recovery for more information. + +Note that you can also maintain a hot failover. See Using Hot Failovers for more information. + +### About Unix Copy Utilities + +If you are copying database files you must copy databases atomically, in multiples of the database page size. In other words, the reads made by the copy program must not be interleaved with writes by other threads of control, and the copy program must read the databases in multiples of the underlying database page size. Generally, this is not a problem because operating systems already make this guarantee and system utilities normally read in power-of-2 sized chunks, which are larger than the largest possible Berkeley DB database page size. + +On some platforms (most notably, some releases of Solaris), the copy utility (`cp`) was implemented using the `mmap()` system call rather than the `read()` system call. Because `mmap()` did not make the same guarantee of read atomicity as did `read()`, the `cp` utility could create corrupted copies of the databases. + +Also, some platforms have implementations of the `tar` utility that performs 10KB block reads by default. Even when an output block size is specified, the utility will still not read the underlying databases in multiples of the specified block size. Again, the result can be a corrupted backup. + +To fix these problems, use the `dd` utility instead of `cp` or `tar`. When you use `dd`, make sure you specify a block size that is equal to, or an even multiple of, your database page size. Finally, if you plan to use a system utility to copy database files, you may want to use a system call trace utility (for example, `ktrace` or `truss`) to make sure you are not using a I/O size that is smaller than your database page size. You can also use these utilities to make sure the system utility is not using a system call other than `read()`. + +### Offline Backups + +To create an offline backup: + +1. Commit or abort all on-going transactions. + +2. Pause all database writes. + +3. Force a checkpoint. See Checkpoints for details. + +4. Copy all your database files to the backup location. Note that you can simply copy all of the database files, or you can determine which database files have been written during the lifetime of the current logs. To do this, use either the `Environment.getArchiveDatabases()`, method or use the **db_archive** command with the `-s` option. + + However, be aware that backing up just the modified databases only works if you have all of your log files. If you have been removing log files for any reason then using `getArchiveDatabases()`, can result in an unrecoverable backup because you might not be notified of a database file that was modified. + +5. Copy the *last* log file to your backup location. Your log files are named `log.`*`xxxxxxxxxx`*, where *xxxxxxxxxx* is a sequential number. The last log file is the file with the highest number. + +### Hot Backup + +To create a hot backup, you do not have to stop database operations. Transactions may be on-going and you can be writing to your database at the time of the backup. However, this means that you do not know exactly what the state of your database is at the time of the backup. + +You can use the **db_hotbackup** command line utility to create a hot backup. This program optionally runs a checkpoint, and then copies all necessary files to a target directory. + +You can also create your own hot backup facility using the `Environment.backup()` method. + +Alternatively, you can manually create a hot backup as follows: + +1. Specify `true` to the `EnvironmentConfig.setHotbackupInProgress()` method. For more information, see the `setHotbackupInProgress()` method in the EnvironmentConfig Javadoc page. + +2. Copy all your database files to the backup location. Note that you can simply copy all of the database files, or you can determine which database files have been written during the lifetime of the current logs. To do this, use either the `Environment.getArchiveDatabases()`, or use the **db_archive** command with the `-s` option. + +3. Copy all logs to your backup location. + +4. Specify `false` to the `EnvironmentConfig.setHotbackupInProgress()` method. + +### Note + +It is important to copy your database files *and then* your logs. In this way, you can complete or roll back any database operations that were only partially completed when you copied the databases. + +### Incremental Backups + +Once you have created a full backup (that is, either a offline or hot backup), you can create incremental backups. To do this, simply copy all of your currently existing log files to your backup location. + +Incremental backups do not require you to run a checkpoint or to cease database write operations. + +If your application uses the transactional bulk insert optimization, it is important to know that a database copy taken prior to a bulk loading event can no longer be used as the target of an incremental backup. This is true because bulk loading omits logging of some record insertions, so recovery cannot roll forward these insertions. It is recommended that a full backup be scheduled following a bulk loading event. + +For more information, see the `setBulk()` method in the TransactionConfig Javadoc page. + +When you are working with incremental backups, remember that the greater the number of log files contained in your backup, the longer recovery will take. You should run full backups on some interval, and then do incremental backups on a shorter interval. How frequently you need to run a full backup is determined by the rate at which your databases change and how sensitive your application is to lengthy recoveries (should one be required). + +You can also shorten recovery time by running recovery against the backup as you take each incremental backup. Running recovery as you go means that there will be less work for DB to do if you should ever need to restore your environment from the backup. diff --git a/docs_src/guides/gsg_txn/java/blocking_deadlocks.md b/docs_src/guides/gsg_txn/java/blocking_deadlocks.md new file mode 100644 index 000000000..1c65d891d --- /dev/null +++ b/docs_src/guides/gsg_txn/java/blocking_deadlocks.md @@ -0,0 +1,160 @@ +--- +title: "Locks, Blocks, and Deadlocks" +api-name: "Locks, Blocks, and Deadlocks" +source: docs/gsg_txn/JAVA/blocking_deadlocks.html +--- +## Locks, Blocks, and Deadlocks + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + +It is important to understand how locking works in a concurrent application before continuing with a description of the concurrency mechanisms DB makes available to you. Blocking and deadlocking have important performance implications for your application. Consequently, this section provides a fundamental description of these concepts, and how they affect DB operations. + +### Locks + +When one thread of control wants to obtain access to an object, it requests a *lock* for that object. This lock is what allows DB to provide your application with its transactional isolation guarantees by ensuring that: + +- no other thread of control can read that object (in the case of an exclusive lock), and + +- no other thread of control can modify that object (in the case of an exclusive or non-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 a transaction handle. For non-transactional operations, the locker is a cursor or a Database or Store handle. + +2. The lock. + + This is the actual data structure that locks the object. In DB, a locked object structure in 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 DB application, the locked object is usually a database page, which in turn contains multiple database entries (key and data). However, for Queue databases, individual database records are locked. + +You can configure how many total lockers, locks, and locked objects your application is allowed to support. See Configuring the Locking Subsystem for details. + +The following figure shows a transaction handle, `Txn A`, that is holding a lock on database page `002`. In this graphic, `Txn A` is the locker, and the locked object is page `002`. Only a single lock is in use in this operation. + +![](simplelock.jpg) + +#### Types of Locks + +DB applications support both exclusive and non-exclusive locks. *Exclusive locks* are granted when a locker wants to write to an object. For this reason, exclusive locks are also sometimes 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 exclusively locked object until the locker is done writing to that object. + +*Non-exclusive locks* are granted for read-only access. For this reason, non-exclusive locks are also sometimes called *read locks*. Since multiple lockers can simultaneously hold read locks 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 while the locker is still reading the object. This is how transactional cursors are able to achieve repeatable reads; by default, the cursor's transaction holds a read lock on any object that the cursor has examined until such a time as the transaction is committed or aborted. You can avoid these read locks by using snapshot isolation. See Using Snapshot Isolation for details. + +In the following figure, `Txn A` and `Txn B` are both holding read locks on page `002`, while `Txn C` is holding a write lock on page `003`: + +![](rwlocks1.jpg) + +#### Lock Lifetime + +A locker holds its locks until such a time as it does not need the lock any more. What this means is: + +1. A transaction holds any locks that it obtains until the transaction is committed or aborted. + +2. All non-transaction operations hold locks until such a time as the operation is completed. For cursor operations, the lock is held until the cursor is moved to a new position or closed. + +### Blocks + +Simply put, a thread of control is blocked when it attempts to obtain a lock, but that attempt is denied because some other thread of control holds a conflicting lock. Once blocked, the thread of control is temporarily unable to make any forward progress until the 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 is attempting 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, the entire 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 object 002, then if `Txn B` tries to obtain a read *or* write lock on that object, the thread of control in which `Txn B` is running is blocked: + +![](writeblock.jpg) + +However, if `Txn A` only holds a read lock (a shared lock) on object `002`, then only those handles that attempt to obtain a write lock on that object will block. + +![](readblock.jpg) + +### Note + +The previous description describes DB's default behavior when it cannot obtain a lock. It is possible to configure DB transactions so that they will not block. Instead, if a lock is unavailable, the application is immediately notified of a deadlock situation. See No Wait on Blocks for more information. + +#### Blocking and Application Performance + +Multi-threaded and multi-process applications typically perform better than simple single-threaded applications because the application can perform one part of its workload (updating a database record, for example) while it is waiting for some other lengthy operation to complete (performing disk or network I/O, for example). This performance improvement is particularly noticeable if you use hardware that offers multiple CPUs, because the threads and processes can run simultaneously. + +That said, concurrent applications can see reduced workload throughput if their threads of control are seeing a large amount of lock contention. That is, if threads are blocking on 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 already holding read locks on the requested object. In this case, the thread in which `Txn C` is running will pause until such a time as `Txn C` either obtains its write lock, or the operation that is requesting the lock is abandoned. The fact that `Txn C`'s thread has temporarily halted 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 will also block until such a time as `Txn C` has obtained and subsequently released its write lock. + +#### Avoiding Blocks + +Reducing lock contention is an important part of performance tuning your concurrent DB application. Applications that have multiple threads of control obtaining exclusive (write) locks are prone to contention issues. Moreover, as you increase the numbers of lockers and as you increase the time that a lock is held, you increase the chances of your application seeing lock contention. + +As you are designing your application, try to do the following in order to reduce lock contention: + +- Reduce the length of time your application holds locks. + + Shorter lived transactions will result in shorter lock lifetimes, which will in turn help to reduce lock contention. + + In addition, by default transactional cursors hold read locks until such a time as the transaction is completed. For this reason, try to minimize the time you keep transactional cursors opened, or reduce your isolation levels – see below. + +- If possible, access heavily accessed (read or write) items toward the end of the transaction. This reduces the amount of time that a heavily used page is locked by the transaction. + +- Reduce your application's isolation guarantees. + + By reducing your isolation guarantees, you reduce the situations in which a lock can block another lock. Try using uncommitted reads for your read operations in order to prevent a read lock being blocked by a write lock. + + In addition, for cursors you can use degree 2 (read committed) isolation, which causes the cursor to release its read locks as soon as it is done reading the record (as opposed to holding its read locks until the transaction ends). + + Be aware that reducing your isolation guarantees can have adverse consequences for your application. Before deciding to reduce your isolation, take care to examine your application's isolation requirements. For information on isolation levels, see Isolation. + +- Use snapshot isolation for read-only threads. + + Snapshot isolation causes the transaction to make a copy of the page on which it is holding a lock. When a reader makes a copy of a page, write locks can still be obtained for the original page. This eliminates entirely read-write contention. + + Snapshot isolation is described in Using Snapshot Isolation. + +- Consider your data access patterns. + + Depending on the nature of your application, this may be something that you can not do anything about. However, if it is possible to create your threads such that they operate only on non-overlapping portions of your database, then you can reduce lock contention because your threads will rarely (if ever) block on one another's locks. + +### Note + +It is possible to configure DB's transactions so that they never wait on blocked lock requests. Instead, if they are blocked on a lock request, they will notify the application of a deadlock (see the next section). + +You configure this behavior on a transaction by transaction basis. See No Wait on Blocks for more information. + +### Deadlocks + +A deadlock occurs when two or more threads of control are blocked, each waiting on a resource held by the other thread. When this happens, there is no possibility of the threads ever making forward progress unless some outside agent takes action to break the deadlock. + +For example, if `Txn A` is blocked by `Txn B` at the same time `Txn B` is blocked by `Txn A` then the threads of control containing `Txn A` and `Txn B` are deadlocked; neither thread can make any forward progress because neither thread will ever release the lock that is blocking the other thread. + +![](deadlock.jpg) + +When two threads of control deadlock, the only solution is to have a mechanism external to the two threads capable of recognizing the deadlock and notifying at least one thread that it is in a deadlock situation. Once notified, a thread of control must abandon the attempted operation in order to resolve the deadlock. DB's locking subsystem offers a deadlock notification mechanism. See Configuring Deadlock Detection for more information. + +Note that when one locker in a thread of control is blocked waiting on a lock held by another locker in that same thread of the control, the thread is said to be *self-deadlocked*. + +#### Deadlock Avoidance + +The things that you do to avoid lock contention also help to reduce deadlocks (see Avoiding Blocks). Beyond that, you can also do the following in order to avoid deadlocks: + +- Never have more than one active transaction at a time in a thread. A common cause of this is for a thread to be using auto-commit for one operation while an explicit transaction is in use in that thread at the same time. + +- Make sure all threads access data in the same order as all other threads. So long as threads lock database pages 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 (indexes), it is not possible to obtain locks in a consistent order because you cannot predict the order in which locks are obtained in secondary databases. If you are writing a concurrent application and you are using secondary databases, you must be prepared to handle deadlocks. + +- If you are using BTrees in which you are constantly adding and then deleting data, turn Btree reverse split off. See Reverse BTree Splits for more information. + +- Declare a read/modify/write lock for those situations where you are reading a record in preparation of modifying and then writing the record. Doing this causes DB to give your read operation a write lock. This means that no other thread of control can share a read lock (which might cause contention), but it also means that the writer thread will not have to wait to obtain a write lock when it is ready to write the modified data back to the database. + + For information on declaring read/modify/write locks, see Read/Modify/Write. diff --git a/docs_src/guides/gsg_txn/java/enabletxn.md b/docs_src/guides/gsg_txn/java/enabletxn.md new file mode 100644 index 000000000..d0548c555 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/enabletxn.md @@ -0,0 +1,219 @@ +--- +title: "Chapter 2. Enabling Transactions" +api-name: "Chapter 2. Enabling Transactions" +source: docs/gsg_txn/JAVA/enabletxn.html +--- +## Chapter 2. Enabling Transactions + +**Table of Contents** + + [Environments](enabletxn.md#environments) + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + + [Opening a Transactional Environment and Store or Database](envopen.md) + +In order to use transactions with your application, you must turn them on. To do this you must: + +- Use an environment (see Environments for details). + +- Turn on transactions for your environment. You do this by using the `EnvironmentConfig.setTransactional()` method. Note that initializing the transactional subsystem implies that the logging subsystem is also initialized. Also, note that if you do not initialize transactions when you first create your environment, then you cannot use transactions for that environment after that. This is because DB allocates certain structures needed for transactional locking that are not available if the environment is created without transactional support. + +- Initialize the in-memory cache by passing `true` to the `EnvironmentConfig.setInitializeCache()` method. + +- Initialize the locking subsystem. This is what provides locking for concurrent applications. It also is used to perform deadlock detection. See Concurrency for more information. + + You initialize the locking subsystem by passing `true` to the `EnvironmentConfig.setInitializeLocking()` method. + +- If you are using the DPL, transaction-enable your stores. You do this by using the `StoreConfig.setTransactional() method.` + +- Transaction-enable your databases. If you are using the base API, transaction-enable your databases. You do this by using the `DatabaseConfig.setTransactional()` method, and then opening the database from within a transaction. Note that the common practice is for auto commit to be used to transaction-protect the database open. To use auto-commit, you must still enable transactions as described here, but you do not have to explicitly use a transaction when you open your database. An example of this is given in the next section. + +## Environments + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + +For simple DB applications, environments are optional. However, in order to transaction protect your database operations, you must use an environment. + +An *environment*, represents an encapsulation of one or more databases and any associated log and region files. They are used to support multi-threaded and multi-process applications by allowing different threads of control to share the in-memory cache, the locking tables, the logging subsystem, and the file namespace. By sharing these things, your concurrent application is more efficient than if each thread of control had to manage these resources on its own. + +By default all DB databases are backed by files on disk. In addition to these files, transactional DB applications create logs that are also by default stored on disk (they can optionally be backed using shared memory). Finally, transactional DB applications also create and use shared-memory regions that are also typically backed by the filesystem. But like databases and logs, the regions can be maintained strictly in-memory if your application requires it. For an example of an application that manages all environment files in-memory, see Base API In-Memory Transaction Example. + +### Warning + +Using environments with some journaling filesystems might result in log file corruption. This can occur if the operating system experiences an unclean shutdown when a log file is being created. Please see Using Recovery on Journaling Filesystems in the *Berkeley DB Programmer's Reference Guide* for more information. + +### File Naming + +In order to operate, your DB application must be able to locate its database files, log files, and region files. If these are stored in the filesystem, then you must tell DB where they are located (a number of mechanisms exist that allow you to identify the location of these files – see below). Otherwise, by default they are located in the current working directory. + +#### Specifying the Environment Home Directory + +The environment home directory is used to determine where DB files are located. Its location is identified using one of the following mechanisms, in the following order of priority: + +- If no information is given as to where to put the environment home, then the current working directory is used. + +- If a home directory is specified on the `Environment()` constructor, then that location is always used for the environment home. + +- If a home directory is not supplied to `Environment()`, then the directory identified by the `DB_HOME` environment variable is used *if* you specify `true` to either the `EnvironmentConfig.setUseEnvironment()` or `EnvironmentConfig.setUseEnvironmentRoot()` method. Both methods allow you to identify the path to the environment's home directory using `DB_HOME`. However, `EnvironmentConfig.setUseEnvironmentRoot()` is honored only if the process is run with root or administrative privileges. + +#### Specifying File Locations + +By default, all DB files are created relative to the environment home directory. For example, suppose your environment home is in `/export/myAppHome`. Also suppose you name your database `data/myDatabase.db`. Then in this case, the database is placed in: `/export/myAppHome/data/myDatabase.db`. + +That said, DB always defers to absolute pathnames. This means that if you provide an absolute filename when you name your database, then that file is *not* placed relative to the environment home directory. Instead, it is placed in the exact location that you specified for the filename. + +On UNIX systems, an absolute pathname is a name that begins with a forward slash ('/'). On Windows systems, an absolute pathname is a name that begins with one of the following: + +- A backslash ('\\). + +- Any alphabetic letter, followed by a colon (':'), followed by a backslash ('\\). + +### Note + +Try not to use absolute path names for your environment's files. Under certain recovery scenarios, absolute path names can render your environment unrecoverable. This occurs if you are attempting to recover your environment on a system that does not support the absolute path name that you used. + +#### Identifying Specific File Locations + +As described in the previous sections, DB will place all its files in or relative to the environment home directory. You can also cause a specific database file to be placed in a particular location by using an absolute path name for its name. In this situation, the environment's home directory is not considered when naming the file. + +It is frequently desirable to place database, log, and region files on separate disk drives. By spreading I/O across multiple drives, you can increase parallelism and improve throughput. Additionally, by placing log files and database files on separate drives, you improve your application's reliability by providing your application with a greater chance of surviving a disk failure. + +You can cause DB's files to be placed in specific locations using the following mechanisms: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File TypeTo Override
database files

You can cause database files to be created in a directory other than the environment home by using the EnvironmentConfig.addDataDir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

This method modifies the directory used for database files created and managed by a single environment handle; it does not configure the entire environment.

+

You can also set a default data location that is used by the entire environment by using the add_data_dir parameter in the environment's DB_CONFIG file. Note that the add_data_dir parameter overrides any value set by the EnvironmentConfig.addDataDir() method.

Log files

You can cause log files to be created in a directory other than the environment home directory by using the EnvironmentConfig.LogDirectory() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

This method modifies the directory used for database files created and managed by a single environment handle; it does not configure the entire environment.

+

You can also set a default log file location that is used by the entire environment by using the set_lg_dir parameter in the environment's DB_CONFIG file. Note that the set_lg_dir parameter overrides any value set by the EnvironmentConfig.setLogDirectory() method.

Temporary files

You can cause temporary files required by the environment to be created in a directory other than the environment home directory by using the EnvironmentConfig.setTemporaryDirectory() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

You can also set a temporary file location by using the set_tmp_dir parameter in the environment's DB_CONFIG file. Note that the set_tmp_dir parameter overrides any value set by the EnvironmentConfig.setTemporaryDirectory() method.

Metadata files

You can cause persistent metadata files required by the replicated applications to be created in a directory other than the environment home directory by using the EnvironmentConfig.setMetadataDir() method. The directory identified here must exist. If a relative path is provided, then the directory location is resolved relative to the environment's home directory.

+

You can also set a metadata directory location by using the set_metadata_dir parameter in the environment's DB_CONFIG file. Note that the set_metadata_dir parameter overrides any value set by the EnvironmentConfig.setMetadataDir() method.

Region filesIf backed by the filesystem, region files are always placed in the environment home directory.
+ +Note that the `DB_CONFIG` must reside in the environment home directory. Parameters are specified in it one parameter to a line. Each parameter is followed by a space, which is followed by the parameter value. For example: + +``` c + add_data_dir /export1/db/env_data_files +``` + +### Error Support + +To simplify error handling and to aid in application debugging, environments offer several useful methods. Note that many of these methods are identical to the error handling methods available for the DatabaseConfig class. They are: + +- `EnvironmentConfig.setErrorStream()` + + Sets the Java `OutputStream` to be used for displaying error messages issued by the DB library. + +- `EnvironmentConfig.setErrorHandler()` + + Defines the message handler that is called when an error message is issued by DB. The error prefix and message are passed to this callback. It is up to the application to display this information correctly. + + Note that the message handler must be an implementation of the `com.sleepycat.db.ErrorHandler` interface. + + This is the recommended way to get error messages from DB. + +- `EnvironmentConfig.setErrorPrefix()` + + Sets the prefix used to for any error messages issued by the DB library. + +### Shared Memory Regions + +The subsystems that you enable for an environment (in our case, transaction, logging, locking, and the memory pool) are described by one or more regions. The regions contain all of the state information that needs to be shared among threads and/or processes using the environment. + +Regions may be backed by the file system, by heap memory, or by system shared memory. + +### Note + +When DB dynamically obtains memory, it uses memory outside of the JVM. Normally the amount of memory that DB obtains is trivial, a few bytes here and there, so you might not notice it. However, if heap or system memory is used to back your region files, then this can represent a significant amount of memory being used by DB above and beyond the memory required by the JVM process. As a result, the JVM process may appear to be using more memory than you told the process it could use. + +#### Regions Backed by Files + +By default, shared memory regions are created as files in the environment's home directory (*not* the environment's data directory). If it is available, the POSIX `mmap` interface is used to map these files into your application's address space. If `mmap` is not available, then the UNIX `shmget` interfaces are used instead (again, if they are available). + +In this default case, the region files are named `__db.###` (for example, `__db.001`, `__db.002`, and so on). + +#### Regions Backed by Heap Memory + +If heap memory is used to back your shared memory regions, then you can only open a single handle for the environment. This means that the environment cannot be accessed by multiple processes. In this case, the regions are managed only in memory, and they are not written to the filesystem. You indicate that heap memory is to be used for the region files by specifying `true` to the `EnvironmentConfig.setPrivate()` method. + +Note that you can also set this flag by using the `set_open_flags` parameter in the `DB_CONFIG` file. See the *Berkeley DB C API Reference Guide* for more information. + +(For an example of an entirely in-memory transactional application, see Base API In-Memory Transaction Example.) + +#### Regions Backed by System Memory + +Finally, you can cause system memory to be used for your regions instead of memory-mapped files. You do this by providing `true` to the `EnvironmentConfig.setSystemMemory()` method. + +When region files are backed by system memory, DB creates a single file in the environment's home directory. This file contains information necessary to identify the system shared memory in use by the environment. By creating this file, DB enables multiple processes to share the environment. + +The system memory that is used is architecture-dependent. For example, on systems supporting X/Open-style shared memory interfaces, such as UNIX systems, the `shmget(2)` and related System V IPC interfaces are used. Additionally, VxWorks systems use system memory. In these cases, an initial segment ID must be specified by the application to ensure that applications do not overwrite each other's environments, so that the number of segments created does not grow without bounds. See the `EnvironmentConfig.setSegmentId()` method for more information. + +On Windows platforms, the use of system memory for the region files is problematic because the operating system uses reference counting to clean up shared objects in the paging file automatically. In addition, the default access permissions for shared objects are different from files, which may cause problems when an environment is accessed by multiple processes running as different users. See Windows notes or more information. + +### Security Considerations + +When using environments, there are some security considerations to keep in mind: + +- Database environment permissions + + The directory used for the environment should have its permissions set to ensure that files in the environment are not accessible to users without appropriate permissions. Applications that add to the user's permissions (for example, UNIX `setuid` or `setgid` applications), must be carefully checked to not permit illegal use of those permissions such as general file access in the environment directory. + +- Environment variables + + Setting `true` for `EnvironmentConfig.setUseEnvironment()` or `EnvironmentConfig.setUseEnvironmentRoot()` so that environment variables can be used during file naming can be dangerous. Setting those flags in DB applications with additional permissions (for example, UNIX `setuid` or `setgid` applications) could potentially allow users to read and write databases to which they would not normally have access. + + For example, suppose you write a DB application that runs `setuid`. This means that when the application runs, it does so under a userid different than that of the application's caller. This is especially problematic if the application is granting stronger privileges to a user than the user might ordinarily have. + + Now, if `true` is specified for `EnvironmentConfig.setUseEnvironment()` or `EnvironmentConfig.setUseEnvironmentRoot()`, then the environment that the application is using is modifiable using the `DB_HOME` environment variable. In this scenario, if the uid used by the application has sufficiently broad privileges, then the application's caller can read and/or write databases owned by another user simply by setting his `DB_HOME` environment variable to the environment used by that other user. + + Note that this scenario need not be malicious; the wrong environment could be used by the application simply by inadvertently specifying the wrong path to `DB_HOME`. + + As always, you should use `setuid` sparingly, if at all. But if you do use `setuid`, then you should refrain from specifying `true` for `EnvironmentConfig.setUseEnvironment()` or `EnvironmentConfig.setUseEnvironmentRoot()` for the environment open. And, of course, if you must use `setuid`, then make sure you use the weakest uid possible – preferably one that is used only by the application itself. + +- File permissions + + By default, DB always creates database and log files readable and writable by the owner and the group (that is, `S_IRUSR`, `S_IWUSR`, `S_IRGRP` and `S_IWGRP`; or octal mode 0660 on historic UNIX systems). The group ownership of created files is based on the system and directory defaults, and is not further specified by DB. + +- Temporary backing files + + If an unnamed database is created and the cache is too small to hold the database in memory, Berkeley DB will create a temporary physical file to enable it to page the database to disk as needed. In this case, environment variables such as `TMPDIR` may be used to specify the location of that temporary file. Although temporary backing files are created readable and writable by the owner only (`S_IRUSR` and `S_IWUSR`, or octal mode 0600 on historic UNIX systems), some filesystems may not sufficiently protect temporary files created in random directories from improper access. To be absolutely safe, applications storing sensitive data in unnamed databases should use the `EnvironmentConfig.setTemporaryDirectory()` method to specify a temporary directory with known permissions. diff --git a/docs_src/guides/gsg_txn/java/envopen.md b/docs_src/guides/gsg_txn/java/envopen.md new file mode 100644 index 000000000..976dd80f9 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/envopen.md @@ -0,0 +1,128 @@ +--- +title: "Opening a Transactional Environment and Store or Database" +api-name: "Opening a Transactional Environment and Store or Database" +source: docs/gsg_txn/JAVA/envopen.html +--- +## Opening a Transactional Environment and Store or Database + +To enable transactions for your environment, you must initialize the transactional subsystem. Note that doing this also initializes the logging subsystem. In addition, you must initialize the memory pool (in-memory cache). You must also initialize the locking subsystem. For example, to do this with the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +EntityStore myStore = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + StoreConfig storeConfig = new StoreConfig(); + + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + storeConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + myStore = new EntityStore(myEnv, "EntityStore", storeConfig); + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +And when using the base API: + +``` c +package db.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +You then can use the `Environment` handle to open your database(s) using `Environment.openDatabase()`. Note that when you do this, you must set `DatabaseConfig.setTransactional()` to `true`. Note that in effect this causes the database open to be transactional protected because it results in auto commit being used for the open (if a transaction is not explicitly used to protect the open). For example: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + dbConfig); +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +### Note + +Never close a database or store that has active transactions. Make sure all transactions are resolved (either committed or aborted) before closing the database. diff --git a/docs_src/guides/gsg_txn/java/exclusivelock.md b/docs_src/guides/gsg_txn/java/exclusivelock.md new file mode 100644 index 000000000..1c96405cf --- /dev/null +++ b/docs_src/guides/gsg_txn/java/exclusivelock.md @@ -0,0 +1,18 @@ +--- +title: "Exclusive Database Handles" +api-name: "Exclusive Database Handles" +source: docs/gsg_txn/JAVA/exclusivelock.html +--- +## Exclusive Database Handles + +In some cases, concurrent applications can benefit from occasionally granting exclusive access to the entire database to a single database handle. This is desirable when a thread will perform an operation that touches all or most of the pages in a database. + +To configure a handle to have exclusive access to a database, you give it a single write lock to the entire database. This causes all other threads to block when they attempt to gain a read or write lock to any part of that database. + +The exclusive lock allows for improved throughput because the handle will not attempt to acquire any further locks once it has the exclusive write lock. It will also never be blocked waiting for a lock, and there is no possibility of a deadlock/retry cycle. + +Note that an exclusive database handle can only have one transaction active for it at a time. + +To configure a database handle with an exclusive lock, you use the method before you open the database handle. Setting a value of `0` to this method means that the handle open operation will block until it can obtain the exclusive lock. A non-zero value means that if the method cannot obtain the exclusive lock immediately when the handle is opened, the open operation will exit with a `DB_LOCK_NOTGRANTED` error return. `DatabaseConfig.setNoWaitDbExclusiveLock()` method on the `DatabaseConfig` object that you use to configure the `Database` handle which is to acquire the exclusive lock. Setting a value of `False` to this method means that the handle open operation will block until it can obtain the exclusive lock. A value of `False` means that if the open operation cannot obtain the exclusive lock immediately, it will throw a `LockNotGrantedException` exception. + +Once configured and opened, a handled configured with an exclusive database lock will hold that lock until the handle is closed. diff --git a/docs_src/guides/gsg_txn/java/filemanagement.md b/docs_src/guides/gsg_txn/java/filemanagement.md new file mode 100644 index 000000000..a599b85df --- /dev/null +++ b/docs_src/guides/gsg_txn/java/filemanagement.md @@ -0,0 +1,246 @@ +--- +title: "Chapter 5. Managing DB Files" +api-name: "Chapter 5. Managing DB Files" +source: docs/gsg_txn/JAVA/filemanagement.html +--- +## Chapter 5. Managing DB Files + +**Table of Contents** + + [Checkpoints](filemanagement.md#checkpoints) + + [Backup Procedures](backuprestore.md) + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + + [Recovery Procedures](recovery.md) + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + + [Designing Your Application for Recovery](architectrecovery.md) + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + + [Using Hot Failovers](hotfailover.md) + + [Removing Log Files](logfileremoval.md) + + [Configuring the Logging Subsystem](logconfig.md) + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + +DB is capable of storing several types of files on disk: + +- Data files, which contain the actual data in your database. + +- Log files, which contain information required to recover your database in the event of a system or application failure. + +- Region files, which contain information necessary for the overall operation of your application. + +- Temporary files, which are created only under certain special circumstances. These files never need to be backed up or otherwise managed and so they are not a consideration for the topics described in this chapter. See Security Considerations for more information on temporary files. + +Of these, you must manage your data and log files by ensuring that they are backed up. You should also pay attention to the amount of disk space your log files are consuming, and periodically remove any unneeded files. Finally, you can optionally tune your logging subsystem to best suit your application's needs and requirements. These topics are discussed in this chapter. + +## Checkpoints + +Before we can discuss DB file management, we need to describe checkpoints. When databases are modified (that is, a transaction is committed), the modifications are recorded in DB's logs, but they are *not* necessarily reflected in the actual database files on disk. + +This means that as time goes on, increasingly more data is contained in your log files that is not contained in your data files. As a result, you must keep more log files around than you might actually need. Also, any recovery run from your log files will take increasingly longer amounts of time, because there is more data in the log files that must be reflected back into the data files during the recovery process. + +You can reduce these problems by periodically running a checkpoint against your environment. The checkpoint: + +- Flushes dirty pages from the in-memory cache. This means that data modifications found in your in-memory cache are written to the database files on disk. Note that a checkpoint also causes data dirtied by an uncommitted transaction to also be written to your database files on disk. In this latter case, DB's normal recovery is used to remove any such modifications that were subsequently abandoned by your application using a transaction abort. + + Normal recovery is describe in Recovery Procedures. + +- Writes a checkpoint record. + +- Flushes the log. This causes all log data that has not yet been written to disk to be written. + +- Writes a list of open databases. + +There are several ways to run a checkpoint. One way is to use the **db_checkpoint** command line utility. (Note, however, that this command line utility cannot be used if your environment was opened using `EnvironmentConfig.setPrivate()`.) + +You can also run a thread that periodically checkpoints your environment for you by calling the `Environment.checkpoint()` method. + +Note that you can prevent a checkpoint from occurring unless more than a specified amount of log data has been written since the last checkpoint. You can also prevent the checkpoint from running unless more than a specified amount of time has occurred since the last checkpoint. These conditions are particularly interesting if you have multiple threads or processes running checkpoints. + +For configuration information, see the CheckpointConfig Javadoc page. + +Note that running checkpoints can be quite expensive. DB must flush every dirty page to the backing database files. On the other hand, if you do not run checkpoints often enough, your recovery time can be unnecessarily long and you may be using more disk space than you really need. Also, you cannot remove log files until a checkpoint is run. Therefore, deciding how frequently to run a checkpoint is one of the most common tuning activity for DB applications. + +For example, the following class performs a checkpoint every 60 seconds, so long as 500 kb of logging data has been written since the last checkpoint: + +``` c +package db.txn; + +import com.sleepycat.db.CheckpointConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; + +public class CheckPointer extends Thread +{ + private CheckpointConfig cpc = new CheckpointConfig(); + private Environment myEnv = null; + private static boolean canRun = true; + + // Constructor. + CheckPointer(Environment env) { + myEnv = env; + // Run a checkpoint only if 500 kbytes of log data has been + // written. + cpc.setKBytes(500); + } + + // Thread method that performs a checkpoint every + // 60 seconds + public void run () { + while (canRun) { + try { + myEnv.checkpoint(cpc); + sleep(60000); + } catch (DatabaseException de) { + System.err.println("Checkpoint error: " + + de.toString()); + } catch (InterruptedException e) { + // Should never get here + System.err.println("got interrupted exception"); + } + } + } + + public static void stopRunning() { + canRun = false; + } +} +``` + +And you use this class as follows. Note that we add the call to shutdown the checkpoint thread in our application's shutdown code: + +``` c +package db.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +public class TryCheckPoint { + + private static String myEnvPath = "./"; + + private static Environment myEnv = null; + + private static void usage() { + System.out.println("TxnGuide [-h ]"); + System.exit(-1); + } + + public static void main(String args[]) { + try { + // Parse the arguments list + parseArgs(args); + // Open the environment and databases + openEnv(); + + // Start the checkpoint thread + CheckPointer cp = new CheckPointer(myEnv); + cp.start(); + + ////////////////////////////////// + // Do database work here as normal + ////////////////////////////////// + + // Once all database work is completed, stop the checkpoint + // thread. + CheckPointer.stopRunning(); + + // Join the checkpoint thread in case it needs some time to + // cleanly shutdown. + cp.join(); + + } catch (Exception e) { + System.err.println("TryCheckPoint: " + e.toString()); + e.printStackTrace(); + } finally { + closeEnv(); + } + System.out.println("All done."); + } + + // Open an environment and databases + private static void openEnv() throws DatabaseException { + System.out.println("opening env"); + + // Set up the environment. + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setAllowCreate(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + // EnvironmentConfig.setThreaded(true) is the default behavior + // in Java, so we do not have to do anything to cause the + // environment handle to be free-threaded. + + try { + // Open the environment + myEnv = new Environment(new File(myEnvPath), // Env home + myEnvConfig); + + // Skipping the database opens and closes for brevity + + } catch (FileNotFoundException fnfe) { + System.err.println("openEnv: " + fnfe.toString()); + System.exit(-1); + } + } + + // Close the environment and databases + private static void closeEnv() { + System.out.println("Closing env"); + if (myEnv != null ) { + try { + myEnv.close(); + } catch (DatabaseException e) { + System.err.println("closeEnv: " + e.toString()); + e.printStackTrace(); + } + } + } + + private TryCheckPoint() {} + + 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(); + } + } + } + } +} +``` diff --git a/docs_src/guides/gsg_txn/java/hotfailover.md b/docs_src/guides/gsg_txn/java/hotfailover.md new file mode 100644 index 000000000..6bb8975f9 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/hotfailover.md @@ -0,0 +1,50 @@ +--- +title: "Using Hot Failovers" +api-name: "Using Hot Failovers" +source: docs/gsg_txn/JAVA/hotfailover.html +--- +## Using Hot Failovers + +You can maintain a backup that can be used for failover purposes. Hot failovers differ from the backup and restore procedures described previously in this chapter in that data used for traditional backups is typically copied to offline storage. Recovery time for a traditional backup is determined by: + +- How quickly you can retrieve that storage media. Typically storage media for critical backups is moved to a safe facility in a remote location, so this step can take a relatively long time. + +- How fast you can read the backup from the storage media to a local disk drive. If you have very large backups, or if your storage media is very slow, this can be a lengthy process. + +- How long it takes you to run catastrophic recovery against the newly restored backup. As described earlier in this chapter, this process can be lengthy because every log file must be examined during the recovery process. + +When you use a hot failover, the backup is maintained at a location that is reasonably fast to access. Usually, this is a second disk drive local to the machine. In this situation, recovery time is very quick because you only have to reopen your environment and database, using the failover environment for the environment open. + +Hot failovers obviously do not protect you from truly catastrophic disasters (such as a fire in your machine room) because the backup is still local to the machine. However, you can guard against more mundane problems (such as a broken disk drive) by keeping the backup on a second drive that is managed by an alternate disk controller. + +To maintain a hot failover: + +1. Copy all the active database files to the failover directory. Use the **db_archive** command line utility with the `-s` option to identify all the active database files. + +2. Identify all the inactive log files in your production environment and *move* these to the failover directory. Use the **db_archive** command with no command line options to obtain a list of these log files. + +3. Identify the active log files in your production environment, and *copy* these to the failover directory. Use the **db_archive** command with the `-l` option to obtain a list of these log files. + +4. Run catastrophic recovery against the failover directory. Use the **db_recover** command with the `-c` option to do this. + +5. Optionally copy the backup to an archival location. + +Once you have performed this procedure, you can maintain an active hot backup by repeating steps 2 - 5 as often as is required by your application. + +### Note + +If you perform step 1, steps 2-5 must follow in order to ensure consistency of your hot backup. + +### Note + +Rather than use the previous procedure, you can use the **db_hotbackup** command line utility to do the same thing. This utility will (optionally) run a checkpoint and then copy all necessary files to a target directory for you. + +To actually perform a failover, simply: + +1. Shut down all processes which are running against the original environment. + +2. If you have an archival copy of the backup environment, you can optionally try copying the remaining log files from the original environment and running catastrophic recovery against that backup environment. Do this *only* if you have a an archival copy of the backup environment. + + This step can allow you to recover data created or modified in the original environment, but which did not have a chance to be reflected in the hot backup environment. + +3. Reopen your environment and databases as normal, but use the backup environment instead of the production environment. diff --git a/docs_src/guides/gsg_txn/java/img/deadlock.jpg b/docs_src/guides/gsg_txn/java/img/deadlock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0995a84d82583bfccce034cc69e3b661fe0cb264 GIT binary patch literal 12599 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/img/readblock.jpg b/docs_src/guides/gsg_txn/java/img/readblock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16a511feb2a1d42e66eb303054f9616b60060390 GIT binary patch literal 10504 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 JQ4#lx{{>E>0o4Ei literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/img/rwlocks1-pdf.jpg b/docs_src/guides/gsg_txn/java/img/rwlocks1-pdf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..11346c0bba5795b50cf4abad3342d0ba94d4d70b GIT binary patch literal 192136 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 Mz6Ad164=51A3IEtI{*Lx literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/img/rwlocks1.jpg b/docs_src/guides/gsg_txn/java/img/rwlocks1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fc88fd31022397e8b5cc6292c4f28000051d7ec GIT binary patch literal 7428 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/img/simplelock-pdf.jpg b/docs_src/guides/gsg_txn/java/img/simplelock-pdf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78f8321d92c359985f7368bf7ef70f542b18b39f GIT binary patch literal 129293 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/img/simplelock.jpg b/docs_src/guides/gsg_txn/java/img/simplelock.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8dca4ad820d049e31ac7c6a7abd682093f72b2c3 GIT binary patch literal 4453 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 literal 0 HcmV?d00001 diff --git a/docs_src/guides/gsg_txn/java/index.md b/docs_src/guides/gsg_txn/java/index.md new file mode 100644 index 000000000..d39dcd24e --- /dev/null +++ b/docs_src/guides/gsg_txn/java/index.md @@ -0,0 +1,188 @@ +--- +title: "Getting Started with Berkeley DB Transaction Processing" +api-name: "Getting Started with Berkeley DB Transaction Processing" +source: docs/gsg_txn/JAVA/index.html +--- +# Getting Started with Berkeley DB Transaction Processing + +**Language:** [C](../index.md) · [C++](../cxx/index.md) · Java (this page) + +**Legal Notice** + +This documentation is distributed under an open source license. You may review the terms of this license at: http://www.oracle.com/technetwork/database/berkeleydb/downloads/oslicense-093458.html + +Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No third-party use is permitted without the express prior written consent of Oracle. + +Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the United States and other countries. + +Other names may be trademarks of their respective owners. + +To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at: http://forums.oracle.com/forums/forum.jspa?forumID=271 + +9/9/2013 + +------------------------------------------------------------------------ + +**Table of Contents** + + [Preface](preface.md) + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + + [1. Introduction](introduction.md) + + [Transaction Benefits](introduction.md#txnintro) + + [A Note on System Failure](sysfailure.md) + + [Application Requirements](apireq.md) + + [Multi-threaded and Multi-process Applications](multithread-intro.md) + + [Recoverability](recovery-intro.md) + + [Performance Tuning](perftune-intro.md) + + [2. Enabling Transactions](enabletxn.md) + + [Environments](enabletxn.md#environments) + + [File Naming](enabletxn.md#filenaming) + + [Error Support](enabletxn.md#errorsupport) + + [Shared Memory Regions](enabletxn.md#sharedmemory) + + [Security Considerations](enabletxn.md#security) + + [Opening a Transactional Environment and Store or Database](envopen.md) + + [3. Transaction Basics](usingtxns.md) + + [Committing a Transaction](usingtxns.md#commitresults) + + [Non-Durable Transactions](nodurabletxn.md) + + [Aborting a Transaction](abortresults.md) + + [Auto Commit](autocommit.md) + + [Nested Transactions](nestedtxn.md) + + [Transactional Cursors](txncursor.md) + + [Using Transactional DPL Cursors](txncursor.md#dplcursors) + + [Secondary Indices with Transaction Applications](txnindices.md) + + [Configuring the Transaction Subsystem](maxtxns.md) + + [4. Concurrency](txnconcurrency.md) + + [Which DB Handles are Free-Threaded](txnconcurrency.md#concurrenthandles) + + [Locks, Blocks, and Deadlocks](blocking_deadlocks.md) + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + + [The Locking Subsystem](lockingsubsystem.md) + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + + [Isolation](isolation.md) + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + + [Transactional Cursors and Concurrent Applications](txn_ccursor.md) + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + + [Exclusive Database Handles](exclusivelock.md) + + [Read/Modify/Write](readmodifywrite.md) + + [No Wait on Blocks](txnnowait.md) + + [Reverse BTree Splits](reversesplit.md) + + [5. Managing DB Files](filemanagement.md) + + [Checkpoints](filemanagement.md#checkpoints) + + [Backup Procedures](backuprestore.md) + + [About Unix Copy Utilities](backuprestore.md#copyutilities) + + [Offline Backups](backuprestore.md#standardbackup) + + [Hot Backup](backuprestore.md#hotbackup) + + [Incremental Backups](backuprestore.md#incrementalbackups) + + [Recovery Procedures](recovery.md) + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + + [Designing Your Application for Recovery](architectrecovery.md) + + [Recovery for Multi-Threaded Applications](architectrecovery.md#multithreadrecovery) + + [Recovery in Multi-Process Applications](architectrecovery.md#multiprocessrecovery) + + [Using Hot Failovers](hotfailover.md) + + [Removing Log Files](logfileremoval.md) + + [Configuring the Logging Subsystem](logconfig.md) + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + + [6. Summary and Examples](wrapup.md) + + [Anatomy of a Transactional Application](wrapup.md#anatomy) + + [Base API Transaction Example](txnexample_java.md) + + [TxnGuide.java](txnexample_java.md#txnguideexample) + + [PayloadData.java](txnexample_java.md#payloaddata) + + [DBWriter.java](txnexample_java.md#dbwriter) + + [DPL Transaction Example](txnexample_dpl.md) + + [TxnGuide.java](txnexample_dpl.md#txnguideexample_dpl) + + [PayloadDataEntity.java](txnexample_dpl.md#payloaddataentity) + + [StoreWriter.java](txnexample_dpl.md#storewriter) + + [Base API In-Memory Transaction Example](inmem_txnexample_java.md) diff --git a/docs_src/guides/gsg_txn/java/inmem_txnexample_java.md b/docs_src/guides/gsg_txn/java/inmem_txnexample_java.md new file mode 100644 index 000000000..23fed4ee8 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/inmem_txnexample_java.md @@ -0,0 +1,420 @@ +--- +title: "Base API In-Memory Transaction Example" +api-name: "Base API In-Memory Transaction Example" +source: docs/gsg_txn/JAVA/inmem_txnexample_java.html +--- +## Base API In-Memory Transaction Example + +DB is sometimes used for applications that simply need to cache data retrieved from some other location (such as a remote database server). DB is also often used in embedded systems. + +In both cases, applications may still want to use transactions for atomicity, consistency, and isolation guarantees, but they may want to forgo the durability guarantee entirely. That is, they may want their DB environment and databases kept entirely in-memory so as to avoid the performance impact of unneeded disk I/O. + +To do this: + +- Refrain from specifying a home directory when you open your environment. The exception to this is if you are using the `DB_CONFIG` configuration file — in that case you must identify the environment's home directory so that the configuration file can be found. + +- Configure your environment to back your regions from system memory instead of the filesystem. + +- Configure your logging subsystem such that log files are kept entirely in-memory. + +- Increase the size of your in-memory log buffer so that it is large enough to hold the largest set of concurrent write operations. + +- Increase the size of your in-memory cache so that it can hold your entire data set. You do not want your cache to page to disk. + +- Do not specify a file name when you open your database(s). + +As an example, this section takes the transaction example provided in Base API Transaction Example and it updates that example so that the environment, database, log files, and regions are all kept entirely in-memory. + +For illustration purposes, we also modify this example so that uncommitted reads are no longer used to enable the `countRecords()` method. Instead, we simply provide a transaction handle to `countRecords()` so as to avoid the self-deadlock. + +The majority of the modifications to the original example are performed in the `TxnGuide` example class (see TxnGuide.java). This is because the majority of the work that we need to do is performed when the environment and databases are opened. + +To begin, we simplify the beginning of the class a bit. We eliminate some variables that the example no longer needs — specifically variables having to do with the location of the environment and the names of the database files. We can also remove our `usage()` method because we no longer require any command line arguments. + +``` c +// File TxnGuideInMemory.java + +package db.txn; + +import com.sleepycat.bind.serial.StoredClassCatalog; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.LockDetectMode; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +public class TxnGuideInMemory { + + // DB handles + private static Database myDb = null; + private static Database myClassDb = null; + private static Environment myEnv = null; + + private static final int NUMTHREADS = 5; +``` + +Next, in our `main()` method, we remove the call to `parseArgs()` because that only existed in the previous example for collecting the environment home location. Everything else is essentially the same. + +``` c + public static void main(String args[]) { + try { + + // 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(); + } + + for (int i = 0; i < NUMTHREADS; i++) { + threadArray[i].join(); + } + } catch (Exception e) { + System.err.println("TxnGuideInMemory: " + e.toString()); + e.printStackTrace(); + } finally { + closeEnv(); + } + System.out.println("All done."); + } +``` + +Next we open our environment as always. However, in doing so we: + +- Set `EnvironmentConfig.setPrivate()` to `true`. This causes our environment to back regions using our application's heap memory rather than by using the filesystem. This is the first important step to keeping our DB data entirely in-memory. + +- Remove `runRecovery()` from the environment configuration. Because all our data will be held entirely in memory, recovery is a non-issue. Note that if we had left the call to `runRecovery()` in, it would be silently ignored. + +``` c + private static void openEnv() throws DatabaseException { + System.out.println("opening env"); + + // Set up the environment. + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + + // Region files are not backed by the filesystem, they are + // backed by heap memory. + myEnvConfig.setPrivate(true); + + myEnvConfig.setAllowCreate(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + // EnvironmentConfig.setThreaded(true) is the default behavior + // in Java, so we do not have to do anything to cause the + // environment handle to be free-threaded. + + // Indicate that we want db to internally perform deadlock + // detection. Also indicate that the transaction that has + // performed the least amount of write activity to + // receive the deadlock notification, if any. + myEnvConfig.setLockDetectMode(LockDetectMode.MINWRITE); +``` + +Now we configure our environment to keep the log files in memory, increase the log buffer size to 10 MB, and increase our in-memory cache to 10 MB. These values should be more than enough for our application's workload. + +``` c + + // Specify in-memory logging + myEnvConfig.setLogInMemory(true); + // Specify the size of the in-memory log buffer + // Must be large enough to handle the log data created by + // the largest transaction. + myEnvConfig.setLogBufferSize(10 * 1024 * 1024); + // Specify the size of the in-memory cache + // Set it large enough so that it won't page. + myEnvConfig.setCacheSize(10 * 1024 * 1024); + + +``` + +Our database configuration is identical to the original example, except that we do not specify `setReadUncomitted()` here. We will be causing our `countRecords()` method to join the transaction rather than perform uncommitted reads, so we do not need our database to support them. + +``` c + // Set up the database + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + myDbConfig.setTransactional(true); + myDbConfig.setSortedDuplicates(true); + // no DatabaseConfig.setThreaded() method available. + // db handles in java are free-threaded so long as the + // env is also free-threaded. +``` + +Next, we open the environment. This is identical to how the example previously worked, except that we do not provide a location for the environment's home directory. + +``` c + try { + // Open the environment + myEnv = new Environment(null, // Env home + myEnvConfig); +``` + +When we open our databases, we also specify `null` for the file names. The causes the database to not be backed by the filesystem; that is, the databases are held entirely in memory. + +``` c + // 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 + null, // Database file name + null, // Database 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 + null, // Database file name + null, // Database name, + myDbConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("openEnv: " + fnfe.toString()); + System.exit(-1); + } + } +``` + +After that, our class is unchanged, except for some very minor modifications. Most notably, we remove the `parseArgs()` method from the application, because we no longer need it. + +``` c + private static void closeEnv() { + System.out.println("Closing env"); + 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: " + + e.toString()); + e.printStackTrace(); + } + } + + if (myEnv != null ) { + try { + myEnv.close(); + } catch (DatabaseException e) { + System.err.println("closeEnv: " + e.toString()); + e.printStackTrace(); + } + } + } + + private TxnGuideInMemory() {} +} +``` + +That completes our modifications to this class. We now turn our attention to our `DBWriter` class (see DBWriter.java). It is unchanged, except for one small modification. In the `run()` method, we call `countRecords()` with a transaction handle, rather than configuring our entire application for uncommitted reads. Both mechanisms work well-enough for preventing a self-deadlock. However, the individual count in this example will tend to be lower than the counts seen in the previous transaction example, because `countRecords()` can no longer see records created but not yet committed by other threads. Additionally, the usage of the transaction handle here will probably cause more deadlocks than using read-uncommitted does, because more locking is being performed in this case. + +``` c +package db.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.db.Cursor; +import com.sleepycat.db.CursorConfig; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DeadlockException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.Transaction; + +import java.io.UnsupportedEncodingException; +import java.util.Random; + +public class DBWriter extends Thread +{ + 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"}; + + // 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); + } + + // Thread method that writes a series of records + // to the database using transaction protection. + // Deadlock handling is demonstrated here. + public void run () { + Transaction txn = null; + + // Perform 50 transactions + for (int i=0; i<50; i++) { + + boolean retry = true; + int retry_count = 0; + // while loop is used for deadlock retries + while (retry) { + // try block used for deadlock detection and + // general db exception handling + try { + + // Get a transaction + txn = myEnv.beginTransaction(null, null); + // 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); + } + + // commit + System.out.println(getName() + + " : committing txn : " + i); + + System.out.println(getName() + " : Found " + + countRecords(txn) + " records in the database."); + try { + txn.commit(); + txn = null; + } catch (DatabaseException e) { + System.err.println("Error on txn commit: " + + e.toString()); + } + retry = false; + + } 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; + } + } catch (DatabaseException e) { + // abort and don't retry + retry = false; + System.err.println(getName() + + " : caught exception: " + e.toString()); + System.err.println(getName() + + " : errno: " + e.getErrno()); + e.printStackTrace(); + } finally { + if (txn != null) { + try { + txn.abort(); + } catch (Exception e) { + System.err.println( + "Error aborting transaction: " + + e.toString()); + e.printStackTrace(); + } + } + } + } + } + } +``` + +Next we update `countRecords()`. The only difference here is that we no longer specify `CursorConfig.setReadUncomitted()` when we open our cursor. Note that even this minor change is not required. If we do not configure our database to support uncommitted reads, `CursorConfig.setReadUncomitted()` is silently ignored. However, we remove the property anyway from the cursor open so as to avoid confusion. + +``` c + // This simply counts the number of records contained in the + // database and returns the result. You can use this method + // in three ways: + // + // First call it with an active txn handle. + // Secondly, configure the cursor for uncommitted reads + // Third, call count_records AFTER the writer has committed + // its transaction. + // + // If you do none of these things, the writer thread will + // self-deadlock. + // + // 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) { + cursor.close(); + } + } + + return count; + + } +} +``` + +This completes our in-memory transactional example. If you would like to experiment with this code, you can find the example in the following location in your DB distribution: + +``` c +DB_INSTALL/examples_java/src/db/txn +``` diff --git a/docs_src/guides/gsg_txn/java/introduction.md b/docs_src/guides/gsg_txn/java/introduction.md new file mode 100644 index 000000000..8c86652f0 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/introduction.md @@ -0,0 +1,50 @@ +--- +title: "Chapter 1. Introduction" +api-name: "Chapter 1. Introduction" +source: docs/gsg_txn/JAVA/introduction.html +--- +## Chapter 1. Introduction + +**Table of Contents** + + [Transaction Benefits](introduction.md#txnintro) + + [A Note on System Failure](sysfailure.md) + + [Application Requirements](apireq.md) + + [Multi-threaded and Multi-process Applications](multithread-intro.md) + + [Recoverability](recovery-intro.md) + + [Performance Tuning](perftune-intro.md) + +This book provides a thorough introduction and discussion on transactions as used with Berkeley DB (DB). Both the base API as well as the Direct Persistence Layer API is used in this manual. It begins by offering a general overview to transactions, the guarantees they provide, and the general application infrastructure required to obtain full transactional protection for your data. + +This book also provides detailed examples on how to write a transactional application. Both single threaded and multi-threaded (as well as multi-process applications) are discussed. A detailed description of various backup and recovery strategies is included in this manual, as is a discussion on performance considerations for your transactional application. + +You should understand the concepts from the *Getting Started with Berkeley DB* guide before reading this book. + +## Transaction Benefits + +Transactions offer your application's data protection from application or system failures. That is, DB transactions offer your application full ACID support: + +- **A**tomicity + + 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 to your databases. Further, in the event that you abort a transaction, all write operations performed during the transaction are discarded. In this event, your database is left in the state it was in before the transaction began, regardless of the number or type of write operations you may have performed during the course of the transaction. + + Note that DB transactions can span one or more database handles. + +- **C**onsistency + + Your databases will never see a partially completed transaction. This is true even if your application fails while there are in-progress transactions. If the application or system fails, then either all of the database changes appear when the application next runs, or none of them appear. + + In other words, whatever consistency requirements your application has will never be violated by DB. If, for example, your application requires every record to include an employee ID, and your code faithfully adds that ID to its database records, then DB will never violate that consistency requirement. The ID will remain in the database records until such a time as your application chooses to delete it. + +- **I**solation + + While a transaction is in progress, your databases will appear to the transaction as if there are no other operations occurring outside of the transaction. That is, operations wrapped inside a transaction will always have a clean and consistent view of your databases. They never have to see updates currently in progress under the protection of another transaction. Note, however, that isolation guarantees can be relaxed from the default setting. See Isolation for more information. + +- **D**urability + + Once committed to your databases, your modifications will persist even in the event of an application or system failure. Note that like isolation, your durability guarantee can be relaxed. See Non-Durable Transactions for more information. diff --git a/docs_src/guides/gsg_txn/java/isolation.md b/docs_src/guides/gsg_txn/java/isolation.md new file mode 100644 index 000000000..efc675f29 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/isolation.md @@ -0,0 +1,606 @@ +--- +title: "Isolation" +api-name: "Isolation" +source: docs/gsg_txn/JAVA/isolation.html +--- +## Isolation + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + +Isolation guarantees are an important aspect of transactional protection. Transactions ensure the data your transaction is working with will not be changed by some other transaction. Moreover, the modifications made by a transaction will never be viewable outside of that transaction until the changes have been committed. + +That said, there are different degrees of isolation, and you can choose to relax your isolation guarantees to one degree or another depending on your application's requirements. The primary reason why you might want to do this is because of performance; the more isolation you ask your transactions to provide, the more locking that 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 relaxing your isolation guarantees, you can *potentially* improve your application's throughput. Whether you actually see any improvement depends, of course, on the nature of your application's data and transactions. + +### Supported Degrees of Isolation + +DB supports the following levels of isolation: + + + + + + + + + + + + + + + + + + + + + + + + + + +
DegreeANSI TermDefinition
1READ UNCOMMITTEDUncommitted reads means that one transaction will never overwrite another transaction's dirty data. Dirty data is data that a transaction has modified but not yet committed to the underlying data store. However, uncommitted reads allows a transaction to see data dirtied by another transaction. In addition, a transaction may read data dirtied by another transaction, but which subsequently is aborted by that other transaction. In this latter case, the reading transaction may be reading data that never really existed in the database.
2READ COMMITTED

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

+

In addition, this isolation level guarantees that data will never change so long as it is addressed by the cursor, but the data may change before the reading cursor is closed. In the case of a transaction, data at the current cursor position will not change, but once the cursor moves, the previous referenced data can change. This means that readers release read locks before the cursor is closed, and therefore, before the transaction completes. Note that this level of isolation causes the cursor to operate in exactly the same way as it does in the absence of a transaction.

3SERIALIZABLE

Committed read is observed, plus the data read by a transaction, T, will never be dirtied by another transaction before T completes. This means that both read and write locks are not released until the transaction completes.

+

In addition, no transactions will see phantoms. Phantoms are records returned as a result of a search, but which were not seen by the same transaction when the identical search criteria was previously used.

+

This is DB's default isolation guarantee.

+ +By default, DB transactions and transactional cursors offer serializable isolation. You can optionally reduce your isolation level by configuring DB to use uncommitted read isolation. See Reading Uncommitted Data for more information. You can also configure DB to use committed read isolation. See Committed Reads for more information. + +Finally, in addition to DB's normal degrees of isolation, you can also use *snapshot isolation*. This allows you to avoid the read locks that serializable isolation requires. See Using Snapshot Isolation for details. + +### Reading Uncommitted Data + +Berkeley DB allows you to configure your application to read data that has been modified but not yet committed by another transaction; that is, dirty data. When you do this, you may see a performance benefit by allowing your application to not have to block waiting for write locks. On the other hand, the data that your application is reading may change before the transaction has completed. + +When used with transactions, uncommitted reads means that one transaction can see data modified but not yet committed by another transaction. When used with transactional cursors, uncommitted reads means that any database reader can see data modified by the cursor before the cursor's transaction has committed. + +Because of this, uncommitted reads allow a transaction to read data that may subsequently be aborted by another transaction. In this case, the reading transaction will have read data that never really existed in the database. + +To configure your application to read uncommitted data: + +1. Open your database such that it will allow uncommitted reads. You do this by specifying `true` to `DatabaseConfig.setReadUncommitted()`. (If you are using the DPL, you must provide this `DatabaseConfig` object to the entity store using the `EntityStore.setPrimaryConfig()` method.) + +2. Specify that you want to use uncommitted reads when you create a transaction or open the cursor. To do this, you use the `setReadUncommitted()` method on the relevant configuration object (`TransactionConfig` or `CursorConfig`). + +For example, the following opens the database such that it supports uncommitted reads, and then creates a transaction that causes all reads performed within it to use uncommitted reads. Remember that simply opening the database to support uncommitted reads is not enough; you must also declare your read operations to be performed using uncommitted reads. + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; +import com.sleepycat.db.TransactionConfig; + +import java.io.File; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + dbConfig.setAllowCreate(true); + dbConfig.setReadUncommitted(true); // Enable uncommitted reads. + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + 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). + + ... +``` + +If you are using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; +import com.sleepycat.db.TransactionConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; + +... + +EntityStore myStore = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the store. + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + // You must set all these fields if you are going to use + // a DatabaseConfig object with your new entity store. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + dbConfig.setType(DatabaseType.BTREE); + dbConfig.setReadUncommitted(true); // Enable uncommitted reads. + + myStore = new EntityStore(myEnv, "store_name", myStoreConfig); + + // Set the DatabaseConfig object, so that the underlying + // database is configured for uncommitted reads. + myStore.setPrimaryConfig(SomeEntityClass.class, 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 store 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 specifying `LockMode.READ_UNCOMMITTED`: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.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 +} +``` + +Using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.Transaction; + +import com.sleepycat.persist.PrimaryIndex; +... + +Environment myEnv = null; +Transaction txn = null; + +try { + + // Environment and database open omitted + + ... + + txn = myEnv.beginTransaction(null, null); + + AnEntityClass aec = aPrimaryIndex.get(txn, "pKeya", + 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 cursor is consistent so long as it is being addressed by the cursor. However, once the cursor is done reading the object or record (that is, reading records from the page that it currently has locked), the cursor releases its lock on that object, record or page. This means that the data the cursor has read and released may change before the cursor's transaction has completed. + +For example, suppose you have two transactions, `Ta` and `Tb`. Suppose further that `Ta` has a cursor that reads `record R`, but does not modify it. Normally, `Tb` would then be unable to write `record R` because `Ta` would be holding a read lock on it. But when you configure your transaction for committed reads, `Tb` *can* modify `record R` before `Ta` completes, so long as the reading cursor is no longer addressing the object, record or page. + +When you configure your application for this level of isolation, you may see better performance throughput because there are fewer read locks being held by your transactions. Read committed isolation is most useful when you have a cursor that is reading and/or writing records in a single direction, and that does not ever have to go back to re-read those same records. In this case, you can allow DB to release read locks as 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: + +- Create your transaction such that it allows committed reads. You do this by specifying `true` to `TransactionConfig.setReadCommitted()`. + +- Specify `true` to `CursorConfig.setReadCommitted()`. + +For example, the following creates a transaction that allows committed reads: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; +import com.sleepycat.db.TransactionConfig; + +import java.io.File; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + // Notice that we do not have to specify any properties to the + // database to allow committed reads (this is as opposed to + // uncommitted reads where we DO have to specify a property on + // the database open. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + dbConfig); + String keyString = "thekey"; + String dataString = "thedata"; + DatabaseEntry key = + new DatabaseEntry(keyString.getBytes("UTF-8")); + DatabaseEntry data = + new DatabaseEntry(dataString.getBytes("UTF-8")); + + TransactionConfig txnConfig = new TransactionConfig(); + + // Open the transaction and enable committed reads. All cursors open + // with this transaction handle will use read committed isolation. + txnConfig.setReadCommitted(true); + 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). + + // Using transactional cursors with concurrent applications is + // described in more detail in the following section. + + ... +``` + +Using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; +import com.sleepycat.db.TransactionConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; + +... + +EntityStore myStore = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Instantiate the store. + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + TransactionConfig txnConfig = new TransactionConfig(); + + // Open the transaction and enable committed reads. All cursors open + // with this transaction handle will use read committed isolation. + txnConfig.setReadCommitted(true); + Transaction txn = myEnv.beginTransaction(null, txnConfig); + + // From here, you perform your store reads and writes as normal, + // committing and aborting the transactions as is necessary, and + // testing for deadlock exceptions as normal (omitted for brevity). + + // Using transactional cursors with concurrent applications is + // described in more detail in the following section. + + ... +``` + +You can also configure read committed isolation on a read-by-read basis by specifying `LockMode.READ_COMMITTED`: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.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 +} +``` + +Using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.Transaction; + +import com.sleepycat.persist.PrimaryIndex; +... + +Environment myEnv = null; +Transaction txn = null; + +try { + + // Environment and database open omitted + + ... + + txn = myEnv.beginTransaction(null, null); + + // Primary index creation omitted + ... + + AnEntityClass aec = aPrimaryIndex.get(txn, "pKeya", + LockMode.READ_COMMITTED); +} catch (Exception e) { + // Exception handling goes here +} +``` + +### Using Snapshot Isolation + +By default DB uses serializable isolation. An important side effect of this isolation level is that read operations obtain read locks on database pages, and then hold those locks until the read operation is completed. When you are using transactional cursors, this means that read locks are held until the transaction commits or aborts. In that case, over time a transactional cursor can gradually block all other transactions from writing to the database. + +You can avoid this by using snapshot isolation. Snapshot isolation uses *multiversion concurrency control* to guarantee repeatable reads. What this means is that every time a writer would take a read lock on a page, instead a copy of the page is made and the writer operates on that page copy. This frees other writers from blocking due to a read lock held on the page. + +### Note + +Snapshot isolation is strongly recommended for read-only threads when writer threads are also running, as this will eliminate read-write contention and greatly improve transaction throughput for your writer threads. However, in order for snapshot isolation to work for your reader-only threads, you must of course use transactions for your DB reads. + +#### Snapshot Isolation Cost + +Snapshot isolation does not come without a cost. Because pages are being duplicated before being operated upon, the cache will fill up faster. This means that you might need a larger cache in order to hold the entire working set in memory. + +If the cache becomes full of page copies before old copies can be discarded, additional I/O will occur as pages are written to temporary "freezer" files on disk. This can substantially reduce throughput, and should be avoided if possible by configuring a large cache and keeping snapshot isolation transactions short. + +You can estimate how large your cache should be by taking a checkpoint, followed by a call to the `Environment.getArchiveLogFiles()` method. The amount of cache required is approximately double the size of the remaining log files (that is, the log files that cannot be archived). + +#### Snapshot Isolation Transactional Requirements + +In addition to an increased cache size, you may also need to increase the number of transactions that your application supports. (See Configuring the Transaction Subsystem for details on how to set this.) In the worst case scenario, you might need to configure your application for one more transaction for every page in the cache. This is because transactions are retained until the last page they created is evicted from the cache. + +#### When to Use Snapshot Isolation + +Snapshot isolation is best used when all or most of the following conditions are true: + +- You can have a large cache relative to your working data set size. + +- You require repeatable reads. + +- You will be using transactions that routinely work on the entire database, or more commonly, there is data in your database that will be very frequently written by more than one transaction. + +- Read/write contention is limiting your application's throughput, or the application is all or mostly read-only and contention for the lock manager mutex is limiting throughput. + +#### How to use Snapshot Isolation + +You use snapshot isolation by: + +- Opening the database or store with multiversion support. You can configure this either when you open your environment or when you open your database or store. Use either the `EnvironmentConfig.setMultiversion()` or the `DatabaseConfig.setMultiversion()` option to configure this support. + +- Configure your cursor or transaction to use snapshot isolation. + + To do this, specify the `TransactionConfig.setSnapshot()` option when you configure your transaction. + +The simplest way to take advantage of snapshot isolation is for queries: keep update transactions using full read/write locking and use snapshot isolation on read-only transactions or cursors. This should minimize blocking of snapshot isolation transactions and will avoid deadlock errors. + +If the application has update transactions which read many items and only update a small set (for example, scanning until a desired record is found, then modifying it), throughput may be improved by running some updates at snapshot isolation as well. But doing this means that you must manage deadlock errors. See Resolving Deadlocks for details. + +The following code fragment turns on snapshot isolation for a transaction: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + myEnvConfig.setMultiversion(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + dbConfig); + +... + + TransactionConfig txnConfig = new TransactionConfig(); + txnConfig.setSnapshot(true); + txn = myEnv.beginTransaction(null, txnConfig); + +... + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +When using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +EntityStore myStore = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + myEnvConfig.setMultiversion(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Instantiate the store + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + myStore = new EntityStore(myEnv, storeName, myStoreConfig); + +... + + TransactionConfig txnConfig = new TransactionConfig(); + txnConfig.setSnapshot(true); + txn = myEnv.beginTransaction(null, txnConfig); + +... + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg_txn/java/lockingsubsystem.md b/docs_src/guides/gsg_txn/java/lockingsubsystem.md new file mode 100644 index 000000000..4aa03a030 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/lockingsubsystem.md @@ -0,0 +1,246 @@ +--- +title: "The Locking Subsystem" +api-name: "The Locking Subsystem" +source: docs/gsg_txn/JAVA/lockingsubsystem.html +--- +## The Locking Subsystem + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + +In order to allow concurrent operations, DB provides the locking subsystem. This subsystem provides inter- and intra- process concurrency mechanisms. It is extensively used by DB concurrent applications, but it can also be generally used for non-DB resources. + +This section describes the locking subsystem as it is used to protect DB resources. In particular, issues on configuration are examined here. For information on using the locking subsystem to manage non-DB resources, see the *Berkeley DB Programmer's Reference Guide*. + +### Configuring the Locking Subsystem + +You initialize the locking subsystem by specifying `true` to the `EnvironmentConfig.setInitializeLocking()` method. + +Before opening your environment, you can configure various values for your locking subsystem. Note that these limits can only be configured before the environment is opened. Also, these methods configure the entire environment, not just a specific environment handle. + +Finally, each bullet below identifies the `DB_CONFIG` file parameter that can be used to specify the specific locking limit. If used, these `DB_CONFIG` file parameters override any value that you might specify using the environment handle. + +The limits that you can configure are as follows: + +- The number of lockers supported by the environment. This value is used by the environment when it is opened to estimate the amount of space that it should allocate for various internal data structures. By default, 1,000 lockers are supported. + + To configure this value, use the `EnvironmentConfig.setMaxLockers()` method. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_lockers` parameter. + +- The number of locks supported by the environment. By default, 1,000 locks are supported. + + To configure this value, use the `EnvironmentConfig.setMaxLocks()` method. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_locks` parameter. + +- The number of locked objects supported by the environment. By default, 1,000 objects can be locked. + + To configure this value, use the `EnvironmentConfig.setMaxLockObjects()` method. + + As an alternative to this method, you can configure this value using the `DB_CONFIG` file's `set_lk_max_objects` parameter. + +For a definition of lockers, locks, and locked objects, see Lock Resources. + +For example, to configure the number of locks that your environment can use: + +``` c +package db.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setMaxLocks(5000); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +### Configuring Deadlock Detection + +In order for DB to know that a deadlock has occurred, some mechanism must be used to perform deadlock detection. There are three ways that deadlock detection can occur: + +1. Allow DB to internally detect deadlocks as they occur. + + To do this, you use `EnvironmentConfig.setLockDetectMode()`. This method causes DB to walk its internal lock table looking for a deadlock whenever a lock request is blocked. This method also identifies how DB decides which lock requests are rejected when deadlocks are detected. For example, DB can decide to reject the lock request for the transaction that has the most number of locks, the least number of locks, holds the oldest lock, holds the most number of write locks, and so forth (see the API reference documentation for a complete list of the lock detection policies). + + You can call this method at any time during your application's lifetime, but typically it is used before you open your environment. + + Note that how you want DB to decide which thread of control should break a deadlock is extremely dependent on the nature of your application. It is not unusual for some performance testing to be required in order to make this determination. That said, a transaction that is holding the most number of locks is usually indicative of the transaction that has performed the most amount of work. Frequently you will not want a transaction that has performed a lot of work to abandon its efforts and start all over again. It is not therefore uncommon for application developers to initially select the transaction with the *minimum* number of write locks to break the deadlock. + + Using this mechanism for deadlock detection means that your application will never have to wait on a lock before discovering that a deadlock has occurred. However, walking the lock table every time a lock request is blocked can be expensive from a performance perspective. + +2. Use a dedicated thread or external process to perform deadlock detection. Note that this thread must be performing no other database operations beyond deadlock detection. + + To externally perform lock detection, you can use either the `Environment.detectDeadlocks()` method, or use the **db_deadlock** command line utility. This method (or command) causes DB to walk the lock table looking for deadlocks. + + Note that like `EnvironmentConfig.setLockDetectMode()`, you also use this method (or command line utility) to identify which lock requests are rejected in the event that a deadlock is detected. + + Applications that perform deadlock detection in this way typically run deadlock detection between every few seconds and a minute. This means that your application may have to wait to be notified of a deadlock, but you also save the overhead of walking the lock table every time a lock request is blocked. + +3. Lock timeouts. + + You can configure your locking subsystem such that it times out any lock that is not released within a specified amount of time. To do this, use the `EnvironmentConfig.setLockTimeout()` method. Note that lock timeouts are only checked when a lock request is blocked or when deadlock detection is otherwise performed. Therefore, a lock can have timed out and still be held for some length of time until DB has a reason to examine its locking tables. + + Be aware that extremely long-lived transactions, or operations that hold locks for a long time, may be inappropriately timed out before the transaction or operation has a chance to complete. You should therefore use this mechanism only if you know your application will hold locks for very short periods of time. + +For example, to configure your application such that DB checks the lock table for deadlocks every time a lock request is blocked: + +``` c +package db.txn; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.LockDetectMode; + +import java.io.File; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + // Configure db to perform deadlock detection internally, and to + // choose the transaction that has performed the least amount + // of writing to break the deadlock in the event that one + // is detected. + envConfig.setLockDetectMode(LockDetectMode.MINWRITE); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // From here, you open your databases, proceed with your + // database operations, and respond to deadlocks as + // is normal (omitted for brevity). + + ... +``` + +Finally, the following command line call causes deadlock detection to be run against the environment contained in `/export/dbenv`. The transaction with the youngest lock is chosen to break the deadlock: + +``` c +> /usr/local/db_install/bin/db_deadlock -h /export/dbenv -a y +``` + +For more information, see the db_deadlock reference documentation. + +### Resolving Deadlocks + +When DB determines that a deadlock has occurred, it will select a thread of control to resolve the deadlock and then throws `DeadlockException` in that thread. If a deadlock is detected, the thread must: + +1. Cease all read and write operations. + +2. Close all open cursors. + +3. Abort the transaction. + +4. Optionally retry the operation. If your application retries deadlocked operations, the new attempt must be made using a new transaction. + +### Note + +If a thread has deadlocked, it may not make any additional database calls using the handle that has deadlocked. + +For example: + +``` c +// 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; + } + } +} +``` + +### Setting Transaction Priorities + +Normally when a thread of control must be selected to resolve a deadlock, DB decides which thread will perform the resolution; you have no way of knowing in advance which thread will be selected to resolve the deadlock. + +However, there may be situations where you know it is better for one thread to resolve a deadlock over another thread. As an example, if you have a background thread running data management activities, and another thread responding to user requests, you might want deadlock resolution to occur in the background thread because you can better afford the throughput costs there. Under these circumstances, you can identify which thread of control will be selected for resolved deadlocks by setting a transaction priorities. + +When two transactions are deadlocked, DB will abort the transaction with the lowest priority. By default, every transaction is given a priority of 100. However, you can set a different priority on a transaction-by-transaction basis by using the `Transaction.setPriority()` method. + +When two or more transactions are tied for the lowest priority, the tie is broken based on the policy provided to the `LockDetectMode` class. You provide this configuration object to the environment using the `EnvironmentConfig.setLockDetectMode()` method. + +A transaction's priority can be changed at any time after the transaction handle has been created and before the transaction has been resolved (committed or aborted). For example: + +``` c +... + +try { + + ... + + Transaction txn = myEnv.beginTransaction(null, null); + txn.setPriority(200); + + try { + myDatabase.put(txn, key, data); + txn.commit(); + } catch (Exception e) { + if (txn != null) { + txn.abort(); + txn = null; + } + } + + ... + +} +``` diff --git a/docs_src/guides/gsg_txn/java/logconfig.md b/docs_src/guides/gsg_txn/java/logconfig.md new file mode 100644 index 000000000..904dce9a2 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/logconfig.md @@ -0,0 +1,127 @@ +--- +title: "Configuring the Logging Subsystem" +api-name: "Configuring the Logging Subsystem" +source: docs/gsg_txn/JAVA/logconfig.html +--- +## Configuring the Logging Subsystem + + [Setting the Log File Size](logconfig.md#logfilesize) + + [Configuring the Logging Region Size](logconfig.md#logregionsize) + + [Configuring In-Memory Logging](logconfig.md#inmemorylogging) + + [Setting the In-Memory Log Buffer Size](logconfig.md#logbuffer) + +You can configure the following aspects of the logging subsystem: + +- Size of the log files. + +- Size of the logging subsystem's region. See Configuring the Logging Region Size. + +- Maintain logs entirely in-memory. See Configuring In-Memory Logging for more information. + +- Size of the log buffer in memory. See Setting the In-Memory Log Buffer Size. + +- On-disk location of your log files. See Identifying Specific File Locations. + +### Setting the Log File Size + +Whenever a pre-defined amount of data is written to a log file (10 MB by default), DB stops using the current log file and starts writing to a new file. You can change the maximum amount of data contained in each log file by using the `EnvironmentConfig.setMaxLogFileSize()` method. Note that this method can be used at any time during an application's lifetime. + +Setting the log file size to something larger than its default value is largely a matter of convenience and a reflection of the application's preference in backup media and frequency. However, if you set the log file size too low relative to your application's traffic patterns, you can cause yourself trouble. + +From a performance perspective, setting the log file size to a low value can cause your active transactions to pause their writing activities more frequently than would occur with larger log file sizes. Whenever a transaction completes the log buffer is flushed to disk. Normally other transactions can continue to write to the log buffer while this flush is in progress. However, when one log file is being closed and another created, all transactions must cease writing to the log buffer until the switch over is completed. + +Beyond performance concerns, using smaller log files can cause you to use more physical files on disk. As a result, your application could run out of log sequence numbers, depending on how busy your application is. + +Every log file is identified with a 10 digit number. Moreover, the maximum number of log files that your application is allowed to create in its lifetime is 2,000,000,000. + +For example, if your application performs 6,000 transactions per second for 24 hours a day, and you are logging 500 bytes of data per transaction into 10 MB log files, then you will run out of log files in around 221 years: + +``` c + (10 * 2^20 * 2000000000) / (6000 * 500 * 365 * 60 *60 * 24) = 221 +``` + +However, if you were writing 2000 bytes of data per transaction, and using 1 MB log files, then the same formula shows you running out of log files in 5 years time. + +All of these time frames are quite long, to be sure, but if you do run out of log files after, say, 5 years of continuous operations, then you must reset your log sequence numbers. To do so: + +1. Backup your databases as if to prepare for catastrophic failure. See Backup Procedures for more information. + +2. Reset the log file's sequence number using the **db_load** utility's `-r` option. + +3. Remove all of the log files from your environment. Note that this is the only situation in which all of the log files are removed from an environment; in all other cases, at least a single log file is retained. + +4. Restart your application. + +### Configuring the Logging Region Size + +The logging subsystem's default region size is 60 KB. The logging region is used to store filenames, and so you may need to increase its size if a large number of files (that is, if you have a very large number of databases) will be opened and registered with DB's log manager. + +You can set the size of your logging region by using the `EnvironmentConfig.setLogRegionSize()` method. Note that this method can only be called before the first environment handle for your application is opened. + +### Configuring In-Memory Logging + +It is possible to configure your logging subsystem such that logs are maintained entirely in memory. When you do this, you give up your transactional durability guarantee. Without log files, you have no way to run recovery so any system or software failures that you might experience can corrupt your databases. + +However, by giving up your durability guarantees, you can greatly improve your application's throughput by avoiding the disk I/O necessary to write logging information to disk. In this case, you still retain your transactional atomicity, consistency, and isolation guarantees. + +To configure your logging subsystem to maintain your logs entirely in-memory: + +- Make sure your log buffer is capable of holding all log information that can accumulate during the longest running transaction. See Setting the In-Memory Log Buffer Size for details. + +- Do not run normal recovery when you open your environment. In this configuration, there are no log files available against which you can run recovery. As a result, if you specify recovery when you open your environment, it is ignored. + +- Specify `true` to the `EnvironmentConfig.setLogInMemory()` method. Note that you must specify this before your application opens its first environment handle. + +For example: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + // Specify in-memory logging + myEnvConfig.setLogInMemory(true); + + // Specify the in-memory log buffer size. + myEnvConfig.setLogBufferSize(10 * 1024 * 1024); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // From here, you open databases, create transactions and + // perform database operations exactly as you would if you + // were logging to disk. This part is omitted for brevity. +``` + +### Setting the In-Memory Log Buffer Size + +When your application is configured for on-disk logging (the default behavior for transactional applications), log information is stored in-memory until the storage space fills up, or a transaction commit forces the log information to be flushed to disk. + +It is possible to increase the amount of memory available to your file log buffer. Doing so improves throughput for long-running transactions, or for transactions that produce a large amount of data. + +When you have your logging subsystem configured to maintain your log entirely in memory (see Configuring In-Memory Logging), it is very important to configure your log buffer size because the log buffer must be capable of holding all log information that can accumulate during the longest running transaction. You must make sure that the in-memory log buffer size is large enough that no transaction will ever span the entire buffer. You must also avoid a state where the in-memory buffer is full and no space can be freed because a transaction that started the first log "file" is still active. + +When your logging subsystem is configured for on-disk logging, the default log buffer space is 32 KB. When in-memory logging is configured, the default log buffer space is 1 MB. + +You can increase your log buffer space using the `EnvironmentConfig.setLogBufferSize()` method. Note that this method can only be called before the first environment handle for your application is opened. diff --git a/docs_src/guides/gsg_txn/java/logfileremoval.md b/docs_src/guides/gsg_txn/java/logfileremoval.md new file mode 100644 index 000000000..b4a16b37b --- /dev/null +++ b/docs_src/guides/gsg_txn/java/logfileremoval.md @@ -0,0 +1,42 @@ +--- +title: "Removing Log Files" +api-name: "Removing Log Files" +source: docs/gsg_txn/JAVA/logfileremoval.html +--- +## Removing Log Files + +By default DB does not delete log files for you. For this reason, DB's log files will eventually grow to consume an unnecessarily large amount of disk space. To guard against this, you should periodically take administrative action to remove log files that are no longer in use by your application. + +You can remove a log file if all of the following are true: + +- the log file is not involved in an active transaction. + +- a checkpoint has been performed *after* the log file was created. + +- the log file is not the only log file in the environment. + +- the log file that you want to remove has already been included in an offline or hot backup. Failure to observe this last condition can cause your backups to be unusable. + +DB provides several mechanisms to remove log files that meet all but the last criteria (DB has no way to know which log files have already been included in a backup). The following mechanisms make it easy to remove unneeded log files, but can result in an unusable backup if the log files are not first saved to your archive location. All of the following mechanisms automatically delete unneeded log files for you: + +- Run the **db_archive** command line utility with the `-d` option. + +- From within your application, call the `Environment.removeOldLogFiles()` method. + +- Specify `true` to the `EnvironmentConfig.setLogAutoRemove()` method. Note that setting this property affects all environment handles opened against the environment; not just the handle used to set the property. + + Note that unlike the other log removal mechanisms identified here, this method actually causes log files to be removed on an on-going basis as they become unnecessary. This is extremely desirable behavior if what you want is to use the absolute minimum amount of disk space possible for your application. This mechanism *will* leave you with the log files that are required to run normal recovery. However, it is highly likely that this mechanism will prevent you from running catastrophic recovery. + + Do NOT use this mechanism if you want to be able to perform catastrophic recovery, or if you want to be able to maintain a hot backup. + +In order to safely remove log files and still be able to perform catastrophic recovery, use the **db_archive** command line utility as follows: + +1. Run either a normal or hot backup as described in Backup Procedures. Make sure that all of this data is safely stored to your backup media before continuing. + +2. If you have not already done so, perform a checkpoint. See Checkpoints for more information. + +3. If you are maintaining a hot backup, perform the hot backup procedure as described in Using Hot Failovers. + +4. Run the **db_archive** command line utility with the `-d` option against your production environment. + +5. Run the **db_archive** command line utility with the `-d` option against your failover environment, if you are maintaining one. diff --git a/docs_src/guides/gsg_txn/java/maxtxns.md b/docs_src/guides/gsg_txn/java/maxtxns.md new file mode 100644 index 000000000..bd58fb371 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/maxtxns.md @@ -0,0 +1,71 @@ +--- +title: "Configuring the Transaction Subsystem" +api-name: "Configuring the Transaction Subsystem" +source: docs/gsg_txn/JAVA/maxtxns.html +--- +## Configuring the Transaction Subsystem + +Most of the configuration activities that you need to perform for your transactional DB application will involve the locking and logging subsystems. See Concurrency and Managing DB Files for details. + +However, there are a couple of things that you can do to configure your transaction subsystem directly. These things are: + +- + + Configure the maximum number of simultaneous transactions needed by your application. In general, you should not need to do this unless you use deeply nested transactions or you have many threads all of which have active transactions. In addition, you may need to configure a higher maximum number of transactions if you are using snapshot isolation. See Snapshot Isolation Transactional Requirements for details. + + By default, your application can support 20 active transactions. + + You can set the maximum number of simultaneous transactions supported by your application using `EnvironmentConfig.setTxnMaxActive()`. + + If your application has exceeded this maximum value, then any attempt to begin a new transaction will fail. + + This value can also be set using the `DB_CONFIG` file's `set_tx_max` parameter. Remember that the `DB_CONFIG` must reside in your environment home directory. + +- + + Configure the timeout value for your transactions. This value represents the longest period of time a transaction can be active. Note, however, that transaction timeouts are checked only when DB examines its lock tables for blocked locks (see Locks, Blocks, and Deadlocks for more information). Therefore, a transaction's timeout can have expired, but the application will not be notified until DB has a reason to examine its lock tables. + + Be aware that some transactions may be inappropriately timed out before the transaction has a chance to complete. You should therefore use this mechanism only if you know your application might have unacceptably long transactions and you want to make sure your application will not stall during their execution. (This might happen if, for example, your transaction blocks or requests too much data.) + + Note that by default transaction timeouts are set to 0 seconds, which means that they never time out. + + To set the maximum timeout value for your transactions, use the `EnvironmentConfig.setTxnTimeout()` method. This method configures the entire environment; not just the handle used to set the configuration. Further, this value may be set at any time during the application's lifetime. (Use `Environment.setConfig()` to set this value after the environment has been opened.) + + This value can also be set using the `DB_CONFIG` file's `set_txn_timeout` parameter. + +For example: + +``` c +package db.txn; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.LockDetectMode; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + // Configure a maximum transaction timeout of 1 second. + myEnvConfig.setTxnTimeout(1000000); + // Configure 40 maximum transactions. + myEnv.setTxnMaxActive(40); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // From here, you open your databases (or store), proceed with your + // database or store operations, and respond to deadlocks as is + // normal (omitted for brevity). + + ... +``` diff --git a/docs_src/guides/gsg_txn/java/moreinfo.md b/docs_src/guides/gsg_txn/java/moreinfo.md new file mode 100644 index 000000000..433070e89 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/moreinfo.md @@ -0,0 +1,30 @@ +--- +title: "For More Information" +api-name: "For More Information" +source: docs/gsg_txn/JAVA/moreinfo.html +--- +## For More Information + + [Contact Us](moreinfo.md#contact_us) + +Beyond this manual, you may also find the following sources of information useful when building a transactional DB application: + +- Getting Started with Berkeley DB for Java + +- Berkeley DB Getting Started with Replicated Applications for Java + +- Berkeley DB Programmer's Reference Guide + +- Berkeley DB Javadoc + +- Berkeley DB Collections Tutorial + +To download the latest Berkeley DB documentation along with white papers and other collateral, visit http://www.oracle.com/technetwork/indexes/documentation/index.html. + +For the latest version of the Oracle Berkeley DB downloads, visit http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html. + +### Contact Us + +You can post your comments and questions at the Oracle Technology (OTN) forum for Oracle Berkeley DB at: http://forums.oracle.com/forums/forum.jspa?forumID=271, or for Oracle Berkeley DB High Availability at: http://forums.oracle.com/forums/forum.jspa?forumID=272. + +For sales or support information, email to: berkeleydb-info_us@oracle.com You can subscribe to a low-volume email announcement list for the Berkeley DB product family by sending email to: bdb-join@oss.oracle.com diff --git a/docs_src/guides/gsg_txn/java/multithread-intro.md b/docs_src/guides/gsg_txn/java/multithread-intro.md new file mode 100644 index 000000000..b228935b5 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/multithread-intro.md @@ -0,0 +1,14 @@ +--- +title: "Multi-threaded and Multi-process Applications" +api-name: "Multi-threaded and Multi-process Applications" +source: docs/gsg_txn/JAVA/multithread-intro.html +--- +## Multi-threaded and Multi-process Applications + +DB is designed to support multi-threaded and multi-process applications, but their usage means you must pay careful attention to issues of concurrency. Transactions help your application's concurrency by providing various levels of isolation for your threads of control. In addition, DB provides mechanisms that allow you to detect and respond to deadlocks. + +*Isolation* means that database modifications made by one transaction will not normally be seen by readers from another transaction until the first commits its changes. Different threads use different transaction handles, so this mechanism is normally used to provide isolation between database operations performed by different threads. + +Note that DB supports different isolation levels. For example, you can configure your application to see uncommitted reads, which means that one transaction can see data that has been modified but not yet committed by another transaction. Doing this might mean your transaction reads data "dirtied" by another transaction, but which subsequently might change before that other transaction commits its changes. On the other hand, lowering your isolation requirements means that your application can experience improved throughput due to reduced lock contention. + +For more information on concurrency, on managing isolation levels, and on deadlock detection, see Concurrency. diff --git a/docs_src/guides/gsg_txn/java/nestedtxn.md b/docs_src/guides/gsg_txn/java/nestedtxn.md new file mode 100644 index 000000000..875d27cae --- /dev/null +++ b/docs_src/guides/gsg_txn/java/nestedtxn.md @@ -0,0 +1,33 @@ +--- +title: "Nested Transactions" +api-name: "Nested Transactions" +source: docs/gsg_txn/JAVA/nestedtxn.html +--- +## Nested Transactions + +A *nested transaction* is used to provide a transactional guarantee for a subset of operations performed within the scope of a larger transaction. Doing this allows you to commit and abort the subset of operations independently of the larger transaction. + +The rules to the usage of a nested transaction are as follows: + +- While the nested (child) transaction is active, the parent transaction may not perform any operations other than to commit or abort, or to create more child transactions. + +- Committing a nested transaction has no effect on the state of the parent transaction. The parent transaction is still uncommitted. However, the parent transaction can now see any modifications made by the child transaction. Those modifications, of course, are still hidden to all other transactions until the parent also commits. + +- Likewise, aborting the nested transaction has no effect on the state of the parent transaction. The only result of the abort is that neither the parent nor any other transactions will see any of the database modifications performed under the protection of the nested transaction. + +- If the parent transaction commits or aborts while it has active children, the child transactions are resolved in the same way as the parent. That is, if the parent aborts, then the child transactions abort as well. If the parent commits, then whatever modifications have been performed by the child transactions are also committed. + +- The locks held by a nested transaction are not released when that transaction commits. Rather, they are now held by the parent transaction until such a time as that parent commits. + +- Any database modifications performed by the nested transaction are not visible outside of the larger encompassing transaction until such a time as that parent transaction is committed. + +- The depth of the nesting that you can achieve with nested transaction is limited only by memory. + +To create a nested transaction, simply pass the parent transaction's handle when you created the nested transaction's handle. For example: + +``` c + // parent transaction + Transaction parentTxn = myEnv.beginTransaction(null, null); + // child transaction + Transaction childTxn = myEnv.beginTransaction(parentTxn, null); +``` diff --git a/docs_src/guides/gsg_txn/java/nodurabletxn.md b/docs_src/guides/gsg_txn/java/nodurabletxn.md new file mode 100644 index 000000000..64cdec0bd --- /dev/null +++ b/docs_src/guides/gsg_txn/java/nodurabletxn.md @@ -0,0 +1,30 @@ +--- +title: "Non-Durable Transactions" +api-name: "Non-Durable Transactions" +source: docs/gsg_txn/JAVA/nodurabletxn.html +--- +## Non-Durable Transactions + +As previously noted, by default transaction commits are durable because they cause the modifications performed under the transaction to be synchronously recorded in your on-disk log files. However, it is possible to use non-durable transactions. + +You may want non-durable transactions for performance reasons. For example, you might be using transactions simply for the isolation guarantee. In this case, you might not want a durability guarantee and so you may want to prevent the disk I/O that normally accompanies a transaction commit. + +There are several ways to remove the durability guarantee for your transactions: + +- Specify `true` to the `EnvironmentConfig.setTxnNoSync()` method. This causes DB to not synchronously force any log data to disk upon transaction commit. That is, the modifications are held entirely in the in-memory cache and the logging information is not forced to the filesystem for long-term storage. Note, however, that the logging data will eventually make it to the filesystem (assuming no application or OS crashes) as a part of DB's management of its logging buffers and/or cache. + + This form of a commit provides a weak durability guarantee because data loss can occur due to an application, JVM, or OS crash. + + This behavior is specified on a per-environment handle basis. In order for your application to exhibit consistent behavior, you need to specify this method for all of the environment handles used in your application. + + You can achieve this behavior on a transaction by transaction basis by using `Transaction.commitNoSync()` to commit your transaction, or by specifying `true` to the `TransactionConfig.setNoSync()` method when starting the transaction. + +- Specify `true` to the `EnvironmentConfig.setTxnWriteNoSync()` method. This causes logging data to 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 system chooses to schedule the activity; the transaction commit can complete successfully before this disk I/O is performed by the OS. + + This form of commit protects you against application and JVM crashes, but not against OS crashes. This method offers less room for the possibility of data loss than does `EnvironmentConfig.setTxnNoSync()`. + + This behavior is specified on a per-environment handle basis. In order for your application to exhibit consistent behavior, you need to specify this method for all of the environment handles used in your application. + + You can achieve this behavior on a transaction by transaction basis by using `Transaction.commitWriteNoSync()` to commit your transaction, or by specifying `true` to `TransactionConfig.setWriteNoSync()` method when starting the transaction. + +- Maintain your logs entirely in-memory. In this case, your logs are never written to disk. The result is that you lose all durability guarantees. See Configuring In-Memory Logging for more information. diff --git a/docs_src/guides/gsg_txn/java/perftune-intro.md b/docs_src/guides/gsg_txn/java/perftune-intro.md new file mode 100644 index 000000000..0d50cc1a3 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/perftune-intro.md @@ -0,0 +1,10 @@ +--- +title: "Performance Tuning" +api-name: "Performance Tuning" +source: docs/gsg_txn/JAVA/perftune-intro.html +--- +## Performance Tuning + +From a performance perspective, the use of transactions is not free. Depending on how you configure them, transaction commits usually require your application to perform disk I/O that a non-transactional application does not perform. Also, for multi-threaded and multi-process applications, the use of transactions can result in increased lock contention due to extra locking requirements driven by transactional isolation guarantees. + +There is therefore a performance tuning component to transactional applications that is not applicable for non-transactional applications (although some tuning considerations do exist whether or not your application uses transactions). Where appropriate, these tuning considerations are introduced in the following chapters. However, for a more complete description of them, see the Transaction tuning and Transaction throughput sections of the *Berkeley DB Programmer's Reference Guide*. diff --git a/docs_src/guides/gsg_txn/java/preface.md b/docs_src/guides/gsg_txn/java/preface.md new file mode 100644 index 000000000..c9073a379 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/preface.md @@ -0,0 +1,60 @@ +--- +title: "Preface" +api-name: "Preface" +source: docs/gsg_txn/JAVA/preface.html +--- +## Preface + +**Table of Contents** + + [Conventions Used in this Book](preface.md#conventions) + + [For More Information](moreinfo.md) + + [Contact Us](moreinfo.md#contact_us) + +This document describes how to use transactions with your Berkeley DB applications. 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 environment infrastructure and administrative tasks required by a transactional application. This book also describes multi-threaded and multi-process DB applications and the requirements they have for deadlock detection. + +This book describes Berkeley DB 11*g* Release 2, which provides library version 11.2.5.3. + +This book is aimed at the software engineer responsible for writing a transactional DB application. + +This book assumes that you have already read and understood the concepts contained in the *Getting Started with Berkeley DB* guide. + +## 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: "The `Environment()` constructor returns an `Environment` class object." + +Variable or non-literal text is presented in *italics*. For example: "Go to your *DB_INSTALL* directory." + +Program examples are displayed in a `monospaced font` on a shaded background. For example: + +``` c +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +``` + +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: + +``` c +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; + +... + +// Allow the database to be created. +DatabaseConfig myDbConfig = new DatabaseConfig(); +myDbConfig.setAllowCreate(true); +Database myDb = new Database("mydb.db", null, myDbConfig); +``` + +### Note + +Finally, notes of special interest are represented using a note block such as this. diff --git a/docs_src/guides/gsg_txn/java/readmodifywrite.md b/docs_src/guides/gsg_txn/java/readmodifywrite.md new file mode 100644 index 000000000..84c023ded --- /dev/null +++ b/docs_src/guides/gsg_txn/java/readmodifywrite.md @@ -0,0 +1,76 @@ +--- +title: "Read/Modify/Write" +api-name: "Read/Modify/Write" +source: docs/gsg_txn/JAVA/readmodifywrite.html +--- +## Read/Modify/Write + +If you are retrieving a record from the database or a class from the store for the purpose of modifying or deleting it, you should declare a read-modify-write cycle at the time that you read the record. Doing so causes DB 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 read lock 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 blocking that your application sees, because readers immediately obtain write locks and write locks cannot be shared. For this reason, you should use read-modify-write cycles only if you 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, specify `com.sleepycat.db.LockMode.RMW` to the database, cursor, `PrimaryIndex`, or `SecondaryIndex` get method. + +For example: + +``` c +// 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); + + // 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 + ... +``` + +Or, with the DPL: + +``` c +// Begin the deadlock retry loop as is normal + while (retry_count < MAX_DEADLOCK_RETRIES) { + try { + txn = myEnv.beginTransaction(null, null); + + ... + // 'store' is an EntityStore and 'Inventory' is an entity class + // Their usage and implementation is omitted for brevity. + ... + + // Read the data, using the PrimaryIndex for the entity object + PrimaryIndex pi = + store.getPrimaryIndex(String.class, Inventory.class); + Inventory iv = pi.get(txn, "somekey", LockMode.RMW); + + // Do something to the retreived object + + // Put the object. 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. + + pi.put(txn, iv); + txn.commit(); + return 0; + + } catch (DeadlockException de) { + // Deadlock detection and exception handling omitted + // for brevity + ... +``` diff --git a/docs_src/guides/gsg_txn/java/recovery-intro.md b/docs_src/guides/gsg_txn/java/recovery-intro.md new file mode 100644 index 000000000..f02219b88 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/recovery-intro.md @@ -0,0 +1,18 @@ +--- +title: "Recoverability" +api-name: "Recoverability" +source: docs/gsg_txn/JAVA/recovery-intro.html +--- +## Recoverability + +An important part of DB's transactional guarantees is durability. *Durability* means that once a transaction has been committed, the database modifications performed under its protection will not be lost due to system failure. + +In order to provide the transactional durability guarantee, DB uses a write-ahead logging system. Every operation performed on your databases is described in a log before it is performed on your databases. This is done in order to ensure that an operation can be recovered in the event of an untimely application or system failure. + +Beyond logging, another important aspect of durability is recoverability. That is, backup and restore. DB supports a normal recovery that runs against a subset of your log files. This is a routine procedure used whenever your environment is first opened upon application startup, and it is intended to ensure that your database is in a consistent state. DB also supports archival backup and recovery in the case of catastrophic failure, such as the loss of a physical disk drive. + +This book describes several different backup procedures you can use to protect your on-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 fatal hardware failure. + +This book also describes the recovery procedures you should use for each of the backup strategies that you might employ. + +For a detailed description of backup and restore procedures, see Managing DB Files. diff --git a/docs_src/guides/gsg_txn/java/recovery.md b/docs_src/guides/gsg_txn/java/recovery.md new file mode 100644 index 000000000..debce992e --- /dev/null +++ b/docs_src/guides/gsg_txn/java/recovery.md @@ -0,0 +1,136 @@ +--- +title: "Recovery Procedures" +api-name: "Recovery Procedures" +source: docs/gsg_txn/JAVA/recovery.html +--- +## Recovery Procedures + + [Normal Recovery](recovery.md#normalrecovery) + + [Catastrophic Recovery](recovery.md#catastrophicrecovery) + +DB supports two types of recovery: + +- Normal recovery, which is run when your environment is opened upon application startup, examines only those log records needed to bring the databases to a consistent state since the last checkpoint. Normal recovery starts with any logs used by any transactions active at the time of the last checkpoint, and examines all logs from then to the current logs. + +- Catastrophic recovery, which is performed in the same way that normal recovery is except that it examines all available log files. You use catastrophic recovery to restore your databases from a previously created backup. + +Of these two, normal recovery should be considered a routine matter; in fact you should run normal recovery whenever you start up your application. + +Catastrophic recovery is run whenever you have lost or corrupted your database files and you want to restore from a backup. You also run catastrophic recovery when you create a hot backup (see Using Hot Failovers for more information). + +### Normal Recovery + +Normal recovery examines the contents of your environment's log files, and uses this information to ensure that your database files are consistent relative to the information contained in the log files. + +Normal recovery also recreates your environment's region files. This has the desired effect of clearing any unreleased locks that your application may have held at the time of an unclean application shutdown. + +Normal recovery is run only against those log files created since the time of your last checkpoint. For this reason, your recovery time is dependent on how much data has been written since the last checkpoint, and therefore on how much log file information there is to examine. If you run checkpoints infrequently, then normal recovery can take a relatively long time. + +### Note + +You should run normal recovery every time you perform application startup. + +To run normal recovery: + +- Make sure all your environment handles are closed. + +- Normal recovery *must be* single-threaded. + +- Specify `true` to `EnvironmentConfig.setRunRecovery()` when you open your environment. + +You can also run recovery by pausing or shutting down your application and using the **db_recover** command line utility. + +For example: + +``` c +package db.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + // Run normal recovery + myEnvConfig.setRunRecovery(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // All other operations are identical from here. Notice, however, + // that we have not created any other threads of control before + // recovery is complete. You want to run recovery for + // the first thread in your application that opens an environment, + // but not for any subsequent threads. + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +### Catastrophic Recovery + +Use catastrophic recovery when you are recovering your databases from a previously created backup. Note that to restore your databases from a previous backup, you should copy the backup to a new environment directory, and then run catastrophic recovery. Failure to do so can lead to the internal database structures being out of sync with your log files. + +Catastrophic recovery must be run single-threaded. + +To run catastrophic recovery: + +- Shutdown all database operations. + +- Restore the backup to an empty directory. + +- Specify `true` to `EnvironmentConfig.setRunRecoveryFatal()` when you open your environment. This environment open must be single-threaded. + +You can also run recovery by pausing or shutting down your application and using the **db_recover** command line utility with the the `-c` option. + +Note that catastrophic recovery examines every available log file — not just those log files created since the last checkpoint as is the case for normal recovery. For this reason, catastrophic recovery is likely to take longer than does normal recovery. + +For example: + +``` c +package db.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + // Run catastrophic recovery + myEnvConfig.setRunFatalRecovery(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg_txn/java/reversesplit.md b/docs_src/guides/gsg_txn/java/reversesplit.md new file mode 100644 index 000000000..0bc2f6d42 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/reversesplit.md @@ -0,0 +1,121 @@ +--- +title: "Reverse BTree Splits" +api-name: "Reverse BTree Splits" +source: docs/gsg_txn/JAVA/reversesplit.html +--- +## Reverse BTree Splits + +If your application is using the Btree access method, and your application is repeatedly deleting then adding records to your database, then you might be able to reduce lock contention by turning off reverse Btree splits. + +As pages are emptied in a database, DB attempts to delete empty pages in order to keep the database as small as possible and minimize search time. Moreover, when a page in the database fills up, DB, of course, adds additional pages to make room for more data. + +Adding and deleting pages in the database requires that the writing thread lock the parent page. Consequently, as the number of pages in your database diminishes, your application will see increasingly more lock contention; the maximum level of concurrency in a database of two pages is far smaller than that in a database of 100 pages, because there are fewer pages that can be locked. + +Therefore, if you prevent the database from being reduced to a minimum number of pages, you can improve your application's concurrency throughput. Note, however, that you should do so only if your application tends to delete and then add the same data. If this is not the case, then preventing reverse Btree splits can harm your database search time. + +To turn off reverse Btree splits, set `DatabaseConfig.setReverseSplitOff()`. to `true`. + +For example: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + + // Set BTree reverse split to off + dbConfig.setReverseSplitOff(true); + + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + "null", // db name + dbConfig); +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +Or, if you are using the DPL: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +EntityStore myStore = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Configure the store. + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + // Configure the underlying database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + dbConfig.setType(DatabaseType.BTREE); + + // Set BTree reverse split to off + dbConfig.setReverseSplitOff(true); + + // Instantiate the store + myStore = new EntityStore(myEnv, "store_name", myStoreConfig); + + // Set the DatabaseConfig object, so that the underlying + // database is configured for uncommitted reads. + myStore.setPrimaryConfig(SomeEntityClass.class, dbConfig); + +} catch (DatabaseException de) { + // Exception handling goes here +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` diff --git a/docs_src/guides/gsg_txn/java/sysfailure.md b/docs_src/guides/gsg_txn/java/sysfailure.md new file mode 100644 index 000000000..fc86c312c --- /dev/null +++ b/docs_src/guides/gsg_txn/java/sysfailure.md @@ -0,0 +1,18 @@ +--- +title: "A Note on System Failure" +api-name: "A Note on System Failure" +source: docs/gsg_txn/JAVA/sysfailure.html +--- +## 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 failures are created equal and no data protection mechanism can protect you against every conceivable way a computing system can find to die. + +Generally, when this book talks about protection against failures, it means that transactions offer protection against the likeliest culprits for system and application crashes. So long as your data modifications have been committed to disk, those modifications should persist even if your application or OS subsequently fails. And, even if the application or OS fails in the middle of a transaction commit (or abort), the data on disk should be either in a consistent state, or there should be enough data available to bring your databases into a consistent state (via a recovery procedure, for example). You may, however, lose whatever data you were committing at the time of the failure, but your databases will be otherwise unaffected. + +### Note + +Be aware that many disks have a disk write cache and on some systems it is enabled by default. This means that a transaction can have committed, and to your application the data may appear to reside on disk, but the data may in fact reside only in the write cache at that time. This means that if the disk write cache is enabled and there is no battery backup for it, data can be lost after an OS crash even when maximum durability mode is in use. For maximum durability, disable the disk write cache or use a disk write cache with a battery backup. + +Of course, if your *disk* fails, then the transactional benefits described in this book are only as good as the backups you have taken. By spreading your data and log files across separate disks, you can minimize the risk of data loss due to a disk failure, but even in this case it is possible to conjure a scenario where even this protection is insufficient (a fire in the machine room, for example) and you must go to your backups for protection. + +Finally, by following the programming examples shown in this book, you can write your code so as to protect your data in the event that your code crashes. However, no programming API can protect you against logic failures in your own code; transactions cannot protect you from simply writing the wrong thing to your databases. diff --git a/docs_src/guides/gsg_txn/java/txn_ccursor.md b/docs_src/guides/gsg_txn/java/txn_ccursor.md new file mode 100644 index 000000000..876eb025b --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txn_ccursor.md @@ -0,0 +1,159 @@ +--- +title: "Transactional Cursors and Concurrent Applications" +api-name: "Transactional Cursors and Concurrent Applications" +source: docs/gsg_txn/JAVA/txn_ccursor.html +--- +## Transactional Cursors and Concurrent Applications + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + +When you use transactional cursors with a concurrent application, remember that in the event of a deadlock you must make sure that you close your cursor before you abort and retry your transaction. This is true of both base API and DPL cursors. + +Also, remember that when you are using the default isolation level, every time your cursor reads a record it locks that record until the encompassing transaction is resolved. This means that walking your database with a transactional cursor increases the chance of lock contention. + +For this reason, if you must routinely walk your database with a transactional cursor, consider using a reduced isolation level such as read committed. This is true of both base API and DPL cursors. + +### Using Cursors with Uncommitted Data + +As described in Reading Uncommitted Data above, it is possible to relax your transaction's isolation level such that it can read data modified but not yet committed by 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 use uncommitted 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 uncommitted reads. + +Either way, you must first configure your database or store handle to support uncommitted reads before you can configure your transactions or your cursors to use them. + +The following example shows how to configure an individual cursor handle to read uncommitted data from within a serializable (full isolation) transaction. For an example of configuring a transaction to perform uncommitted reads in general, see Reading Uncommitted Data. + +``` c +package db.txn; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.CursorConfig; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import java.io.File; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + dbConfig.setReadUncommitted(true); // Enable uncommitted reads. + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + dbConfig); + + // Open the transaction. Note that this is a degree 3 + // 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). + + ... +``` + +If you are using the DPL: + +``` c +package persist.txn; + +import com.sleepycat.db.CursorConfig; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityCursor; +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.PrimaryIndex; +import com.sleepycat.persist.StoreConfig; + +import java.util.Iterator; + +import java.io.File; + +... + +EntityStore myStore = null; +Environment myEnv = null; +PrimaryIndex pKey; + +try { + + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setTransactional(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Set up the entity store + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + // Configure uncommitted reads + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + dbConfig.setAllowCreate(true); + dbConfig.setReadUncommitted(true); // Enable uncommitted reads. + + // Instantiate the store + myStore = new EntityStore(myEnv, storeName, myStoreConfig); + + // Set the DatabaseConfig object, so that the underlying + // database is configured for uncommitted reads. + myStore.setPrimaryConfig(AnEntityClass.class, myDbConfig); + + // Open the transaction. Note that this is a degree 3 + // transaction. + Transaction txn = myEnv.beginTransaction(null, null); + + //Configure our cursor for uncommitted reads. + CursorConfig cconfig = new CursorConfig(); + cconfig.setReadUncommitted(true); + + // Get our cursor. Note that we pass the transaction + // handle here. Note also that we cause the cursor + // to perform uncommitted reads. + EntityCursor cursor = pKey.entities(txn, cconfig); + + try { + // 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). + + ... +``` diff --git a/docs_src/guides/gsg_txn/java/txnconcurrency.md b/docs_src/guides/gsg_txn/java/txnconcurrency.md new file mode 100644 index 000000000..3ea342759 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txnconcurrency.md @@ -0,0 +1,116 @@ +--- +title: "Chapter 4. Concurrency" +api-name: "Chapter 4. Concurrency" +source: docs/gsg_txn/JAVA/txnconcurrency.html +--- +## Chapter 4. Concurrency + +**Table of Contents** + + [Which DB Handles are Free-Threaded](txnconcurrency.md#concurrenthandles) + + [Locks, Blocks, and Deadlocks](blocking_deadlocks.md) + + [Locks](blocking_deadlocks.md#locks) + + [Blocks](blocking_deadlocks.md#blocks) + + [Deadlocks](blocking_deadlocks.md#deadlocks) + + [The Locking Subsystem](lockingsubsystem.md) + + [Configuring the Locking Subsystem](lockingsubsystem.md#configuringlock) + + [Configuring Deadlock Detection](lockingsubsystem.md#configdeadlkdetect) + + [Resolving Deadlocks](lockingsubsystem.md#deadlockresolve) + + [Setting Transaction Priorities](lockingsubsystem.md#setpriority) + + [Isolation](isolation.md) + + [Supported Degrees of Isolation](isolation.md#degreesofisolation) + + [Reading Uncommitted Data](isolation.md#dirtyreads) + + [Committed Reads](isolation.md#readcommitted) + + [Using Snapshot Isolation](isolation.md#snapshot_isolation) + + [Transactional Cursors and Concurrent Applications](txn_ccursor.md) + + [Using Cursors with Uncommitted Data](txn_ccursor.md#cursordirtyreads) + + [Exclusive Database Handles](exclusivelock.md) + + [Read/Modify/Write](readmodifywrite.md) + + [No Wait on Blocks](txnnowait.md) + + [Reverse BTree Splits](reversesplit.md) + +DB offers a great deal of support for multi-threaded and multi-process applications even when transactions are not in use. Many of DB's handles are thread-safe, or can be made thread-safe by providing the appropriate flag at handle creation time, and DB provides a flexible locking subsystem for managing databases in a concurrent application. Further, DB provides a robust mechanism for detecting and responding to deadlocks . All of these concepts are explored in this chapter. + +Before continuing, it is useful to define a few terms that will appear throughout this chapter: + +- *Thread of control* + + Refers to a thread that is performing work in your application. Typically, in this book that thread will be performing DB operations. + + Note that this term can also be taken to mean a separate process that is performing work — DB supports multi-process operations on your databases. + +- *Locking* + + When a thread of control obtains access to a shared resource, it is said to be *locking* that resource. Note that DB supports both exclusive and non-exclusive locks. See Locks for more information. + +- *Free-threaded* + + Data structures and objects are free-threaded if they can be shared across threads of control without any explicit locking on the part of the application. Some books, libraries, and programming languages may use the term *thread-safe* for data structures or objects that have this characteristic. The two terms mean the same thing. + + For a description of free-threaded DB objects, see Which DB Handles are Free-Threaded. + +- *Blocked* + + When a thread cannot obtain a lock because some other thread already holds a lock on that object, the lock attempt is said to be *blocked*. See Blocks for more information. + +- *Deadlock* + + Occurs when two or more threads of control attempt to access conflicting resource in such a way as none of the threads can any longer make further progress. + + For example, if Thread A is blocked waiting for a resource held by Thread B, while at the same time Thread B is blocked waiting for a resource held by Thread A, then neither thread can make any forward progress. In this situation, Thread A and Thread B are said to be *deadlocked.* + + For more information, see Deadlocks. + +## Which DB Handles are Free-Threaded + +The following describes to what extent and under what conditions individual handles are free-threaded. + +- `Environment` and the DPL `EntityStore` + + Free-threaded so long as `EnvironmentConfig.setThreaded()` is set to `true`. + +- `Database` and the DPL `PrimaryIndex` + + Free-threaded so long as the database or DPL `PrimaryIndex` is opened in a free-threaded environment. + +- `SecondaryDatabase` and DPL `SecondaryIndex` + + Same conditions apply as for `Database` and `PrimaryIndex` handles. + +- `Cursor` and the DPL `EntityCursor` + + Cursors are not free-threaded. However, they can be used by multiple threads of control so long as the application serializes access to the handle. + +- `SecondaryCursor` + + Same conditions apply as for `Cursor` handles. + +- `Transaction` + + Access must be serialized by the application across threads of control. + +### Note + +All other classes found in the DPL (`com.sleepycat.persist.*`) and not mentioned above are free-threaded. + +All classes found in the bind APIs (`com.sleepycat.bind.*`) are free-threaded. diff --git a/docs_src/guides/gsg_txn/java/txncursor.md b/docs_src/guides/gsg_txn/java/txncursor.md new file mode 100644 index 000000000..c99b5849b --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txncursor.md @@ -0,0 +1,138 @@ +--- +title: "Transactional Cursors" +api-name: "Transactional Cursors" +source: docs/gsg_txn/JAVA/txncursor.html +--- +## Transactional Cursors + + [Using Transactional DPL Cursors](txncursor.md#dplcursors) + +You can transaction-protect your cursor operations by specifying a transaction handle at the time that you create your cursor. Beyond that, you do not ever provide a transaction handle directly to a cursor method. + +Note that if you transaction-protect a cursor, then you must make sure that the cursor is closed before you either commit or abort the transaction. For example: + +``` c +package db.txn; + +import com.sleepycat.db.Cursor; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.Transaction; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + + // Database and environment opens omitted + + 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(); + txn = null; + } catch (Exception e) { + if (cursor != null) { + cursor.close(); + } + if (txn != null) { + txn.abort(); + txn = null; + } + } + +} catch (DatabaseException de) { + // Exception handling goes here +} +``` + +### Using Transactional DPL Cursors + +When using the DPL, you create the cursor using the entity class's primary or secondary index (see the *Getting Started with Berkeley DB for Java* guide for details). At the time that you create the cursor, you pass a transaction handle to the `entities()` method, and this causes all subsequent operations performed using that cursor to be performed within the scope of the transaction. + +Note that if you are using a transaction-enabled store, then you must provide a transaction handle when you open your cursor. + +For example: + +``` c +package persist.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; + +import com.sleepycat.persist.EntityCursor; +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.PrimaryIndex; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +EntityStore store = null; + +... + + // Store and environment open omitted, as is the DataAccessor + // instantiation. + +... + + Transaction txn = myEnv.beginTransaction(null, null); + PrimaryIndex pi = + store.getPrimaryIndex(String.class, Inventory.class); + EntityCursor pi_cursor = pi.entities(txn, null); + + try { + for (Inventory ii : pi_cursor) { + // do something with each object "ii" + // A transactional handle is not required for any write + // operations. All operations performed using this cursor + // will be done within the scope of the transaction, txn. + } + pi_cursor.close(); + pi_cursor = null; + txn.commit(); + txn = null; + // Always make sure the cursor is closed when we are done with it. + } catch (Exception e) { + if (pi_cursor != null) { + pi_cursor.close(); + } + if (txn != null) { + txn.abort(); + txn = null; + } + } +``` diff --git a/docs_src/guides/gsg_txn/java/txnexample_dpl.md b/docs_src/guides/gsg_txn/java/txnexample_dpl.md new file mode 100644 index 000000000..c0ca9cf82 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txnexample_dpl.md @@ -0,0 +1,507 @@ +--- +title: "DPL Transaction Example" +api-name: "DPL Transaction Example" +source: docs/gsg_txn/JAVA/txnexample_dpl.html +--- +## DPL Transaction Example + + [TxnGuide.java](txnexample_dpl.md#txnguideexample_dpl) + + [PayloadDataEntity.java](txnexample_dpl.md#payloaddataentity) + + [StoreWriter.java](txnexample_dpl.md#storewriter) + +The following Java code provides a fully functional example of a multi-threaded transactional DB application using the DPL. This example is nearly identical to the example provided in the previous section, except that it uses an entity class and entity store to manage its data. + +As is the case with the previous examples, this example opens an environment and then an entity store. It then creates 5 threads, each of which writes 500 records to the database. The primary key for these writes are based on pre-determined integers, while the data is randomly generated 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 stay out of the way of the main points of this example. + +Each thread writes 10 records under a single transaction before committing and writing another 10 (this is repeated 50 times). At the end of each transaction, but before committing, each thread calls a function that uses a cursor to read every record in the database. We do this in order to make some points about database reads in a transactional environment. + +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 store management, spawns threads, and creates the data that is placed in the database. See TxnGuide.java for implementation details. + +- `StoreWriter.java` + + This class extends `java.lang.Thread`, and as such it is our thread implementation. It is responsible for actually reading and writing store. It also performs all of our transaction management. See StoreWriter.java for implementation details. + +- `PayloadDataEntity.java` + + This is an entity class used to encapsulate several data fields. See PayloadDataEntity.java for implementation details. + +### TxnGuide.java + +The main class in our example application is used to open and close our environment and store. It also spawns all the threads that we need. We start with the normal series of Java package and import statements, followed by our class declaration: + +``` c +// File TxnGuideDPL.java + +package persist.txn; + +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.LockDetectMode; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +public class TxnGuideDPL { +``` + +Next we declare our class' private data members. Mostly these are used for constants such as the name of the database that we are opening and the number of threads that we are spawning. However, we also declare our environment and database handles here. + +``` c + private static String myEnvPath = "./"; + private static String storeName = "exampleStore"; + + // Handles + private static EntityStore myStore = null; + private static Environment myEnv = null; + private static final int NUMTHREADS = 5; +``` + +Next, we implement our `usage()` method. This application optionally accepts a single command line argument which is used to identify the environment home directory. + +``` c + private static void usage() { + System.out.println("TxnGuideDPL [-h ]"); + System.exit(-1); + } +``` + +Now we implement our `main()` method. This method simply calls the methods to parse the command line arguments and open the environment and store. It also creates and then joins the store writer threads. + +``` c + public static void main(String args[]) { + try { + // Parse the arguments list + parseArgs(args); + // Open the environment and store + openEnv(); + + // Start the threads + StoreWriter[] threadArray; + threadArray = new StoreWriter[NUMTHREADS]; + for (int i = 0; i < NUMTHREADS; i++) { + threadArray[i] = new StoreWriter(myEnv, myStore); + threadArray[i].start(); + } + + for (int i = 0; i < NUMTHREADS; i++) { + threadArray[i].join(); + } + } catch (Exception e) { + System.err.println("TxnGuideDPL: " + 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 an entity store in that environment. Along the way, we make sure that every handle is free-threaded, and that the transactional subsystem is correctly initialized. Because this is a concurrent application, we also declare how we want deadlock detection to be performed. In this case, we use DB's internal block detector to determine whether a deadlock has occurred when a thread attempts to acquire a lock. We also indicate that we want the deadlocked thread with the *youngest* lock to receive deadlock notification. + +Notice that we also cause normal recovery to be run when we open the environment. This is the standard and recommended thing to do whenever you start up a transactional application. + +Finally, notice that we open the database such that it supports uncommitted reads. We do this so that some cursor activity later in this example can read uncommitted data. If we did not do this, then our `countObjects()` method described later in this example would cause our thread to self-deadlock. This is because the cursor could not be opened to support uncommitted reads (that flag on the cursor open would, in fact, be silently ignored). + +``` c + private static void openEnv() throws DatabaseException { + System.out.println("opening env and store"); + + // Set up the environment. + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setAllowCreate(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setRunRecovery(true); + myEnvConfig.setTransactional(true); + // EnvironmentConfig.setThreaded(true) is the default behavior + // in Java, so we do not have to do anything to cause the + // environment handle to be free-threaded. + + // Indicate that we want db to internally perform deadlock + // detection. Also indicate that the transaction that has + // performed the least amount of write activity to + // receive the deadlock notification, if any. + myEnvConfig.setLockDetectMode(LockDetectMode.MINWRITE); + + // Set up the entity store + StoreConfig myStoreConfig = new StoreConfig(); + myStoreConfig.setAllowCreate(true); + myStoreConfig.setTransactional(true); + + // Need a DatabaseConfig object so as to set uncommitted read + // support. + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + myDbConfig.setTransactional(true); + myDbConfig.setReadUncommitted(true); + + try { + // Open the environment + myEnv = new Environment(new File(myEnvPath), // Env home + myEnvConfig); + + // Open the store + myStore = new EntityStore(myEnv, storeName, myStoreConfig); + + // Set the DatabaseConfig object, so that the underlying + // database is configured for uncommitted reads. + myStore.setPrimaryConfig(PayloadDataEntity.class, myDbConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("openEnv: " + fnfe.toString()); + System.exit(-1); + } + } +``` + +Finally, we implement the methods used to close our environment and databases, parse the command line arguments, and provide our class constructor. This is fairly standard code and it is mostly uninteresting from the perspective of this manual. We include it here purely for the purpose of completeness. + +``` c + private static void closeEnv() { + System.out.println("Closing env and store"); + if (myStore != null ) { + try { + myStore.close(); + } catch (DatabaseException e) { + System.err.println("closeEnv: myStore: " + + e.toString()); + e.printStackTrace(); + } + } + + if (myEnv != null ) { + try { + myEnv.close(); + } catch (DatabaseException e) { + System.err.println("closeEnv: " + e.toString()); + e.printStackTrace(); + } + } + } + + private TxnGuideDPL() {} + + private static void parseArgs(String args[]) { + int nArgs = args.length; + for(int i = 0; i < args.length; ++i) { + if (args[i].startsWith("-")) { + switch(args[i].charAt(1)) { + case 'h': + if (i < nArgs - 1) { + myEnvPath = new String(args[++i]); + } + break; + default: + usage(); + } + } + } + } +} +``` + +### PayloadDataEntity.java + +Before we show the implementation of the store writer thread, we need to show the class that we will be placing into the store. This class is fairly minimal. It simply allows you to store and retrieve an `int`, a `String`, and a `double`. The `int` is our primary key. + +``` c +package persist.txn; +import com.sleepycat.persist.model.Entity; +import com.sleepycat.persist.model.PrimaryKey; +import com.sleepycat.persist.model.SecondaryKey; +import static com.sleepycat.persist.model.Relationship.*; + +@Entity +public class PayloadDataEntity { + @PrimaryKey + private int oID; + + @SecondaryKey(relate=MANY_TO_ONE) + private String threadName; + + private double doubleData; + + PayloadDataEntity() {} + + public double getDoubleData() { return doubleData; } + public int getID() { return oID; } + public String getThreadName() { return threadName; } + + public void setDoubleData(double dd) { doubleData = dd; } + public void setID(int id) { oID = id; } + public void setThreadName(String tn) { threadName = tn; } +} +``` + +### StoreWriter.java + +`StoreWriter.java` provides the implementation for our entity store writer thread. It is responsible for: + +- All transaction management. + +- Responding to deadlock exceptions. + +- Providing data to be stored in the entity store. + +- Writing the data to the store. + +In order to show off some of the ACID properties provided by DB's transactional support, `StoreWriter.java` does some things in a less efficient way than you would probably decide to use in a true production application. First, it groups 10 database writes together in a single 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 means your code would be slightly simpler and you would run a *much* smaller chance of encountering blocked and deadlocked operations. However, by doing things this way, we are able to show transactional atomicity, as well as deadlock handling. + +To begin, we provide the usual package and import statements, and we declare our class: + +``` c +package persist.txn; + +import com.sleepycat.db.CursorConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DeadlockException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.Transaction; + +import com.sleepycat.persist.EntityCursor; +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.PrimaryIndex; + +import java.util.Iterator; +import java.util.Random; +import java.io.UnsupportedEncodingException; + +public class StoreWriter extends Thread +{ +``` + +Next we declare our private data members. Notice that we get handles for the environment and the entity store. The random number generator that we instantiate is used to generate unique data for storage in the database. Finally, the `MAX_RETRY` variable is used to define how many times we will retry a transaction in the face of a deadlock. + +``` c + private EntityStore myStore = null; + private Environment myEnv = null; + private PrimaryIndex pdIndex; + private Random generator = new Random(); + private boolean passTxn = false; + + private static final int MAX_RETRY = 20; +``` + +Next we implement our class constructor. The most interesting thing about our constructor is that we use it to obtain our entity class's primary index. + +``` c + // Constructor. Get our handles from here + StoreWriter(Environment env, EntityStore store) + + throws DatabaseException { + myStore = store; + myEnv = env; + + // Open the data accessor. This is used to store persistent + // objects. + pdIndex = myStore.getPrimaryIndex(Integer.class, + PayloadDataEntity.class); + } +``` + +Now we implement our thread's `run()` method. This is the method that is run when `StoreWriter` threads are started in the main program (see TxnGuide.java). + +``` c + // 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 50 transactions. + +``` c + 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 should result in our retrying the operation. We also declare a `retry_count` variable that is used to make sure we do not retry a transaction forever in the unlikely event that the thread is unable to ever get a necessary lock. (The only thing that might cause this is if some other thread dies while holding an important lock. This is the only code that we have to guard against that because the simplicity of this application makes it highly unlikely that it will ever occur.) + +``` c + 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 our transaction here. + +``` c + // try block used for deadlock detection and + // general exception handling + try { + + // Get a transaction + txn = myEnv.beginTransaction(null, null); +``` + +Now we write 10 objects under the transaction that we have just begun. By combining multiple writes together under a single transaction, we increase the likelihood that a deadlock will occur. Normally, you want to reduce the potential for a deadlock and in this 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 per transaction we can actually observe deadlocks occurring. We also want to underscore the idea that you can combing multiple database operations together in a single atomic unit of work. So for our example, we do the (slightly) wrong thing. + +``` c + + // Write 10 PayloadDataEntity objects to the + // store for each transaction + for (int j = 0; j < 10; j++) { + // Instantiate an object + PayloadDataEntity pd = new PayloadDataEntity(); + + // Set the Object ID. This is used as the + // primary key. + pd.setID(i + j); + + // The thread name is used as a secondary key, and + // it is retrieved by this class's getName() + // method. + pd.setThreadName(getName()); + + // The last bit of data that we use is a double + // that we generate randomly. This data is not + // indexed. + pd.setDoubleData(generator.nextDouble()); + + // Do the put + pdIndex.put(txn, pd); + } +``` + +Having completed the inner database write loop, we could simply commit the transaction and continue on to the next block of 10 writes. However, we want to first illustrate a few points about transactional processing so instead we call our `countObjects()` method before calling the transaction commit. `countObjects()` uses a cursor to read every object in the entity store and return a count of the number of objects that it found. + +Because `countObjects()` reads every object in the store, if used incorrectly the thread will self-deadlock. The writer thread has just written 500 objects to the database, but because the transaction used for that write has not yet been committed, each of those 500 objects are still locked by the thread's transaction. If we then simply run a non-transactional cursor over the store from within the same thread that has locked those 500 objects, the cursor will block when it tries to read one of those transactional protected records. The thread immediately stops operation at that point while the cursor waits 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 for the thread. + +There are three ways to prevent this self-deadlock: + +1. We can move the call to `countObjects()` to a point after the thread's transaction has committed. + +2. We can allow `countObjects()` to operate under the same transaction as all of the writes were performed. + +3. We can reduce our isolation guarantee for the application by allowing uncommitted reads. + +For this example, we choose to use option 3 (uncommitted reads) to avoid the deadlock. This means that we have to open our underlying database such that it supports uncommitted reads, and we have to open our cursor handle so that it knows to perform uncommitted reads. + +``` c + // commit + System.out.println(getName() + " : committing txn : " + + i); + System.out.println(getName() + " : Found " + + countObjects(txn) + " objects in the store."); +``` + +Having performed this somewhat inelegant counting of the objects in the database, we can now commit the transaction. + +``` c + 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 of 10 objects to add to the store. However, in the event of an error, we must handle our exceptions correctly. The first of these is a deadlock exception. In the event of a deadlock, we want to abort and retry the transaction, provided that we have not already exceeded our retry limit for this transaction. + +``` c + } 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 exception and then give up (the transaction is not retried). + +``` c + } catch (DatabaseException e) { + // abort and don't retry + retry = false; + System.err.println(getName() + + " : caught exception: " + e.toString()); + System.err.println(getName() + + " : errno: " + e.getErrno()); + e.printStackTrace(); +``` + +And, finally, we always abort the transaction if the transaction handle is not null. Note that immediately after committing our transaction, we set the transaction handle to null to guard against aborting a transaction that has already been committed. + +``` c + } 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 `StoreWriter` class is the `countObjects()` implementation. Notice how in this example we open the cursor such that it performs uncommitted reads: + +``` c + // A method that counts every object in the store. + + private int countObjects(Transaction txn) throws DatabaseException { + int count = 0; + + CursorConfig cc = new CursorConfig(); + // This is ignored if the store is not opened with uncommitted read + // support. + cc.setReadUncommitted(true); + EntityCursor cursor = pdIndex.entities(txn, cc); + + try { + for (PayloadDataEntity pdi : cursor) { + count++; + } + } finally { + if (cursor != null) { + 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 DB distribution: + +``` c +DB_INSTALL/examples_java/src/persist/txn +``` diff --git a/docs_src/guides/gsg_txn/java/txnexample_java.md b/docs_src/guides/gsg_txn/java/txnexample_java.md new file mode 100644 index 000000000..654a9c07a --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txnexample_java.md @@ -0,0 +1,533 @@ +--- +title: "Base API Transaction Example" +api-name: "Base API Transaction Example" +source: docs/gsg_txn/JAVA/txnexample_java.html +--- +## Base API Transaction Example + + [TxnGuide.java](txnexample_java.md#txnguideexample) + + [PayloadData.java](txnexample_java.md#payloaddata) + + [DBWriter.java](txnexample_java.md#dbwriter) + +The following Java code provides a fully functional example of a multi-threaded transactional DB application. The example opens an environment and database, and then creates 5 threads, each of which writes 500 records to the database. The keys used for these writes are pre-determined strings, while the data is a class that contains randomly generated 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 stay out of the way of the main points of this example. + +Each thread writes 10 records under a single transaction before committing and writing another 10 (this is repeated 50 times). At the end of each transaction, but before committing, each thread calls a function that uses a cursor to read every record in the database. We do this in order to make some points about database reads in a transactional environment. + +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 database management, spawns threads, and creates the data that is placed in the database. See TxnGuide.java for implementation details. + +- `DBWriter.java` + + This class extends `java.lang.Thread`, and as such it is our thread implementation. It is responsible for actually reading and writing to the database. It also performs all of our transaction management. See DBWriter.java 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 serialize it for storage in the database. See PayloadData.java for implementation details. + +### TxnGuide.java + +The main class in our example application is used to open and close our environment and database. It also spawns all the threads that we need. We start with the normal series of Java package and import statements, followed by our class declaration: + +``` c +// File TxnGuide.java + +package db.txn; + +import com.sleepycat.bind.serial.StoredClassCatalog; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.LockDetectMode; + +import com.sleepycat.db.Environment; +import com.sleepycat.db.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 constants such as the name of the database that we are opening and the number of threads that we are spawning. However, we also declare our environment and database handles here. + +``` c + 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 single command line argument which is used to identify the environment home directory. + +``` c + private static void usage() { + System.out.println("TxnGuide [-h ]"); + System.exit(-1); + } +``` + +Now we implement our `main()` method. This method simply calls the methods to parse the command line arguments and open the environment and database. It also creates the stored class catalog that we use for serializing the data that we want to store in our database. Finally, it creates and then joins the database writer threads. + +``` c + 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 a database in that environment. Along the way, we make sure that every handle is free-threaded, and that the transactional subsystem is correctly initialized. Because this is a concurrent application, we also declare how we want deadlock detection to be performed. In this case, we use DB's internal block detector to determine whether a deadlock has occurred when a thread attempts to acquire a lock. We also indicate that we want the deadlocked thread with the *youngest* lock to receive deadlock notification. + +Notice that we also cause normal recovery to be run when we open the environment. This is the standard and recommended thing to do whenever you start up a transactional application. + +For the database open, notice that we open the database such that it supports duplicate records. This is required purely by the data that we are writing to the database, and it is only necessary if you run the application more than once without first deleting the environment. + +Finally, notice that we open the database such that it supports uncommitted reads. We do this so that some cursor activity later in this example can read uncommitted data. If we did not do this, then our `countRecords()` method described later in this example would cause our thread to self-deadlock. This is because the cursor could not be opened to support uncommitted reads (that flag on the cursor open would, in fact, be silently ignored). + +``` c + private static void openEnv() throws DatabaseException { + System.out.println("opening env"); + + // Set up the environment. + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setAllowCreate(true); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setRunRecovery(true); + myEnvConfig.setTransactional(true); + // EnvironmentConfig.setThreaded(true) is the default behavior + // in Java, so we do not have to do anything to cause the + // environment handle to be free-threaded. + + // Indicate that we want db to internally perform deadlock + // detection. Also indicate that the transaction that has + // performed the least amount of write activity to + // receive the deadlock notification, if any. + myEnvConfig.setLockDetectMode(LockDetectMode.MINWRITE); + + // Set up the database + DatabaseConfig myDbConfig = new DatabaseConfig(); + myDbConfig.setType(DatabaseType.BTREE); + myDbConfig.setAllowCreate(true); + myDbConfig.setTransactional(true); + myDbConfig.setSortedDuplicates(true); + myDbConfig.setReadUncommitted(true); + // no DatabaseConfig.setThreaded() method available. + // db handles in java are free-threaded so long as the + // env is also free-threaded. + + try { + // 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 + null, // Database 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 + null, // Database name, + myDbConfig); + } catch (FileNotFoundException fnfe) { + System.err.println("openEnv: " + fnfe.toString()); + System.exit(-1); + } + } +``` + +Finally, we implement the methods used to close our environment and databases, parse the command line arguments, and provide our class constructor. This is fairly standard code and it is mostly uninteresting from the perspective of this manual. We include it here purely for the purpose of completeness. + +``` c + 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: " + + 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 the class that we will be placing into the database. This class is fairly minimal. It simply allows you to store and retrieve an `int`, a `String`, and a `double`. We will be using the DB bind API from within the writer thread to serialize instances of this class and place them into our database. + +``` c +package db.txn; + +import java.io.Serializable; + +public class PayloadData implements Serializable { + private int oID; + private String threadName; + private double doubleData; + + 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 responsible for: + +- 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 DB's transactional support, `DBWriter.java` does some things in a less efficient way than you would probably decide to use in a true production application. First, it groups 10 database writes together in a single 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 means your code would be slightly simpler and you would run a *much* smaller chance of encountering blocked and deadlocked operations. However, by doing things this way, we are 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 by way of counting the number of records currently existing in the database. There are better ways to discover this information, but in this case we want to make some points regarding cursors, transactional applications, and deadlocking (we get into this in more detail later in this section). + +To begin, we provide the usual package and import statements, and we declare our class: + +``` c +package db.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.db.Cursor; +import com.sleepycat.db.CursorConfig; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.DeadlockException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.LockMode; +import com.sleepycat.db.OperationStatus; +import com.sleepycat.db.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 environment and the database. We also obtain a handle for an `EntryBinding`. We will use this to serialize `PayloadData` class instances (see PayloadData.java) for storage in the database. The random number generator that we instantiate is used to generate unique data for storage in the database. The `MAX_RETRY` variable is used to define how many times we will retry a transaction in the face of a deadlock. And, finally, `keys` is a `String` array that holds the keys used for our database entries. + +``` c + 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 is instantiate a serial binding for serializing `PayloadData` instances. + +``` c + // 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 when `DBWriter` threads are started in the main program (see TxnGuide.java). + +``` c + // 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 50 transactions. + +``` c + 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 should result in our retrying the operation. We also declare a `retry_count` variable that is used to make sure we do not retry a transaction forever in the unlikely event that the thread is unable to ever get a necessary lock. (The only thing that might cause this is if some other thread dies while holding an important lock. This is the only code that we have to guard against that because the simplicity of this application makes it highly unlikely that it will ever occur.) + +``` c + 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 our transaction here. + +``` c + // 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 combining multiple writes together under a single transaction, we increase the likelihood that a deadlock will occur. Normally, you want to reduce the potential for a deadlock and in this 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 per transaction we can actually observe deadlocks occurring. We also want to underscore the idea that you can combing multiple database operations together in a single atomic unit of work. So for our example, we do the (slightly) wrong thing. + +Further, notice that we store our key into a `DatabaseEntry` using `com.sleepycat.bind.tuple.StringBinding` to perform the serialization. Also, when we instantiate the `PayloadData` object, we call `getName()` which gives us the string representation of this thread's name, as well as `Random.nextDouble()` which gives us a random double value. This latter value is used so as to avoid duplicate records in the database. + +``` c + // 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 transaction and continue on to the next block of 10 writes. However, we want to first illustrate a few points about transactional processing so instead we call our `countRecords()` method before calling the transaction commit. `countRecords()` uses a cursor to read every record in the database and return a count of the number of records that it found. + +Because `countRecords()` reads every record in the database, if used incorrectly the thread will self-deadlock. The writer thread has just written 500 records to the database, but because the transaction used for that write has not yet been committed, each of those 500 records are still locked by the thread's transaction. If we then simply run a non-transactional cursor over the database from within the same thread that has locked those 500 records, the cursor will block when it tries to read one of those transactional protected records. The thread immediately stops operation at that point while the cursor waits 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 for the 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 has committed. + +2. We can allow `countRecords()` to operate under the same transaction as all of the writes were performed. + +3. We can reduce our isolation guarantee for the application by allowing uncommitted reads. + +For this example, we choose to use option 3 (uncommitted reads) to avoid the deadlock. This means that we have to open our database such that it supports uncommitted reads, and we have to open our cursor handle so that it knows to perform uncommitted reads. + +Note that in Base API In-Memory Transaction Example, we simply perform the cursor operation using the same transaction as is used for the thread's writes. + +``` c + // 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, we can now commit the transaction. + +``` c + 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 of 10 records to add to the database. However, in the event of an error, we must handle our exceptions correctly. The first of these is a deadlock exception. In the event of a deadlock, we want to abort and retry the transaction, provided that we have not already exceeded our retry limit for this transaction. + +``` c + } 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 exception and then give up (the transaction is not retried). + +``` c + } catch (DatabaseException e) { + // abort and don't retry + retry = false; + System.err.println(getName() + + " : caught exception: " + e.toString()); + System.err.println(getName() + + " : errno: " + e.getErrno()); + e.printStackTrace(); +``` + +And, finally, we always abort the transaction if the transaction handle is not null. Note that immediately after committing our transaction, we set the transaction handle to null to guard against aborting a transaction that has already been committed. + +``` c + } 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 how in this example we open the cursor such that it performs uncommitted reads: + +``` c + // 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.setReadUncommitted(true); + cursor = myDb.openCursor(txn, cc); + while (cursor.getNext(key, data, LockMode.DEFAULT) == + OperationStatus.SUCCESS) { + + count++; + } + } finally { + if (cursor != null) { + 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 DB distribution: + +``` c +DB_INSTALL/examples_java/src/db/txn +``` diff --git a/docs_src/guides/gsg_txn/java/txnindices.md b/docs_src/guides/gsg_txn/java/txnindices.md new file mode 100644 index 000000000..4d1c375f2 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txnindices.md @@ -0,0 +1,73 @@ +--- +title: "Secondary Indices with Transaction Applications" +api-name: "Secondary Indices with Transaction Applications" +source: docs/gsg_txn/JAVA/txnindices.html +--- +## Secondary Indices with Transaction Applications + +You can use transactions with your secondary indices so long as you open the secondary index so that it is transactional. + +All other aspects of using secondary indices with transactions are identical to using secondary indices without transactions. In addition, transaction-protecting secondary cursors is performed just as you protect normal cursors — you simply have to make sure the cursor is opened using a transaction handle, and that the cursor is closed before the handle is either either committed or aborted. See Transactional Cursors for details. + +Note that when you use transactions to protect your database writes, your secondary indices are protected from corruption because updates to the primary and the secondaries are performed in a single atomic transaction. + +### Note + +If you are using the DPL, then be aware that you never have to provide a transactional handle when opening an index, be it a primary or a secondary. However, if transactions are enabled for your store, then all of the indexes that you open will be enabled for transactional usage. Moreover, any write operation performed using that index will be done using a transaction, regardless of whether you explicitly provide a transactional handle to the write operation. + +If you do not explicitly provide a transaction handle to DPL write operations performed on a transactional store, then auto commit is silently used for that operation. + +For example: + +``` c +package db.GettingStarted; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseType; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.SecondaryDatabase; +import com.sleepycat.db.SecondaryConfig; + +import java.io.FileNotFoundException; + +... + +// Environment and primary database opens omitted. + +SecondaryConfig mySecConfig = new SecondaryConfig(); +mySecConfig.setAllowCreate(true); +mySecConfig.setType(DatabaseType.BTREE); +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 details. + 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) { + // Exception handling goes here ... +} catch (FileNotFoundException fnfe) { + // Exception handling goes here ... +} +``` diff --git a/docs_src/guides/gsg_txn/java/txnnowait.md b/docs_src/guides/gsg_txn/java/txnnowait.md new file mode 100644 index 000000000..79a0bd69c --- /dev/null +++ b/docs_src/guides/gsg_txn/java/txnnowait.md @@ -0,0 +1,26 @@ +--- +title: "No Wait on Blocks" +api-name: "No Wait on Blocks" +source: docs/gsg_txn/JAVA/txnnowait.html +--- +## No Wait on Blocks + +Normally when a DB transaction is blocked on a lock request, it must wait until the requested lock becomes available before its thread-of-control can proceed. However, it is possible to configure a transaction handle such that it will report a deadlock rather than wait for the block to clear. + +You do this on a transaction by transaction basis by specifying `true` to the `TransactionConfig.setNoWait()` method. + +For example: + +``` c + Transaction txn = null; + try { + TransactionConfig tc = new TransactionConfig(); + tc.setNoWait(true); + txn = myEnv.beginTransaction(null, tc); + + ... + } catch (DatabaseException de) { + // Deadlock detection and exception handling omitted + // for brevity + ... +``` diff --git a/docs_src/guides/gsg_txn/java/usingtxns.md b/docs_src/guides/gsg_txn/java/usingtxns.md new file mode 100644 index 000000000..81c8f1ccc --- /dev/null +++ b/docs_src/guides/gsg_txn/java/usingtxns.md @@ -0,0 +1,208 @@ +--- +title: "Chapter 3. Transaction Basics" +api-name: "Chapter 3. Transaction Basics" +source: docs/gsg_txn/JAVA/usingtxns.html +--- +## Chapter 3. Transaction Basics + +**Table of Contents** + + [Committing a Transaction](usingtxns.md#commitresults) + + [Non-Durable Transactions](nodurabletxn.md) + + [Aborting a Transaction](abortresults.md) + + [Auto Commit](autocommit.md) + + [Nested Transactions](nestedtxn.md) + + [Transactional Cursors](txncursor.md) + + [Using Transactional DPL Cursors](txncursor.md#dplcursors) + + [Secondary Indices with Transaction Applications](txnindices.md) + + [Configuring the Transaction Subsystem](maxtxns.md) + +Once you have enabled transactions for your environment and your databases, you can use them to protect your database operations. You do this by acquiring a transaction handle and then using that handle for any database operation that you want to participate in 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 using `Transaction.abort()`. + +Any transaction handle that has been committed or aborted can no longer be used by your application. + +Finally, you must make sure that all transaction handles are either committed or aborted before closing your databases and environment. + +### Note + +If you only want to transaction protect a single database write operation, you can use auto commit to perform the transaction administration. When you use auto commit, you do not need an explicit transaction handle. See Auto Commit for more information. + +For example, the following example opens a transactional-enabled environment and store, obtains a transaction handle, and then performs a write operation under its protection. In the event of any failure in the write operation, the transaction is aborted and the store is left in a state as if no operations had ever been attempted in the first place. + +``` c +package persist.txn; + +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; + +import com.sleepycat.persist.EntityStore; +import com.sleepycat.persist.StoreConfig; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Environment myEnv = null; +EntityStore store = null; + +// Our convenience data accessor class, used for easy access to +// EntityClass indexes. +DataAccessor da; + +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + StoreConfig storeConfig = new StoreConfig(); + storeConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + EntityStore store = new EntityStore(myEnv, + "EntityStore", storeConfig); + + da = new DataAccessor(store); + + // Assume that Inventory is an entity class. + Inventory theInventory = new Inventory(); + theInventory.setItemName("Waffles"); + theInventory.setItemSku("waf23rbni"); + + Transaction txn = myEnv.beginTransaction(null, null); + + try { + // Put the object to the store using the transaction handle. + da.inventoryBySku.put(txn, theInventory); + + // Commit the transaction. The data is now safely written to the + // store. + txn.commit(); + // If there is a problem, abort the transaction + } catch (Exception e) { + if (txn != null) { + txn.abort(); + txn = null; + } + } + +} catch (DatabaseException de) { + // Exception handling goes here + } catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +The same thing can be done with the base API; the database in use is left unchanged if the write operation fails: + +``` c +package db.txn; + +import com.sleepycat.db.Database; +import com.sleepycat.db.DatabaseConfig; +import com.sleepycat.db.DatabaseEntry; +import com.sleepycat.db.DatabaseException; +import com.sleepycat.db.Environment; +import com.sleepycat.db.EnvironmentConfig; +import com.sleepycat.db.Transaction; + +import java.io.File; +import java.io.FileNotFoundException; + +... + +Database myDatabase = null; +Environment myEnv = null; +try { + EnvironmentConfig myEnvConfig = new EnvironmentConfig(); + myEnvConfig.setInitializeCache(true); + myEnvConfig.setInitializeLocking(true); + myEnvConfig.setInitializeLogging(true); + myEnvConfig.setTransactional(true); + + myEnv = new Environment(new File("/my/env/home"), + myEnvConfig); + + // Open the database. + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setType(DatabaseType.BTREE); + myDatabase = myEnv.openDatabase(null, // txn handle + "sampleDatabase", // db file name + null, // db name + 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 +} catch (FileNotFoundException fnfe) { + // Exception handling goes here +} +``` + +## Committing a Transaction + +In order to fully understand what is happening when you commit a transaction, you must first understand a little about what DB is doing with the logging subsystem. Logging causes all database or store write operations to be identified in logs, and by default these logs are backed by files on disk. These logs are used to restore your databases or store in the event of a system or application failure, so by performing logging, DB ensures the integrity of your data. + +Moreover, DB performs *write-ahead* logging. This means that information is written to the logs *before* the actual database or store is changed. This means that all write activity performed under the protection of the transaction is noted in the log before the transaction is committed. Be aware, however, that database maintains logs in-memory. If you are backing your logs on disk, the log information will eventually be written to the log files, but while the transaction is on-going the log data may be held only in memory. + +When you commit a transaction, the following occurs: + +- A commit record is written to the log. This indicates that the modifications made by the transaction are now permanent. By default, this write is performed synchronously to 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. Note that this requirement can be relaxed, depending on the type of commit you perform. See Non-Durable Transactions for more information. Also, if you are maintaining your logs entirely in-memory, then this step will of course not be taken. To configure your logging system for in-memory usage, see Configuring In-Memory Logging. + +- All locks held by the transaction are released. This means that read operations performed by other transactions or threads of control can now see the modifications without resorting to uncommitted reads (see Reading Uncommitted Data for more information). + +To commit a transaction, you simply call `Transaction.commit()`. + +Notice that committing a transaction does not necessarily cause data modified in your memory cache to be written to the files backing your databases on disk. Dirtied database pages are written for a number of reasons, but a transactional commit is not one of them. The following are the things that can cause a dirtied database page to be written to the backing database file: + +- Checkpoints. + + Checkpoints cause all dirtied pages currently existing in the cache to be written to disk, and a checkpoint record is then written to the logs. You can run checkpoints explicitly. For more information on checkpoints, see Checkpoints. + +- Cache is full. + + If the in-memory cache fills up, then dirtied pages might be written to disk in order to free up space for other pages that your application needs to use. Note that if dirtied pages are written to the database files, then any log records that describe how those pages were dirtied are written to disk before the database pages are written. + +Be aware that because your transaction commit caused database or store modifications recorded in your logs to be forced to disk, your modifications are by default "persistent" in that they can be recovered in the event of an application or system failure. However, recovery time is gated by how much data has been modified since the last checkpoint, so for applications that perform a lot of writes, you may want to run a checkpoint with some frequency. + +Note that once you have committed a transaction, the transaction handle that you used for the transaction is no longer valid. To perform database activities under the control of a new transaction, you must obtain a fresh transaction handle. diff --git a/docs_src/guides/gsg_txn/java/wrapup.md b/docs_src/guides/gsg_txn/java/wrapup.md new file mode 100644 index 000000000..8dabfe342 --- /dev/null +++ b/docs_src/guides/gsg_txn/java/wrapup.md @@ -0,0 +1,88 @@ +--- +title: "Chapter 6. Summary and Examples" +api-name: "Chapter 6. Summary and Examples" +source: docs/gsg_txn/JAVA/wrapup.html +--- +## Chapter 6. Summary and Examples + +**Table of Contents** + + [Anatomy of a Transactional Application](wrapup.md#anatomy) + + [Base API Transaction Example](txnexample_java.md) + + [TxnGuide.java](txnexample_java.md#txnguideexample) + + [PayloadData.java](txnexample_java.md#payloaddata) + + [DBWriter.java](txnexample_java.md#dbwriter) + + [DPL Transaction Example](txnexample_dpl.md) + + [TxnGuide.java](txnexample_dpl.md#txnguideexample_dpl) + + [PayloadDataEntity.java](txnexample_dpl.md#payloaddataentity) + + [StoreWriter.java](txnexample_dpl.md#storewriter) + + [Base API In-Memory Transaction Example](inmem_txnexample_java.md) + +Throughout this manual we have presented the concepts and mechanisms that you need to provide transactional protection for your application. In this chapter, we summarize these mechanisms, and we provide a complete example of a multi-threaded transactional DB 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 following subsystems be used: + + - Transactional Subsystem (this also initializes the logging subsystem). + + - Memory pool (the in-memory cache). + + - Logging subsystem. + + - Locking subsystem (if your application is multi-process or multi-threaded). + + It is also highly recommended that you run normal recovery upon first environment open. Normal recovery examines only those logs required to ensure your database files are consistent relative to the information found in your log files. + +3. Optionally spawn off any utility threads that you might need. Utility threads can be used to run checkpoints periodically, or to periodically run a deadlock detector if you do not want to use DB's built-in deadlock detector. + +4. If you are using the base API, open whatever database handles that you need. Otherwise, open your store such that it is configured for transactions. + +5. Spawn off worker threads. How many of these you need and how they split their DB workload is entirely up to your application's requirements. However, any worker threads that perform write operations will do the following: + + 1. Begin a transaction. + + 2. Perform one or more read and write operations. + + 3. Commit the transaction if all goes well. + + 4. Abort and retry the operation if a deadlock is detected. + + 5. Abort the transaction for most other errors. + +6. On application shutdown: + + 1. Make sure there are no opened cursors. + + 2. Make sure there are no active transactions. Either abort or commit all transactions before shutting down. + + 3. Close your databases or store. + + 4. Close your environment. + +### Note + +Robust DB applications should monitor their worker threads to make sure they have not 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 to do this). This is the only way to clear any resources (such as a lock or a mutex) that the abnormally exiting worker thread might have been holding at the time that it died. + +Failure to perform this recovery can cause your still-functioning worker threads to eventually block forever while waiting for a lock that will never be released. + +In addition to these activities, which are all entirely handled by code within your application, there are some administrative activities that you should perform: + +- Periodically checkpoint your application. Checkpoints will reduce the time to run recovery in the event that one is required. See Checkpoints for details. + +- Periodically back up your database and log files. This is required in order to fully obtain the durability guarantee made by DB's transaction ACID support. See Backup Procedures for more information. + +- You may want to maintain a hot failover if 24x7 processing with rapid restart in the face of a disk hit is important to you. See Using Hot Failovers for more information. diff --git a/docs_src/index.md b/docs_src/index.md index 90c006582..1d3fc7ed7 100644 --- a/docs_src/index.md +++ b/docs_src/index.md @@ -29,10 +29,16 @@ transactional key/value storage engine. Generated from Markdown source by logging, transactions, replication, XA, and tuning. - [Getting Started with Data Storage](guides/gsg/index.html) — databases, cursors, secondary indexes, the four access methods. + ([C](guides/gsg/index.html) · [C++](guides/gsg/cxx/index.html) · + [Java](guides/gsg/java/index.html)) - [Getting Started with Transactions](guides/gsg_txn/index.html) — ACID, isolation, deadlocks, recovery, checkpoints. + ([C](guides/gsg_txn/index.html) · [C++](guides/gsg_txn/cxx/index.html) · + [Java](guides/gsg_txn/java/index.html)) - [Getting Started with Replication](guides/gsg_db_rep/index.html) — the replication framework and Replication Manager. + ([C](guides/gsg_db_rep/index.html) · [C++](guides/gsg_db_rep/cxx/index.html) · + [Java](guides/gsg_db_rep/java/index.html)) - [Collections (Bindings) Tutorial](guides/collections/index.html) — the Java-style collections/bindings API. - [Berkeley DB SQL](guides/bdb-sql/index.html) — the SQLite-compatible SQL @@ -57,6 +63,9 @@ page, e.g. `man libdb`, `man db_get`. Each book is also available as a PDF (see the release assets / `docs-build/pdf/`): `api_c.pdf`, `api_stl.pdf`, `guides_programmer_reference.pdf`, `guides_gsg.pdf`, `guides_gsg_txn.pdf`, `guides_gsg_db_rep.pdf`, +`guides_gsg_cxx.pdf`, `guides_gsg_java.pdf`, +`guides_gsg_txn_cxx.pdf`, `guides_gsg_txn_java.pdf`, +`guides_gsg_db_rep_cxx.pdf`, `guides_gsg_db_rep_java.pdf`, `guides_collections.pdf`, `guides_bdb-sql.pdf`, `guides_installation.pdf`, `guides_upgrading.pdf`, `guides_porting.pdf`, `guides_articles_inmemory.pdf`, `guides_articles_mssgtxt.pdf`. From 565b6968104bcd229abbf7199bdf186377b5e5db Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 3 Aug 2026 12:11:21 -0400 Subject: [PATCH 3/3] docs(ssi): document DB_TXN_SNAPSHOT_SAFE + flag reconcile DB_TXN_SNAPSHOT_SAFE (0x800) is our SSI addition -- snapshot isolation plus Cahill serializable conflict detection -- but was undocumented. Document it wherever DB_TXN_SNAPSHOT is documented, with wording verified against the engine (src/txn/txn.c, src/lock/lock.c, src/env/env_method.c): - api/c/txnbegin.md: DB_TXN_SNAPSHOT_SAFE flag entry -- implies DB_TXN_SNAPSHOT, layers serializable conflict detection, may abort with DB_SNAPSHOT_UNSAFE / DB_SNAPSHOT_CONFLICT, and cannot be prepared for 2PC (DB_TXN->prepare returns EINVAL). Also documented DB_TXN_FAMILY, the one other txn_begin flag that was missing. - api/c/envset_flags.md: note that SSI is per-transaction only -- the env OK_FLAGS mask accepts DB_TXN_SNAPSHOT but not _SAFE. - guides/gsg_txn/isolation.md + guides/programmer_reference/transapp_read.md: a Serializable Snapshot Isolation subsection (SI vs SSI, the return codes, the 2PC restriction). - guides/programmer_reference/program_errorret.md: the DB_SNAPSHOT_UNSAFE (-30967) and DB_SNAPSHOT_CONFLICT (-30968) return codes. FLAG RECONCILE (docs_src/_migrate/flag-reconcile.md): diffed the public txn/env flag surfaces (txn.c __db_fchk mask, env OK_FLAGS) against the api docs. Every txn_begin flag is now documented. DB_READ_COMMITTED/DB_READ_UNCOMMITTED/ DB_MPOOL_AIO were already documented. DB_DATABASE_LOCKING and DB_NOFLUSH remain undocumented set_flags flags, but they are pre-existing non-SSI gaps and left out of this SSI-focused pass. There is no DB_TXN_SNAPSHOT_UNSAFE flag -- DB_SNAPSHOT_UNSAFE is a return code. Baselined the verbatim legacy typos the new GSG C++/Java variants carried over from DocBook (matching the C variant's existing baseline entries) and trimmed one stale baseline entry for the removed csharp.md. --- docs_src/_migrate/codespell-baseline.txt | 10 +- docs_src/_migrate/flag-reconcile.md | 95 +++++++++++++++++++ docs_src/api/c/envset_flags.md | 2 + docs_src/api/c/txnbegin.md | 12 +++ docs_src/guides/gsg_txn/isolation.md | 15 +++ .../programmer_reference/program_errorret.md | 8 ++ .../programmer_reference/transapp_read.md | 8 ++ 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 docs_src/_migrate/flag-reconcile.md diff --git a/docs_src/_migrate/codespell-baseline.txt b/docs_src/_migrate/codespell-baseline.txt index 4bc6a0a78..604b12393 100644 --- a/docs_src/_migrate/codespell-baseline.txt +++ b/docs_src/_migrate/codespell-baseline.txt @@ -137,7 +137,6 @@ docs_src/guides/porting/modscope.md platfrom docs_src/guides/programmer_reference/arch_apis.md extention docs_src/guides/programmer_reference/bt_conf.md preceeding docs_src/guides/programmer_reference/ch13s02.md pre-emptive -docs_src/guides/programmer_reference/csharp.md libaries docs_src/guides/programmer_reference/embedded.md seemlessly docs_src/guides/programmer_reference/env_encrypt.md Documenation docs_src/guides/programmer_reference/intro_products.md informaion @@ -151,3 +150,12 @@ docs_src/guides/programmer_reference/transapp_atomicity.md ACI docs_src/guides/programmer_reference/transapp_throughput.md ACI docs_src/guides/programmer_reference/txn_config.md ACI docs_src/guides/upgrading/changelog_4_7.md invalide +docs_src/guides/gsg/cxx/secondaryDelete.md desireable +docs_src/guides/gsg_db_rep/cxx/elections.md desireable +docs_src/guides/gsg_db_rep/cxx/fwrkmasterreplica.md applicaton +docs_src/guides/gsg_db_rep/cxx/rep_init_code.md peformed +docs_src/guides/gsg_db_rep/java/elections.md desireable +docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md occured +docs_src/guides/gsg_db_rep/java/fwrkmasterreplica.md applicaton +docs_src/guides/gsg_db_rep/java/processingloop.md optins +docs_src/guides/gsg_txn/java/readmodifywrite.md retreived diff --git a/docs_src/_migrate/flag-reconcile.md b/docs_src/_migrate/flag-reconcile.md new file mode 100644 index 000000000..ae444b02e --- /dev/null +++ b/docs_src/_migrate/flag-reconcile.md @@ -0,0 +1,95 @@ +# Public DB_* flag reconcile (docs vs. engine) + +One-time reconcile of the public `DB_*` flags the engine actually accepts +against what `docs_src/api/c` documents, focused on the transaction / +environment / database flag surfaces the SSI work touched. Sources of truth: + +- `src/dbinc_auto/api_flags.in` — the public flag bit values. +- `src/dbinc/db.in` — public return codes. +- `src/txn/txn.c __txn_begin` `__db_fchk` mask — the valid `txn_begin` flags. +- `src/env/env_method.c __env_set_flags` `OK_FLAGS` — the valid env + `set_flags` flags. + +"Documented" = the flag token appears (word-boundary) in some +`docs_src/api/c/*.md` page. + +## DB_ENV->txn_begin() flags (src/txn/txn.c mask) + +| Flag | Before | After this change | +|------------------------|--------|-------------------| +| DB_IGNORE_LEASE | doc | doc | +| DB_READ_COMMITTED | doc | doc | +| DB_READ_UNCOMMITTED | doc | doc | +| DB_TXN_BULK | doc | doc | +| DB_TXN_FAMILY | **MISSING** | **added** (txnbegin.md) | +| DB_TXN_NOSYNC | doc | doc | +| DB_TXN_NOWAIT | doc | doc | +| DB_TXN_SNAPSHOT | doc | doc | +| DB_TXN_SNAPSHOT_SAFE | **MISSING** | **added** (txnbegin.md, +guides, +return codes) | +| DB_TXN_SYNC | doc | doc | +| DB_TXN_WAIT | doc | doc | +| DB_TXN_WRITE_NOSYNC | doc | doc | + +Every `txn_begin` flag the engine accepts is now documented. + +## DB_ENV->set_flags() flags (src/env/env_method.c OK_FLAGS) + +| Flag | Status | +|--------------------------|--------| +| DB_AUTO_COMMIT | doc | +| DB_CDB_ALLDB | doc | +| DB_DATABASE_LOCKING | **MISSING** (pre-existing gap; not SSI-era; left as-is) | +| DB_DIRECT_DB | doc | +| DB_DSYNC_DB | doc | +| DB_MPOOL_AIO | doc (engine work; already present) | +| DB_MULTIVERSION | doc | +| DB_NOFLUSH | **MISSING** (pre-existing gap; not SSI-era; left as-is) | +| DB_NOLOCKING | doc | +| DB_NOMMAP | doc | +| DB_NOPANIC | doc | +| DB_OVERWRITE | doc | +| DB_PANIC_ENVIRONMENT | doc | +| DB_REGION_INIT | doc | +| DB_TIME_NOTGRANTED | doc | +| DB_TXN_NOSYNC | doc | +| DB_TXN_NOWAIT | doc | +| DB_TXN_SNAPSHOT | doc | +| DB_TXN_WRITE_NOSYNC | doc | +| DB_YIELDCPU | doc | +| DB_HOTBACKUP_IN_PROGRESS | doc | + +Note: `DB_TXN_SNAPSHOT_SAFE` is deliberately **not** a valid `set_flags` +flag — SSI is a per-transaction property only (the env `OK_FLAGS` mask accepts +plain `DB_TXN_SNAPSHOT` but not `_SAFE`). `envset_flags.md` now states this +explicitly under `DB_TXN_SNAPSHOT`. + +## Public return codes (src/dbinc/db.in) — SSI-era + +| Return code | Value | Before | After | +|------------------------|----------|--------|-------| +| DB_SNAPSHOT_UNSAFE | -30967 | **undocumented** | **added** (program_errorret.md) | +| DB_SNAPSHOT_CONFLICT | -30968 | **undocumented** | **added** (program_errorret.md) | + +There is **no** `DB_TXN_SNAPSHOT_UNSAFE` *flag* — `DB_SNAPSHOT_UNSAFE` is a +return *code* an SSI transaction may get, not a `txn_begin` option. + +## What this change fixed + +- `DB_TXN_SNAPSHOT_SAFE` (0x800) — the SSI flag our engine work added: + documented on `api/c/txnbegin.md`, cross-noted on `api/c/envset_flags.md` + (per-transaction only), and explained in the transactions guide + (`guides/gsg_txn/isolation.md`) and the programmer's reference + (`guides/programmer_reference/transapp_read.md`). +- `DB_SNAPSHOT_UNSAFE` / `DB_SNAPSHOT_CONFLICT` return codes documented in + `guides/programmer_reference/program_errorret.md`. +- `DB_TXN_FAMILY` — a legacy public `txn_begin` flag that was never + documented; a short entry was added to complete the `txn_begin` flag list. +- `DB_READ_COMMITTED` / `DB_READ_UNCOMMITTED` / `DB_MPOOL_AIO` — verified + already documented; no change needed. + +## Out of scope (deliberately left) + +`DB_DATABASE_LOCKING` and `DB_NOFLUSH` are valid `set_flags` flags that are +undocumented, but they are pre-existing Oracle-era gaps unrelated to the SSI +work; documenting them accurately needs their own audit and is not part of +this SSI-focused reconcile. diff --git a/docs_src/api/c/envset_flags.md b/docs_src/api/c/envset_flags.md index 35d79a757..90c71695b 100644 --- a/docs_src/api/c/envset_flags.md +++ b/docs_src/api/c/envset_flags.md @@ -162,6 +162,8 @@ The **flags** parameter must be set by bitwise inclusively **OR**'ing together o The DB_TXN_SNAPSHOT flag may be used to configure Berkeley DB at any time during the life of the application. + Note that only plain snapshot isolation can be configured environment-wide. The serializable-snapshot-isolation flag DB_TXN_SNAPSHOT_SAFE is a per-transaction flag only: it is accepted by DB_ENV->txn_begin() but not by `DB_ENV->set_flags()`, so SSI must be requested for each transaction individually. + - `DB_TXN_WRITE_NOSYNC` If set, Berkeley DB will write, but will not synchronously flush, the log on transaction commit. This means that transactions exhibit the ACI (atomicity, consistency, and isolation) properties, but not D (durability); that is, database integrity will be maintained, but if the system fails, it is possible some number of the most recently committed transactions may be undone during recovery. The number of transactions at risk is governed by how often the system flushes dirty buffers to disk and how often the log is checkpointed. diff --git a/docs_src/api/c/txnbegin.md b/docs_src/api/c/txnbegin.md index 573288bc7..1fc7d21b5 100644 --- a/docs_src/api/c/txnbegin.md +++ b/docs_src/api/c/txnbegin.md @@ -57,6 +57,10 @@ The **flags** parameter must be set to 0 or by bitwise inclusively **OR**'ing to The bulk insert optimization is effective only for top-level transactions. The `DB_TXN_BULK` flag is ignored when **parent** is non-null. +- `DB_TXN_FAMILY` + + Start this transaction as part of a transaction family: a group of transactions that share locks and never conflict with one another. A family transaction is read-only from the caller's perspective and does not itself perform updates; it is used to hand a common locking context to a set of cooperating transactions (for example, a client/server design that spreads one logical unit of work across several transaction handles). When this flag is set the transaction is created read-only and shares the locker of its family, so operations performed under different members of the same family do not deadlock against each other. + - `DB_TXN_NOSYNC` Do not synchronously flush the log when this transaction commits or prepares. This means the transaction will exhibit the ACI (atomicity, consistency, and isolation) properties, but not D (durability); that is, database integrity will be maintained but it is possible that this transaction may be undone during recovery. @@ -75,6 +79,14 @@ The **flags** parameter must be set to 0 or by bitwise inclusively **OR**'ing to The error `DB_LOCK_DEADLOCK` will be returned from update operations if a snapshot transaction attempts to update data which was modified after the snapshot transaction read it. +- `DB_TXN_SNAPSHOT_SAFE` + + This transaction will execute with **serializable snapshot isolation** (SSI). This is a Berkeley DB extension beyond `DB_TXN_SNAPSHOT`: it implies `DB_TXN_SNAPSHOT` (the transaction reads a consistent snapshot as of its start, as above) and additionally enables serializable conflict detection (the Cahill SSI algorithm). Berkeley DB tracks read/write anti-dependencies between concurrent snapshot-safe transactions and, when it detects a dependency cycle that could produce a non-serializable schedule, aborts one of the transactions so the committed history is equivalent to some serial order. + + A transaction that hits such a potential anomaly is aborted with one of two Berkeley DB-specific error returns instead of committing: DB_SNAPSHOT_UNSAFE (a potential serializable-snapshot anomaly was detected via a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update was detected). On either return the application must abort the transaction and may retry it. As with `DB_TXN_SNAPSHOT`, snapshot behavior applies to databases opened with DB_MULTIVERSION. + + A `DB_TXN_SNAPSHOT_SAFE` (SSI) transaction **cannot be prepared for two-phase commit**: DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored after the transaction had entered the prepared state. + - `DB_TXN_SYNC` Synchronously flush the log when this transaction commits or prepares. This means the transaction will exhibit all of the ACID (atomicity, consistency, isolation, and durability) properties. diff --git a/docs_src/guides/gsg_txn/isolation.md b/docs_src/guides/gsg_txn/isolation.md index 1750c33a4..123816139 100644 --- a/docs_src/guides/gsg_txn/isolation.md +++ b/docs_src/guides/gsg_txn/isolation.md @@ -393,3 +393,18 @@ main(void) ``` + +#### Serializable Snapshot Isolation + +Berkeley DB also offers *serializable snapshot isolation* (SSI), a stronger mode selected with the `DB_TXN_SNAPSHOT_SAFE` flag. Plain snapshot isolation (`DB_TXN_SNAPSHOT`, above) gives each transaction a consistent view as of its start and avoids read locks, but it permits a small class of anomalies (write skew) that a fully serializable schedule would not. SSI closes that gap: it runs the transaction under snapshot isolation *and* tracks read/write anti-dependencies between concurrent snapshot-safe transactions, aborting a transaction whenever it detects a dependency cycle that could produce a non-serializable outcome. + +`DB_TXN_SNAPSHOT_SAFE` is a per-transaction flag; it implies `DB_TXN_SNAPSHOT` and is passed to DB_ENV->txn_begin() the same way. Unlike `DB_TXN_SNAPSHOT`, it cannot be enabled environment-wide through `DB_ENV->set_flags()` — each transaction that wants serializable snapshot isolation must request it. + +Because SSI aborts transactions to preserve serializability, a serializable-snapshot transaction may fail to commit with one of two Berkeley DB-specific return codes: + +- `DB_SNAPSHOT_UNSAFE` — a potential serializability anomaly was detected via a read/write anti-dependency (the transaction is the pivot of a dangerous structure). +- `DB_SNAPSHOT_CONFLICT` — a conflicting snapshot update was detected. + +In both cases the application must abort the transaction and may retry it, exactly as it would for `DB_LOCK_DEADLOCK`. See Error Returns to Applications for the return-code descriptions. + +One restriction applies: a `DB_TXN_SNAPSHOT_SAFE` transaction cannot be prepared for two-phase commit. DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored once the transaction had entered the prepared state. diff --git a/docs_src/guides/programmer_reference/program_errorret.md b/docs_src/guides/programmer_reference/program_errorret.md index 446444fc8..d440d76c5 100644 --- a/docs_src/guides/programmer_reference/program_errorret.md +++ b/docs_src/guides/programmer_reference/program_errorret.md @@ -55,3 +55,11 @@ Applications can handle such fatal errors in one of two ways: first, by checking **DB_SECONDARY_BAD** The DB_SECONDARY_BAD error is returned if a secondary index has been corrupted. This may be the result of an application operating on related databases without first associating them. + +**DB_SNAPSHOT_CONFLICT** + +The DB_SNAPSHOT_CONFLICT error is returned to a transaction started with the DB_TXN_SNAPSHOT_SAFE flag (serializable snapshot isolation, SSI) when Berkeley DB detects a conflicting snapshot update — an attempt to commit an update that conflicts with another concurrent snapshot-safe transaction. The affected transaction must be aborted; the application may then retry it. This return is specific to serializable snapshot isolation and does not occur for plain DB_TXN_SNAPSHOT transactions. + +**DB_SNAPSHOT_UNSAFE** + +The DB_SNAPSHOT_UNSAFE error is returned to a transaction started with the DB_TXN_SNAPSHOT_SAFE flag (serializable snapshot isolation, SSI) when Berkeley DB detects a potential serializability anomaly through a read/write anti-dependency: the transaction sits as the pivot of a dangerous structure (it is both the read end and the write end of anti-dependency edges among committed and running transactions) that could otherwise produce a non-serializable schedule. The affected transaction is aborted rather than allowed to commit; the application may then retry it. As with DB_SNAPSHOT_CONFLICT, this return is specific to serializable snapshot isolation. diff --git a/docs_src/guides/programmer_reference/transapp_read.md b/docs_src/guides/programmer_reference/transapp_read.md index 7d7f00d4b..9667187c3 100644 --- a/docs_src/guides/programmer_reference/transapp_read.md +++ b/docs_src/guides/programmer_reference/transapp_read.md @@ -36,3 +36,11 @@ So when *should* applications use snapshot isolati The simplest way to take advantage of snapshot isolation is for queries: keep update transactions using full read/write locking and set DB_TXN_SNAPSHOT on read-only transactions or cursors. This should minimize blocking of snapshot isolation transactions and will avoid introducing new DB_LOCK_DEADLOCK errors. If the application has update transactions which read many items and only update a small set (for example, scanning until a desired record is found, then modifying it), throughput may be improved by running some updates at snapshot isolation as well. + +### Serializable Snapshot Isolation + +Snapshot isolation as configured with DB_TXN_SNAPSHOT gives each transaction a consistent view as of its start and avoids read locks, but it is not fully serializable: it admits a class of anomalies (write skew) in which two transactions each read data the other then updates. Berkeley DB adds *serializable snapshot isolation* (SSI), selected with the DB_TXN_SNAPSHOT_SAFE flag, which runs the transaction under snapshot isolation and additionally detects the read/write anti-dependency cycles that cause those anomalies, aborting a transaction rather than allowing a non-serializable schedule to commit. SSI implements the Cahill serializable-snapshot-isolation algorithm. + +`DB_TXN_SNAPSHOT_SAFE` implies `DB_TXN_SNAPSHOT` and is likewise passed to DB_ENV->txn_begin(); the same `DB_MULTIVERSION` database configuration applies. It is a per-transaction flag only and cannot be enabled environment-wide with DB_ENV->set_flags(). + +A serializable-snapshot transaction that would violate serializability is aborted before commit and returns one of two Berkeley DB-specific codes: DB_SNAPSHOT_UNSAFE (a potential anomaly detected through a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update). The application handles these exactly as it handles DB_LOCK_DEADLOCK: abort the transaction and, if appropriate, retry it. Finally, a `DB_TXN_SNAPSHOT_SAFE` transaction cannot be prepared for two-phase commit — DB_TXN->prepare() returns `EINVAL` because SSI's conflict status is not frozen at prepare time.