389-ds-base/0054-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
2026-04-07 06:30:11 -04:00

539 lines
25 KiB
Diff

From 6ce19a9a3e36213a5604144aa5eb3cba666e5ed4 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 18 Feb 2026 09:26:57 +0100
Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
parentid (#7264)
Description:
integerOrderingMatch was introduced as a requirement for parentid and
ancestorid indexes for performance reasons. But after #7096 the order
for parentid doesn't make a lot of difference.
Fix Description:
* Remove integerOrderingMatch requirement for parentid.
* Read only first 100 keys from dbscan in index ordering check
* Do not run dsctl index-check during RPM upgrade
Relates: https://github.com/389ds/389-ds-base/pull/7223
Reviewed by: @progier389, @tbordaz (Thanks!)
---
.../healthcheck/health_system_indexes_test.py | 83 ++++----------
ldap/servers/slapd/upgrade.c | 105 ------------------
rpm/389-ds-base.spec.in | 3 -
src/lib389/lib389/backend.py | 5 +-
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++---------
5 files changed, 73 insertions(+), 222 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index dd42cd197..8dc82c779 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -179,7 +179,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
def test_missing_matching_rule(topology_st, log_buffering_enabled):
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
+ """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch.
+ Both lexicographic and integer orderings are valid for parentid.
:id: 7ffa71db-8995-430a-bed8-59bce944221c
:setup: Standalone instance
@@ -189,19 +190,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
3. Use healthcheck without --json option
4. Use healthcheck with --json option
5. Re-add the matching rule
- 6. Use healthcheck without --json option
- 7. Use healthcheck with --json option
:expectedresults:
1. Success
2. Success
- 3. healthcheck reports DSBLE0007 code and related details
- 4. healthcheck reports DSBLE0007 code and related details
+ 3. healthcheck reports no issues found
+ 4. healthcheck reports no issues found
5. Success
- 6. healthcheck reports no issues found
- 7. healthcheck reports no issues found
"""
- RET_CODE = "DSBLE0007"
PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
standalone = topology_st.standalone
@@ -210,17 +206,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
parentid_index = Index(standalone, PARENTID_DN)
parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
log.info("Re-add the integerOrderingMatch matching rule")
parentid_index = Index(standalone, PARENTID_DN)
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
standalone.restart()
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-
def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
"""Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
@@ -910,7 +903,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
def test_index_check_fixes_missing_matching_rule(topology_st):
- """Check if dsctl index-check --fix adds missing integerOrderingMatch
+ """Check that removing integerOrderingMatch from parentid config is not
+ flagged as an issue when disk ordering cannot be determined.
+ Both lexicographic and integer orderings are valid for parentid.
:id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
:setup: Standalone instance
@@ -918,18 +913,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
1. Create DS instance
2. Stop the server
3. Remove integerOrderingMatch from parentid index using DSEldif
- 4. Run dsctl index-check (should detect issue)
- 5. Run dsctl index-check --fix
- 6. Verify integerOrderingMatch was added back
- 7. Start the server
+ 4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown)
+ 5. Start the server
:expectedresults:
1. Success
2. Success
3. Success
- 4. index-check returns False and detects missing matching rule
- 5. index-check returns True after fix
- 6. integerOrderingMatch is present
- 7. Success
+ 4. index-check returns True (no issues, disk ordering unknown)
+ 5. Success
"""
from lib389.cli_ctl.dbtasks import dbtasks_index_check
from lib389.dseldif import DSEldif
@@ -963,34 +954,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
f"integerOrderingMatch should be removed, but found: {mr}"
log.info("integerOrderingMatch removed from parentid index")
- log.info("Run index-check without --fix (should detect issue)")
+ log.info("Run index-check (should NOT detect issue - disk ordering unknown)")
args = FakeArgs()
args.backend = "userRoot"
args.fix = False
result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
- assert result is False, "index-check should detect missing matching rule"
- assert topology_st.logcap.contains("missing integerOrderingMatch")
+ assert result is True, \
+ "index-check should not flag missing integerOrderingMatch when disk ordering is unknown"
+ assert topology_st.logcap.contains("could not determine disk ordering")
topology_st.logcap.flush()
- log.info("Run index-check with --fix")
- args.fix = True
- result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
- assert result is True, "index-check --fix should succeed"
- assert topology_st.logcap.contains("integerOrderingMatch")
- topology_st.logcap.flush()
-
- log.info("Verify integerOrderingMatch was added back")
- dse_ldif = DSEldif(standalone) # Reload to get fresh data
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- assert matching_rules is not None, "nsMatchingRule should be present"
- found_int_order = False
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- found_int_order = True
- break
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
- log.info("integerOrderingMatch successfully added back")
+ log.info("Restore integerOrderingMatch and start the server")
+ dse_ldif = DSEldif(standalone)
+ dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch")
log.info("Start the server")
standalone.start()
@@ -1080,7 +1057,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
:steps:
1. Create DS instance
2. Stop the server
- 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
+ 3. Add multiple issues: scanlimit and ancestorid config
4. Run dsctl index-check (should detect all issues)
5. Run dsctl index-check --fix
6. Verify all issues were fixed
@@ -1122,14 +1099,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
]
dse_ldif.add_entry(ancestorid_entry)
- log.info("Add issue 3: Remove integerOrderingMatch from parentid")
- dse_ldif = DSEldif(standalone) # Reload
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- if matching_rules:
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
-
log.info("Run index-check without --fix (should detect all issues)")
args = FakeArgs()
args.backend = "userRoot"
@@ -1160,16 +1129,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
- # Check matching rule added back
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- found_int_order = False
- if matching_rules:
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- found_int_order = True
- break
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
-
log.info("All issues verified as fixed")
log.info("Run index-check again to confirm all clear")
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index 6b1b012da..9557e9066 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -551,107 +551,6 @@ upgrade_remove_ancestorid_index_config(void)
return uresult;
}
-/*
- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
- * matching rule.
- *
- * This function logs a warning if we detect this condition, advising
- * the administrator to reindex the affected attributes.
- */
-static upgrade_status
-upgrade_check_id_index_matching_rule(void)
-{
- struct slapi_pblock *pb = slapi_pblock_new();
- Slapi_Entry **backends = NULL;
- const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
- const char *be_filter = "(objectclass=nsBackendInstance)";
- const char *attrs_to_check[] = {"parentid", NULL};
- upgrade_status uresult = UPGRADE_SUCCESS;
-
- /* Search for all backend instances */
- slapi_search_internal_set_pb(
- pb, be_base_dn,
- LDAP_SCOPE_ONELEVEL,
- be_filter, NULL, 0, NULL, NULL,
- plugin_get_default_component_id(), 0);
- slapi_search_internal_pb(pb);
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
-
- if (backends) {
- for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
- const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
- const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
- if (!be_dn || !be_name) {
- continue;
- }
-
- /* Check each attribute that should have integerOrderingMatch */
- for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
- const char *attr_name = attrs_to_check[attr_idx];
- struct slapi_pblock *idx_pb = slapi_pblock_new();
- Slapi_Entry **idx_entries = NULL;
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
- attr_name, be_dn);
- char *idx_filter = "(objectclass=nsIndex)";
- PRBool has_matching_rule = PR_FALSE;
-
- if (!idx_dn) {
- slapi_pblock_destroy(idx_pb);
- continue;
- }
-
- slapi_search_internal_set_pb(
- idx_pb, idx_dn,
- LDAP_SCOPE_BASE,
- idx_filter, NULL, 0, NULL, NULL,
- plugin_get_default_component_id(), 0);
- slapi_search_internal_pb(idx_pb);
- slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
-
- if (idx_entries && idx_entries[0]) {
- /* Index exists, check if it has integerOrderingMatch */
- Slapi_Attr *mr_attr = NULL;
- if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
- Slapi_Value *sval = NULL;
- int idx;
- for (idx = slapi_attr_first_value(mr_attr, &sval);
- idx != -1;
- idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
- const struct berval *bval = slapi_value_get_berval(sval);
- if (bval && bval->bv_val &&
- strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
- has_matching_rule = PR_TRUE;
- break;
- }
- }
- }
-
- if (!has_matching_rule) {
- /* Index exists but doesn't have integerOrderingMatch, log a warning */
- slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
- "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
- "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
- "To fix this, add the matching rule and reindex: "
- "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
- "dsconf <instance> backend index reindex --attr %s %s. "
- "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
- "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
- attr_name, be_name, attr_name, be_name, attr_name, be_name);
- }
- }
-
- slapi_ch_free_string(&idx_dn);
- slapi_free_search_results_internal(idx_pb);
- slapi_pblock_destroy(idx_pb);
- }
- }
- }
-
- slapi_free_search_results_internal(pb);
- slapi_pblock_destroy(pb);
-
- return uresult;
-}
/*
* Upgrade the base config of the PAM PTA plugin.
@@ -879,10 +778,6 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}
- if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
- return UPGRADE_FAILURE;
- }
-
return UPGRADE_SUCCESS;
}
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 0e0e28285..370e3abd4 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -650,9 +650,6 @@ for dir in "$instbase"/slapd-* ; do
else
echo "instance $inst is not running" >> "$output" 2>&1 || :
fi
- # Run index-check on all instances (running or not)
- # This fixes index ordering mismatches from older versions
- dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
ninst=$((ninst + 1))
done
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index f3dbe7c92..6c8cbc018 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -647,9 +647,10 @@ class Backend(DSLdapObject):
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
# Note: entryrdn and ancestorid are internal system indexes that are not
# exposed in cn=config - they are managed internally by the server.
- # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
+ # parentid works correctly with both lexicographic and integer ordering,
+ # so integerOrderingMatch is not required.
expected_system_indexes = {
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
+ 'parentid': {'types': ['eq'], 'matching_rule': None},
'objectClass': {'types': ['eq'], 'matching_rule': None},
'aci': {'types': ['pres'], 'matching_rule': None},
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
index cd96cdaf7..b02de203f 100644
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
@@ -10,6 +10,7 @@
import glob
import os
import re
+import signal
import subprocess
from enum import Enum
from lib389._constants import TaskWarning
@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
if not index_file:
return IndexOrdering.UNKNOWN
+ # Only read the first 100 lines from dbscan to avoid scanning the
+ # entire index (which can take hours on large databases).
try:
- result = subprocess.run(
+ proc = subprocess.Popen(
[dbscan_path, "-f", index_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
- timeout=60,
)
- if result.returncode != 0:
- log.warning(" dbscan returned non-zero exit code for %s", index_file)
- return IndexOrdering.UNKNOWN
-
- # Parse keys from dbscan output
keys = []
- for line in result.stdout.split("\n"):
+ line_count = 0
+ assert proc.stdout is not None
+ for line in proc.stdout:
+ line_count += 1
+ if line_count > 100:
+ break
line = line.strip()
if line.startswith("="):
match = re.match(r"^=(\d+)", line)
if match:
keys.append(int(match.group(1)))
+ proc.terminate()
+ try:
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ proc.wait()
+
+ if proc.returncode not in (0, -signal.SIGTERM):
+ log.warning(" dbscan returned non-zero exit code for %s", index_file)
+ return IndexOrdering.UNKNOWN
+
if len(keys) < 2:
return IndexOrdering.UNKNOWN
# Check if keys are in integer order by looking for decreasing numeric values
# (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
prev_id = keys[0]
- for i in range(1, min(len(keys), 100)):
- current_id = keys[i]
+ for current_id in keys[1:]:
if prev_id > current_id:
return IndexOrdering.LEXICOGRAPHIC
prev_id = current_id
return IndexOrdering.INTEGER
- except subprocess.TimeoutExpired:
- log.warning(" dbscan timed out for %s", index_file)
- return IndexOrdering.UNKNOWN
except OSError as e:
log.warning(" Error running dbscan: %s", e)
return IndexOrdering.UNKNOWN
@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args):
# Track all issues found
all_ok = True
- mismatches = [] # (backend, index_name) tuples needing reindex
- missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch
+ config_fixes = [] # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr"
scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit
ancestorid_configs_to_remove = [] # backend names with ancestorid config entries
remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes
@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args):
if disk_ordering == IndexOrdering.UNKNOWN:
log.info(" %s - could not determine disk ordering, skipping", index_name)
- # For parentid, still check if matching rule is missing
- if index_name == "parentid":
- config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
- if not config_has_int_order:
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
- missing_matching_rules.append((backend, index_name))
- all_ok = False
continue
config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args):
log.info(" %s - config: %s, disk: %s",
index_name, config_desc, disk_ordering.value)
- # For parentid, the desired state is always integer ordering
+ # Both orderings are valid for parentid, but config must match disk.
if index_name == "parentid":
- if not config_has_int_order:
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
- if (backend, index_name) not in missing_matching_rules:
- missing_matching_rules.append((backend, index_name))
+ if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC:
+ log.warning(" %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name)
+ config_fixes.append((backend, index_name, "remove_mr"))
all_ok = False
-
- if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
- log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name)
- if (backend, index_name) not in mismatches:
- mismatches.append((backend, index_name))
+ elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER:
+ log.warning(" %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name)
+ config_fixes.append((backend, index_name, "add_mr"))
all_ok = False
# Handle issues
@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args):
log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e)
return False
- # Add missing matching rules to dse.ldif
- for backend, index_name in missing_matching_rules:
+ # Fix config-vs-disk ordering mismatches by adjusting config to match disk
+ for backend, index_name, action in config_fixes:
index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
index_name, backend
)
- log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
- try:
- dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
- log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
- except Exception as e:
- log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
- return False
-
- # Reindex indexes with disk ordering issues
- for backend, index_name in mismatches:
- log.info(" Reindexing %s in backend %s...", index_name, backend)
- if not inst.db2index(bename=backend, attrs=[index_name]):
- log.error(" Failed to reindex %s", index_name)
- return False
- log.info(" Reindex of %s completed successfully", index_name)
+ if action == "add_mr":
+ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
+ try:
+ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
+ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
+ except Exception as e:
+ log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
+ return False
+ elif action == "remove_mr":
+ log.info(" Removing integerOrderingMatch from %s in backend %s...", index_name, backend)
+ try:
+ dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch")
+ log.info(" Removed integerOrderingMatch from %s", index_name)
+ except Exception as e:
+ log.error(" Failed to remove integerOrderingMatch from %s: %s", index_name, e)
+ return False
log.info("All issues fixed")
return True
@@ -563,5 +562,5 @@ def create_parser(subcommands):
index_check_parser.add_argument('backend', nargs='?', default=None,
help="Backend to check. If not specified, all backends are checked.")
index_check_parser.add_argument('--fix', action='store_true', default=False,
- help="Fix mismatches by reindexing affected indexes")
+ help="Fix mismatches by adjusting config to match on-disk data")
index_check_parser.set_defaults(func=dbtasks_index_check)
--
2.52.0