db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From krist...@apache.org
Subject svn commit: r1044413 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/iapi/services/daemon/ engine/org/apache/derby/iapi/sql/dictionary/ engine/org/apache/derby/impl/db/ engine/org/apache/derby/impl/servic...
Date Fri, 10 Dec 2010 16:04:04 GMT
Author: kristwaa
Date: Fri Dec 10 16:04:04 2010
New Revision: 1044413

URL: http://svn.apache.org/viewvc?rev=1044413&view=rev
Log:
DERBY-4936: Add core istat daemon 

Added the core daemon code that will coordinate the automatic updates of index
cardinality statistics. Note that this code will do nothing until enabled, it
depends on an external client to schedule work units.

Most of the code actually generating statistics has been copied from
AlterTableConstantAction (I plan to remove this code and call the daemon from
AlterTableConstantAction).

To daemon can operate in two modes: explicit and background.
The former will behave very much like the existing code run by
SYS_CS.UPDATE_STATISTICS.
The latter will run in the background and try to stay out of the way for
concurrent user activity. It will use much weaker locking, being prepared to
handle the issues that might cause (for instance that the table is removed
while being scanned).

See class JavaDoc and the JIRA issue(s) for details.

Patch file: derby-4936-1b-core_istat_daemon.diff


Added:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java   (with props)
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
    db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java?rev=1044413&r1=1044412&r2=1044413&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java Fri Dec 10 16:04:04 2010
@@ -645,6 +645,63 @@ public interface Property { 
 	String STATEMENT_CACHE_SIZE = "derby.language.statementCacheSize";
 	int STATEMENT_CACHE_SIZE_DEFAULT = 100;
 
+    /**
+     * Tells if automatic index statistics update is enabled (default is true).
+     */
+    String STORAGE_AUTO_INDEX_STATS = "derby.storage.indexStats.auto";
+
+    /**
+     * Tells if activities related to automatic index statistics update should
+     * be written to the Derby system log file (derby.log).
+     */
+    String STORAGE_AUTO_INDEX_STATS_LOGGING = "derby.storage.indexStats.log";
+
+    /**
+     * Tells if more detailed activities related to automatic index statistics
+     * update should be traced. Accepted values are: *none*, stdout, log, both
+     */
+    String STORAGE_AUTO_INDEX_STATS_TRACING = "derby.storage.indexStats.trace";
+
+    /**
+     * Specifies the lower threshold for the number of rows in a table before
+     * creating statistics for the associated indexes.
+     * <p>
+     * <em>NOTE:</em> This is a debug property which will be removed or renamed.
+     */
+    String STORAGE_AUTO_INDEX_STATS_DEBUG_CREATE_THRESHOLD =
+            "derby.storage.indexStats.debug.createThreshold";
+    int STORAGE_AUTO_INDEX_STATS_DEBUG_CREATE_THRESHOLD_DEFAULT = 100;
+
+    /**
+     * Specifies the lower threshold for the absolute difference between the
+     * row estimate for the table and the row estimate for the index before
+     * creating statistics for the associated indexes.
+     * <p>
+     * <em>NOTE:</em> This is a debug property which will be removed or renamed.
+     */
+    String STORAGE_AUTO_INDEX_STATS_DEBUG_ABSDIFF_THRESHOLD =
+            "derby.storage.indexStats.debug.absdiffThreshold";
+    int STORAGE_AUTO_INDEX_STATS_DEBUG_ABSDIFF_THRESHOLD_DEFAULT = 0;
+
+    /**
+     * Specifies the lower threshold for the logarithmical (natural logarithm e)
+     * difference between the row estimate for the table and the row estimate
+     * for the index before creating statistics for the associated indexes.
+     * <p>
+     * <em>NOTE:</em> This is a debug property which will be removed or renamed.
+     */
+    String STORAGE_AUTO_INDEX_STATS_DEBUG_LNDIFF_THRESHOLD =
+            "derby.storage.indexStats.debug.lndiffThreshold";
+    double STORAGE_AUTO_INDEX_STATS_DEBUG_LNDIFF_THRESHOLD_DEFAULT = 1.0;
+
+    /**
+     * Specifies the size of the work unit queue in the index statistics update
+     * daemon.
+     */
+    String STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE =
+            "derby.storage.indexStats.debug.queueSize";
+    int STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE_DEFAULT = 20;
+
 	/*
 	** Transactions
 	*/

Added: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java?rev=1044413&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java Fri Dec 10 16:04:04 2010
@@ -0,0 +1,84 @@
+/*
+
+   Derby - Class org.apache.derby.iapi.services.daemon.IndexStatisticsDaemon
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.iapi.services.daemon;
+
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
+
+/**
+ * Daemon acting as a coordinator for creating and updating index statistics.
+ * <p>
+ * There are two modes of operation:
+ * <ul> <li>explicit - generates index statistics due to an explict request from
+ *          the user. The entrypoint is <tt>runExplicitly</tt>.</li>
+ *      <li>background - generates index statistics as a background task due to
+ *          an event that has triggered a statistics update. The entrypoint
+ *          is <tt>schedule</tt>.</li>
+ * <ul>
+ * <p>
+ * The modes differ in how the operation affects other operations in the running
+ * system, and also how errors are dealt with. The background mode will try to
+ * affect other operations as little as possible, and errors won't be reported
+ * unless they are severe. The explicit mode will do more to make sure the
+ * operation succeeds (for instance by using locks), and will report all errors.
+ */
+public interface IndexStatisticsDaemon {
+
+    /**
+     * Creates/updates index statistics for the specified conglomerates/indexes.
+     *
+     * @param lcc connection used to carry out the work
+     * @param td base table
+     * @param cds index conglomerates (non-index conglomerates are ignored)
+     * @param runContext descriptive text for the context in which the work is
+     *      being run (i.e. ALTER TABLE)
+     * @throws StandardException if something goes wrong
+     */
+    public void runExplicitly(LanguageConnectionContext lcc,
+                              TableDescriptor td,
+                              ConglomerateDescriptor[] cds,
+                              String runContext)
+            throws StandardException;
+
+    /**
+     * Schedules creation/update of the index statistics associated with the
+     * specified table.
+     * <p>
+     * Note that the scheduling request may be denied. Typical situations where
+     * that will happen is if the work queue is full, or if work has already
+     * been scheduled for the specified table.
+     *
+     * @param td base table
+     */
+    public void schedule(TableDescriptor td);
+
+    /**
+     * Stops the background daemon.
+     * <p>
+     * Any ongoing tasks will be aborted as soon as possible, and it will not
+     * be possible to schedule new tasks. Note that <tt>runExplicitly</tt> can
+     * still be used.
+     */
+    public void stop();
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/daemon/IndexStatisticsDaemon.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=1044413&r1=1044412&r2=1044413&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Fri Dec 10 16:04:04 2010
@@ -22,9 +22,12 @@
 package org.apache.derby.iapi.sql.dictionary;
 
 import org.apache.derby.iapi.sql.depend.DependencyManager;
+
+import org.apache.derby.iapi.db.Database;
 import org.apache.derby.iapi.types.DataTypeDescriptor;
 import org.apache.derby.iapi.types.NumberDataValue;
 import org.apache.derby.iapi.types.DataValueFactory;
+import org.apache.derby.iapi.services.daemon.IndexStatisticsDaemon;
 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 import org.apache.derby.iapi.sql.execute.ExecutionFactory;
 import org.apache.derby.iapi.error.StandardException;
@@ -2200,5 +2203,55 @@ public interface DataDictionary
     public void dropAllPermDescriptors(UUID objectID, TransactionController tc)
             throws StandardException;
 
+    /**
+     * Tells if an index statistics refresher should be created for this
+     * database.
+     * <p>
+     * The only reason not to create an index statistics refresher is if one
+     * already exists.
+     *
+     * @return {@code true} if an index statistics refresher should be created,
+     *      {@code false} if one already exists.
+     */
+    public boolean doCreateIndexStatsRefresher();
+
+    /**
+     * Creates an index statistics refresher for this data dictionary.
+     * <p>
+     * The index statistics refresher is used to create and refresh index
+     * cardinality statistics, either automatically or on user demand (i.e.
+     * by invoking SYSCS_UTIL.SYSCS_UPDATE_STATISTICS).
+     *
+     * @param db the database object associated with this data dictionary
+     * @param databaseName the name of the database
+     */
+    public void createIndexStatsRefresher(Database db, String databaseName);
+
+    /**
+     * Returns the index statistics refresher.
+     *
+     * @param asDaemon whether the usage is automatic ({@code true}) or
+     *      explicit ({@code false})
+     * @return The index statistics refresher instance, or {@code null} if
+     *      disabled. If {@code asDaemon} is {@code false}, an instance will
+     *      always be returned.
+     */
+    public IndexStatisticsDaemon getIndexStatsRefresher(boolean asDaemon);
 
+    /**
+     * Disables automatic refresh/creation of index statistics at runtime.
+     * <p>
+     * If the daemon is disabled, it can only be enabled again by rebooting
+     * the database. Note that this method concerns diabling the daemon at
+     * runtime, and only the automatic updates of statistics. If wanted, the
+     * user would disable the daemon at boot-time by setting a property
+     * (system-wide or database property).
+     * <p>
+     * <em>Usage note:</em> This method was added to allow the index refresher
+     * itself to notify the data dictionary that it should be disabled. This
+     * only happens if the refresher/daemon experiences severe errors, or a
+     * large amount of errors. It would then disable itself to avoid eating up
+     * system resources and potentially cause side-effects due to the errors.
+     */
+    public void disableIndexStatsRefresher();
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java?rev=1044413&r1=1044412&r2=1044413&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java Fri Dec 10 16:04:04 2010
@@ -24,11 +24,9 @@ package org.apache.derby.impl.db;
 import org.apache.derby.iapi.error.PublicAPI;
 
 import org.apache.derby.iapi.reference.Property;
-import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.reference.EngineType;
 import org.apache.derby.iapi.util.DoubleProperties;
 import org.apache.derby.iapi.util.IdUtil;
-import org.apache.derby.iapi.services.info.JVMInfo;
 
 import org.apache.derby.iapi.services.property.PropertyUtil;
 import org.apache.derby.iapi.services.loader.ClassFactory;
@@ -41,27 +39,21 @@ import org.apache.derby.iapi.services.mo
 import org.apache.derby.iapi.services.monitor.Monitor;
 import org.apache.derby.iapi.services.monitor.ModuleFactory;
 import org.apache.derby.iapi.services.sanity.SanityManager;
-import org.apache.derby.iapi.services.property.PersistentSet;
 import org.apache.derby.iapi.db.Database;
 import org.apache.derby.iapi.db.DatabaseContext;
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.sql.execute.ExecutionFactory;
 import org.apache.derby.iapi.types.DataValueFactory;
-import org.apache.derby.iapi.sql.compile.OptimizerFactory;
 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
-import org.apache.derby.iapi.sql.conn.ConnectionUtil;
 
 import org.apache.derby.iapi.sql.conn.LanguageConnectionFactory;
 
 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
 import org.apache.derby.iapi.sql.dictionary.FileInfoDescriptor;
 import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
-import org.apache.derby.iapi.sql.dictionary.SPSDescriptor;
 
-import org.apache.derby.iapi.sql.depend.DependencyManager;
 import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.sql.LanguageFactory;
-import org.apache.derby.iapi.sql.ResultSet;
 import org.apache.derby.iapi.store.access.AccessFactory;
 import org.apache.derby.iapi.store.access.FileResource;
 import org.apache.derby.iapi.services.property.PropertyFactory;
@@ -75,19 +67,11 @@ import org.apache.derby.catalog.UUID;
 
 import org.apache.derby.iapi.store.replication.slave.SlaveFactory;
 
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.Serializable;
-import java.io.File;
-import java.sql.Date;
-import java.sql.Timestamp;
 import java.sql.SQLException;
 import java.util.Properties;
 import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
 import java.util.Locale;
-import java.lang.reflect.Method;
 import java.text.DateFormat;
 
 /**
@@ -249,7 +233,12 @@ public class BasicDatabase implements Mo
 
 		active = true;
 
-	}
+        // Create an index statistics update daemon.
+        if (dd.doCreateIndexStatsRefresher()) {
+            dd.createIndexStatsRefresher(this, allParams.getProperty(
+                        Property.PROPERTY_RUNTIME_PREFIX + "serviceDirectory"));
+        }
+    }
 
 	public void stop() {
 		active = false;

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java?rev=1044413&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java Fri Dec 10 16:04:04 2010
@@ -0,0 +1,1157 @@
+/*
+
+   Derby - Class org.apache.derby.impl.services.daemon.IndexStatisticsDaemonImpl
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.derby.impl.services.daemon;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.util.ArrayList;
+
+import org.apache.derby.catalog.UUID;
+import org.apache.derby.catalog.types.StatisticsImpl;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.db.Database;
+import org.apache.derby.iapi.error.ExceptionSeverity;
+import org.apache.derby.iapi.error.ShutdownException;
+import org.apache.derby.iapi.reference.Property;
+import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.services.context.ContextService;
+import org.apache.derby.iapi.services.daemon.IndexStatisticsDaemon;
+import org.apache.derby.iapi.services.property.PropertyUtil;
+import org.apache.derby.iapi.services.sanity.SanityManager;
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
+import org.apache.derby.iapi.services.uuid.UUIDFactory;
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
+import org.apache.derby.iapi.sql.depend.DependencyManager;
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;
+import org.apache.derby.iapi.sql.dictionary.StatisticsDescriptor;
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
+import org.apache.derby.iapi.sql.execute.ExecIndexRow;
+import org.apache.derby.iapi.store.access.ConglomerateController;
+import org.apache.derby.iapi.store.access.GroupFetchScanController;
+import org.apache.derby.iapi.store.access.ScanController;
+import org.apache.derby.iapi.store.access.TransactionController;
+import org.apache.derby.iapi.types.DataValueDescriptor;
+
+/**
+ * Daemon acting as a coordinator for creating and updating index cardinality
+ * statistics.
+ * <p>
+ * The need for updated statistics is currently determined when compiling a
+ * SELECT query. The unit of work is then scheduled with this daemon, and the
+ * work itself will be carried out in a separate thread. If the worker thread
+ * doesn't exist it is created, if it is idle the unit of work will be
+ * processed immediately, and if it is busy the unit of work has to wait in the
+ * queue.
+ * <p>
+ * The daemon code has a notion of a background task. If the update is run as a
+ * background task, it will try to affect other activity in the Derby database
+ * as little as possible. As far as possible, it will not set locks on the
+ * conglomerates it scans, and if it needs to take locks it will give up
+ * immediately if the locks cannot be obtained. In some cases it will also roll
+ * back to release locks already taken, ad then retry. Since we are accessing
+ * shared structures the background work may still interfere with the user
+ * activity in the database due to locking, but all such operations carried out
+ * by the daemon are of short duration.
+ * <p>
+ * The high level flow of an update to index statistics is:
+ * <ol>
+ *      <li>schedule update (the only action carried out by the user thread)<li>
+ *      <li>for each index:</li>
+ *      <ol>
+ *          <li>scan index</li>
+ *          <li>invalidate statements dependent on current statistics</li>
+ *          <li>drop existing statistics</li>
+ *          <li>add new statistics</li>
+ *      </ol>
+ * </ol>
+ * <p>
+ * List of possible improvements:
+ * <ol>
+ *      <li>Reduce potential impact of multiple invalidations (per table),
+ *          probably by finding a way to invalidate only once after all indexes
+ *          for a table have had their statistics updated. So far invalidation
+ *          has proven to be the most difficult piece of the puzzle due to the
+ *          interaction with the data dictionary and sensitivity to concurrent
+ *          activity for the table.</li>
+ * </ol>
+ * <p>
+ * <em>Implementation notes:</em> List of potential cleanups before going into
+ * a release:
+ * <ol>
+ *      <li>Consider removing all tracing code. May involve improving logging
+ *          if parts of the trace output is valuable enough.</li>
+ * </ol>
+ */
+public class IndexStatisticsDaemonImpl
+        implements IndexStatisticsDaemon, Runnable {
+
+    private static final boolean AS_BACKGROUND_TASK = true;
+    private static final boolean AS_EXPLICIT_TASK = false;
+    /** Maximum number of work units allowed in the queue. */
+    // TODO: Replace with constant after testing/tuning phase.
+    private static final int MAX_QUEUE_LENGTH;
+    static {
+        MAX_QUEUE_LENGTH = PropertyUtil.getSystemInt(
+                Property.STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE,
+                Property.STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE_DEFAULT);
+    }
+
+    private final HeaderPrintWriter logStream;
+    /** Tells if logging is enabled. */
+    private final boolean doLog;
+    // TODO: Consider removing the trace functionality after testing/tuning.
+    /** Tells if tracing is enabled. */
+    private final boolean doTrace;
+    /** Tells if traces are written to the Derby log file. */
+    private final boolean traceToDerbyLog;
+    /** Tells if traces are written to standard out. */
+    private final boolean traceToStdOut;
+
+    /** Tells if the daemon has been disabled. */
+    // @GuardedBy("queue")
+    private boolean daemonDisabled;
+    /** Tells if the daemon has been stopped. */
+    private volatile boolean daemonStopped;
+    /** The context manager for the worker thread. */
+    private final ContextManager ctxMgr;
+    /** The language connection context for the worker thread. */
+    private LanguageConnectionContext daemonLCC;
+    /**
+     * The database object for the database we are handling automatic index
+     * statistics update for.
+     */
+    private final Database db;
+    /** The name of the database owner. */
+    private final String dbOwner;
+    private final String databaseName;
+
+    /**
+     * A list of tables that shall have their index statistics updated.
+     * Note that the descriptor isn't removed before the work has
+     * been completed.
+     */
+    private final ArrayList queue = new ArrayList(MAX_QUEUE_LENGTH);
+    /**
+     * The thread in which the index statistics refresh operation is being
+     * executed, if any. Created as needed, but there will only be one
+     * thread doing the work. The thread is allowed to die since it is assumed
+     * that index statistics regeneration is rather infrequent.
+     */
+    //@GuardedBy("queue")
+    private Thread runningThread;
+
+    /**
+     * Number of consecutive errors, used as a metric to decide if the damoen
+     * should be automatically shut down.
+     */
+    private int errorsConsecutive;
+    // Counters used for tracing / logging (wu = work unit).
+    private long errorsUnknown;
+    private long errorsKnown;
+    private long wuProcessed;
+    private long wuScheduled;
+    private long wuRejectedDup; // Duplicates
+    private long wuRejectedFQ; // Full queue
+    private long wuRejectedOther; // Daemon disabled
+
+    /** Specifies when the daemon was created. */
+    private final long timeOfCreation;
+    /**
+     * The period of time (ms) for which the daemon has been doing active work.
+     */
+    private long runTime;
+
+    /**
+     * Creates a new daemon.
+     *
+     * @param log the log to write to
+     * @param doLog whether to log activity information
+     * @param traceLevel whether, and to where, trace information should be
+     *      written ("off|log|stdout|both")
+     * @param db the database ("off|log|stdout|both")
+     * @param userName the name of the database owner
+     * @param databaseName the name of the database (not stored in the db obj)
+     */
+    public IndexStatisticsDaemonImpl(HeaderPrintWriter log, boolean doLog,
+                                     String traceLevel, Database db,
+                                     String userName, String databaseName) {
+        // Make sure we can log errors.
+        if (log == null) {
+            throw new IllegalArgumentException("log stream cannot be null");
+        }
+        // Configure logging/tracing
+        this.logStream = log;
+        this.doLog = doLog;
+        this.traceToDerbyLog = (traceLevel.equalsIgnoreCase("both") ||
+                traceLevel.equalsIgnoreCase("log"));
+        this.traceToStdOut = (traceLevel.equalsIgnoreCase("both") ||
+                traceLevel.equalsIgnoreCase("stdout"));
+        this.doTrace = traceToDerbyLog || traceToStdOut;
+
+        this.db = db;
+        this.dbOwner = userName;
+        this.databaseName = databaseName;
+        this.ctxMgr = ContextService.getFactory().newContextManager();
+        this.timeOfCreation = System.currentTimeMillis();
+        trace(0, "created{log=" + doLog + ", traceLog=" +
+                traceToDerbyLog + ", traceOut=" + traceToStdOut +
+                /* Will be fixed by DERBY-4936.
+                ", createThreshold=" +
+                TableDescriptor.ISTATS_CREATE_THRESHOLD +
+                ", absdiffThreshold=" +
+                TableDescriptor.ISTATS_ABSDIFF_THRESHOLD +
+                ", lndiffThreshold=" +
+                TableDescriptor.ISTATS_LNDIFF_THRESHOLD +
+                */
+                ", queueLength=" + MAX_QUEUE_LENGTH +
+                "}) -> " + databaseName);
+    }
+
+    /**
+     * Schedules an update of the index statistics for the specified table.
+     * <p>
+     * Assume the descriptor will be valid until we get around to generate
+     * the statistics. If it turns out to be invalid, it will be discarded.
+     *
+     * @param td base table descriptor to update index statistics for
+     */
+    public void schedule(TableDescriptor td) {
+        // TODO: Will be fixed in DERBY-4936.
+        String schedulingReason = null; // td.getIndexStatsUpdateReason();
+        trace(0, "scheduling " + td.getQualifiedName() +
+                    (schedulingReason == null
+                        ? ""
+                        : " reason=[" + schedulingReason + "]"));
+        synchronized (queue) {
+            if (acceptWork(td)) {
+                // Add the work description for the given table.
+                queue.add(td);
+                wuScheduled++;
+                log(AS_BACKGROUND_TASK, td,
+                        "update scheduled - " + td.getUUID() +
+                        (schedulingReason == null
+                            ? ""
+                            : " reason=[" + schedulingReason + "]") +
+                        " (queueSize=" + queue.size() + ")");
+                // If we're idle, fire off the worker thread.
+                if (runningThread == null) {
+                    runningThread = new Thread(this, "index-stat-thread");
+                    // Make the thread a daemon thread, we don't want it to stop
+                    // the JVM from exiting. This is a precaution.
+                    runningThread.setDaemon(true);
+                    runningThread.start();
+                }
+            }
+        }
+    }
+
+    /**
+     * Determines if the given work can be accepted.
+     *
+     * @param td the table descriptor to check
+     * @return {@code true} if work can be accepted, {@code false} if not.
+     */
+    //@GuardedBy("queue")
+    private boolean acceptWork(TableDescriptor td) {
+        // Don't allow unbounded growth.
+        boolean accept = !(daemonDisabled || queue.size() >= MAX_QUEUE_LENGTH);
+        if (accept && !queue.isEmpty()) {
+            // See if work is already scheduled for this table. If so, we
+            // give the already scheduled or in progress task precedence.
+            String table = td.getName();
+            String schema = td.getSchemaName();
+            // Since the queue size is limited, iterating through it to find
+            // duplicates should yield acceptable performance. Also, we don't
+            // look for duplicates if the queue is already full.
+            for (int i=0; i < queue.size(); i++) {
+                TableDescriptor work = (TableDescriptor)queue.get(i);
+                if (work.tableNameEquals(table, schema)) {
+                    accept = false;
+                    break;
+                }
+            }
+        }
+
+        // If the work was rejected, trace it.
+        if (!accept) {
+            if (daemonDisabled) {
+                wuRejectedOther++;
+                trace(1, "daemon disabled - work not scheduled");
+            } else if (queue.size() >= MAX_QUEUE_LENGTH) {
+                wuRejectedFQ++;
+                trace(1, "queue full - work not scheduled");
+            } else {
+                wuRejectedDup++;
+                trace(1, "duplicate found - not scheduled");
+            }
+        }
+        return accept;
+    }
+
+    /**
+     * Generates index statistics for all indexes associated with the given
+     * table descriptor.
+     *
+     * @param lcc connection context to use to perform the work
+     * @param td target base table descriptor
+     * @throws StandardException if accessing the conglomerates fail
+     */
+    private void generateStatistics(LanguageConnectionContext lcc,
+                                    TableDescriptor td)
+            throws StandardException {
+        trace(0, "generateStatistics::start {" + td.getQualifiedName() + "}");
+        boolean lockConflictSeen = false;
+        while (true) {
+            try {
+                ConglomerateDescriptor[] cds = td.getConglomerateDescriptors();
+                updateIndexStatsMinion(lcc, td, cds, AS_BACKGROUND_TASK);
+                break;
+            } catch (StandardException se) {
+                // At this level, we retry the whole operation. If this happens,
+                // it normally means that a lengthy operation, or possibly DDL,
+                // is taking place (for instance compress table). We retry only
+                // once, but wait rather long before doing so.
+                // Note that some lower level operations may have tried to
+                // aquire the locks several times already, and we may not be
+                // able to complete the work if we get here.
+                if (SQLState.LOCK_TIMEOUT.equals(se.getMessageId()) &&
+                        !lockConflictSeen) {
+                    trace(1, "locks unavailable, retrying");
+                    lockConflictSeen = true;
+                    lcc.internalRollback(); // Get rid of any locks
+                    sleep(1000);
+                } else {
+                    // Rethrow exception, because:
+                    //   o error is not a lock timeout
+                    //           - or -
+                    //   o too many lock timeouts
+                    // Transaction will be cleaned up elsewhere.
+                    throw se;
+                }
+            }
+        }
+        trace(0, "generateStatistics::end");
+    }
+
+    /**
+     * Updates the index statistics for the given table and the specified
+     * indexes.
+     *
+     * @param lcc language connection context used to perform the work
+     * @param td the table to update index stats for
+     * @param cds the conglomerates to update statistics for (non-index
+     *      conglomerates will be ignored)
+     * @param asBackgroundTask whether the updates are done automatically as
+     *      part of a background task or if explicitly invoked by the user
+     * @throws StandardException if something goes wrong
+     */
+    private void updateIndexStatsMinion(LanguageConnectionContext lcc,
+                                        TableDescriptor td,
+                                        ConglomerateDescriptor[] cds,
+                                        boolean asBackgroundTask)
+            throws StandardException {
+        // Extract/derive information from the table descriptor
+        long[] conglomerateNumber = new long[cds.length];
+        ExecIndexRow[] indexRow = new ExecIndexRow[cds.length];
+        UUID[] objectUUID = new UUID[cds.length];
+
+        TransactionController tc = lcc.getTransactionExecute();
+        ConglomerateController heapCC =
+            tc.openConglomerate(td.getHeapConglomerateId(), false,
+                    0,
+                    TransactionController.MODE_RECORD,
+                    asBackgroundTask
+                        ? TransactionController.ISOLATION_READ_UNCOMMITTED
+                        : TransactionController.ISOLATION_REPEATABLE_READ
+                );
+        try
+        {
+            for (int i = 0; i < cds.length; i++)
+            {
+                if (!cds[i].isIndex())
+                {
+                    conglomerateNumber[i] = -1;
+                    continue;
+                }
+
+                conglomerateNumber[i] = cds[i].getConglomerateNumber();
+
+                objectUUID[i] = cds[i].getUUID();
+
+                indexRow[i] =
+                    cds[i].getIndexDescriptor().getNullIndexRow(
+                        td.getColumnDescriptorList(),
+                        heapCC.newRowLocationTemplate());
+            }
+        }
+        finally
+        {
+            heapCC.close();
+        }
+
+        // [x][0] = conglomerate number, [x][1] = start time, [x][2] = stop time
+        long[][] scanTimes = new long[conglomerateNumber.length][3];
+        int sci = 0;
+        for (int indexNumber = 0;
+             indexNumber < conglomerateNumber.length;
+             indexNumber++)
+        {
+            if (conglomerateNumber[indexNumber] == -1)
+                continue;
+            // Check if daemon has been disabled.
+            if (asBackgroundTask) {
+                synchronized (queue) {
+                    if (daemonDisabled) {
+                        break;
+                    }
+                }
+            }
+
+            scanTimes[sci][0] = conglomerateNumber[indexNumber];
+            scanTimes[sci][1] = System.currentTimeMillis();
+            // Subtract one for the RowLocation added for indexes.
+            int numCols = indexRow[indexNumber].nColumns() - 1;
+            long[] cardinality = new long[numCols];
+            KeyComparator cmp = new KeyComparator(indexRow[indexNumber]);
+
+            /* Read uncommitted, with record locking. Actually CS store may
+               not hold record locks */
+            GroupFetchScanController gsc =
+                tc.openGroupFetchScan(
+                        conglomerateNumber[indexNumber],
+                        false,  // hold
+                        0,
+                        TransactionController.MODE_RECORD, // locking
+                        TransactionController.ISOLATION_READ_UNCOMMITTED,
+                        null,   // scancolumnlist-- want everything.
+                        null,   // startkeyvalue-- start from the beginning.
+                        0,
+                        null,   // qualifiers, none!
+                        null,   // stopkeyvalue,
+                        0);
+
+            try
+            {
+                int rowsFetched = 0;
+                while ((rowsFetched = cmp.fetchRows(gsc)) > 0)
+                {
+                    for (int i = 0; i < rowsFetched; i++)
+                    {
+                        int whichPositionChanged = cmp.compareWithPrevKey(i);
+                        if (whichPositionChanged >= 0) {
+                            for (int j = whichPositionChanged; j < numCols; j++)
+                                cardinality[j]++;
+                        }
+                    }
+                } // while
+                gsc.setEstimatedRowCount(cmp.getRowCount());
+            } // try
+            finally
+            {
+                gsc.close();
+                gsc = null;
+            }
+            scanTimes[sci++][2] = System.currentTimeMillis();
+            // We have scanned the indexes, so let's give this a few attempts
+            // before giving up.
+            int retries = 0;
+            while (true) {
+                try {
+                    writeUpdatedStats(lcc, td, objectUUID[indexNumber],
+                            cmp.getRowCount(), cardinality, asBackgroundTask);
+                    break;
+                } catch (StandardException se) {
+                    retries++;
+                    if (SQLState.LOCK_TIMEOUT.equals(se.getMessageId()) &&
+                            retries < 3) {
+                        trace(2, "lock timeout when writing stats, retrying");
+                        sleep(100*retries);
+                    } else {
+                        // Rethrow exception, because:
+                        //   o error is not a lock timeout
+                        //           - or -
+                        //   o too many lock timeouts
+                        throw se;
+                    }
+                }
+            }
+        }
+        log(asBackgroundTask, td, fmtScanTimes(scanTimes));
+    }
+
+    /**
+     * Writes updated statistics for the specified index to the data dictionary.
+     *
+     * @param lcc connection context to use to perform the work
+     * @param td the base table
+     * @param index the index of the base table
+     * @param numRows number of rows in the base table
+     * @param cardinality the number of unique values in the index (per number
+     *      of leading columns)
+     * @param asBackgroundTask whether the update is done automatically as
+     *      part of a background task or if explicitly invoked by the user
+     * @throws StandardException if updating the data dictionary fails
+     */
+    private void writeUpdatedStats(LanguageConnectionContext lcc,
+                                   TableDescriptor td, UUID index,
+                                   long numRows, long[] cardinality,
+                                   boolean asBackgroundTask)
+            throws StandardException {
+        TransactionController tc = lcc.getTransactionExecute();
+        trace(1, "writing new stats (xid=" + tc.getTransactionIdString() + ")");
+        UUID table = td.getUUID();
+        DataDictionary dd = lcc.getDataDictionary();
+        UUIDFactory uf = dd.getUUIDFactory();
+
+        // Update the heap row count estimate.
+        setHeapRowEstimate(tc, td.getHeapConglomerateId(), numRows);
+        // Invalidate statments accessing the given table.
+        // Note that due to retry logic, swithcing the data dictionary to
+        // write mode is done inside invalidateStatements.
+        invalidateStatements(lcc, td, asBackgroundTask);
+        // Drop existing index statistics for this index.
+        dd.dropStatisticsDescriptors(table, index, tc);
+
+        // Don't write statistics if the table is empty.
+        if (numRows == 0) {
+            trace(2, "empty table, no stats written");
+        } else {
+            // Construct and add the statistics entries.
+            for (int i=0; i < cardinality.length; i++) {
+                StatisticsDescriptor statDesc = new StatisticsDescriptor(
+                        dd, uf.createUUID(), index, table, "I",
+                     new StatisticsImpl(numRows, cardinality[i]),
+                     i+1);
+                dd.addDescriptor(statDesc, null,
+                        DataDictionary.SYSSTATISTICS_CATALOG_NUM, true, tc);
+            }
+
+            // Log some information.
+            log(asBackgroundTask, td,
+                    "wrote stats for index " + index + " (rows=" + numRows +
+                    ", card=" + cardToStr(cardinality) + ")");
+        }
+
+        // Only commit tx as we go if running as background task.
+        if (asBackgroundTask) {
+            lcc.internalCommit(true);
+        }
+    }
+
+    /**
+     * Performs an invalidation action for the given table (the event being
+     * statistics update).
+     *
+     * @param lcc connection context to use to perform the work
+     * @param td the table to invalidate for
+     * @param asBackgroundTask whether the update is done automatically as
+     *      part of a background task or if explicitly invoked by the user
+     * @throws StandardException if the invalidation request fails
+     */
+    private void invalidateStatements(LanguageConnectionContext lcc,
+                                      TableDescriptor td,
+                                      boolean asBackgroundTask)
+            throws StandardException {
+        // Invalidate compiled statements accessing the table.
+        DataDictionary dd = lcc.getDataDictionary();
+        DependencyManager dm = dd.getDependencyManager();
+        boolean inWrite = false;
+        int retries = 0;
+        while (true) {
+            try {
+                if (!inWrite) {
+                    dd.startWriting(lcc);
+                    inWrite = true;
+                }
+                dm.invalidateFor(
+                        td, DependencyManager.UPDATE_STATISTICS, lcc);
+                trace(1, "invalidation completed");
+                break;
+            } catch (StandardException se) {
+                // Special handling when running as background task.
+                if (SQLState.LOCK_TIMEOUT.equals(se.getMessageId()) &&
+                        asBackgroundTask && retries < 3) {
+                    retries++;
+                    // If this is the first time we retry, don't roll back.
+                    // If we already waited once, but still didn't get the
+                    // locks, back down by releasing our own locks.
+                    if (retries > 1) {
+                        trace(2, "releasing locks");
+                        lcc.internalRollback();
+                        inWrite = false;
+                    }
+                    trace(2, "lock timeout when invalidating");
+                    sleep(100*(1+retries)); // adaptive sleeping...
+                } else {
+                    // Throw exception because of one of:
+                    //  o it isn't a lock timeout
+                    //          - or -
+                    //  o we gave up retrying
+                    //          - or -
+                    //  o we are running in explicit mode
+                    trace(1, "invalidation failed");
+                    throw se;
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the row estimate for the heap conglomerate.
+     *
+     * @param tc transaction to use
+     * @param tableId the heap table
+     * @param rowEstimate estimate of number of rows in the table
+     * @throws StandardException if accessing the table fails
+     */
+    private void setHeapRowEstimate(TransactionController tc, long tableId,
+                                    long rowEstimate)
+            throws StandardException {
+        // DERBY-4116: If we know the row count, update the store estimated row
+        // count for the table.
+        ScanController sc = tc.openScan(
+                tableId,
+                false,  // hold
+                0,      // openMode: for read
+                TransactionController.MODE_RECORD, // locking
+                TransactionController.ISOLATION_READ_UNCOMMITTED, //iso level
+                null,   // scancolumnlist-- want everything.
+                null,   // startkeyvalue-- start from the beginning.
+                0,
+                null,   // qualifiers, none!
+                null,   // stopkeyvalue,
+                0);
+
+        try {
+            sc.setEstimatedRowCount(rowEstimate);
+        } finally {
+            sc.close();
+        }
+    }
+
+    /**
+     * Drives the statistics generation.
+     * <p>
+     * This method will be run in a separate thread, and it will keep working
+     * as long as there is work to do. When the queue is exhausted, the method
+     * will exit (the thread dies).
+     */
+    public void run() {
+        final long runStart = System.currentTimeMillis();
+        final ContextService ctxService = ContextService.getFactory();
+        ctxService.setCurrentContextManager(ctxMgr);
+        // If we don't have a connection to the database, create one.
+        if (daemonLCC == null) {
+            try {
+                daemonLCC =
+                        db.setupConnection(ctxMgr, dbOwner, null, databaseName);
+                trace(1, "got database connection");
+                // Initialize the lcc/transaction.
+                // TODO: Would be nice to name the transaction.
+                daemonLCC.setIsolationLevel(
+                        Connection.TRANSACTION_READ_UNCOMMITTED);
+                // Don't wait for any locks.
+                daemonLCC.getTransactionExecute().setNoLockWait(true);
+            } catch (StandardException se) {
+                log(AS_BACKGROUND_TASK, null, se,
+                        "failed to setup index statistics updater");
+                ctxService.resetCurrentContextManager(ctxMgr);
+                return;
+            }
+        }
+
+        TransactionController tc = null;
+        try {
+            tc = daemonLCC.getTransactionExecute();
+            trace(0, "worker thread started (xid=" +
+                    tc.getTransactionIdString() + ")");
+
+            TableDescriptor td = null;
+            long start = 0;
+            while (true) {
+                synchronized (queue) {
+                    if (daemonDisabled) {
+                        // Clean the lcc and exit.
+                        try {
+                            tc.destroy();
+                        } catch (ShutdownException se) {
+                            // Ignore
+                        }
+                        tc = null;
+                        daemonLCC = null;
+                        queue.clear();
+                    }
+                    if (queue.isEmpty()) {
+                        trace(1, "queue empty");
+                        break;
+                    }
+                    td = (TableDescriptor)queue.get(0);
+                }
+                try {
+                    start = System.currentTimeMillis();
+                    log(AS_BACKGROUND_TASK, td, "generating index statistics");
+                    generateStatistics(daemonLCC, td);
+                    wuProcessed++;
+                    // Reset consecutive error counter.
+                    errorsConsecutive = 0;
+                    log(AS_BACKGROUND_TASK, td, "generation complete (" +
+                            ((System.currentTimeMillis() - start))  + " ms)");
+                } catch (StandardException se) {
+                    errorsConsecutive++;
+                    // Assume handling of fatal errors will clean up properly.
+                    // For less severe errors, rollback tx to clean up.
+                    if (!handleFatalErrors(ctxMgr, se)) {
+                        boolean handled = handleExpectedErrors(td, se);
+                        if (!handled) {
+                            handled = handleUnexpectedErrors(td, se);
+                        }
+                        daemonLCC.internalRollback();
+                        if (SanityManager.DEBUG) {
+                            SanityManager.ASSERT(handled);
+                        }
+                    }
+                } finally {
+                    // Whatever happened, discard the unit of work.
+                    synchronized (queue) {
+                        // Queue may have been cleared due to shutdown.
+                        if (!queue.isEmpty()) {
+                            queue.remove(0);
+                        }
+                    }
+                    // If we have seen too many consecutive errors, disable
+                    // the daemon. 50 was chosen based on gut-feeling...
+                    // Hopefully it can withstand shortlived "hick-ups", but
+                    // will cause shutdown if there is a real problem.
+                    // Create an exception to force logging of the message.
+                    if (errorsConsecutive >= 50) {
+                        log(AS_BACKGROUND_TASK, null,
+                                new IllegalStateException("degraded state"),
+                                "shutting down daemon, " + errorsConsecutive +
+                                " consecutive errors seen");
+                        stop();
+                    }
+                }
+            }
+            trace(0, "run::normal_exit");
+        } catch (StandardException se) {
+            log(AS_BACKGROUND_TASK, null, se, "thread died");
+            // Do nothing, just let the thread die.
+        } catch (ShutdownException se) {
+            stop(); // Call stop to log activity statistics.
+            ctxMgr.cleanupOnError(se);
+        } finally {
+            synchronized (queue) {
+                runningThread = null;
+            }
+            if (daemonLCC != null && !daemonLCC.isTransactionPristine()) {
+                if (SanityManager.DEBUG) {
+                    SanityManager.THROWASSERT("transaction not pristine");
+                }
+                log(AS_BACKGROUND_TASK, null,
+                        "transaction not pristine - forcing rollback");
+                try {
+                    daemonLCC.internalRollback();
+                } catch (StandardException se) {
+                    // Log, then continue.
+                    log(AS_BACKGROUND_TASK, null, se, "forced rollback failed");
+                }
+            }
+            ctxService.resetCurrentContextManager(ctxMgr);
+            runTime += (System.currentTimeMillis() - runStart);
+        }
+    }
+
+    /**
+     * Runs the statistics update sequence explicitly as requested by the user.
+     *
+     * @param lcc connection context to use to perform the work
+     * @param td the base table
+     * @param cds the indexes to update (non-index conglomerates are ignored)
+     * @param runContext the context in which the operation is run (i.e.
+     *      'ALTER TABLE', may be {@code null})
+     * @throws StandardException if updating the index statistics fails
+     */
+    public void runExplicitly(LanguageConnectionContext lcc,
+                              TableDescriptor td,
+                              ConglomerateDescriptor[] cds,
+                              String runContext)
+            throws StandardException {
+        trace(0, "explicit run" + (runContext != null
+                                        ? " (" + runContext + "): "
+                                        : ":") +
+                                    td.getQualifiedName());
+        updateIndexStatsMinion(lcc, td, cds, AS_EXPLICIT_TASK);
+    }
+
+    /**
+     * Stops the daemon.
+     * <p>
+     * Will also clear the queue and print runtime statistics to the log the
+     * first time the method is invoked.
+     */
+    public void stop() {
+        if (!daemonStopped) {
+            daemonStopped = true;
+            synchronized (queue) {
+                StringBuffer sb = new StringBuffer(100);
+                sb.append("stopping daemon, active=").
+                        append(runningThread != null).
+                        append(", work/age=").append(runTime).append('/').
+                        append(System.currentTimeMillis() - timeOfCreation).
+                        append(' ');
+                appendRunStats(sb);
+                log(AS_BACKGROUND_TASK, null, sb.toString());
+                // If there is no running thread and the daemon lcc is still
+                // around, destroy the transaction and clear the lcc reference.
+                if (runningThread == null && daemonLCC != null) {
+                    daemonLCC.getTransactionExecute().destroy();
+                    daemonLCC = null;
+                }
+                daemonDisabled = true;
+                runningThread = null;
+                queue.clear();
+            }
+        }
+    }
+
+    /**
+     * Handles fatal errors that will cause the daemon to be shut down.
+     *
+     * @param cm context manager
+     * @param se the exception to handle
+     * @return {@code true} if the error was handled, {@code false} otherwise
+     */
+    private boolean handleFatalErrors(ContextManager cm, StandardException se) {
+        boolean disable = false;
+        if (SQLState.DATA_CONTAINER_READ_ONLY.equals(se.getMessageId())) {
+            // We are not allowed to write into the database, most likely the
+            // data dictionary. No point to keep doing work we can't gain from.
+            disable = true;
+        } else if (se.getSeverity() >= ExceptionSeverity.DATABASE_SEVERITY) {
+            // The database or system is going down. Probably handled elsewhere
+            // but disable daemon anyway.
+            disable = true;
+            cm.cleanupOnError(se);
+        }
+
+        if (disable) {
+            daemonLCC.getDataDictionary().disableIndexStatsRefresher();
+            stop();
+        }
+        return disable;
+    }
+
+    /**
+     * Handles expected errors.
+     * <p>
+     * The logging of expected errors is for observability purposes only. The
+     * daemon is capable of dealing with these errors, and no interaction from
+     * the user is expected.
+     *
+     * @param se the exception to handle
+     * @return {@code true} if the error was handled, {@code false} otherwise
+     */
+    private boolean handleExpectedErrors(TableDescriptor td,
+                                         StandardException se) {
+        String state = se.getMessageId();
+        // Accept that the heap/index/conglomerate has been deleted since the
+        // work for it was scheduled. Just ignore the unit of work and continue.
+        if (SQLState.STORE_CONGLOMERATE_DOES_NOT_EXIST.equals(state) ||
+                SQLState.HEAP_CONTAINER_NOT_FOUND.equals(state) ||
+                SQLState.FILE_IO_INTERRUPTED.equals(state) ||
+                SQLState.LOCK_TIMEOUT.equals(state)) {
+            errorsKnown++;
+            log(AS_BACKGROUND_TASK, td, "generation aborted (reason: " +
+                    state + ") {" + extractIstatInfo(se) + "}");
+            trace(1, "top level expected exception: " + extractIstatInfo(se));
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handles unexpected errors.
+     * <p>
+     * Unexpected errors are error conditions the daemon isn't set up to handle
+     * specifically. For this reason the stack trace will be logged to allow
+     * for later investigation.
+     * <p>
+     * In general it is expected that the daemon will be able to recover by
+     * dropping the current unit of work and move on to the next one (if any).
+     *
+     * @param se the exception to handle
+     * @return {@code true} if the error was handled, {@code false} otherwise
+     */
+    private boolean handleUnexpectedErrors(TableDescriptor td,
+                                           StandardException se) {
+        errorsUnknown++;
+        log(AS_BACKGROUND_TASK, td, se, "generation failed");
+        return true;
+    }
+
+    /**
+     * Puts the current thread to sleep for maximum {@code ms} milliseconds.
+     * <p>
+     * No guarantee is provided for the minimum amount of time slept. If
+     * interrupted, the interrupt flag will be set again.
+     *
+     * @param ms target sleep time
+     */
+    private static void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException ie) {
+            // Set the interrupt flag again.
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /** Format array of scan durations as a string. */
+    private static String fmtScanTimes(long[][] timings) {
+        // timings[x] = [conglomId, start, end]
+        StringBuffer sb = new StringBuffer("scan durations (");
+        for (int i=0; i < timings.length && timings[i][0] > 0; i++) {
+            long duration = timings[i][2] - timings[i][1];
+            sb.append('c').append(timings[i][0]).append('=').append(duration).
+                    append("ms,");
+        }
+        sb.deleteCharAt(sb.length() -1).append(")");
+        return sb.toString();
+    }
+
+    /** @see #log(boolean, TableDescriptor, Throwable, String)  */
+    private void log(boolean asBackgroundTask, TableDescriptor td, String msg) {
+        log(asBackgroundTask, td, null, msg);
+    }
+
+    /**
+     * Logs the information given.
+     * <p>
+     * Note that if {@code asBackgroundTask} is false, nothing will be logged
+     * currently.
+     *
+     * @param asBackgroundTask {@code true} if logging for the background
+     *      daemon automatically updating stats, {@code false} if not
+     * @param td current table descriptor being worked on, may be {@code null}
+     * @param t raised error, may be {@code null}
+     * @param msg the message to log
+     */
+    private void log(boolean asBackgroundTask, TableDescriptor td, Throwable t,
+            String msg) {
+        if (asBackgroundTask && (doLog || t != null)) {
+            PrintWriter pw = null;
+            String hdrMsg = "{istat} " +
+                    (td == null ? "" : td.getQualifiedName() + ": ") + msg;
+            if (t != null) {
+                pw = new PrintWriter(logStream.getPrintWriter(), false);
+                pw.print(logStream.getHeader().getHeader());
+                pw.println(hdrMsg);
+                t.printStackTrace(pw);
+                pw.flush();
+            } else {
+                logStream.printlnWithHeader(hdrMsg);
+            }
+        }
+    }
+
+    // @GuardedBy("this")
+    private final StringBuffer tsb = new StringBuffer();
+    private synchronized void trace(int indentLevel, String msg) {
+        if (doTrace) {
+            tsb.setLength(0);
+            tsb.append("{istat,trace@").append(hashCode()).append("} ");
+            for (int i=0; i < indentLevel; i++) {
+                tsb.append("    ");
+            }
+            tsb.append(msg).append(' ');
+            if (indentLevel == 0) {
+                appendRunStats(tsb);
+            }
+            if (traceToDerbyLog && logStream != null) {
+                logStream.printlnWithHeader(tsb.toString());
+            }
+            if (traceToStdOut) {
+                System.out.println(tsb.toString());
+            }
+        }
+    }
+
+    /**
+     * Appends runtime statistics to the given string buffer.
+     *
+     * @param sb the string buffer to append to
+     */
+    // @GuardedBy("queue")
+    private void appendRunStats(StringBuffer sb) {
+        // Print rather detailed and cryptic stats.
+        sb.append("[q/p/s=").append(queue.size()). // current queue size
+                append('/').append(wuProcessed). // scheduled
+                append('/').append(wuScheduled). //processed
+                append(",err:k/u/c=").
+                append(errorsKnown). // known errors
+                append('/').append(errorsUnknown). // unexpected errors
+                append('/').append(errorsConsecutive). // consecutive errors
+                append(",rej:f/d/o=").
+                append(wuRejectedFQ). // rejected, full queue
+                append('/').append(wuRejectedDup). // rejected, duplicate
+                append('/').append(wuRejectedOther). // rejected, other
+                append(']');
+    }
+
+    /**
+     * Produces a textual representation of the cardinality numbers.
+     *
+     * @param cardinality index cardinality
+     * @return A string.
+     */
+    private static String cardToStr(long[] cardinality) {
+        if (cardinality.length == 1) {
+            return "[" + Long.toString(cardinality[0]) + "]";
+        } else {
+            StringBuffer sb = new StringBuffer("[");
+            for (int i=0; i < cardinality.length; i++) {
+                sb.append(cardinality[i]).append(',');
+            }
+            sb.deleteCharAt(sb.length() -1).append(']');
+            return sb.toString();
+        }
+    }
+
+    /** Purely for debugging, to avoid printing too much info. */
+    private static String extractIstatInfo(StandardException se) {
+        StackTraceElement[] stack = se.getStackTrace();
+        String trace = "<n/a>";
+        for (int i=0; i < stack.length ; i++) {
+            StackTraceElement ste = stack[i];
+            if (IndexStatisticsDaemonImpl.class.getName().equals(
+                    ste.getClassName())) {
+                trace = ste.getMethodName() + "#" + ste.getLineNumber();
+                if (i > 0) {
+                    ste = stack[i -1];
+                    trace += " -> " + ste.getClassName() + "." +
+                            ste.getMethodName() + "#" + ste.getLineNumber();
+                }
+                break;
+            }
+        }
+        return trace + " got " + se.getSQLState() +
+                " (" +  se.getMessage() + ")";
+    }
+
+    /**
+     * Support class used to compare keys when scanning indexes.
+     */
+    //@NotThreadSafe
+    private static class KeyComparator {
+
+        /** Number of rows fetched per iteration. */
+        private static final int FETCH_SIZE = 16;
+        private final DataValueDescriptor[][] rowBufferArray;
+        private DataValueDescriptor[] lastUniqueKey;
+        private DataValueDescriptor[] curr;
+        private DataValueDescriptor[] prev;
+        private int rowsReadLastRead = -1;
+        private long numRows;
+
+        /**
+         * Creates a key comparator for the given index.
+         *
+         * @param ir index row (template)
+         */
+        public KeyComparator(ExecIndexRow ir) {
+            rowBufferArray = new DataValueDescriptor[FETCH_SIZE][];
+            rowBufferArray[0] = ir.getRowArray(); // 1 gets old objects.
+            lastUniqueKey = ir.getRowArrayClone();
+        }
+
+        /**
+         * Fetches rows from the scan controller.
+         *
+         * @param gsc the scan controller
+         * @return Number of rows fetched.
+         * @throws StandardException if fetching rows fails
+         */
+        public int fetchRows(GroupFetchScanController gsc)
+                throws StandardException {
+            // Save state (and optimize) for next read.
+            // Assumes that we always read as many rows as we can per iteration.
+            if (rowsReadLastRead == FETCH_SIZE) {
+                // Reuse curr to reference the current last unique key.
+                curr = rowBufferArray[FETCH_SIZE - 1];
+                // Reuse the old last unique row array for the coming fetch.
+                rowBufferArray[FETCH_SIZE - 1] = lastUniqueKey;
+                // Finally we update the pointer to the last unique key.
+                lastUniqueKey = curr;
+            }
+            rowsReadLastRead = gsc.fetchNextGroup(rowBufferArray, null);
+            return rowsReadLastRead;
+        }
+
+        /**
+         * Compares the key at the specified index with the previous key.
+         *
+         * @param index row index
+         * @return {@code -1} if the current and previous key are identical,
+         *      the index of the changed part of the key otherwise
+         *      ([0, key length>)
+         * @throws StandardException if comparing the two keys fails
+         */
+        public int compareWithPrevKey(int index)
+                throws StandardException {
+            if (index > rowsReadLastRead) {
+                throw new IllegalStateException(
+                        "invalid access, rowsReadLastRead=" + rowsReadLastRead +
+                        ", index=" + index + ", numRows=" + numRows);
+            }
+            numRows++;
+            // First row ever is always a distinct key.
+            if (numRows == 1) {
+                return 0;
+            }
+
+            prev = (index == 0) ? lastUniqueKey
+                                : rowBufferArray[index - 1];
+            curr = rowBufferArray[index];
+            DataValueDescriptor dvd;
+            // no point trying to do rowlocation; hence - 1
+            for (int i = 0; i < (prev.length - 1); i++) {
+                dvd = (DataValueDescriptor)prev[i];
+
+                // NULLs are counted as unique values.
+                if (dvd.isNull() || prev[i].compare(curr[i]) != 0) {
+                  return i;
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Returns the number of rows fetched.
+         *
+         * @return Number of rows fetched.
+         */
+        public long getRowCount() {
+            return numRows;
+        }
+    }
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=1044413&r1=1044412&r2=1044413&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java Fri Dec 10 16:04:04 2010
@@ -114,6 +114,7 @@ import org.apache.derby.iapi.services.mo
 import org.apache.derby.iapi.services.context.ContextManager;
 import org.apache.derby.iapi.services.context.ContextService;
 
+import org.apache.derby.iapi.db.Database;
 import org.apache.derby.iapi.error.StandardException;
 
 // RESOLVE - paulat - remove this import when track 3677 is fixed
@@ -134,6 +135,7 @@ import org.apache.derby.iapi.services.lo
 
 import org.apache.derby.iapi.services.property.PropertyUtil;
 
+import org.apache.derby.impl.services.daemon.IndexStatisticsDaemonImpl;
 import org.apache.derby.impl.services.locks.Timeout;
 
 import org.apache.derby.iapi.services.uuid.UUIDFactory;
@@ -144,6 +146,7 @@ import org.apache.derby.catalog.UUID;
 import org.apache.derby.catalog.types.BaseTypeIdImpl;
 import org.apache.derby.catalog.types.RoutineAliasInfo;
 
+import org.apache.derby.iapi.services.daemon.IndexStatisticsDaemon;
 import org.apache.derby.iapi.services.io.FormatableBitSet;
 import org.apache.derby.iapi.services.locks.CompatibilitySpace;
 import org.apache.derby.iapi.services.locks.ShExLockable;
@@ -366,6 +369,8 @@ public final class	DataDictionaryImpl
 
 	private	ExecutionFactory	exFactory;
 	protected UUIDFactory		uuidFactory;
+    /** Daemon creating and refreshing index cardinality statistics. */
+    private IndexStatisticsDaemon indexRefresher;
 
 	Properties			startupParameters;
 	int					engineType;
@@ -421,6 +426,18 @@ public final class	DataDictionaryImpl
 
 	*/
 	public boolean readOnlyUpgrade;
+    /**
+     * Tells if the automatic index statistics refresher has been disabled.
+     * <p>
+     * The refresher can be disabled explicitly by the user by setting a
+     * property (system wide or database property), or if the daemon encounters
+     * an exception it doesn't know how to recover from.
+     */
+    private boolean indexStatsUpdateDisabled;
+    private boolean indexStatsUpdateLogging;
+    /** TODO: Remove this when code goes into production (i.e. a release). */
+    private boolean indexStatsUpdateLoggingExplicitlySet;
+    private String indexStatsUpdateTracing;
 
 	//systemSQLNameNumber is the number used as the last digit during the previous call to getSystemSQLName.
 	//If it is 9 for a given calendarForLastSystemSQLName, we will restart the counter to 0
@@ -622,6 +639,28 @@ public final class	DataDictionaryImpl
 		permissionsCacheSize = PropertyUtil.intPropertyValue(Property.LANG_PERMISSIONS_CACHE_SIZE, value,
 									   0, Integer.MAX_VALUE, Property.LANG_PERMISSIONS_CACHE_SIZE_DEFAULT);
 
+        // See if automatic index statistics update is disabled through a
+        // system wide property. May be overridden by a database specific
+        // property later on.
+        // The default is that automatic index statistics update is enabled.
+        indexStatsUpdateDisabled = !PropertyUtil.getSystemBoolean(
+                // TODO: Disabled by default for now, fix in DERBY-4939.
+                //       Note that the daemon does nothing automatically
+                //       without DERBY-4938.
+                Property.STORAGE_AUTO_INDEX_STATS, false);
+                //Property.STORAGE_AUTO_INDEX_STATS, true);
+
+        // See if we should enable logging of index stats activities.
+        indexStatsUpdateLogging = PropertyUtil.getSystemBoolean(
+                Property.STORAGE_AUTO_INDEX_STATS_LOGGING);
+        // TODO: Remove this when going into production code (i.e. a release).
+        indexStatsUpdateLoggingExplicitlySet =
+                PropertyUtil.getSystemProperty(
+                    Property.STORAGE_AUTO_INDEX_STATS_LOGGING) != null;
+
+        // See if we should enable tracing of index stats activities.
+        indexStatsUpdateTracing = PropertyUtil.getSystemProperty(
+                Property.STORAGE_AUTO_INDEX_STATS_TRACING, "off");
 
 		/*
 		 * data dictionary contexts are only associated with connections.
@@ -781,6 +820,37 @@ public final class	DataDictionaryImpl
 				// Get the ids for non-core tables
 				loadDictionaryTables(bootingTC, ddg, startParams);
 
+                // See if index stats update is disabled by a database prop.
+                String dbIndexStatsUpdateAuto =
+                        PropertyUtil.getDatabaseProperty(bootingTC,
+                                    Property.STORAGE_AUTO_INDEX_STATS);
+                if (dbIndexStatsUpdateAuto != null) {
+                    indexStatsUpdateDisabled = !Boolean.valueOf(
+                            dbIndexStatsUpdateAuto).booleanValue();
+                }
+                String dbEnableIndexStatsLogging =
+                        PropertyUtil.getDatabaseProperty(bootingTC,
+                            Property.STORAGE_AUTO_INDEX_STATS_LOGGING);
+                if (dbEnableIndexStatsLogging != null) {
+                    indexStatsUpdateLogging = Boolean.valueOf(
+                            dbEnableIndexStatsLogging).booleanValue();
+                    indexStatsUpdateLoggingExplicitlySet = true;
+                }
+                // TODO: This property may go away in production code.
+                String dbEnableIndexStatsTracing =
+                        PropertyUtil.getDatabaseProperty(bootingTC,
+                            Property.STORAGE_AUTO_INDEX_STATS_TRACING);
+                if (dbEnableIndexStatsTracing != null) {
+                    if (!(dbEnableIndexStatsTracing.equalsIgnoreCase("off") ||
+                          dbEnableIndexStatsTracing.equalsIgnoreCase("log") ||
+                          dbEnableIndexStatsTracing.equalsIgnoreCase("stdout") ||
+                          dbEnableIndexStatsTracing.equalsIgnoreCase("both"))) {
+                        indexStatsUpdateTracing = "off";
+                    } else {
+                        indexStatsUpdateTracing = dbEnableIndexStatsTracing;
+                    }
+                }
+
 				String sqlAuth = PropertyUtil.getDatabaseProperty(bootingTC,
 										Property.SQL_AUTHORIZATION_PROPERTY);
 
@@ -901,9 +971,14 @@ public final class	DataDictionaryImpl
 	 * Stop this module.  In this case, nothing needs to be done.
 	 */
 
-	public void stop()
-	{
-	}
+    public void stop() {
+        // Shut down the index statistics refresher, mostly to make it print
+        // processing stats
+        if (indexRefresher != null) {
+            indexRefresher.stop();
+            indexRefresher = null;
+        }
+    }
 
 	/*
 	** CacheableFactory interface
@@ -13649,5 +13724,49 @@ public final class	DataDictionaryImpl
             ti.deleteRow(tc, uuidKey, rf.PERMS_UUID_IDX_NUM);
         }
     }
-}
 
+    /** {@inheritDoc} */
+    public IndexStatisticsDaemon getIndexStatsRefresher(boolean asDaemon) {
+        if (indexStatsUpdateDisabled && asDaemon) {
+            return null;
+        } else {
+            return indexRefresher;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void disableIndexStatsRefresher() {
+        indexStatsUpdateDisabled = true;
+        // NOTE: This will stop the automatic updates of index statistics,
+        //       but users can still do this explicitly (i.e. by invoking
+        //       the SYSCS_UTIL.SYSCS_UPDATE_STATISTICS system procedure).
+        // Set at boot time, we expect it to be non-null.
+        indexRefresher.stop();
+    }
+
+    /** {@inheritDoc} */
+    public boolean doCreateIndexStatsRefresher() {
+        return (indexRefresher == null);
+    }
+
+    /** {@inheritDoc} */
+    public void createIndexStatsRefresher(Database db, String dbName) {
+        // Check if the access factory is read-only.
+        if (af.isReadOnly()) {
+            indexStatsUpdateDisabled = true;
+            return;
+        }
+
+        // TODO: Remove this override after initial testing.
+        //       Unless logging has been explicitly disabled, turn it on to
+        //       make sure we have some information if things go wrong.
+        if (!indexStatsUpdateLoggingExplicitlySet) {
+            indexStatsUpdateLogging = true;
+        }
+
+        indexRefresher = new IndexStatisticsDaemonImpl(
+                   Monitor.getStream(), indexStatsUpdateLogging,
+                   indexStatsUpdateTracing, db, authorizationDatabaseOwner,
+                   dbName);
+    }
+}

Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=1044413&r1=1044412&r2=1044413&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java (original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java Fri Dec 10 16:04:04 2010
@@ -25,8 +25,10 @@ import java.util.List;
 import java.util.Properties;
 
 import org.apache.derby.catalog.UUID;
+import org.apache.derby.iapi.db.Database;
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.reference.EngineType;
+import org.apache.derby.iapi.services.daemon.IndexStatisticsDaemon;
 import org.apache.derby.iapi.services.monitor.ModuleSupportable;
 import org.apache.derby.iapi.services.monitor.Monitor;
 import org.apache.derby.iapi.services.uuid.UUIDFactory;
@@ -881,4 +883,20 @@ public class EmptyDictionary implements 
             throws StandardException {
         // TODO Auto-generated method stub
     }
+
+    public IndexStatisticsDaemon getIndexStatsRefresher(boolean asDaemon) {
+        return null;
+    }
+
+    public void disableIndexStatsRefresher() {
+        // Do nothing here...
+    }
+
+    public boolean doCreateIndexStatsRefresher() {
+        return false;
+    }
+
+    public void createIndexStatsRefresher(Database db, String dbName) {
+        // Do nothing
+    }
 }



Mime
View raw message