An index manager defect corrupts the index. The issue is only possible on databases that have very large indexes where the dbkeys straddle the 32/64-bit boundary, in a multi-level index, when a split occurs to accommodate an additional index block. This may result in corruption. This does not effect OpenEdge 10.1B, Progress 9 or earlier which cannot hold recids higher than the 32-bit boundary.
IDXFIX is not able to detect this type of index corruption as it traverses the indexes differently.
In OpenEdge versions prior to 11.7, the index check utility (IDXCHECK) with Option 3 -
Validate record for each key, in the Validation menu, is able to detect the index corruption but may crash as a result of the index corruption.
After Image files and OpenEdge Replication can reproduce the index corruption when the changes are applied in the target databases.
To determine if the 64 bit boundary for block identifiers has been crossed perform the following calculations:
- Review the records per block defined for all areas containing indexes.
- Determine how many blocks are in the area.
If the area has more blocks than (2 ^ 31 records / <records per /block>) it is possible to encounter this problem.
Run a prostrct statistics report (online or offline) and look at the active blocks and records/block listed for the area.
Example for sports2000 Cust_Index Area:
Database Block Usage for Area: Cust_Index
Active blocks: 21
Data blocks: 21
Free blocks: 0
Empty blocks: 75
Total blocks: 96
Extent blocks: 2
Records/Block: 32
Cluster size: 1
Maximum # of blocks (for 2 Billion records) = 2,147,483,648 records / (32 records / block) = 67,108,864 blocks
Active blocks: 21 < 67,108,864 blocks
No problems here.
Another example:
Database Block Usage for Area: Index ABC
Active blocks: 19863639
Data blocks: 19863635
Free blocks: 4
Empty blocks: 121
Total blocks: 19863760
Extent blocks: 83
Records/Block: 256
Cluster size: 8
Maximum # of blocks (for 2 Billion records) = 2,147,483,648 records / (256 records / block) = 8,388,608 blocks
Active blocks: 19,863,639 > 8,388,608 blocks
This area is vulnerable.
The following ABL code can be run to review the output of
prostrct statistics [dbname] > statistics_output.txt to determine if a database is susceptible to this defect:
DEFINE VARIABLE cInFile AS CHAR NO-UNDO INIT "./statistics_output.txt".
DEFINE VARIABLE cHolder AS CHAR NO-UNDO.
DEFINE VARIABLE cTempName AS CHAR NO-UNDO.
DEFINE VARIABLE iCount AS INT NO-UNDO INIT 0.
DEFINE VARIABLE iMaxRecords AS INT64 INIT 2147483648 NO-UNDO.
DEFINE TEMP-TABLE ttAreas
FIELD cAreaName AS CHAR
FIELD iActiveBlocks AS INT64 FORMAT ">>>>>>>>>>>>>>>9"
FIELD iRPB AS INT
FIELD iClusterSize AS INT
INDEX INDEX-name cAreaName
INDEX INDEX-clustersize iClusterSize.
IF (OPSYS = "WIN32") THEN
DO:
ASSIGN DEFAULT-WINDOW:WIDTH-CHARS = 180
CURRENT-WINDOW = DEFAULT-WINDOW
SESSION:SYSTEM-ALERT-BOX = TRUE.
END.
INPUT FROM VALUE(cInFile).
REPEAT:
IMPORT UNFORMATTED cHolder NO-ERROR.
IF ERROR-STATUS:ERROR THEN LEAVE.
REPEAT WHILE INDEX(cHolder,"Database Block Usage for Area") < 1:
IMPORT UNFORMATTED cHolder NO-ERROR.
IF ERROR-STATUS:ERROR THEN LEAVE.
IF INDEX(cHolder,"Control Area") > 1 OR
INDEX(cHolder,"Primary Recovery Area") > 1 THEN
DO:
IMPORT UNFORMATTED cHolder NO-ERROR.
NEXT.
END.
IF INDEX(cHolder,"Database Block Usage for Area") > 0 THEN
DO:
cTempName=TRIM(ENTRY(2,cHolder,":")).
FIND FIRST ttAreas where cAreaName = cTempName NO-ERROR.
IF NOT AVAILABLE ttAreas THEN
DO:
CREATE ttAreas.
ASSIGN ttAreas.cAreaName = cTempName.
END.
END.
END.
REPEAT WHILE INDEX(cHolder,"Active blocks") < 1:
IMPORT UNFORMATTED cHolder NO-ERROR.
IF ERROR-STATUS:ERROR THEN LEAVE.
IF INDEX(cHolder,"Active blocks") > 0 THEN
DO:
IF AVAILABLE ttAreas THEN
DO:
ASSIGN ttAreas.iActiveBlocks = INT64(TRIM(ENTRY(2,cHolder,":"))).
END.
ELSE
DO:
CREATE ttAreas.
ASSIGN ttAreas.iActiveBlocks = INT64(TRIM(ENTRY(2,cHolder,":"))).
END.
END.
END.
REPEAT WHILE INDEX(cHolder,"Records/Block") < 1:
IMPORT UNFORMATTED cHolder NO-ERROR.
IF ERROR-STATUS:ERROR THEN LEAVE.
IF INDEX(cHolder,"Records/Block") > 0 THEN
DO:
IF AVAILABLE ttAreas THEN
DO:
ASSIGN ttAreas.iRPB = INT(TRIM(ENTRY(2,cHolder,":"))).
END.
END.
END.
REPEAT WHILE INDEX(cHolder,"Cluster size") < 1:
IMPORT UNFORMATTED cHolder NO-ERROR.
IF ERROR-STATUS:ERROR THEN LEAVE.
IF INDEX(cHolder,"Cluster size") > 0 THEN
DO:
IF AVAILABLE ttAreas THEN
DO:
ASSIGN ttAreas.iClusterSize = int(TRIM(ENTRY(2,cHolder,":"))).
END.
END.
END.
iCount = iCount + 1.
IF iCount > 20 THEN LEAVE.
END.
FOR EACH ttAreas NO-LOCK:
IF (ttAreas.iActiveBlocks > (iMaxRecords / ttAreas.iRPB)) THEN
DISPLAY
ttAreas.cAreaName FORMAT "x(20)"
STRING(ttAreas.iActiveBlocks - (iMaxRecords / ttAreas.iRPB)) "
is susceptible." WITH WIDTH 100.
ELSE
DISPLAY ttAreas.cAreaName FORMAT "x(20)"
STRING(ttAreas.iActiveBlocks - (iMaxRecords / ttAreas.iRPB)) "
is not susceptible." WITH WIDTH 100.
END.
INPUT CLOSE.
Alternatively, the following VST's will identify vulnerable bit boundary areas.
DEFINE VARIABLE Vulnerable AS LOGICAL NO-UNDO FORMAT "*/ ".
DEFINE VARIABLE RowsPerBlock AS INTEGER NO-UNDO.
DEFINE VARIABLE Limit AS INT64 NO-UNDO.
DEFINE VARIABLE TwoBillion AS INT64 NO-UNDO.
ASSIGN TwoBillion = EXP(2, 31).
FOR EACH _Area WHERE _Area-number GT 3,
FIRST _areastatus WHERE _Areastatus-areanum = _Area-number ,
FIRST _storageobject OF _area WHERE _object-type = 2:
ASSIGN
RowsPerBlock = EXP(2, _area-recbits)
Limit = (TwoBillion / RowsPerBlock)
Vulnerable = (_areastatus-HiWater >= Limit).
DISPLAY
_area-name RowsPerBlock label "r/block"
_areastatus-HiWater Vulnerable LABEL "Vuln".
END