Blame SOURCES/0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch

91ce38
From c79630de8012a893ed3d1c46b41bc7871a07a3e2 Mon Sep 17 00:00:00 2001
91ce38
From: Mark Reynolds <mreynolds@redhat.com>
91ce38
Date: Wed, 26 May 2021 13:32:13 -0400
91ce38
Subject: [PATCH 11/12] Issue 4778 - RFE - Allow setting TOD for db compaction
91ce38
 and add task
91ce38
91ce38
Description:  Since database compaction can be costly it should be allowed
91ce38
              to set a time to execute it during offpeak hours.  Once the
91ce38
              compaction interval has been met, it will wait for the configured
91ce38
              time of day to do the compaction.  The default is just before
91ce38
              midnight: 23:59
91ce38
91ce38
              A task was also created that can run compaction on demand,
91ce38
              and can also just target the replication changelog.  This could
91ce38
              be used in conjunction with a cronjob for more complex
91ce38
              execution patterns.
91ce38
91ce38
ASAN tested and approved.
91ce38
91ce38
relates: https://github.com/389ds/389-ds-base/issues/4778
91ce38
91ce38
Reviewed by: spichugi(Thanks!)
91ce38
---
91ce38
 .../tests/suites/config/compact_test.py       |  81 ++++++
91ce38
 ldap/schema/01core389.ldif                    |   3 +-
91ce38
 ldap/servers/plugins/replication/cl5.h        |   1 +
91ce38
 ldap/servers/plugins/replication/cl5_api.c    |  70 ++++-
91ce38
 ldap/servers/plugins/replication/cl5_api.h    |   2 +-
91ce38
 .../servers/plugins/replication/cl5_clcache.c |   3 -
91ce38
 ldap/servers/plugins/replication/cl5_config.c | 102 ++++++-
91ce38
 ldap/servers/plugins/replication/cl5_init.c   |   2 +-
91ce38
 .../servers/plugins/replication/repl_shared.h |   2 +
91ce38
 ldap/servers/plugins/retrocl/retrocl.c        |   1 -
91ce38
 .../slapd/back-ldbm/db-bdb/bdb_config.c       |  79 ++++++
91ce38
 .../slapd/back-ldbm/db-bdb/bdb_layer.c        | 258 ++++++++++++------
91ce38
 .../slapd/back-ldbm/db-bdb/bdb_layer.h        |   4 +-
91ce38
 ldap/servers/slapd/back-ldbm/init.c           |   2 +
91ce38
 ldap/servers/slapd/back-ldbm/ldbm_config.h    |   1 +
91ce38
 .../servers/slapd/back-ldbm/proto-back-ldbm.h |   1 +
91ce38
 ldap/servers/slapd/filtercmp.c                |   5 +-
91ce38
 ldap/servers/slapd/pblock.c                   |  17 +-
91ce38
 ldap/servers/slapd/slap.h                     |   2 +
91ce38
 ldap/servers/slapd/slapi-private.h            |   1 +
91ce38
 ldap/servers/slapd/task.c                     | 102 ++++++-
91ce38
 src/cockpit/389-console/src/database.jsx      |   1 +
91ce38
 .../src/lib/database/databaseConfig.jsx       |  16 +-
91ce38
 src/lib389/lib389/_constants.py               |   1 +
91ce38
 src/lib389/lib389/backend.py                  |   1 +
91ce38
 src/lib389/lib389/cli_conf/backend.py         |  24 +-
91ce38
 src/lib389/lib389/cli_conf/replication.py     |   3 +
91ce38
 src/lib389/lib389/tasks.py                    |  14 +-
91ce38
 28 files changed, 689 insertions(+), 110 deletions(-)
91ce38
 create mode 100644 dirsrvtests/tests/suites/config/compact_test.py
91ce38
91ce38
diff --git a/dirsrvtests/tests/suites/config/compact_test.py b/dirsrvtests/tests/suites/config/compact_test.py
91ce38
new file mode 100644
91ce38
index 000000000..1f1c097e4
91ce38
--- /dev/null
91ce38
+++ b/dirsrvtests/tests/suites/config/compact_test.py
91ce38
@@ -0,0 +1,81 @@
91ce38
+import logging
91ce38
+import pytest
91ce38
+import os
91ce38
+import time
91ce38
+from lib389.tasks import DBCompactTask
91ce38
+from lib389.backend import DatabaseConfig
91ce38
+from lib389.replica import Changelog5
91ce38
+from lib389.topologies import topology_m1 as topo
91ce38
+
91ce38
+log = logging.getLogger(__name__)
91ce38
+
91ce38
+
91ce38
+def test_compact_db_task(topo):
91ce38
+    """Specify a test case purpose or name here
91ce38
+
91ce38
+    :id: 1b3222ef-a336-4259-be21-6a52f76e1859
91ce38
+    :setup: Standalone Instance
91ce38
+    :steps:
91ce38
+        1. Create task
91ce38
+        2. Check task was successful
91ce38
+        3. Check errors log to show task was run
91ce38
+        3. Create task just for replication
91ce38
+    :expectedresults:
91ce38
+        1. Success
91ce38
+        2. Success
91ce38
+        3. Success
91ce38
+        4. Success
91ce38
+    """
91ce38
+    inst = topo.ms["supplier1"]
91ce38
+
91ce38
+    task = DBCompactTask(inst)
91ce38
+    task.create()
91ce38
+    task.wait()
91ce38
+    assert task.get_exit_code() == 0
91ce38
+
91ce38
+    # Check errors log to make sure task actually compacted db
91ce38
+    assert inst.searchErrorsLog("Compacting databases")
91ce38
+    inst.deleteErrorLogs(restart=False)
91ce38
+
91ce38
+
91ce38
+def test_compaction_interval_and_time(topo):
91ce38
+    """Specify a test case purpose or name here
91ce38
+
91ce38
+    :id: f361bee9-d7e7-4569-9255-d7b60dd9d92e
91ce38
+    :setup: Supplier Instance
91ce38
+    :steps:
91ce38
+        1. Configure compact interval and time for database and changelog
91ce38
+        2. Check compaction occurs as expected
91ce38
+    :expectedresults:
91ce38
+        1. Success
91ce38
+        2. Success
91ce38
+    """
91ce38
+
91ce38
+    inst = topo.ms["supplier1"]
91ce38
+
91ce38
+    # Configure DB compaction
91ce38
+    config = DatabaseConfig(inst)
91ce38
+    config.set([('nsslapd-db-compactdb-interval', '2'), ('nsslapd-db-compactdb-time', '00:01')])
91ce38
+
91ce38
+    # Configure changelog compaction
91ce38
+    cl5 = Changelog5(inst)
91ce38
+    cl5.replace_many(
91ce38
+        ('nsslapd-changelogcompactdb-interval', '2'),
91ce38
+        ('nsslapd-changelogcompactdb-time', '00:01'),
91ce38
+        ('nsslapd-changelogtrim-interval',  '2')
91ce38
+    )
91ce38
+    inst.deleteErrorLogs()
91ce38
+
91ce38
+    # Check is compaction occurred
91ce38
+    time.sleep(6)
91ce38
+    assert inst.searchErrorsLog("Compacting databases")
91ce38
+    assert inst.searchErrorsLog("compacting replication changelogs")
91ce38
+    inst.deleteErrorLogs(restart=False)
91ce38
+
91ce38
+
91ce38
+if __name__ == '__main__':
91ce38
+    # Run isolated
91ce38
+    # -s for DEBUG mode
91ce38
+    CURRENT_FILE = os.path.realpath(__file__)
91ce38
+    pytest.main(["-s", CURRENT_FILE])
91ce38
+
91ce38
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
91ce38
index 9e9a26c21..0c73e5114 100644
91ce38
--- a/ldap/schema/01core389.ldif
91ce38
+++ b/ldap/schema/01core389.ldif
91ce38
@@ -285,6 +285,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2310 NAME 'nsds5ReplicaFlowControlWindow
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2311 NAME 'nsds5ReplicaFlowControlPause' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2313 NAME 'nsslapd-changelogtrim-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2314 NAME 'nsslapd-changelogcompactdb-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
+attributeTypes: ( 2.16.840.1.113730.3.1.2385 NAME 'nsslapd-changelogcompactdb-time' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2315 NAME 'nsDS5ReplicaWaitForAsyncResults' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2316 NAME 'nsslapd-auditfaillog-maxlogsize' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
 attributeTypes: ( 2.16.840.1.113730.3.1.2317 NAME 'nsslapd-auditfaillog-logrotationsync-enabled' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
91ce38
@@ -345,5 +346,5 @@ objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape
91ce38
 objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsSSLToken $ nsSSLPersonalityssl $ nsSSLActivation $ ServerKeyExtractFile $ ServerCertExtractFile ) X-ORIGIN 'Netscape' )
91ce38
 objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( rootdn-open-time $ rootdn-close-time $ rootdn-days-allowed $ rootdn-allow-host $ rootdn-deny-host $ rootdn-allow-ip $ rootdn-deny-ip ) X-ORIGIN 'Netscape' )
91ce38
 objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top  MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' )
91ce38
-objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' )
91ce38
+objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-changelogcompactdb-time $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' )
91ce38
 objectClasses: ( 2.16.840.1.113730.3.2.337 NAME 'rewriterEntry' DESC '' SUP top MUST ( nsslapd-libPath ) MAY ( cn $ nsslapd-filterrewriter $ nsslapd-returnedAttrRewriter ) X-ORIGIN '389 Directory Server' )
91ce38
diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h
91ce38
index 2af57e369..99ea1c6a2 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5.h
91ce38
+++ b/ldap/servers/plugins/replication/cl5.h
91ce38
@@ -29,6 +29,7 @@ typedef struct changelog5Config
91ce38
     char *symmetricKey;
91ce38
     long compactInterval;
91ce38
     long trimInterval;
91ce38
+    char *compactTime;
91ce38
 } changelog5Config;
91ce38
 
91ce38
 /* initializes changelog*/
91ce38
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
91ce38
index 403a6a666..75a2f46f5 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5_api.c
91ce38
+++ b/ldap/servers/plugins/replication/cl5_api.c
91ce38
@@ -158,6 +158,7 @@ typedef struct cl5trim
91ce38
     time_t maxAge;       /* maximum entry age in seconds                            */
91ce38
     int maxEntries;      /* maximum number of entries across all changelog files    */
91ce38
     int compactInterval; /* interval to compact changelog db */
91ce38
+    char *compactTime;   /* time to compact changelog db */
91ce38
     int trimInterval;    /* trimming interval */
91ce38
     PRLock *lock;        /* controls access to trimming configuration            */
91ce38
 } CL5Trim;
91ce38
@@ -184,6 +185,7 @@ typedef struct cl5desc
91ce38
     PRLock *clLock;         /* Lock associated to clVar, used to notify threads on close */
91ce38
     PRCondVar *clCvar;      /* Condition Variable used to notify threads on close */
91ce38
     void *clcrypt_handle;   /* for cl encryption */
91ce38
+    char *compact_time;     /* Time to execute changelog compaction */
91ce38
 } CL5Desc;
91ce38
 
91ce38
 typedef void (*VFP)(void *);
91ce38
@@ -1025,7 +1027,7 @@ cl5GetState()
91ce38
                 CL5_BAD_STATE if changelog is not open
91ce38
  */
91ce38
 int
91ce38
-cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval)
91ce38
+cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval)
91ce38
 {
91ce38
     if (s_cl5Desc.dbState == CL5_STATE_NONE) {
91ce38
         slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
91ce38
@@ -1061,6 +1063,10 @@ cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int t
91ce38
         s_cl5Desc.dbTrim.compactInterval = compactInterval;
91ce38
     }
91ce38
 
91ce38
+    if (strcmp(compactTime, CL5_STR_IGNORE) != 0) {
91ce38
+        s_cl5Desc.dbTrim.compactTime = slapi_ch_strdup(compactTime);
91ce38
+    }
91ce38
+
91ce38
     if (trimInterval != CL5_NUM_IGNORE) {
91ce38
         s_cl5Desc.dbTrim.trimInterval = trimInterval;
91ce38
     }
91ce38
@@ -3077,16 +3083,48 @@ _cl5TrimCleanup(void)
91ce38
 {
91ce38
     if (s_cl5Desc.dbTrim.lock)
91ce38
         PR_DestroyLock(s_cl5Desc.dbTrim.lock);
91ce38
+    slapi_ch_free_string(&s_cl5Desc.dbTrim.compactTime);
91ce38
 
91ce38
     memset(&s_cl5Desc.dbTrim, 0, sizeof(s_cl5Desc.dbTrim));
91ce38
 }
91ce38
 
91ce38
+static time_t
91ce38
+_cl5_get_tod_expiration(char *expire_time)
91ce38
+{
91ce38
+    time_t start_time, todays_elapsed_time, now = time(NULL);
91ce38
+    struct tm *tm_struct = localtime(&now;;
91ce38
+    char hour_str[3] = {0};
91ce38
+    char min_str[3] = {0};
91ce38
+    char *s = expire_time;
91ce38
+    char *endp = NULL;
91ce38
+    int32_t hour, min, expiring_time;
91ce38
+
91ce38
+    /* Get today's start time */
91ce38
+    todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec);
91ce38
+    start_time = slapi_current_utc_time() - todays_elapsed_time;
91ce38
+
91ce38
+    /* Get the hour and minute and calculate the expiring time.  The time was
91ce38
+     * already validated in bdb_config.c:  HH:MM */
91ce38
+    hour_str[0] = *s++;
91ce38
+    hour_str[1] = *s++;
91ce38
+    s++;  /* skip colon */
91ce38
+    min_str[0] = *s++;
91ce38
+    min_str[1] = *s++;
91ce38
+    hour = strtoll(hour_str, &endp, 10);
91ce38
+    min = strtoll(min_str, &endp, 10);
91ce38
+    expiring_time = (hour * 60 * 60) + (min * 60);
91ce38
+
91ce38
+    return start_time + expiring_time;
91ce38
+}
91ce38
+
91ce38
 static int
91ce38
 _cl5TrimMain(void *param __attribute__((unused)))
91ce38
 {
91ce38
     time_t timePrev = slapi_current_utc_time();
91ce38
     time_t timeCompactPrev = slapi_current_utc_time();
91ce38
     time_t timeNow;
91ce38
+    PRBool compacting = PR_FALSE;
91ce38
+    int32_t compactdb_time = 0;
91ce38
 
91ce38
     PR_AtomicIncrement(&s_cl5Desc.threadCount);
91ce38
 
91ce38
@@ -3097,11 +3135,26 @@ _cl5TrimMain(void *param __attribute__((unused)))
91ce38
             timePrev = timeNow;
91ce38
             _cl5DoTrimming();
91ce38
         }
91ce38
+
91ce38
+        if (!compacting) {
91ce38
+            /* Once we know we want to compact we need to stop refreshing the
91ce38
+             * TOD expiration. Otherwise if the compact time is close to
91ce38
+             * midnight we could roll over past midnight during the checkpoint
91ce38
+             * sleep interval, and we'd never actually compact the databases.
91ce38
+             * We also need to get this value before the sleep.
91ce38
+            */
91ce38
+            compactdb_time = _cl5_get_tod_expiration(s_cl5Desc.dbTrim.compactTime);
91ce38
+        }
91ce38
         if ((s_cl5Desc.dbTrim.compactInterval > 0) &&
91ce38
-            (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval)) {
91ce38
-            /* time to trim */
91ce38
-            timeCompactPrev = timeNow;
91ce38
-            _cl5CompactDBs();
91ce38
+            (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval))
91ce38
+        {
91ce38
+            compacting = PR_TRUE;
91ce38
+            if (slapi_current_utc_time() > compactdb_time) {
91ce38
+				/* time to trim */
91ce38
+				timeCompactPrev = timeNow;
91ce38
+				_cl5CompactDBs();
91ce38
+				compacting = PR_FALSE;
91ce38
+            }
91ce38
         }
91ce38
         if (NULL == s_cl5Desc.clLock) {
91ce38
             /* most likely, emergency */
91ce38
@@ -3215,6 +3268,10 @@ _cl5CompactDBs(void)
91ce38
                       rc, db_strerror(rc));
91ce38
         goto bail;
91ce38
     }
91ce38
+
91ce38
+
91ce38
+    slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl,
91ce38
+                  "_cl5CompactDBs - compacting replication changelogs...\n");
91ce38
     for (fileObj = objset_first_obj(s_cl5Desc.dbFiles);
91ce38
          fileObj;
91ce38
          fileObj = objset_next_obj(s_cl5Desc.dbFiles, fileObj)) {
91ce38
@@ -3235,6 +3292,9 @@ _cl5CompactDBs(void)
91ce38
                       "_cl5CompactDBs - %s - %d pages freed\n",
91ce38
                       dbFile->replName, c_data.compact_pages_free);
91ce38
     }
91ce38
+
91ce38
+    slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl,
91ce38
+                  "_cl5CompactDBs - compacting replication changelogs finished.\n");
91ce38
 bail:
91ce38
     if (fileObj) {
91ce38
         object_release(fileObj);
91ce38
diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h
91ce38
index 302af97a0..4b0949fb3 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5_api.h
91ce38
+++ b/ldap/servers/plugins/replication/cl5_api.h
91ce38
@@ -236,7 +236,7 @@ int cl5GetState(void);
91ce38
    Return:        CL5_SUCCESS if successful;
91ce38
                 CL5_BAD_STATE if changelog has not been open
91ce38
  */
91ce38
-int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval);
91ce38
+int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval);
91ce38
 
91ce38
 void cl5DestroyIterator(void *iterator);
91ce38
 
91ce38
diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c
91ce38
index 90dec4d54..e5a39c9c1 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5_clcache.c
91ce38
+++ b/ldap/servers/plugins/replication/cl5_clcache.c
91ce38
@@ -452,9 +452,6 @@ static int
91ce38
 clcache_cursor_set(DBC *cursor, CLC_Buffer *buf)
91ce38
 {
91ce38
     int rc;
91ce38
-    uint32_t ulen;
91ce38
-    uint32_t dlen;
91ce38
-    uint32_t size;
91ce38
 
91ce38
     rc = cursor->c_get(cursor, &buf->buf_key, &buf->buf_data, DB_SET);
91ce38
     if (rc == DB_BUFFER_SMALL) {
91ce38
diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c
91ce38
index e0530bed2..b32686788 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5_config.c
91ce38
+++ b/ldap/servers/plugins/replication/cl5_config.c
91ce38
@@ -131,6 +131,7 @@ changelog5_config_done(changelog5Config *config)
91ce38
         /* slapi_ch_free_string accepts NULL pointer */
91ce38
         slapi_ch_free_string(&config->maxAge);
91ce38
         slapi_ch_free_string(&config->dir);
91ce38
+        slapi_ch_free_string(&config->compactTime);
91ce38
         slapi_ch_free_string(&config->symmetricKey);
91ce38
         slapi_ch_free_string(&config->dbconfig.encryptionAlgorithm);
91ce38
         slapi_ch_free_string(&config->dbconfig.symmetricKey);
91ce38
@@ -211,7 +212,7 @@ changelog5_config_add(Slapi_PBlock *pb __attribute__((unused)),
91ce38
     }
91ce38
 
91ce38
     /* set trimming parameters */
91ce38
-    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
91ce38
+    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
91ce38
     if (rc != CL5_SUCCESS) {
91ce38
         *returncode = 1;
91ce38
         if (returntext) {
91ce38
@@ -302,6 +303,7 @@ changelog5_config_modify(Slapi_PBlock *pb,
91ce38
     config.compactInterval = CL5_NUM_IGNORE;
91ce38
     slapi_ch_free_string(&config.maxAge);
91ce38
     config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE);
91ce38
+    config.compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME);
91ce38
     config.trimInterval = CL5_NUM_IGNORE;
91ce38
 
91ce38
     slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods;;
91ce38
@@ -375,6 +377,55 @@ changelog5_config_modify(Slapi_PBlock *pb,
91ce38
                         *returncode = LDAP_UNWILLING_TO_PERFORM;
91ce38
                         goto done;
91ce38
                     }
91ce38
+                } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE) == 0) {
91ce38
+                	if (config_attr_value && config_attr_value[0] != '\0') {
91ce38
+                	    char *val = slapi_ch_strdup(config_attr_value);
91ce38
+                        char *endp = NULL;
91ce38
+                        char *hour_str = NULL;
91ce38
+                        char *min_str = NULL;
91ce38
+                        int32_t hour, min;
91ce38
+                        errno = 0;
91ce38
+
91ce38
+                        slapi_ch_free_string(&config.compactTime);
91ce38
+
91ce38
+                      	if (strstr(val, ":")) {
91ce38
+                            /* Get the hour and minute */
91ce38
+                            hour_str = ldap_utf8strtok_r(val, ":", &min_str);
91ce38
+                  	        /* Validate hour */
91ce38
+                   	        hour = strtoll(hour_str, &endp, 10);
91ce38
+                 	        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
91ce38
+          	       	            slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                   	                    "Invalid hour set (%s), must be a two digit number between 00 and 23",
91ce38
+                   	                    hour_str);
91ce38
+                   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+                                       "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+                	       	                    "Using default of 23:59\n", hour_str);
91ce38
+                                *returncode = LDAP_UNWILLING_TO_PERFORM;
91ce38
+                   	            goto done;
91ce38
+           	       	        }
91ce38
+       	        	        /* Validate minute */
91ce38
+           	       	        min = strtoll(min_str, &endp, 10);
91ce38
+           	      	        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
91ce38
+                   	            slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                  	                    "Invalid minute set (%s), must be a two digit number between 00 and 59",
91ce38
+                   	                    hour_str);
91ce38
+                   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+                   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+                   	                    "Using default of 23:59\n", min_str);
91ce38
+                                *returncode = LDAP_UNWILLING_TO_PERFORM;
91ce38
+                   	            goto done;
91ce38
+                   	        }
91ce38
+                   	    } else {
91ce38
+                   	        /* Wrong format */
91ce38
+                   	        slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                   	                "Invalid setting (%s), must have a time format of HH:MM", val);
91ce38
+                   	        slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+                   	                "Invalid setting (%s), must have a time format of HH:MM\n", val);
91ce38
+                            *returncode = LDAP_UNWILLING_TO_PERFORM;
91ce38
+                   	        goto done;
91ce38
+                   	    }
91ce38
+                        config.compactTime = slapi_ch_strdup(config_attr_value);
91ce38
+                    }
91ce38
                 } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_TRIM_ATTRIBUTE) == 0) {
91ce38
                     if (slapi_is_duration_valid(config_attr_value)) {
91ce38
                         config.trimInterval = (long)slapi_parse_duration(config_attr_value);
91ce38
@@ -419,6 +470,11 @@ changelog5_config_modify(Slapi_PBlock *pb,
91ce38
         if (originalConfig->maxAge)
91ce38
             config.maxAge = slapi_ch_strdup(originalConfig->maxAge);
91ce38
     }
91ce38
+    if (strcmp(config.compactTime, CL5_STR_IGNORE) == 0) {
91ce38
+        slapi_ch_free_string(&config.compactTime);
91ce38
+        if (originalConfig->compactTime)
91ce38
+            config.compactTime = slapi_ch_strdup(originalConfig->compactTime);
91ce38
+    }
91ce38
 
91ce38
     /* attempt to change chagelog dir */
91ce38
     if (config.dir) {
91ce38
@@ -519,7 +575,7 @@ changelog5_config_modify(Slapi_PBlock *pb,
91ce38
     if (config.maxEntries != CL5_NUM_IGNORE ||
91ce38
         config.trimInterval != CL5_NUM_IGNORE ||
91ce38
         strcmp(config.maxAge, CL5_STR_IGNORE) != 0) {
91ce38
-        rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
91ce38
+        rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
91ce38
         if (rc != CL5_SUCCESS) {
91ce38
             *returncode = 1;
91ce38
             if (returntext) {
91ce38
@@ -689,6 +745,7 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config)
91ce38
 {
91ce38
     const char *arg;
91ce38
     char *max_age = NULL;
91ce38
+    char *val = NULL;
91ce38
 
91ce38
     memset(config, 0, sizeof(*config));
91ce38
     config->dir = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DIR_ATTRIBUTE);
91ce38
@@ -711,6 +768,47 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config)
91ce38
         config->compactInterval = CHANGELOGDB_COMPACT_INTERVAL;
91ce38
     }
91ce38
 
91ce38
+    arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE);
91ce38
+    if (arg) {
91ce38
+        char *endp = NULL;
91ce38
+        char *hour_str = NULL;
91ce38
+        char *min_str = NULL;
91ce38
+        int32_t hour, min;
91ce38
+        errno = 0;
91ce38
+
91ce38
+        val = slapi_ch_strdup((char *)arg);
91ce38
+    	if (strstr(val, ":")) {
91ce38
+            /* Get the hour and minute */
91ce38
+            hour_str = ldap_utf8strtok_r(val, ":", &min_str);
91ce38
+    	        /* Validate hour */
91ce38
+   	        hour = strtoll(hour_str, &endp, 10);
91ce38
+   	        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
91ce38
+   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+   	                    "Using default of 23:59\n", hour_str);
91ce38
+   	            goto set_default;
91ce38
+   	        }
91ce38
+    	        /* Validate minute */
91ce38
+   	        min = strtoll(min_str, &endp, 10);
91ce38
+  	        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
91ce38
+   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+   	                    "Using default of 23:59\n", min_str);
91ce38
+   	            goto set_default;
91ce38
+   	        }
91ce38
+   	    } else {
91ce38
+   	        /* Wrong format */
91ce38
+   	        slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
91ce38
+   	                "Invalid setting (%s), must have a time format of HH:MM\n", val);
91ce38
+   	        goto set_default;
91ce38
+   	    }
91ce38
+        config->compactTime = slapi_ch_strdup(arg);
91ce38
+    } else {
91ce38
+    	set_default:
91ce38
+        config->compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME);
91ce38
+    }
91ce38
+    slapi_ch_free_string(&val;;
91ce38
+
91ce38
     arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_TRIM_ATTRIBUTE);
91ce38
     if (arg) {
91ce38
         if (slapi_is_duration_valid(arg)) {
91ce38
diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c
91ce38
index 112c4ece4..251859714 100644
91ce38
--- a/ldap/servers/plugins/replication/cl5_init.c
91ce38
+++ b/ldap/servers/plugins/replication/cl5_init.c
91ce38
@@ -57,7 +57,7 @@ changelog5_init()
91ce38
     }
91ce38
 
91ce38
     /* set trimming parameters */
91ce38
-    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
91ce38
+    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
91ce38
     if (rc != CL5_SUCCESS) {
91ce38
         slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
91ce38
                       "changelog5_init: failed to configure changelog trimming\n");
91ce38
diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h
91ce38
index b1ed86934..6708e12f7 100644
91ce38
--- a/ldap/servers/plugins/replication/repl_shared.h
91ce38
+++ b/ldap/servers/plugins/replication/repl_shared.h
91ce38
@@ -26,11 +26,13 @@
91ce38
 
91ce38
 #define CHANGELOGDB_TRIM_INTERVAL 300        /* 5 minutes */
91ce38
 #define CHANGELOGDB_COMPACT_INTERVAL 2592000 /* 30 days */
91ce38
+#define CHANGELOGDB_COMPACT_TIME "23:55" /* 30 days */
91ce38
 
91ce38
 #define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir"
91ce38
 #define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries"
91ce38
 #define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage"
91ce38
 #define CONFIG_CHANGELOG_COMPACTDB_ATTRIBUTE "nsslapd-changelogcompactdb-interval"
91ce38
+#define CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE "nsslapd-changelogcompactdb-time"
91ce38
 #define CONFIG_CHANGELOG_TRIM_ATTRIBUTE "nsslapd-changelogtrim-interval"
91ce38
 /* Changelog Internal Configuration Parameters -> Changelog Cache related */
91ce38
 #define CONFIG_CHANGELOG_ENCRYPTION_ALGORITHM "nsslapd-encryptionalgorithm"
91ce38
diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c
91ce38
index 2a620301c..f73c81528 100644
91ce38
--- a/ldap/servers/plugins/retrocl/retrocl.c
91ce38
+++ b/ldap/servers/plugins/retrocl/retrocl.c
91ce38
@@ -400,7 +400,6 @@ retrocl_start(Slapi_PBlock *pb)
91ce38
 
91ce38
         for (size_t i = 0; i < num_vals; i++) {
91ce38
             char *value = values[i];
91ce38
-            size_t length = strlen(value);
91ce38
 
91ce38
             char *pos = strchr(value, ':');
91ce38
             if (pos == NULL) {
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
91ce38
index 167644943..4261c6ce2 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
91ce38
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
91ce38
@@ -678,6 +678,84 @@ bdb_config_db_compactdb_interval_set(void *arg,
91ce38
     return retval;
91ce38
 }
91ce38
 
91ce38
+static void *
91ce38
+bdb_config_db_compactdb_time_get(void *arg)
91ce38
+{
91ce38
+    struct ldbminfo *li = (struct ldbminfo *)arg;
91ce38
+    return (void *)slapi_ch_strdup(BDB_CONFIG(li)->bdb_compactdb_time);
91ce38
+}
91ce38
+
91ce38
+static int
91ce38
+bdb_config_db_compactdb_time_set(void *arg,
91ce38
+                                 void *value,
91ce38
+                                 char *errorbuf __attribute__((unused)),
91ce38
+                                 int phase __attribute__((unused)),
91ce38
+                                 int apply)
91ce38
+{
91ce38
+    struct ldbminfo *li = (struct ldbminfo *)arg;
91ce38
+    char *val = slapi_ch_strdup((char *)value);
91ce38
+    char *endp = NULL;
91ce38
+    char *hour_str = NULL;
91ce38
+    char *min_str = NULL;
91ce38
+    char *default_time = "23:59";
91ce38
+    int32_t hour, min;
91ce38
+    int retval = LDAP_SUCCESS;
91ce38
+    errno = 0;
91ce38
+
91ce38
+    if (strstr(val, ":")) {
91ce38
+        /* Get the hour and minute */
91ce38
+        hour_str = ldap_utf8strtok_r(val, ":", &min_str);
91ce38
+
91ce38
+        /* Validate hour */
91ce38
+        hour = strtoll(hour_str, &endp, 10);
91ce38
+        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
91ce38
+            slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                    "Invalid hour set (%s), must be a two digit number between 00 and 23",
91ce38
+                    hour_str);
91ce38
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
91ce38
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+                    "Using default of 23:59\n", hour_str);
91ce38
+            retval = LDAP_OPERATIONS_ERROR;
91ce38
+            goto done;
91ce38
+        }
91ce38
+
91ce38
+        /* Validate minute */
91ce38
+        min = strtoll(min_str, &endp, 10);
91ce38
+        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
91ce38
+            slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59",
91ce38
+                    hour_str);
91ce38
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
91ce38
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
91ce38
+                    "Using default of 23:59\n", min_str);
91ce38
+            retval = LDAP_OPERATIONS_ERROR;
91ce38
+            goto done;
91ce38
+        }
91ce38
+    } else {
91ce38
+        /* Wrong format */
91ce38
+        slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
91ce38
+                "Invalid setting (%s), must have a time format of HH:MM", val);
91ce38
+        slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
91ce38
+                "Invalid setting (%s), must have a time format of HH:MM\n", val);
91ce38
+        retval = LDAP_OPERATIONS_ERROR;
91ce38
+        goto done;
91ce38
+    }
91ce38
+
91ce38
+done:
91ce38
+    if (apply) {
91ce38
+        slapi_ch_free((void **)&(BDB_CONFIG(li)->bdb_compactdb_time));
91ce38
+        if (retval) {
91ce38
+            /* Something went wrong, use the default */
91ce38
+            BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup(default_time);
91ce38
+        } else {
91ce38
+            BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup((char *)value);
91ce38
+        }
91ce38
+    }
91ce38
+    slapi_ch_free_string(&val;;
91ce38
+
91ce38
+    return retval;
91ce38
+}
91ce38
+
91ce38
 static void *
91ce38
 bdb_config_db_page_size_get(void *arg)
91ce38
 {
91ce38
@@ -1473,6 +1551,7 @@ static config_info bdb_config_param[] = {
91ce38
     {CONFIG_DB_TRANSACTION_WAIT, CONFIG_TYPE_ONOFF, "off", &bdb_config_db_transaction_wait_get, &bdb_config_db_transaction_wait_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
     {CONFIG_DB_CHECKPOINT_INTERVAL, CONFIG_TYPE_INT, "60", &bdb_config_db_checkpoint_interval_get, &bdb_config_db_checkpoint_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
     {CONFIG_DB_COMPACTDB_INTERVAL, CONFIG_TYPE_INT, "2592000" /*30days*/, &bdb_config_db_compactdb_interval_get, &bdb_config_db_compactdb_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
+    {CONFIG_DB_COMPACTDB_TIME, CONFIG_TYPE_STRING, "23:59", &bdb_config_db_compactdb_time_get, &bdb_config_db_compactdb_time_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
     {CONFIG_DB_TRANSACTION_BATCH, CONFIG_TYPE_INT, "0", &bdb_get_batch_transactions, &bdb_set_batch_transactions, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
     {CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_min_sleep, &bdb_set_batch_txn_min_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
     {CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_max_sleep, &bdb_set_batch_txn_max_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
91ce38
index 2f25f67a2..ec1976d38 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
91ce38
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
91ce38
@@ -2126,6 +2126,7 @@ bdb_post_close(struct ldbminfo *li, int dbmode)
91ce38
          */
91ce38
         slapi_ch_free_string(&conf->bdb_dbhome_directory);
91ce38
         slapi_ch_free_string(&conf->bdb_home_directory);
91ce38
+        slapi_ch_free_string(&conf->bdb_compactdb_time);
91ce38
     }
91ce38
 
91ce38
     return return_value;
91ce38
@@ -3644,6 +3645,39 @@ log_flush_threadmain(void *param)
91ce38
     return 0;
91ce38
 }
91ce38
 
91ce38
+/*
91ce38
+ * This refreshes the TOD expiration.  So live changes to the configuration
91ce38
+ * will take effect immediately.
91ce38
+ */
91ce38
+static time_t
91ce38
+bdb_get_tod_expiration(char *expire_time)
91ce38
+{
91ce38
+    time_t start_time, todays_elapsed_time, now = time(NULL);
91ce38
+    struct tm *tm_struct = localtime(&now;;
91ce38
+    char hour_str[3] = {0};
91ce38
+    char min_str[3] = {0};
91ce38
+    char *s = expire_time;
91ce38
+    char *endp = NULL;
91ce38
+    int32_t hour, min, expiring_time;
91ce38
+
91ce38
+    /* Get today's start time */
91ce38
+    todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec);
91ce38
+    start_time = slapi_current_utc_time() - todays_elapsed_time;
91ce38
+
91ce38
+    /* Get the hour and minute and calculate the expiring time.  The time was
91ce38
+     * already validated in bdb_config.c:  HH:MM */
91ce38
+    hour_str[0] = *s++;
91ce38
+    hour_str[1] = *s++;
91ce38
+    s++;  /* skip colon */
91ce38
+    min_str[0] = *s++;
91ce38
+    min_str[1] = *s++;
91ce38
+    hour = strtoll(hour_str, &endp, 10);
91ce38
+    min = strtoll(min_str, &endp, 10);
91ce38
+    expiring_time = (hour * 60 * 60) + (min * 60);
91ce38
+
91ce38
+    return start_time + expiring_time;
91ce38
+}
91ce38
+
91ce38
 /*
91ce38
  * create a thread for checkpoint_threadmain
91ce38
  */
91ce38
@@ -3685,7 +3719,9 @@ checkpoint_threadmain(void *param)
91ce38
     time_t checkpoint_interval_update = 0;
91ce38
     time_t compactdb_interval = 0;
91ce38
     time_t checkpoint_interval = 0;
91ce38
-    back_txn txn;
91ce38
+    int32_t compactdb_time = 0;
91ce38
+    PRBool compacting = PR_FALSE;
91ce38
+
91ce38
 
91ce38
     PR_ASSERT(NULL != param);
91ce38
     li = (struct ldbminfo *)param;
91ce38
@@ -3724,22 +3760,35 @@ checkpoint_threadmain(void *param)
91ce38
     slapi_timespec_expire_at(checkpoint_interval, &checkpoint_expire);
91ce38
 
91ce38
     while (!BDB_CONFIG(li)->bdb_stop_threads) {
91ce38
-        /* sleep for a while */
91ce38
-        /* why aren't we sleeping exactly the right amount of time ? */
91ce38
-        /* answer---because the interval might be changed after the server
91ce38
-         * starts up */
91ce38
+        PR_Lock(li->li_config_mutex);
91ce38
+        checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval;
91ce38
+        compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval;
91ce38
+        if (!compacting) {
91ce38
+            /* Once we know we want to compact we need to stop refreshing the
91ce38
+             * TOD expiration. Otherwise if the compact time is close to
91ce38
+             * midnight we could roll over past midnight during the checkpoint
91ce38
+             * sleep interval, and we'd never actually compact the databases.
91ce38
+             * We also need to get this value before the sleep.
91ce38
+             */
91ce38
+            compactdb_time = bdb_get_tod_expiration((char *)BDB_CONFIG(li)->bdb_compactdb_time);
91ce38
+        }
91ce38
+        PR_Unlock(li->li_config_mutex);
91ce38
+
91ce38
+        if (compactdb_interval_update != compactdb_interval) {
91ce38
+            /* Compact interval was changed, so reset the timer */
91ce38
+            slapi_timespec_expire_at(compactdb_interval_update, &compactdb_expire);
91ce38
+        }
91ce38
 
91ce38
+        /* Sleep for a while ...
91ce38
+         * Why aren't we sleeping exactly the right amount of time ?
91ce38
+         * Answer---because the interval might be changed after the server
91ce38
+         * starts up */
91ce38
         DS_Sleep(interval);
91ce38
 
91ce38
         if (0 == BDB_CONFIG(li)->bdb_enable_transactions) {
91ce38
             continue;
91ce38
         }
91ce38
 
91ce38
-        PR_Lock(li->li_config_mutex);
91ce38
-        checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval;
91ce38
-        compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval;
91ce38
-        PR_Unlock(li->li_config_mutex);
91ce38
-
91ce38
         /* If the checkpoint has been updated OR we have expired */
91ce38
         if (checkpoint_interval != checkpoint_interval_update ||
91ce38
             slapi_timespec_expire_check(&checkpoint_expire) == TIMER_EXPIRED) {
91ce38
@@ -3807,94 +3856,37 @@ checkpoint_threadmain(void *param)
91ce38
 
91ce38
         /*
91ce38
          * Remember that if compactdb_interval is 0, timer_expired can
91ce38
-         * never occur unless the value in compctdb_interval changes.
91ce38
+         * never occur unless the value in compactdb_interval changes.
91ce38
          *
91ce38
-         * this could have been a bug infact, where compactdb_interval
91ce38
+         * this could have been a bug in fact, where compactdb_interval
91ce38
          * was 0, if you change while running it would never take effect ....
91ce38
          */
91ce38
-        if (compactdb_interval_update != compactdb_interval ||
91ce38
-            slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) {
91ce38
-            int rc = 0;
91ce38
-            Object *inst_obj;
91ce38
-            ldbm_instance *inst;
91ce38
-            DB *db = NULL;
91ce38
-            DB_COMPACT c_data = {0};
91ce38
-
91ce38
-            for (inst_obj = objset_first_obj(li->li_instance_set);
91ce38
-                 inst_obj;
91ce38
-                 inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) {
91ce38
-                inst = (ldbm_instance *)object_get_data(inst_obj);
91ce38
-                rc = dblayer_get_id2entry(inst->inst_be, &db);
91ce38
-                if (!db || rc) {
91ce38
-                    continue;
91ce38
-                }
91ce38
-                slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain", "Compacting DB start: %s\n",
91ce38
-                              inst->inst_name);
91ce38
-
91ce38
-                /*
91ce38
-                 * It's possible for this to heap us after free because when we access db
91ce38
-                 * *just* as the server shut's down, we don't know it. So we should probably
91ce38
-                 * do something like wrapping access to the db var in a rwlock, and have "read"
91ce38
-                 * to access, and take writes to change the state. This would prevent the issue.
91ce38
-                 */
91ce38
-                DBTYPE type;
91ce38
-                rc = db->get_type(db, &type);
91ce38
-                if (rc) {
91ce38
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain",
91ce38
-                                  "compactdb: failed to determine db type for %s: db error - %d %s\n",
91ce38
-                                  inst->inst_name, rc, db_strerror(rc));
91ce38
-                    continue;
91ce38
-                }
91ce38
+        if (slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) {
91ce38
+            compacting = PR_TRUE;
91ce38
+            if (slapi_current_utc_time() < compactdb_time) {
91ce38
+                /* We have passed the interval, but we need to wait for a
91ce38
+                 * particular TOD to pass before compacting */
91ce38
+                continue;
91ce38
+            }
91ce38
 
91ce38
-                rc = dblayer_txn_begin(inst->inst_be, NULL, &txn);
91ce38
-                if (rc) {
91ce38
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: transaction begin failed: %d\n", rc);
91ce38
-                    break;
91ce38
-                }
91ce38
-                /*
91ce38
-                 * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf
91ce38
-                 * "DB_FREELIST_ONLY
91ce38
-                 * Do no page compaction, only returning pages to the filesystem that are already free and at the end
91ce38
-                 * of the file. This flag must be set if the database is a Hash access method database."
91ce38
-                 *
91ce38
-                 */
91ce38
+            /* Time to compact the DB's */
91ce38
+            dblayer_force_checkpoint(li);
91ce38
+            bdb_compact(li);
91ce38
+            dblayer_force_checkpoint(li);
91ce38
 
91ce38
-                uint32_t compact_flags = DB_FREE_SPACE;
91ce38
-                if (type == DB_HASH) {
91ce38
-                    compact_flags |= DB_FREELIST_ONLY;
91ce38
-                }
91ce38
-                rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/,
91ce38
-                                 &c_data, compact_flags, NULL /*end*/);
91ce38
-                if (rc) {
91ce38
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain",
91ce38
-                                  "compactdb: failed to compact %s; db error - %d %s\n",
91ce38
-                                  inst->inst_name, rc, db_strerror(rc));
91ce38
-                    if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) {
91ce38
-                        slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to abort txn (%s) db error - %d %s\n",
91ce38
-                                      inst->inst_name, rc, db_strerror(rc));
91ce38
-                        break;
91ce38
-                    }
91ce38
-                } else {
91ce38
-                    slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain",
91ce38
-                                  "compactdb: compact %s - %d pages freed\n",
91ce38
-                                  inst->inst_name, c_data.compact_pages_free);
91ce38
-                    if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) {
91ce38
-                        slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to commit txn (%s) db error - %d %s\n",
91ce38
-                                      inst->inst_name, rc, db_strerror(rc));
91ce38
-                        break;
91ce38
-                    }
91ce38
-                }
91ce38
-            }
91ce38
+            /* Now reset the timer and compacting flag */
91ce38
             compactdb_interval = compactdb_interval_update;
91ce38
             slapi_timespec_expire_at(compactdb_interval, &compactdb_expire);
91ce38
+            compacting = PR_FALSE;
91ce38
         }
91ce38
     }
91ce38
-    slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Check point before leaving\n");
91ce38
+    slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Check point before leaving\n");
91ce38
     rval = dblayer_force_checkpoint(li);
91ce38
+
91ce38
 error_return:
91ce38
 
91ce38
     DECR_THREAD_COUNT(pEnv);
91ce38
-    slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n");
91ce38
+    slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n");
91ce38
     return rval;
91ce38
 }
91ce38
 
91ce38
@@ -6209,3 +6201,99 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info)
91ce38
 
91ce38
     return rc;
91ce38
 }
91ce38
+
91ce38
+int32_t
91ce38
+ldbm_back_compact(Slapi_Backend *be)
91ce38
+{
91ce38
+    struct ldbminfo *li = NULL;
91ce38
+    int32_t rc = -1;
91ce38
+
91ce38
+    li = (struct ldbminfo *)be->be_database->plg_private;
91ce38
+    dblayer_force_checkpoint(li);
91ce38
+    rc = bdb_compact(li);
91ce38
+    dblayer_force_checkpoint(li);
91ce38
+    return rc;
91ce38
+}
91ce38
+
91ce38
+
91ce38
+int32_t
91ce38
+bdb_compact(struct ldbminfo *li)
91ce38
+{
91ce38
+    Object *inst_obj;
91ce38
+    ldbm_instance *inst;
91ce38
+    DB *db = NULL;
91ce38
+    back_txn txn = {0};
91ce38
+    int rc = 0;
91ce38
+    DB_COMPACT c_data = {0};
91ce38
+
91ce38
+    slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact",
91ce38
+                  "Compacting databases ...\n");
91ce38
+    for (inst_obj = objset_first_obj(li->li_instance_set);
91ce38
+        inst_obj;
91ce38
+        inst_obj = objset_next_obj(li->li_instance_set, inst_obj))
91ce38
+    {
91ce38
+        inst = (ldbm_instance *)object_get_data(inst_obj);
91ce38
+        rc = dblayer_get_id2entry(inst->inst_be, &db);
91ce38
+        if (!db || rc) {
91ce38
+            continue;
91ce38
+        }
91ce38
+        slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting DB start: %s\n",
91ce38
+                      inst->inst_name);
91ce38
+
91ce38
+        /*
91ce38
+         * It's possible for this to heap us after free because when we access db
91ce38
+         * *just* as the server shut's down, we don't know it. So we should probably
91ce38
+         * do something like wrapping access to the db var in a rwlock, and have "read"
91ce38
+         * to access, and take writes to change the state. This would prevent the issue.
91ce38
+         */
91ce38
+        DBTYPE type;
91ce38
+        rc = db->get_type(db, &type);
91ce38
+        if (rc) {
91ce38
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact",
91ce38
+                          "compactdb: failed to determine db type for %s: db error - %d %s\n",
91ce38
+                          inst->inst_name, rc, db_strerror(rc));
91ce38
+            continue;
91ce38
+        }
91ce38
+
91ce38
+        rc = dblayer_txn_begin(inst->inst_be, NULL, &txn);
91ce38
+        if (rc) {
91ce38
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: transaction begin failed: %d\n", rc);
91ce38
+            break;
91ce38
+        }
91ce38
+        /*
91ce38
+         * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf
91ce38
+         * "DB_FREELIST_ONLY
91ce38
+         * Do no page compaction, only returning pages to the filesystem that are already free and at the end
91ce38
+         * of the file. This flag must be set if the database is a Hash access method database."
91ce38
+         *
91ce38
+         */
91ce38
+        uint32_t compact_flags = DB_FREE_SPACE;
91ce38
+        if (type == DB_HASH) {
91ce38
+            compact_flags |= DB_FREELIST_ONLY;
91ce38
+        }
91ce38
+        rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/,
91ce38
+                         &c_data, compact_flags, NULL /*end*/);
91ce38
+        if (rc) {
91ce38
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact",
91ce38
+                    "compactdb: failed to compact %s; db error - %d %s\n",
91ce38
+                    inst->inst_name, rc, db_strerror(rc));
91ce38
+            if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) {
91ce38
+                slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to abort txn (%s) db error - %d %s\n",
91ce38
+                              inst->inst_name, rc, db_strerror(rc));
91ce38
+                break;
91ce38
+            }
91ce38
+        } else {
91ce38
+            slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact",
91ce38
+                          "compactdb: compact %s - %d pages freed\n",
91ce38
+                          inst->inst_name, c_data.compact_pages_free);
91ce38
+            if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) {
91ce38
+                slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to commit txn (%s) db error - %d %s\n",
91ce38
+                              inst->inst_name, rc, db_strerror(rc));
91ce38
+                break;
91ce38
+            }
91ce38
+        }
91ce38
+    }
91ce38
+    slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting databases finished.\n");
91ce38
+
91ce38
+    return rc;
91ce38
+}
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
91ce38
index 6bb04d21a..e3a49dbac 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
91ce38
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
91ce38
@@ -79,7 +79,8 @@ typedef struct bdb_config
91ce38
     int bdb_previous_lock_config;  /* Max lock count when we last shut down--
91ce38
                                       * used to determine if we delete the mpool */
91ce38
     u_int32_t bdb_deadlock_policy; /* i.e. the atype to DB_ENV->lock_detect in deadlock_threadmain */
91ce38
-    int bdb_compactdb_interval;    /* interval to execute compact id2entry dbs */
91ce38
+    int32_t bdb_compactdb_interval; /* interval to execute compact id2entry dbs */
91ce38
+    char *bdb_compactdb_time;       /* time of day to execute compact id2entry dbs */
91ce38
 } bdb_config;
91ce38
 
91ce38
 int bdb_init(struct ldbminfo *li, config_info *config_array);
91ce38
@@ -96,6 +97,7 @@ int bdb_db_size(Slapi_PBlock *pb);
91ce38
 int bdb_upgradedb(Slapi_PBlock *pb);
91ce38
 int bdb_upgradednformat(Slapi_PBlock *pb);
91ce38
 int bdb_upgradeddformat(Slapi_PBlock *pb);
91ce38
+int32_t bdb_compact(struct ldbminfo *li);
91ce38
 int bdb_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task);
91ce38
 int bdb_cleanup(struct ldbminfo *li);
91ce38
 int bdb_txn_begin(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool use_lock);
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/init.c b/ldap/servers/slapd/back-ldbm/init.c
91ce38
index 4165c8fad..42c9bd00a 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/init.c
91ce38
+++ b/ldap/servers/slapd/back-ldbm/init.c
91ce38
@@ -180,6 +180,8 @@ ldbm_back_init(Slapi_PBlock *pb)
91ce38
                            (void *)ldbm_back_set_info);
91ce38
     rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_CTRL_INFO_FN,
91ce38
                            (void *)ldbm_back_ctrl_info);
91ce38
+    rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_COMPACT_FN,
91ce38
+                           (void *)ldbm_back_compact);
91ce38
 
91ce38
     if (rc != 0) {
91ce38
         slapi_log_err(SLAPI_LOG_CRIT, "ldbm_back_init", "Failed %d\n", rc);
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
91ce38
index 6fa8292eb..48446193e 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
91ce38
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
91ce38
@@ -84,6 +84,7 @@ struct config_info
91ce38
 #define CONFIG_DB_TRANSACTION_WAIT "nsslapd-db-transaction-wait"
91ce38
 #define CONFIG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval"
91ce38
 #define CONFIG_DB_COMPACTDB_INTERVAL "nsslapd-db-compactdb-interval"
91ce38
+#define CONFIG_DB_COMPACTDB_TIME "nsslapd-db-compactdb-time"
91ce38
 #define CONFIG_DB_TRANSACTION_BATCH "nsslapd-db-transaction-batch-val"
91ce38
 #define CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP "nsslapd-db-transaction-batch-min-wait"
91ce38
 #define CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP "nsslapd-db-transaction-batch-max-wait"
91ce38
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
91ce38
index 5d618a89c..30c9003bf 100644
91ce38
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
91ce38
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
91ce38
@@ -478,6 +478,7 @@ void ldbm_back_search_results_release(void **search_results);
91ce38
 int ldbm_back_init(Slapi_PBlock *pb);
91ce38
 void ldbm_back_prev_search_results(Slapi_PBlock *pb);
91ce38
 int ldbm_back_isinitialized(void);
91ce38
+int32_t ldbm_back_compact(Slapi_Backend *be);
91ce38
 
91ce38
 /*
91ce38
  * vlv.c
91ce38
diff --git a/ldap/servers/slapd/filtercmp.c b/ldap/servers/slapd/filtercmp.c
91ce38
index f7e3ed4d5..c886267bd 100644
91ce38
--- a/ldap/servers/slapd/filtercmp.c
91ce38
+++ b/ldap/servers/slapd/filtercmp.c
91ce38
@@ -344,7 +344,6 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2)
91ce38
     struct berval *inval1[2], *inval2[2], **outval1, **outval2;
91ce38
     int ret;
91ce38
     Slapi_Attr sattr;
91ce38
-    int cmplen;
91ce38
 
91ce38
     slapi_log_err(SLAPI_LOG_TRACE, "slapi_filter_compare", "=>\n");
91ce38
 
91ce38
@@ -379,11 +378,11 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2)
91ce38
         if (key1 && key2) {
91ce38
             struct berval bvkey1 = {
91ce38
                 slapi_value_get_length(key1[0]),
91ce38
-                slapi_value_get_string(key1[0])
91ce38
+				(char *)slapi_value_get_string(key1[0])
91ce38
             };
91ce38
             struct berval bvkey2 = {
91ce38
                 slapi_value_get_length(key2[0]),
91ce38
-                slapi_value_get_string(key2[0])
91ce38
+				(char *)slapi_value_get_string(key2[0])
91ce38
             };
91ce38
             ret = slapi_berval_cmp(&bvkey1, &bvkey2);
91ce38
         }
91ce38
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
91ce38
index f7d1f8885..fcac53839 100644
91ce38
--- a/ldap/servers/slapd/pblock.c
91ce38
+++ b/ldap/servers/slapd/pblock.c
91ce38
@@ -925,6 +925,12 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
91ce38
         }
91ce38
         (*(IFP *)value) = pblock->pb_plugin->plg_db2ldif;
91ce38
         break;
91ce38
+    case SLAPI_PLUGIN_DB_COMPACT_FN:
91ce38
+        if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
91ce38
+            return (-1);
91ce38
+        }
91ce38
+        (*(IFP *)value) = pblock->pb_plugin->plg_dbcompact;
91ce38
+        break;
91ce38
     case SLAPI_PLUGIN_DB_DB2INDEX_FN:
91ce38
         if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
91ce38
             return (-1);
91ce38
@@ -2925,7 +2931,12 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
91ce38
         }
91ce38
         pblock->pb_backend->be_noacl = *((int *)value);
91ce38
         break;
91ce38
-
91ce38
+    case SLAPI_PLUGIN_DB_COMPACT_FN:
91ce38
+        if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
91ce38
+            return (-1);
91ce38
+        }
91ce38
+        pblock->pb_plugin->plg_dbcompact = (IFP)value;
91ce38
+        break;
91ce38
 
91ce38
     /* extendedop plugin functions */
91ce38
     case SLAPI_PLUGIN_EXT_OP_FN:
91ce38
@@ -4137,8 +4148,8 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
91ce38
         break;
91ce38
 
91ce38
     case SLAPI_URP_TOMBSTONE_CONFLICT_DN:
91ce38
-	pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value;
91ce38
-	break;
91ce38
+        pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value;
91ce38
+        break;
91ce38
 
91ce38
     case SLAPI_URP_TOMBSTONE_UNIQUEID:
91ce38
         _pblock_assert_pb_intop(pblock);
91ce38
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
91ce38
index 3126a65f3..c48516157 100644
91ce38
--- a/ldap/servers/slapd/slap.h
91ce38
+++ b/ldap/servers/slapd/slap.h
91ce38
@@ -1041,6 +1041,7 @@ struct slapdplugin
91ce38
             IFP plg_un_db_ldif2db;              /* ldif 2 database */
91ce38
             IFP plg_un_db_db2ldif;              /* database 2 ldif */
91ce38
             IFP plg_un_db_db2index;             /* database 2 index */
91ce38
+            IFP plg_un_db_dbcompact;            /* compact database */
91ce38
             IFP plg_un_db_archive2db;           /* ldif 2 database */
91ce38
             IFP plg_un_db_db2archive;           /* database 2 ldif */
91ce38
             IFP plg_un_db_upgradedb;            /* convert old idl to new */
91ce38
@@ -1082,6 +1083,7 @@ struct slapdplugin
91ce38
 #define plg_result plg_un.plg_un_db.plg_un_db_result
91ce38
 #define plg_ldif2db plg_un.plg_un_db.plg_un_db_ldif2db
91ce38
 #define plg_db2ldif plg_un.plg_un_db.plg_un_db_db2ldif
91ce38
+#define plg_dbcompact plg_un.plg_un_db.plg_un_db_dbcompact
91ce38
 #define plg_db2index plg_un.plg_un_db.plg_un_db_db2index
91ce38
 #define plg_archive2db plg_un.plg_un_db.plg_un_db_archive2db
91ce38
 #define plg_db2archive plg_un.plg_un_db.plg_un_db_db2archive
91ce38
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
91ce38
index b956ebe63..570765e47 100644
91ce38
--- a/ldap/servers/slapd/slapi-private.h
91ce38
+++ b/ldap/servers/slapd/slapi-private.h
91ce38
@@ -928,6 +928,7 @@ int proxyauth_get_dn(Slapi_PBlock *pb, char **proxydnp, char **errtextp);
91ce38
 #define SLAPI_PLUGIN_DB_GET_INFO_FN               290
91ce38
 #define SLAPI_PLUGIN_DB_SET_INFO_FN               291
91ce38
 #define SLAPI_PLUGIN_DB_CTRL_INFO_FN              292
91ce38
+#define SLAPI_PLUGIN_DB_COMPACT_FN                294
91ce38
 
91ce38
 /**** End of database plugin interface. **************************************/
91ce38
 
91ce38
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
91ce38
index 93d31b806..4c7262ab3 100644
91ce38
--- a/ldap/servers/slapd/task.c
91ce38
+++ b/ldap/servers/slapd/task.c
91ce38
@@ -1,6 +1,6 @@
91ce38
 /** BEGIN COPYRIGHT BLOCK
91ce38
  * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
91ce38
- * Copyright (C) 2005 Red Hat, Inc.
91ce38
+ * Copyright (C) 2021 Red Hat, Inc.
91ce38
  * All rights reserved.
91ce38
  *
91ce38
  * License: GPL (version 3 or any later version).
91ce38
@@ -2928,6 +2928,105 @@ des2aes_task_destructor(Slapi_Task *task)
91ce38
                   "des2aes_task_destructor <--\n");
91ce38
 }
91ce38
 
91ce38
+struct task_compact_data
91ce38
+{
91ce38
+    char *suffix;
91ce38
+    Slapi_Task *task;
91ce38
+};
91ce38
+
91ce38
+static void
91ce38
+compact_db_task_destructor(Slapi_Task *task)
91ce38
+{
91ce38
+    slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task",
91ce38
+                  "compact_db_task_destructor -->\n");
91ce38
+    if (task) {
91ce38
+        struct task_compact_data *mydata = (struct task_compact_data *)slapi_task_get_data(task);
91ce38
+        while (slapi_task_get_refcount(task) > 0) {
91ce38
+            /* Yield to wait for the task to finish */
91ce38
+            DS_Sleep(PR_MillisecondsToInterval(100));
91ce38
+        }
91ce38
+        if (mydata) {
91ce38
+            slapi_ch_free((void **)&mydata);
91ce38
+        }
91ce38
+    }
91ce38
+    slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task",
91ce38
+                  "compact_db_task_destructor <--\n");
91ce38
+}
91ce38
+
91ce38
+static void
91ce38
+task_compact_thread(void *arg)
91ce38
+{
91ce38
+    struct task_compact_data *task_data = arg;
91ce38
+    Slapi_Task *task = task_data->task;
91ce38
+    Slapi_Backend *be = NULL;
91ce38
+    char *cookie = NULL;
91ce38
+    int32_t rc = -1;
91ce38
+
91ce38
+    slapi_task_inc_refcount(task);
91ce38
+    slapi_task_begin(task, 1);
91ce38
+
91ce38
+    be = slapi_get_first_backend(&cookie);
91ce38
+    while (be) {
91ce38
+        if (be->be_private == 0) {
91ce38
+            /* Found a non-private backend, start compacting */
91ce38
+            rc = (be->be_database->plg_dbcompact)(be);
91ce38
+            break;
91ce38
+        }
91ce38
+        be = (backend *)slapi_get_next_backend(cookie);
91ce38
+    }
91ce38
+    slapi_ch_free_string(&cookie);
91ce38
+
91ce38
+    slapi_task_finish(task, rc);
91ce38
+    slapi_task_dec_refcount(task);
91ce38
+}
91ce38
+
91ce38
+/*
91ce38
+ * compact the BDB database
91ce38
+ *
91ce38
+ *  dn: cn=compact_it,cn=compact db,cn=tasks,cn=config
91ce38
+ *  objectclass: top
91ce38
+ *  objectclass: extensibleObject
91ce38
+ *  cn: compact_it
91ce38
+ */
91ce38
+static int
91ce38
+task_compact_db_add(Slapi_PBlock *pb,
91ce38
+                    Slapi_Entry *e,
91ce38
+                    Slapi_Entry *eAfter __attribute__((unused)),
91ce38
+                    int *returncode,
91ce38
+                    char *returntext,
91ce38
+                    void *arg __attribute__((unused)))
91ce38
+{
91ce38
+    Slapi_Task *task = slapi_new_task(slapi_entry_get_ndn(e));
91ce38
+    struct task_compact_data *task_data = NULL;
91ce38
+    PRThread *thread = NULL;
91ce38
+
91ce38
+    slapi_task_log_notice(task, "Beginning database compaction task...\n");
91ce38
+
91ce38
+    /* Register our destructor for cleaning up our private data */
91ce38
+    slapi_task_set_destructor_fn(task, compact_db_task_destructor);
91ce38
+
91ce38
+    task_data = (struct task_compact_data *)slapi_ch_calloc(1, sizeof(struct task_compact_data));
91ce38
+    task_data->task = task;
91ce38
+    slapi_task_set_data(task, task_data);
91ce38
+
91ce38
+    /* Start the compaction as a separate thread */
91ce38
+    thread = PR_CreateThread(PR_USER_THREAD, task_compact_thread,
91ce38
+             (void *)task_data, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
91ce38
+             PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
91ce38
+    if (thread == NULL) {
91ce38
+        slapi_log_err(SLAPI_LOG_ERR, "task_compact_db_add", "Unable to create db compact thread!\n");
91ce38
+        *returncode = LDAP_OPERATIONS_ERROR;
91ce38
+        slapi_ch_free((void **)&task_data);
91ce38
+    }
91ce38
+
91ce38
+    if (*returncode != LDAP_SUCCESS) {
91ce38
+        slapi_task_finish(task, *returncode);
91ce38
+        return SLAPI_DSE_CALLBACK_ERROR;
91ce38
+    }
91ce38
+
91ce38
+    return SLAPI_DSE_CALLBACK_OK;
91ce38
+}
91ce38
+
91ce38
 /* cleanup old tasks that may still be in the DSE from a previous session
91ce38
  * (this can happen if the server crashes [no matter how unlikely we like
91ce38
  * to think that is].)
91ce38
@@ -3010,6 +3109,7 @@ task_init(void)
91ce38
     slapi_task_register_handler("sysconfig reload", task_sysconfig_reload_add);
91ce38
     slapi_task_register_handler("fixup tombstones", task_fixup_tombstones_add);
91ce38
     slapi_task_register_handler("des2aes", task_des2aes);
91ce38
+    slapi_task_register_handler("compact db", task_compact_db_add);
91ce38
 }
91ce38
 
91ce38
 /* called when the server is shutting down -- abort all existing tasks */
91ce38
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
91ce38
index 11cae972c..b73dc8460 100644
91ce38
--- a/src/cockpit/389-console/src/database.jsx
91ce38
+++ b/src/cockpit/389-console/src/database.jsx
91ce38
@@ -196,6 +196,7 @@ export class Database extends React.Component {
91ce38
                                     dblocksMonitoringPause: attrs['nsslapd-db-locks-monitoring-pause'],
91ce38
                                     chxpoint: attrs['nsslapd-db-checkpoint-interval'],
91ce38
                                     compactinterval: attrs['nsslapd-db-compactdb-interval'],
91ce38
+                                    compacttime: attrs['nsslapd-db-compactdb-time'],
91ce38
                                     importcacheauto: attrs['nsslapd-import-cache-autosize'],
91ce38
                                     importcachesize: attrs['nsslapd-import-cachesize'],
91ce38
                                 },
91ce38
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
91ce38
index 6a71c138d..1fa9f2cc2 100644
91ce38
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
91ce38
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
91ce38
@@ -36,6 +36,7 @@ export class GlobalDatabaseConfig extends React.Component {
91ce38
             dblocksMonitoringPause: this.props.data.dblocksMonitoringPause,
91ce38
             chxpoint: this.props.data.chxpoint,
91ce38
             compactinterval: this.props.data.compactinterval,
91ce38
+            compacttime: this.props.data.compacttime,
91ce38
             importcachesize: this.props.data.importcachesize,
91ce38
             importcacheauto: this.props.data.importcacheauto,
91ce38
             // These variables store the original value (used for saving config)
91ce38
@@ -55,6 +56,7 @@ export class GlobalDatabaseConfig extends React.Component {
91ce38
             _dblocksMonitoringPause: this.props.data.dblocksMonitoringPause,
91ce38
             _chxpoint: this.props.data.chxpoint,
91ce38
             _compactinterval: this.props.data.compactinterval,
91ce38
+            _compacttime: this.props.data.compacttime,
91ce38
             _importcachesize: this.props.data.importcachesize,
91ce38
             _importcacheauto: this.props.data.importcacheauto,
91ce38
             _db_cache_auto: this.props.data.db_cache_auto,
91ce38
@@ -186,6 +188,10 @@ export class GlobalDatabaseConfig extends React.Component {
91ce38
             cmd.push("--compactdb-interval=" + this.state.compactinterval);
91ce38
             requireRestart = true;
91ce38
         }
91ce38
+        if (this.state._compacttime != this.state.compacttime) {
91ce38
+            cmd.push("--compactdb-time=" + this.state.compacttime);
91ce38
+            requireRestart = true;
91ce38
+        }
91ce38
         if (this.state.import_cache_auto) {
91ce38
             // Auto cache is selected
91ce38
             if (this.state._import_cache_auto != this.state.import_cache_auto) {
91ce38
@@ -485,7 +491,15 @@ export class GlobalDatabaseConfig extends React.Component {
91ce38
                                             Database Compact Interval
91ce38
                                         
91ce38
                                         
91ce38
-                                            <input id="compactinterval" value={this.state.compactinterval} onChange={this.handleChange} className="ds-input-auto" type="text" />
91ce38
+                                            <input id="compactinterval" value={this.state.compactinterval} onChange={this.handleChange} className="ds-input-auto" type="number" />
91ce38
+                                        
91ce38
+                                    </Row>
91ce38
+                                    <Row className="ds-margin-top" title="The Time Of Day to perform the database compaction after the compact interval has been met.  Uses the format: 'HH:MM' and defaults to '23:59'. (nsslapd-db-compactdb-time)">
91ce38
+                                        
91ce38
+                                            Database Compact Time
91ce38
+                                        
91ce38
+                                        
91ce38
+                                            <input id="compacttime" value={this.state.compacttime} onChange={this.handleChange} className="ds-input-auto" type="number" />
91ce38
                                         
91ce38
                                     </Row>
91ce38
                                     <Row className="ds-margin-top" title="The number of database locks (nsslapd-db-locks).">
91ce38
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
91ce38
index c184c8d4f..d6161cebb 100644
91ce38
--- a/src/lib389/lib389/_constants.py
91ce38
+++ b/src/lib389/lib389/_constants.py
91ce38
@@ -154,6 +154,7 @@ DN_EUUID_TASK = "cn=entryuuid task,%s" % DN_TASKS
91ce38
 DN_TOMB_FIXUP_TASK = "cn=fixup tombstones,%s" % DN_TASKS
91ce38
 DN_FIXUP_LINKED_ATTIBUTES = "cn=fixup linked attributes,%s" % DN_TASKS
91ce38
 DN_AUTOMEMBER_REBUILD_TASK = "cn=automember rebuild membership,%s" % DN_TASKS
91ce38
+DN_COMPACTDB_TASK = "cn=compact db,%s" % DN_TASKS
91ce38
 
91ce38
 # Script Constants
91ce38
 LDIF2DB = 'ldif2db'
91ce38
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
91ce38
index 13bb27842..ad78a6ffe 100644
91ce38
--- a/src/lib389/lib389/backend.py
91ce38
+++ b/src/lib389/lib389/backend.py
91ce38
@@ -1005,6 +1005,7 @@ class DatabaseConfig(DSLdapObject):
91ce38
                     'nsslapd-db-transaction-wait',
91ce38
                     'nsslapd-db-checkpoint-interval',
91ce38
                     'nsslapd-db-compactdb-interval',
91ce38
+                    'nsslapd-db-compactdb-time',
91ce38
                     'nsslapd-db-page-size',
91ce38
                     'nsslapd-db-transaction-batch-val',
91ce38
                     'nsslapd-db-transaction-batch-min-wait',
91ce38
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
91ce38
index 722764d10..7b2f32c23 100644
91ce38
--- a/src/lib389/lib389/cli_conf/backend.py
91ce38
+++ b/src/lib389/lib389/cli_conf/backend.py
91ce38
@@ -1,5 +1,5 @@
91ce38
 # --- BEGIN COPYRIGHT BLOCK ---
91ce38
-# Copyright (C) 2020 Red Hat, Inc.
91ce38
+# Copyright (C) 2021 Red Hat, Inc.
91ce38
 # Copyright (C) 2019 William Brown <william@blackhats.net.au>
91ce38
 # All rights reserved.
91ce38
 #
91ce38
@@ -19,6 +19,7 @@ from lib389.chaining import (ChainingLinks)
91ce38
 from lib389.monitor import MonitorLDBM
91ce38
 from lib389.replica import Replicas
91ce38
 from lib389.utils import ensure_str, is_a_dn, is_dn_parent
91ce38
+from lib389.tasks import DBCompactTask
91ce38
 from lib389._constants import *
91ce38
 from lib389.cli_base import (
91ce38
     _format_status,
91ce38
@@ -41,6 +42,7 @@ arg_to_attr = {
91ce38
         'txn_wait': 'nsslapd-db-transaction-wait',
91ce38
         'checkpoint_interval': 'nsslapd-db-checkpoint-interval',
91ce38
         'compactdb_interval': 'nsslapd-db-compactdb-interval',
91ce38
+        'compactdb_time': 'nsslapd-db-compactdb-time',
91ce38
         'txn_batch_val': 'nsslapd-db-transaction-batch-val',
91ce38
         'txn_batch_min': 'nsslapd-db-transaction-batch-min-wait',
91ce38
         'txn_batch_max': 'nsslapd-db-transaction-batch-max-wait',
91ce38
@@ -789,6 +791,18 @@ def backend_reindex_vlv(inst, basedn, log, args):
91ce38
     log.info("Successfully reindexed VLV indexes")
91ce38
 
91ce38
 
91ce38
+def backend_compact(inst, basedn, log, args):
91ce38
+    task = DBCompactTask(inst)
91ce38
+    task_properties = {}
91ce38
+    if args.only_changelog:
91ce38
+        task_properties = {'justChangelog': 'yes'}
91ce38
+    task.create(properties=task_properties)
91ce38
+    task.wait()
91ce38
+    if task.get_exit_code() != 0:
91ce38
+        raise ValueError("Failed to create Database Compaction Task")
91ce38
+    log.info("Successfully started Database Compaction Task")
91ce38
+
91ce38
+
91ce38
 def create_parser(subparsers):
91ce38
     backend_parser = subparsers.add_parser('backend', help="Manage database suffixes and backends")
91ce38
     subcommands = backend_parser.add_subparsers(help="action")
91ce38
@@ -994,6 +1008,7 @@ def create_parser(subparsers):
91ce38
     set_db_config_parser.add_argument('--checkpoint-interval', help='Sets the amount of time in seconds after which the Directory Server sends a '
91ce38
                                                                     'checkpoint entry to the database transaction log')
91ce38
     set_db_config_parser.add_argument('--compactdb-interval', help='Sets the interval in seconds when the database is compacted')
91ce38
+    set_db_config_parser.add_argument('--compactdb-time', help='Sets the Time Of Day to compact the database after the "compactdb interval" has been reached:  Use this format to set the hour and minute: HH:MM')
91ce38
     set_db_config_parser.add_argument('--txn-batch-val', help='Specifies how many transactions will be batched before being committed')
91ce38
     set_db_config_parser.add_argument('--txn-batch-min', help='Controls when transactions should be flushed earliest, independently of '
91ce38
                                                               'the batch count (only works when txn-batch-val is set)')
91ce38
@@ -1121,3 +1136,10 @@ def create_parser(subparsers):
91ce38
     #######################################################
91ce38
     get_tree_parser = subcommands.add_parser('get-tree', help='Get a representation of the suffix tree')
91ce38
     get_tree_parser.set_defaults(func=backend_get_tree)
91ce38
+
91ce38
+    #######################################################
91ce38
+    # Run the db compaction task
91ce38
+    #######################################################
91ce38
+    compact_parser = subcommands.add_parser('compact-db', help='Compact the database and the replication changelog')
91ce38
+    compact_parser.set_defaults(func=backend_compact)
91ce38
+    compact_parser.add_argument('--only-changelog', action='store_true', help='Only compact the Replication Change Log')
91ce38
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
91ce38
index 04886f632..3478a0a1f 100644
91ce38
--- a/src/lib389/lib389/cli_conf/replication.py
91ce38
+++ b/src/lib389/lib389/cli_conf/replication.py
91ce38
@@ -37,6 +37,7 @@ arg_to_attr = {
91ce38
         'max_entries': 'nsslapd-changelogmaxentries',
91ce38
         'max_age': 'nsslapd-changelogmaxage',
91ce38
         'compact_interval': 'nsslapd-changelogcompactdb-interval',
91ce38
+        'compact_time': 'nsslapd-changelogcompactdb-time',
91ce38
         'trim_interval': 'nsslapd-changelogtrim-interval',
91ce38
         'encrypt_algo': 'nsslapd-encryptionalgorithm',
91ce38
         'encrypt_key': 'nssymmetrickey',
91ce38
@@ -1216,6 +1217,8 @@ def create_parser(subparsers):
91ce38
     repl_set_cl.add_argument('--max-entries', help="The maximum number of entries to get in the replication changelog")
91ce38
     repl_set_cl.add_argument('--max-age', help="The maximum age of a replication changelog entry")
91ce38
     repl_set_cl.add_argument('--compact-interval', help="The replication changelog compaction interval")
91ce38
+    repl_set_cl.add_argument('--compact-time', help='Sets the Time Of Day to compact the database after the changelog "compact interval" '
91ce38
+                                                    'has been reached:  Use this format to set the hour and minute: HH:MM')
91ce38
     repl_set_cl.add_argument('--trim-interval', help="The interval to check if the replication changelog can be trimmed")
91ce38
 
91ce38
     repl_get_cl = repl_subcommands.add_parser('get-changelog', help='Display replication changelog attributes.')
91ce38
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
91ce38
index 590c6ee79..b64bc6ce5 100644
91ce38
--- a/src/lib389/lib389/tasks.py
91ce38
+++ b/src/lib389/lib389/tasks.py
91ce38
@@ -217,6 +217,19 @@ class EntryUUIDFixupTask(Task):
91ce38
         self._must_attributes.extend(['basedn'])
91ce38
 
91ce38
 
91ce38
+class DBCompactTask(Task):
91ce38
+    """A single instance of compactdb task entry
91ce38
+
91ce38
+    :param instance: An instance
91ce38
+    :type instance: lib389.DirSrv
91ce38
+    """
91ce38
+
91ce38
+    def __init__(self, instance, dn=None):
91ce38
+        self.cn = 'compact_db_' + Task._get_task_date()
91ce38
+        dn = "cn=" + self.cn + "," + DN_COMPACTDB_TASK
91ce38
+        super(DBCompactTask, self).__init__(instance, dn)
91ce38
+
91ce38
+
91ce38
 class SchemaReloadTask(Task):
91ce38
     """A single instance of schema reload task entry
91ce38
 
91ce38
@@ -227,7 +240,6 @@ class SchemaReloadTask(Task):
91ce38
     def __init__(self, instance, dn=None):
91ce38
         self.cn = 'schema_reload_' + Task._get_task_date()
91ce38
         dn = "cn=" + self.cn + ",cn=schema reload task," + DN_TASKS
91ce38
-
91ce38
         super(SchemaReloadTask, self).__init__(instance, dn)
91ce38
 
91ce38
 
91ce38
-- 
91ce38
2.26.3
91ce38