@harperfast/harper 5.2.9 → 5.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/DatabaseTransaction.d.ts +3 -0
- package/dist/resources/DatabaseTransaction.js +90 -2
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +2 -1
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/Table.js +13 -5
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.js +142 -53
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/databases.d.ts +10 -0
- package/dist/resources/databases.js +369 -97
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/search.js +33 -7
- package/dist/resources/search.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +7 -0
- package/dist/server/storageReclamation.js +10 -0
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +1 -0
- package/dist/server/threads/manageThreads.js +7 -0
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/npm-shrinkwrap.json +41 -41
- package/package.json +2 -2
- package/resources/DatabaseTransaction.ts +88 -2
- package/resources/PrimaryRocksDatabase.ts +2 -1
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/Table.ts +13 -5
- package/resources/auditStore.ts +144 -52
- package/resources/databases.ts +351 -93
- package/resources/search.ts +32 -7
- package/server/storageReclamation.ts +10 -0
- package/server/threads/manageThreads.js +7 -0
- package/studio/web/assets/Chat-CokJOjyX.js +2267 -0
- package/studio/web/assets/FloatingChat-h9BJo6YG.js +23 -0
- package/studio/web/assets/{abnfDiagram-VCTEODGH-C0_BAZyO.js → abnfDiagram-VCTEODGH-B0BebmD2.js} +1 -1
- package/studio/web/assets/{alertDialog-DIHt7Z0r.js → alertDialog-CQyAJJhl.js} +1 -1
- package/studio/web/assets/{apiToken-c3Rd-w6g.js → apiToken-B_g12QrM.js} +1 -1
- package/studio/web/assets/applications-Dlq5kJ2O.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +1 -0
- package/studio/web/assets/{architectureDiagram-5GKGNRK7-BWzrASgm.js → architectureDiagram-5GKGNRK7-7SW3GD-K.js} +1 -1
- package/studio/web/assets/authStore-C3Nfubqr.js +3 -0
- package/studio/web/assets/{blockDiagram-NRAW4CY4-BdJX9Khj.js → blockDiagram-I7D4REHJ-BqguiadH.js} +2 -2
- package/studio/web/assets/{button-DhiX-njv.js → button-BIsUKRZq.js} +2 -2
- package/studio/web/assets/{c4Diagram-UCG6FXSJ-CI6MzGmQ.js → c4Diagram-7LVT6UL2-LBNf8t_X.js} +1 -1
- package/studio/web/assets/channel-yictG-U-.js +1 -0
- package/studio/web/assets/{chevron-up-Df2c8uoU.js → chevron-up-DtKGqDn3.js} +1 -1
- package/studio/web/assets/{chunk-TEH6E4GO-P87k5mNi.js → chunk-4HAMMTFA-DWtTut21.js} +1 -1
- package/studio/web/assets/{chunk-75Z2AOVW-BT8tVmks.js → chunk-75Z2AOVW-QGQD6th2.js} +1 -1
- package/studio/web/assets/{chunk-DU6HZSFF-9kAOOmI6.js → chunk-DU6HZSFF-Chq20Ba5.js} +1 -1
- package/studio/web/assets/{chunk-F27PBJKO-BW7ao8AY.js → chunk-F27PBJKO-BVA5EPhV.js} +1 -1
- package/studio/web/assets/{chunk-GMAD6QVW-BNyXpoQO.js → chunk-GMAD6QVW-BeS7S07A.js} +1 -1
- package/studio/web/assets/{chunk-OBVCFTLP-D4wWOqDQ.js → chunk-GVQU2GXP-sbwVIQ8i.js} +1 -1
- package/studio/web/assets/{chunk-G27WJ6UU-COyLMcgK.js → chunk-IMKFNOWR-Bnh3tAVd.js} +1 -1
- package/studio/web/assets/{chunk-JQ64N6SF-Cyz1IeLf.js → chunk-L3NEJ4N5-COfUyKII.js} +1 -1
- package/studio/web/assets/chunk-OSK3NFVY-ByciRftO.js +10 -0
- package/studio/web/assets/{chunk-P2QGCYS3-DmIFY4d7.js → chunk-P2QGCYS3-CP1VhG_c.js} +1 -1
- package/studio/web/assets/{chunk-POPQ4Y6H-BPrvMyKz.js → chunk-POPQ4Y6H-ClWhhkwW.js} +1 -1
- package/studio/web/assets/{chunk-PWAF6VOD-2zB6IW9i.js → chunk-PWAF6VOD-1z1THyS5.js} +1 -1
- package/studio/web/assets/{chunk-RHFEMEQ7-2FgyI8YU.js → chunk-SHT3W25Y-LpQkMsah.js} +2 -2
- package/studio/web/assets/{chunk-SVP7TREG-FwtbH2QC.js → chunk-SVP7TREG-jtdAHw0S.js} +1 -1
- package/studio/web/assets/{chunk-LCL6LL3I-HOzK_ppE.js → chunk-TICWLB2K-VOwzetX-.js} +1 -1
- package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +1 -0
- package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +1 -0
- package/studio/web/assets/{createLucideIcon-BKGPfjm2.js → createLucideIcon-CzW9508A.js} +1 -1
- package/studio/web/assets/{cssMode-CEN2mzSA.js → cssMode-DAjzPoBf.js} +1 -1
- package/studio/web/assets/{cynefin-OW5HDTMX-BRkpLFQV.js → cynefin-OW5HDTMX-BbdbCvub.js} +1 -1
- package/studio/web/assets/{cynefinDiagram-5FMLGOSQ-CHT1DaX6.js → cynefinDiagram-5FMLGOSQ-TP-aIqbt.js} +1 -1
- package/studio/web/assets/{dagre-3AP2YEHR-DpUXBh63.js → dagre-GXQ25YYZ-DShnGpGo.js} +1 -1
- package/studio/web/assets/{diagram-S7CK7UJ4-BuymVFZT.js → diagram-S7CK7UJ4-aoCVTtcy.js} +1 -1
- package/studio/web/assets/{diagram-UQ7AKVKN-CyP148RM.js → diagram-UQ7AKVKN-DglXtQ6x.js} +1 -1
- package/studio/web/assets/{diagram-VSXAHHWV-CoCAg3M9.js → diagram-VSXAHHWV-fhEdmkwM.js} +1 -1
- package/studio/web/assets/{diagram-VX7I27RA-BpOqCFca.js → diagram-VX7I27RA-DccVJet6.js} +1 -1
- package/studio/web/assets/{diagram-Z3DM3KII-Bfpw7Vbj.js → diagram-Z3DM3KII-D-RyJJb7.js} +1 -1
- package/studio/web/assets/{dialog-CBf0Mr1d.js → dialog-Cn2uWgD4.js} +1 -1
- package/studio/web/assets/{dist-lkA3O3eM.js → dist-DP8UjMB_.js} +1 -1
- package/studio/web/assets/{download-BtTOBem-.js → download-B5T5r7ss.js} +1 -1
- package/studio/web/assets/{ebnfDiagram-PWID7BFC-DS_6aWqL.js → ebnfDiagram-PWID7BFC-DJGpIpz_.js} +1 -1
- package/studio/web/assets/{editor-D8oDeCTL.js → editor-VUNQ-5Ye.js} +1 -1
- package/studio/web/assets/{erDiagram-SSCWMZ5O-DJNk6Fgw.js → erDiagram-RLTQ6QDP-CIfNlgkC.js} +1 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +1 -0
- package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +1 -0
- package/studio/web/assets/{ganttDiagram-EL5Y4UJY-2pOExxMY.js → ganttDiagram-EL5Y4UJY-BxToTzzD.js} +1 -1
- package/studio/web/assets/{getAnalytics-D4LKGeVy.js → getAnalytics-GHK8ORfM.js} +1 -1
- package/studio/web/assets/{gitGraph-4MIJSDKK-CH5ZxwzF.js → gitGraph-4MIJSDKK-D2s2w8lE.js} +1 -1
- package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-DVIsIhbO.js → gitGraphDiagram-WWUBYQGX-Dwntd4-x.js} +1 -1
- package/studio/web/assets/{html-u3vOg7LJ.js → html-CFqhtdfa.js} +1 -1
- package/studio/web/assets/{htmlMode-DyO31v-P.js → htmlMode-CpgOTfjz.js} +1 -1
- package/studio/web/assets/{index-Cxj2_wsl.css → index-7RMEgVG1.css} +1 -1
- package/studio/web/assets/index-ZQ1upnh7.js +824 -0
- package/studio/web/assets/index.lazy-HDafSRxx.js +2 -0
- package/studio/web/assets/{info-A6RAGUB7-CPQfTnaG.js → info-A6RAGUB7-DYjkvb0C.js} +1 -1
- package/studio/web/assets/{infoDiagram-RXCK75RN-DlwLYlwm.js → infoDiagram-27XIBGKW-Bnp1FJE5.js} +1 -1
- package/studio/web/assets/{ishikawaDiagram-5VMMS53U-BRXRp29U.js → ishikawaDiagram-5VMMS53U-D9Xh2r6X.js} +1 -1
- package/studio/web/assets/{javascript-CUvxOyTC.js → javascript-9l5JXJwc.js} +1 -1
- package/studio/web/assets/{journeyDiagram-EYS64GPL-B0ou8k0n.js → journeyDiagram-3NMN7TZE-CokIi6ll.js} +2 -2
- package/studio/web/assets/{jsonMode-f_IwbF3D.js → jsonMode-BJBH-iR0.js} +1 -1
- package/studio/web/assets/{kanban-definition-3QL26DDD-uYg7iYzp.js → kanban-definition-UXKFOSKX-CukSFJfX.js} +1 -1
- package/studio/web/assets/{languageServices-DXtZ6rEF.js → languageServices-0ZvgO63b.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-B4pCF1zO.js → lspLanguageFeatures-sNVDopx3.js} +1 -1
- package/studio/web/assets/{mermaid-parser.core-Ck-fC8b7.js → mermaid-parser.core-BlEsOWNO.js} +3 -3
- package/studio/web/assets/{mermaid.core-CP8aNNYm.js → mermaid.core-BlkGaMIH.js} +5 -5
- package/studio/web/assets/{mindmap-definition-FBJOCRG2-CgTZ-rit.js → mindmap-definition-YA3MSWOX-IprMc_0j.js} +1 -1
- package/studio/web/assets/notifications-BukjVuVe.js +1 -0
- package/studio/web/assets/{notifications-D3tIQ4sg.js → notifications-DvBJIIO0.js} +1 -1
- package/studio/web/assets/{packet-AYTQ26CC-DEyoPtPb.js → packet-AYTQ26CC-Bi3V04Zi.js} +1 -1
- package/studio/web/assets/{pegDiagram-XKGWAZYB-DrD-7sD9.js → pegDiagram-XKGWAZYB-BNuPDLZY.js} +1 -1
- package/studio/web/assets/{pie-WAS4IAKB-wjj-EI1d.js → pie-WAS4IAKB-_6DoDbng.js} +1 -1
- package/studio/web/assets/{pieDiagram-E7YTZNPT-GntqDCzv.js → pieDiagram-E7YTZNPT-DqNb6Ht2.js} +1 -1
- package/studio/web/assets/{profile-DZWU7MgT.js → profile-B-jyYdzV.js} +1 -1
- package/studio/web/assets/{quadrantDiagram-AXDQQJYC-0UeqXQGd.js → quadrantDiagram-AXDQQJYC-BGH9E2YR.js} +1 -1
- package/studio/web/assets/{radar-RG4KPBEZ-DFSA5h7k.js → radar-RG4KPBEZ-DDdVczcL.js} +1 -1
- package/studio/web/assets/{railroad-74A4TZTK-CaOUG9wR.js → railroad-74A4TZTK-BJUP4Jds.js} +1 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +1 -0
- package/studio/web/assets/{railroadDiagram-O6MQD6OU-yHUELZaV.js → railroadDiagram-O6MQD6OU-DGTPh2KZ.js} +1 -1
- package/studio/web/assets/{regions-CkyurXzE.js → regions-GN6Mu5aH.js} +1 -1
- package/studio/web/assets/{register-BUyhWjBO.js → register-CU-gCfLP.js} +3 -3
- package/studio/web/assets/{requirementDiagram-EFPCY7ZU-DNEGFjuW.js → requirementDiagram-BXWQKSXE-BJnO6uLz.js} +1 -1
- package/studio/web/assets/{sankeyDiagram-P5KCCOFB-DpyAmSVR.js → sankeyDiagram-P5KCCOFB-0vSOdymH.js} +1 -1
- package/studio/web/assets/{sequenceDiagram-WJ2MYXX4-TyaT7xNk.js → sequenceDiagram-WJ2MYXX4-hETizDWE.js} +1 -1
- package/studio/web/assets/{setComponentFile-CeyKSZAa.js → setComponentFile-D7Fww0lC.js} +1 -1
- package/studio/web/assets/{setup-D2kn7cAA.js → setup-DG3Ws0Gg.js} +2 -2
- package/studio/web/assets/{stateDiagram-HBIQ2CUA-CeEdTArZ.js → stateDiagram-D77RDMKH-CdYQ_KtC.js} +1 -1
- package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +1 -0
- package/studio/web/assets/status-koJd66XF.js +61 -0
- package/studio/web/assets/{swimlanes-XN3QIQJK-B54FmF46.js → swimlanes-42K2YHIH-B8cHIpU4.js} +1 -1
- package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +8 -0
- package/studio/web/assets/{tabs-B_G5zscN.js → tabs-BrHu7gJi.js} +1 -1
- package/studio/web/assets/{timeline-definition-24CTP7MA-D-a9ujbo.js → timeline-definition-24CTP7MA-BJWYSXqF.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-C0UW6rI2.js → toggleHighContrast-CGksgPgv.js} +1 -1
- package/studio/web/assets/{treeView-Q6P3EWNA-CrW_6JnS.js → treeView-Q6P3EWNA-qxe_v6CQ.js} +1 -1
- package/studio/web/assets/{treemap-WGGIJYW6-BxyYLdP_.js → treemap-WGGIJYW6-dDo97XXF.js} +1 -1
- package/studio/web/assets/{tsMode-DBC0zmDx.js → tsMode-D3T_5QzW.js} +1 -1
- package/studio/web/assets/{typescript-DApRQir3.js → typescript-BegjUvtB.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-DB6JStU1.js → useEntityRestURL-BicFpx_N.js} +1 -1
- package/studio/web/assets/{useLocalStorage-Dtj1QS8_.js → useLocalStorage-BqMR3D8_.js} +1 -1
- package/studio/web/assets/vendor-core-c2JRRJpV.js +58 -0
- package/studio/web/assets/vendor-datadog-CLUcJXOo.js +6 -0
- package/studio/web/assets/{vendor-react-Dyj4O3HE.js → vendor-react-CJV_K1u4.js} +1 -1
- package/studio/web/assets/vendor-tanstack-DxzraizX.js +1 -0
- package/studio/web/assets/{vendor-ui-vhu-UHhF.js → vendor-ui-BUjK0h8a.js} +2 -2
- package/studio/web/assets/{vennDiagram-4TSXK5OY-Cy7s7Mpy.js → vennDiagram-4TSXK5OY-A3i-lCdl.js} +1 -1
- package/studio/web/assets/{wardley-WFR3VGLG-BeBL35g2.js → wardley-WFR3VGLG-B0ik-_6g.js} +1 -1
- package/studio/web/assets/{wardleyDiagram-VM6X3IG4-BylmIGSg.js → wardleyDiagram-VM6X3IG4-CjrkKWUR.js} +1 -1
- package/studio/web/assets/{workers-C0bFIedw.js → workers-Bmx4l_9B.js} +1 -1
- package/studio/web/assets/x-DIzaLEdK.js +1 -0
- package/studio/web/assets/{xml-HWd01lU-.js → xml-Bg40Rcra.js} +1 -1
- package/studio/web/assets/{xychartDiagram-S5SC5T6Z-CoKALMXr.js → xychartDiagram-S5SC5T6Z-Biok4GYV.js} +1 -1
- package/studio/web/assets/{yaml-CIH0Nt-h.js → yaml-DolDvfXE.js} +1 -1
- package/studio/web/index.html +14 -14
- package/studio/web/assets/Chat-JpO8EtUu.js +0 -2067
- package/studio/web/assets/FloatingChat-Bcj3xSZu.js +0 -23
- package/studio/web/assets/applications-ByqLRKyZ.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +0 -1
- package/studio/web/assets/authStore-qKmCZcaf.js +0 -3
- package/studio/web/assets/channel-DtCV8PTL.js +0 -1
- package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +0 -10
- package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +0 -1
- package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +0 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +0 -1
- package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +0 -1
- package/studio/web/assets/index-aSt5tY-L.js +0 -824
- package/studio/web/assets/index.lazy-B9jiPwT8.js +0 -2
- package/studio/web/assets/notifications-CUoYgU98.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +0 -1
- package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +0 -1
- package/studio/web/assets/status-D7Xn5ePA.js +0 -61
- package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +0 -8
- package/studio/web/assets/vendor-core-RCcadM3e.js +0 -73
- package/studio/web/assets/vendor-datadog-BRv-mOv1.js +0 -6
- package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +0 -1
- package/studio/web/assets/x-B9o9hsep.js +0 -1
- /package/studio/web/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
|
|
39
|
+
exports.ABANDONED_MARK_LOCK_TIMEOUT = exports.CHECKPOINT_ALGORITHM = exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
|
|
40
40
|
exports.isReadOnlyMode = isReadOnlyMode;
|
|
41
41
|
exports.getRocksCompression = getRocksCompression;
|
|
42
42
|
exports.resetRocksCompression = resetRocksCompression;
|
|
@@ -53,6 +53,9 @@ exports.closeDatabase = closeDatabase;
|
|
|
53
53
|
exports.closeLoadedDatabases = closeLoadedDatabases;
|
|
54
54
|
exports.table = table;
|
|
55
55
|
exports.canonicalizeIndexOptions = canonicalizeIndexOptions;
|
|
56
|
+
exports.setIndexingCheckpointPeriod = setIndexingCheckpointPeriod;
|
|
57
|
+
exports.resumeStartKey = resumeStartKey;
|
|
58
|
+
exports.tryAcquireUpdateAttributesLock = tryAcquireUpdateAttributesLock;
|
|
56
59
|
exports.dropTableMeta = dropTableMeta;
|
|
57
60
|
exports.onUpdatedTable = onUpdatedTable;
|
|
58
61
|
exports.onRemovedTable = onRemovedTable;
|
|
@@ -60,6 +63,7 @@ exports.onRemovedDB = onRemovedDB;
|
|
|
60
63
|
exports.getDefaultCompression = getDefaultCompression;
|
|
61
64
|
exports.flushDatabases = flushDatabases;
|
|
62
65
|
const node_events_1 = require("node:events");
|
|
66
|
+
const node_crypto_1 = require("node:crypto");
|
|
63
67
|
const environmentManager_ts_1 = require("../utility/environment/environmentManager.js");
|
|
64
68
|
const terms_ts_1 = require("../utility/lmdb/terms.js");
|
|
65
69
|
const lmdb_1 = require("lmdb");
|
|
@@ -81,6 +85,7 @@ const harper_logger_ts_1 = __importDefault(require("../utility/logging/harper_lo
|
|
|
81
85
|
const { forComponent } = harper_logger_ts_1.default;
|
|
82
86
|
const manageThreads = __importStar(require("../server/threads/manageThreads.js"));
|
|
83
87
|
const auditStore_ts_1 = require("./auditStore.js");
|
|
88
|
+
const storageReclamation_ts_1 = require("../server/storageReclamation.js");
|
|
84
89
|
const RecordEncoder_ts_1 = require("./RecordEncoder.js");
|
|
85
90
|
const blob_ts_1 = require("./blob.js");
|
|
86
91
|
const customIndexes_ts_1 = require("./indexes/customIndexes.js");
|
|
@@ -90,7 +95,6 @@ const PrimaryRocksDatabase_ts_1 = require("./PrimaryRocksDatabase.js");
|
|
|
90
95
|
const replayLogs_ts_1 = require("./replayLogs.js");
|
|
91
96
|
const node_os_1 = require("node:os");
|
|
92
97
|
const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
|
|
93
|
-
const when_ts_1 = require("../utility/when.js");
|
|
94
98
|
const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
|
|
95
99
|
const processManagement_js_1 = require("../utility/processManagement/processManagement.js");
|
|
96
100
|
const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
|
|
@@ -321,6 +325,23 @@ const rocksdbDatabaseEnvs = new Map();
|
|
|
321
325
|
(0, globals_js_1._assignPackageExport)('databases', exports.databases);
|
|
322
326
|
(0, globals_js_1._assignPackageExport)('tables', exports.tables);
|
|
323
327
|
const NEXT_TABLE_ID = Symbol.for('next-table-id');
|
|
328
|
+
/**
|
|
329
|
+
* True when a descriptor claims an index build no live operation in this process can own. The PID and
|
|
330
|
+
* worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
|
|
331
|
+
* generation back at 1 while the persisted one is higher. A descriptor with no incarnation was written
|
|
332
|
+
* before the field existed, so it belongs to an earlier process; a thread started without one of its
|
|
333
|
+
* own cannot judge, and falls back rather than declaring a live build dead.
|
|
334
|
+
*/
|
|
335
|
+
function isAbandonedIndexBuild(descriptor, currentRestartGeneration) {
|
|
336
|
+
if (!descriptor)
|
|
337
|
+
return false;
|
|
338
|
+
if (descriptor.indexingPID && descriptor.indexingPID !== process.pid)
|
|
339
|
+
return true;
|
|
340
|
+
if (descriptor.restartNumber < currentRestartGeneration)
|
|
341
|
+
return true;
|
|
342
|
+
const incarnation = manageThreads.processIncarnation;
|
|
343
|
+
return !!descriptor.indexingPID && incarnation != null && descriptor.indexingIncarnation !== incarnation;
|
|
344
|
+
}
|
|
324
345
|
// How many times the schema load will try to finish a tombstoned drop before
|
|
325
346
|
// giving up for the rest of this process's lifetime. A drop that fails once
|
|
326
347
|
// almost always fails identically forever - the usual cause is a RocksDB
|
|
@@ -590,6 +611,7 @@ function readMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAME, au
|
|
|
590
611
|
rootStore = (0, lmdb_1.open)(envInit);
|
|
591
612
|
lmdbDatabaseEnvs.set(path, rootStore);
|
|
592
613
|
}
|
|
614
|
+
rootStore.dbisDb?.resetReadTxn();
|
|
593
615
|
return initStores(path, rootStore, databaseName, defaultTable, auditPath, isLegacy);
|
|
594
616
|
}
|
|
595
617
|
catch (error) {
|
|
@@ -835,6 +857,8 @@ function initStores(path, rootStore, databaseName, defaultTable, auditPath, isLe
|
|
|
835
857
|
indices[attribute.name] = dbi;
|
|
836
858
|
indices[attribute.name].indexNulls = attribute.indexNulls;
|
|
837
859
|
}
|
|
860
|
+
// the only way a thread that never declares the schema reaches Table.indices
|
|
861
|
+
indices[attribute.name].isIndexing = !!attribute.indexingPID;
|
|
838
862
|
const existingAttribute = existingAttributes.find((existingAttribute) => existingAttribute.name === attribute.name);
|
|
839
863
|
if (existingAttribute)
|
|
840
864
|
existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1, attribute);
|
|
@@ -1147,6 +1171,10 @@ async function dropDatabase(databaseName) {
|
|
|
1147
1171
|
delete exports.databases[databaseName];
|
|
1148
1172
|
exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
|
|
1149
1173
|
if (rootStore) {
|
|
1174
|
+
// awaited: retirement stops the loop admitting work, the barrier is what says the pass that was
|
|
1175
|
+
// already running has released the stores this is about to close and unlink
|
|
1176
|
+
await rootStore.auditStore?.stopAuditCleanup?.();
|
|
1177
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1150
1178
|
if (rootStore.status === 'open') {
|
|
1151
1179
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1152
1180
|
rootStore.close();
|
|
@@ -1164,6 +1192,8 @@ async function dropDatabase(databaseName) {
|
|
|
1164
1192
|
// the drop lock now (still before any destructive step)
|
|
1165
1193
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase)
|
|
1166
1194
|
lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
|
|
1195
|
+
await rootStore.auditStore?.stopAuditCleanup?.();
|
|
1196
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1167
1197
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1168
1198
|
rootStore.close();
|
|
1169
1199
|
rootStore.destroy();
|
|
@@ -1206,10 +1236,6 @@ function closeDatabase(databaseName) {
|
|
|
1206
1236
|
continue;
|
|
1207
1237
|
if (table.primaryStore.rootStore)
|
|
1208
1238
|
rootStores.add(table.primaryStore.rootStore);
|
|
1209
|
-
for (const indexName in table.indices || {}) {
|
|
1210
|
-
closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
|
|
1211
|
-
}
|
|
1212
|
-
closeStore(table.primaryStore, `table ${tableName}`);
|
|
1213
1239
|
}
|
|
1214
1240
|
// a database with no tables (an empty schema, or one whose tables were all dropped) still holds
|
|
1215
1241
|
// an open root store, tracked only on the defined-database entry rather than any table — include
|
|
@@ -1217,7 +1243,23 @@ function closeDatabase(databaseName) {
|
|
|
1217
1243
|
const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
|
|
1218
1244
|
if (definedRoot)
|
|
1219
1245
|
rootStores.add(definedRoot);
|
|
1246
|
+
// before any table store closes, so no further pass is admitted. This is synchronous, so it cannot
|
|
1247
|
+
// await the drain barrier stopAuditCleanup() returns; what covers it is the in-pass status checks,
|
|
1248
|
+
// plus the fact that its production callers reach it only for RocksDB databases, whose pass is one
|
|
1249
|
+
// synchronous purgeLogs() call with nothing suspended mid-removal.
|
|
1250
|
+
for (const rootStore of rootStores)
|
|
1251
|
+
rootStore.auditStore?.stopAuditCleanup?.();
|
|
1252
|
+
for (const tableName in dbTables) {
|
|
1253
|
+
const table = dbTables[tableName];
|
|
1254
|
+
if (!table?.primaryStore)
|
|
1255
|
+
continue;
|
|
1256
|
+
for (const indexName in table.indices || {}) {
|
|
1257
|
+
closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
|
|
1258
|
+
}
|
|
1259
|
+
closeStore(table.primaryStore, `table ${tableName}`);
|
|
1260
|
+
}
|
|
1220
1261
|
for (const rootStore of rootStores) {
|
|
1262
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1221
1263
|
closeStore(rootStore.dbisDb, 'attributes store');
|
|
1222
1264
|
closeStore(rootStore, 'root store');
|
|
1223
1265
|
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
@@ -1778,15 +1820,13 @@ function table(tableDefinition) {
|
|
|
1778
1820
|
if (changed ||
|
|
1779
1821
|
indexFormatNeedsPersist ||
|
|
1780
1822
|
attributeDescriptor?.indexingFailed ||
|
|
1781
|
-
(attributeDescriptor
|
|
1782
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
1823
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
1783
1824
|
hasChanges = true;
|
|
1784
1825
|
exclusiveLock();
|
|
1785
1826
|
attributeDescriptor = attributesDbi.getSync(dbiKey);
|
|
1786
1827
|
if (structurallyChanged ||
|
|
1787
1828
|
attributeDescriptor?.indexingFailed ||
|
|
1788
|
-
(attributeDescriptor
|
|
1789
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
1829
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
1790
1830
|
hasChanges = true;
|
|
1791
1831
|
if (attribute.indexNulls === undefined)
|
|
1792
1832
|
attribute.indexNulls = true;
|
|
@@ -1805,9 +1845,20 @@ function table(tableDefinition) {
|
|
|
1805
1845
|
// representation-only option difference — preserve the checkpoint so the backfill
|
|
1806
1846
|
// resumes rather than restarts. Canonicalized to match structurallyChanged above.
|
|
1807
1847
|
const indexOptionsChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1848
|
+
// Only a checkpoint runIndexing stamped with its own key resumes: earlier releases advanced
|
|
1849
|
+
// lastIndexedKey past failed and unflushed index writes, so any other is a full rebuild.
|
|
1850
|
+
const uncertifiedCheckpoint = attributeDescriptor?.lastIndexedKey !== undefined &&
|
|
1851
|
+
(attributeDescriptor.checkpointCertified === undefined ||
|
|
1852
|
+
attributeDescriptor.checkpointAlgorithm !== exports.CHECKPOINT_ALGORITHM ||
|
|
1853
|
+
(0, lmdb_1.compareKeys)(attributeDescriptor.checkpointCertified, attributeDescriptor.lastIndexedKey) !== 0);
|
|
1854
|
+
attribute.lastIndexedKey =
|
|
1855
|
+
indexOptionsChanged || uncertifiedCheckpoint
|
|
1856
|
+
? undefined
|
|
1857
|
+
: (attributeDescriptor?.lastIndexedKey ?? undefined);
|
|
1858
|
+
if (attribute.lastIndexedKey !== undefined) {
|
|
1859
|
+
attribute.checkpointCertified = attribute.lastIndexedKey;
|
|
1860
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
1861
|
+
}
|
|
1811
1862
|
// Explicit reindex is the upgrade path from a legacy (un-versioned) custom-index
|
|
1812
1863
|
// object store to the versioned, VT-cacheable format. A full rebuild from scratch
|
|
1813
1864
|
// (lastIndexedKey === undefined) clears the store and rewrites every node, so the
|
|
@@ -1829,6 +1880,9 @@ function table(tableDefinition) {
|
|
|
1829
1880
|
// the new process reuses the old PID. Cleared on clean completion; left in place
|
|
1830
1881
|
// on failure/crash so the next, higher-numbered restart re-triggers the backfill.
|
|
1831
1882
|
attribute.restartNumber = currentRestartGeneration;
|
|
1883
|
+
if (manageThreads.processIncarnation != null)
|
|
1884
|
+
attribute.indexingIncarnation = manageThreads.processIncarnation;
|
|
1885
|
+
attribute.indexingBuildId = (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
1832
1886
|
delete attribute.indexingFailed; // clear failure flag for the new run
|
|
1833
1887
|
dbi.isIndexing = true;
|
|
1834
1888
|
Object.defineProperty(attribute, 'dbi', { value: dbi, configurable: true, enumerable: false });
|
|
@@ -1844,6 +1898,12 @@ function table(tableDefinition) {
|
|
|
1844
1898
|
reindexReasons.push(`crash-recovery(pid=${attributeDescriptor.indexingPID})`);
|
|
1845
1899
|
if (attributeDescriptor?.restartNumber < currentRestartGeneration)
|
|
1846
1900
|
reindexReasons.push('restart-number');
|
|
1901
|
+
if (uncertifiedCheckpoint)
|
|
1902
|
+
reindexReasons.push('uncertified-checkpoint');
|
|
1903
|
+
if (attributeDescriptor?.indexingPID === process.pid &&
|
|
1904
|
+
manageThreads.processIncarnation != null &&
|
|
1905
|
+
attributeDescriptor.indexingIncarnation !== manageThreads.processIncarnation)
|
|
1906
|
+
reindexReasons.push('abandoned-build(previous process incarnation)');
|
|
1847
1907
|
logger.info(`reindex ${databaseName}.${tableName}.${attribute.name}: reason=${reindexReasons.join(',') || 'unknown'}`);
|
|
1848
1908
|
// we only set indexing nulls to true if new or reindexing, we can't have partial indexing of null
|
|
1849
1909
|
attributesToIndex.push(attribute);
|
|
@@ -1856,12 +1916,22 @@ function table(tableDefinition) {
|
|
|
1856
1916
|
// workers / a reload would treat the still-partial index as ready and return incomplete results.
|
|
1857
1917
|
attribute.indexingPID = attributeDescriptor.indexingPID;
|
|
1858
1918
|
attribute.lastIndexedKey = attributeDescriptor.lastIndexedKey;
|
|
1919
|
+
if (attributeDescriptor.checkpointCertified !== undefined) {
|
|
1920
|
+
attribute.checkpointCertified = attributeDescriptor.checkpointCertified;
|
|
1921
|
+
attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
|
|
1922
|
+
}
|
|
1859
1923
|
// Carry the in-progress restart generation too, so persisting this metadata-only
|
|
1860
1924
|
// change doesn't drop it and break the crash-recovery trigger for the running backfill.
|
|
1861
1925
|
attribute.restartNumber = attributeDescriptor.restartNumber;
|
|
1926
|
+
attribute.indexingIncarnation = attributeDescriptor.indexingIncarnation;
|
|
1927
|
+
attribute.indexingBuildId = attributeDescriptor.indexingBuildId;
|
|
1862
1928
|
if (attributeDescriptor.indexingFailed)
|
|
1863
1929
|
attribute.indexingFailed = attributeDescriptor.indexingFailed;
|
|
1864
1930
|
}
|
|
1931
|
+
// The declared attribute never carries the stamp, so any rewrite of a descriptor that has
|
|
1932
|
+
// one would drop it and make a completed index look like a pre-stamp build.
|
|
1933
|
+
if (attribute.checkpointAlgorithm === undefined && attributeDescriptor?.checkpointAlgorithm !== undefined)
|
|
1934
|
+
attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
|
|
1865
1935
|
attributesDbi.put(dbiKey, attribute);
|
|
1866
1936
|
}
|
|
1867
1937
|
// If a migration is in progress (indexingPID set), any newly opened dbi must also
|
|
@@ -1892,7 +1962,10 @@ function table(tableDefinition) {
|
|
|
1892
1962
|
}
|
|
1893
1963
|
logger.trace(`${tableName} table loading, running index`);
|
|
1894
1964
|
if (attributesToIndex.length > 0 || indicesToRemove.length > 0) {
|
|
1895
|
-
|
|
1965
|
+
// captured before the backfill can rewrite the attributes
|
|
1966
|
+
const buildIds = new Map(attributesToIndex.map((attribute) => [attribute, attribute.indexingBuildId]));
|
|
1967
|
+
const markSettled = () => markAbandonedIndexBuild(Table, rootStore, buildIds);
|
|
1968
|
+
Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove).then(markSettled, markSettled);
|
|
1896
1969
|
}
|
|
1897
1970
|
else if (hasChanges)
|
|
1898
1971
|
signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
|
|
@@ -1964,40 +2037,238 @@ function canonicalizeIndexOptions(value) {
|
|
|
1964
2037
|
}
|
|
1965
2038
|
return value;
|
|
1966
2039
|
}
|
|
2040
|
+
// Bumped when a change alters which keys a checkpoint may certify. A descriptor stamped by any other
|
|
2041
|
+
// version resumes as uncertified (full rebuild) rather than being trusted, and a completed index keeps
|
|
2042
|
+
// the stamp of the build that wrote it.
|
|
2043
|
+
exports.CHECKPOINT_ALGORITHM = 2;
|
|
1967
2044
|
const MAX_OUTSTANDING_INDEXING = 1000;
|
|
1968
2045
|
const MIN_OUTSTANDING_INDEXING = 10;
|
|
2046
|
+
const INDEXING_YIELD_INTERVAL = 100;
|
|
2047
|
+
// A resumable checkpoint is written only after a flush (see flushIndexStores), at most once per period
|
|
2048
|
+
// and never before this many more records: the flush seals every column family in the database, so a
|
|
2049
|
+
// slow backfill must not impose the period's flush rate on unrelated tables.
|
|
2050
|
+
let indexingCheckpointPeriodMs = 5000;
|
|
2051
|
+
let indexingCheckpointMinRecords = 10000;
|
|
2052
|
+
function setIndexingCheckpointPeriod(ms, minRecords = indexingCheckpointMinRecords) {
|
|
2053
|
+
const previous = { ms: indexingCheckpointPeriodMs, minRecords: indexingCheckpointMinRecords };
|
|
2054
|
+
indexingCheckpointPeriodMs = ms;
|
|
2055
|
+
indexingCheckpointMinRecords = minRecords;
|
|
2056
|
+
return previous;
|
|
2057
|
+
}
|
|
2058
|
+
const yieldEventTurn = () => new Promise((resolve) => setImmediate(resolve));
|
|
2059
|
+
// RocksDB index stores have no WAL (openRocksDatabase defaults disableWAL), so a flush is what makes the
|
|
2060
|
+
// entries a checkpoint certifies durable. A flush only covers writes issued before it started, so a caller
|
|
2061
|
+
// never joins one in flight: it joins the next one, which every backfill on that database asking meanwhile
|
|
2062
|
+
// shares — at most one in flight and one queued.
|
|
2063
|
+
const indexingFlushes = new WeakMap();
|
|
2064
|
+
function flushIndexStores(rootStore) {
|
|
2065
|
+
if (!(rootStore instanceof rocksdb_js_1.RocksDatabase))
|
|
2066
|
+
return;
|
|
2067
|
+
let flushes = indexingFlushes.get(rootStore);
|
|
2068
|
+
if (!flushes)
|
|
2069
|
+
indexingFlushes.set(rootStore, (flushes = {}));
|
|
2070
|
+
if (flushes.queued)
|
|
2071
|
+
return flushes.queued;
|
|
2072
|
+
const start = () => {
|
|
2073
|
+
flushes.queued = undefined;
|
|
2074
|
+
const flush = rootStore.flush().finally(() => {
|
|
2075
|
+
if (flushes.inFlight === flush)
|
|
2076
|
+
flushes.inFlight = undefined;
|
|
2077
|
+
});
|
|
2078
|
+
flushes.inFlight = flush;
|
|
2079
|
+
return flush;
|
|
2080
|
+
};
|
|
2081
|
+
if (!flushes.inFlight)
|
|
2082
|
+
return start();
|
|
2083
|
+
return (flushes.queued = flushes.inFlight.then(start, start));
|
|
2084
|
+
}
|
|
2085
|
+
function resumeStartKey(attributes) {
|
|
2086
|
+
let start;
|
|
2087
|
+
for (const attribute of attributes) {
|
|
2088
|
+
if (attribute.lastIndexedKey == undefined)
|
|
2089
|
+
return undefined;
|
|
2090
|
+
if (start === undefined || (0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
|
|
2091
|
+
start = attribute.lastIndexedKey;
|
|
2092
|
+
}
|
|
2093
|
+
return start;
|
|
2094
|
+
}
|
|
2095
|
+
// Bounded, unlike the exclusiveLock() spin above: that one runs on the declaring path, where the
|
|
2096
|
+
// caller is waiting on the result and there is nothing useful to do without the lock. This one runs
|
|
2097
|
+
// after a backfill has already settled, so a holder that never releases would wedge the worker's
|
|
2098
|
+
// event loop for nothing. Giving up costs only the marker, and the next load of the table
|
|
2099
|
+
// re-triggers the build regardless.
|
|
2100
|
+
exports.ABANDONED_MARK_LOCK_TIMEOUT = 10000;
|
|
2101
|
+
const abandonedMarkLockWait = new Int32Array(new SharedArrayBuffer(4));
|
|
2102
|
+
// `timeout` is the test seam; production callers take the default.
|
|
2103
|
+
function tryAcquireUpdateAttributesLock(rootStore, timeout = exports.ABANDONED_MARK_LOCK_TIMEOUT) {
|
|
2104
|
+
if (rootStore.tryLock('update-attributes'))
|
|
2105
|
+
return true;
|
|
2106
|
+
const startTime = performance.now();
|
|
2107
|
+
let waitTime = 1;
|
|
2108
|
+
while (!rootStore.tryLock('update-attributes')) {
|
|
2109
|
+
const elapsed = performance.now() - startTime;
|
|
2110
|
+
if (elapsed >= timeout)
|
|
2111
|
+
return false;
|
|
2112
|
+
// Atomics.wait rather than a busy spin: the section this guards is synchronous, so the wait
|
|
2113
|
+
// blocks this thread either way, but sleeping does not burn a core while it does.
|
|
2114
|
+
if (elapsed >= 2) {
|
|
2115
|
+
Atomics.wait(abandonedMarkLockWait, 0, 0, Math.min(waitTime, timeout - elapsed));
|
|
2116
|
+
if (waitTime < 16)
|
|
2117
|
+
waitTime *= 2;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return true;
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Persists the failure marker for a build that ended without running one of runIndexing's own exit
|
|
2124
|
+
* paths, so something re-triggers it. Fenced on `indexingBuildId` inside the storage engine's catalog
|
|
2125
|
+
* serialization boundary, because a replacement generation (or another thread declaring different index
|
|
2126
|
+
* options) can claim the attribute before an outgoing build's promise settles, and marking that would fail
|
|
2127
|
+
* a live build. The fence read and write stay synchronous, and nothing here may throw because
|
|
2128
|
+
* `Table.indexingOperation` reaches operations-API callers.
|
|
2129
|
+
*/
|
|
2130
|
+
async function markAbandonedIndexBuild(Table, rootStore, buildIds) {
|
|
2131
|
+
for (const [attribute, buildId] of buildIds) {
|
|
2132
|
+
try {
|
|
2133
|
+
let marked;
|
|
2134
|
+
if (buildId == null || Table.dbisDB.getSync(attribute.key)?.indexingBuildId !== buildId)
|
|
2135
|
+
continue;
|
|
2136
|
+
const markIfOwned = () => {
|
|
2137
|
+
const descriptor = Table.dbisDB.getSync(attribute.key);
|
|
2138
|
+
if (descriptor?.indexingBuildId === buildId && !descriptor.indexingFailed) {
|
|
2139
|
+
Table.dbisDB.putSync(attribute.key, { ...descriptor, indexingFailed: true });
|
|
2140
|
+
marked = true;
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
2144
|
+
if (!tryAcquireUpdateAttributesLock(rootStore)) {
|
|
2145
|
+
logger.warn(`Could not mark the abandoned index build of ${Table.databaseName}.${Table.tableName}.${attribute.name}: ` +
|
|
2146
|
+
`timed out after ${exports.ABANDONED_MARK_LOCK_TIMEOUT}ms waiting for the exclusive 'update-attributes' lock. ` +
|
|
2147
|
+
`The index stays incomplete and the next load of the table re-triggers the backfill.`);
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
2150
|
+
try {
|
|
2151
|
+
markIfOwned();
|
|
2152
|
+
}
|
|
2153
|
+
finally {
|
|
2154
|
+
rootStore.unlock('update-attributes');
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
else {
|
|
2158
|
+
rootStore.transactionSync(markIfOwned);
|
|
2159
|
+
}
|
|
2160
|
+
if (marked)
|
|
2161
|
+
logger.warn(`Indexing of ${Table.databaseName}.${Table.tableName}.${attribute.name} ended without completing. ` +
|
|
2162
|
+
`The index stays incomplete and every query on the attribute reports it as not indexed yet; ` +
|
|
2163
|
+
`the next load of the table retries the backfill from the last checkpoint (indexingFailed=true).`);
|
|
2164
|
+
}
|
|
2165
|
+
catch (error) {
|
|
2166
|
+
// A store closed by shutdown is the common case, and it cannot be written to at all.
|
|
2167
|
+
try {
|
|
2168
|
+
logger.debug(`Could not mark the abandoned index build of ${Table.tableName}.${attribute.name}`, error);
|
|
2169
|
+
}
|
|
2170
|
+
catch { }
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
1969
2174
|
async function runIndexing(Table, attributes, indicesToRemove) {
|
|
2175
|
+
let checkpointing;
|
|
2176
|
+
let hadIndexingErrors = false;
|
|
2177
|
+
const attributeErrorReported = {};
|
|
2178
|
+
const onIndexPutRejected = (property, error) => {
|
|
2179
|
+
hadIndexingErrors = true;
|
|
2180
|
+
if (attributeErrorReported[property])
|
|
2181
|
+
return;
|
|
2182
|
+
attributeErrorReported[property] = true;
|
|
2183
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
2184
|
+
};
|
|
2185
|
+
const putRejectionHandlers = attributes.map((attribute) => (error) => onIndexPutRejected(attribute.name, error));
|
|
1970
2186
|
try {
|
|
1971
2187
|
logger.info(`Indexing ${Table.tableName} attributes`, attributes);
|
|
1972
2188
|
await signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
|
|
1973
2189
|
let lastResolution;
|
|
2190
|
+
// The checkpoint and completion barriers have to cover every mutation still in flight: any of them
|
|
2191
|
+
// may reject after those barriers read hadIndexingErrors.
|
|
2192
|
+
const pendingMutations = new Set();
|
|
2193
|
+
let settleWaiter;
|
|
2194
|
+
const track = (result, onRejected) => {
|
|
2195
|
+
if (!result?.then)
|
|
2196
|
+
return result;
|
|
2197
|
+
const tracked = result.then(() => {
|
|
2198
|
+
pendingMutations.delete(tracked);
|
|
2199
|
+
settleWaiter?.();
|
|
2200
|
+
return false;
|
|
2201
|
+
}, (error) => {
|
|
2202
|
+
pendingMutations.delete(tracked);
|
|
2203
|
+
settleWaiter?.();
|
|
2204
|
+
onRejected(error);
|
|
2205
|
+
return true;
|
|
2206
|
+
});
|
|
2207
|
+
pendingMutations.add(tracked);
|
|
2208
|
+
return result;
|
|
2209
|
+
};
|
|
2210
|
+
// The tracked promises absorb their own rejections, so one failure never abandons its siblings.
|
|
2211
|
+
const drainMutations = async () => {
|
|
2212
|
+
if (!pendingMutations.size)
|
|
2213
|
+
return false;
|
|
2214
|
+
return (await Promise.all([...pendingMutations])).some(Boolean);
|
|
2215
|
+
};
|
|
2216
|
+
// Waiting on a chosen entry would stall behind a slow one the others have already overtaken.
|
|
2217
|
+
const nextSettlement = () => new Promise((resolve) => {
|
|
2218
|
+
settleWaiter = () => {
|
|
2219
|
+
settleWaiter = undefined;
|
|
2220
|
+
resolve(undefined);
|
|
2221
|
+
};
|
|
2222
|
+
});
|
|
1974
2223
|
for (const index of indicesToRemove) {
|
|
1975
|
-
|
|
2224
|
+
track(index.drop(), (error) => onIndexPutRejected(index.name, error));
|
|
1976
2225
|
}
|
|
1977
2226
|
let interrupted;
|
|
1978
|
-
let hadIndexingErrors = false;
|
|
1979
|
-
const attributeErrorReported = {};
|
|
1980
2227
|
let indexed = 0;
|
|
1981
2228
|
const attributesLength = attributes.length;
|
|
1982
2229
|
await new Promise((resolve) => setImmediate(resolve)); // yield event turn, indexing should consistently take at least one event turn
|
|
1983
2230
|
if (attributesLength > 0) {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
if ((0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
|
|
1988
|
-
start = attribute.lastIndexedKey;
|
|
1989
|
-
if (attribute.lastIndexedKey == undefined) {
|
|
1990
|
-
// if we are starting from the beginning, clear out any previous index entries since we are rewriting
|
|
2231
|
+
const start = resumeStartKey(attributes);
|
|
2232
|
+
if (start === undefined) {
|
|
2233
|
+
for (const attribute of attributes) {
|
|
1991
2234
|
if (attribute.dbi.clearAsync) {
|
|
1992
|
-
// LMDB
|
|
1993
|
-
|
|
2235
|
+
// LMDB enqueues this ahead of the index writes, so the scan need not wait for it — but the
|
|
2236
|
+
// barriers must, or a rejected clear certifies a checkpoint over stale entries.
|
|
2237
|
+
track(attribute.dbi.clearAsync(), (error) => onIndexPutRejected(attribute.name, error));
|
|
1994
2238
|
}
|
|
1995
2239
|
else {
|
|
1996
2240
|
await attribute.dbi.clear();
|
|
1997
2241
|
}
|
|
1998
2242
|
}
|
|
1999
2243
|
}
|
|
2000
|
-
|
|
2244
|
+
// A resumed scan starts at the checkpoint, so it must only name a key whose every predecessor is
|
|
2245
|
+
// durably indexed: persisted once the writes it covers have settled and flushed, frozen after any
|
|
2246
|
+
// record fails so the retry re-covers it, and stamped with its own key (see the trigger in table()).
|
|
2247
|
+
const persistCheckpoint = async (key) => {
|
|
2248
|
+
if (hadIndexingErrors)
|
|
2249
|
+
return;
|
|
2250
|
+
try {
|
|
2251
|
+
// Everything still in flight was issued for a key at or before this one: the scan has not
|
|
2252
|
+
// moved past it yet. So a failure among them is a failure this checkpoint would cover.
|
|
2253
|
+
const failed = await drainMutations();
|
|
2254
|
+
if (failed)
|
|
2255
|
+
return;
|
|
2256
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
2257
|
+
const puts = [];
|
|
2258
|
+
for (const attribute of attributes) {
|
|
2259
|
+
attribute.lastIndexedKey = key;
|
|
2260
|
+
attribute.checkpointCertified = key;
|
|
2261
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
2262
|
+
puts.push(Table.dbisDB.put(attribute.key, attribute));
|
|
2263
|
+
}
|
|
2264
|
+
await Promise.all(puts);
|
|
2265
|
+
}
|
|
2266
|
+
catch (error) {
|
|
2267
|
+
logger.warn(`Could not persist the indexing checkpoint for ${Table.tableName}`, error);
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
let nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
2271
|
+
let nextCheckpointRecord = indexingCheckpointMinRecords;
|
|
2001
2272
|
// this means that a new attribute has been introduced that needs to be indexed
|
|
2002
2273
|
for (const { key, value: record } of Table.primaryStore.getRange({
|
|
2003
2274
|
start,
|
|
@@ -2005,96 +2276,90 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2005
2276
|
versions: true,
|
|
2006
2277
|
snapshot: false, // don't hold a read transaction this whole time
|
|
2007
2278
|
})) {
|
|
2008
|
-
|
|
2009
|
-
continue; // deletion entry
|
|
2279
|
+
const atInterval = ++indexed % INDEXING_YIELD_INTERVAL === 0;
|
|
2010
2280
|
// TODO: Do we ever need to interrupt due to a schema change that was not a restart?
|
|
2011
2281
|
//if (Table.schemaVersion !== schemaVersion) return; // break out if there are any schema changes and let someone else pick it up
|
|
2012
|
-
|
|
2013
|
-
//
|
|
2014
|
-
// outstanding-based yield below never fires for them. Track that this row did synchronous
|
|
2282
|
+
// Custom indexes (e.g. HNSW) index synchronously and leave pendingMutations empty, so the
|
|
2283
|
+
// backpressure yield below never fires for them. Track that this row did synchronous
|
|
2015
2284
|
// indexing work so we can still yield the event loop after it.
|
|
2016
2285
|
let didSynchronousIndexing = false;
|
|
2017
2286
|
// every index operation needs to be guarded by the version still be the same. If it has already changed before
|
|
2018
2287
|
// we index, that's fine because indexing is idempotent, we can just put the same values again. If it changes
|
|
2019
2288
|
// during the indexing, the indexing here will fail. This is also fine because it means the other thread will have
|
|
2020
2289
|
// performed indexing and we don't need to do anything further
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2290
|
+
if (record) {
|
|
2291
|
+
for (let i = 0; i < attributesLength; i++) {
|
|
2292
|
+
const attribute = attributes[i];
|
|
2293
|
+
const property = attribute.name;
|
|
2294
|
+
const index = attribute.dbi;
|
|
2295
|
+
const onPutRejected = putRejectionHandlers[i];
|
|
2296
|
+
try {
|
|
2297
|
+
const resolver = attribute.resolve;
|
|
2298
|
+
const value = record && (resolver ? resolver(record) : record[property]);
|
|
2299
|
+
if (index.customIndex) {
|
|
2300
|
+
index.customIndex.index(key, value);
|
|
2301
|
+
didSynchronousIndexing = true;
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
|
|
2305
|
+
if (values) {
|
|
2306
|
+
for (let i = 0, l = values.length; i < l; i++) {
|
|
2307
|
+
track(index.put(values[i], key), onPutRejected);
|
|
2308
|
+
}
|
|
2037
2309
|
}
|
|
2038
2310
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2311
|
+
catch (error) {
|
|
2312
|
+
hadIndexingErrors = true;
|
|
2313
|
+
if (!attributeErrorReported[property]) {
|
|
2314
|
+
// just report an indexing error once per attribute so we don't spam the logs.
|
|
2315
|
+
// A store closed by worker shutdown surfaces here as "Database not open"; that is
|
|
2316
|
+
// a benign interruption (the next generation re-runs the backfill), so don't log
|
|
2317
|
+
// it as an error — the outer catch returns quietly once the iterator also throws.
|
|
2318
|
+
attributeErrorReported[property] = true;
|
|
2319
|
+
if (Table.primaryStore?.rootStore?.status === 'closed')
|
|
2320
|
+
logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
|
|
2321
|
+
else
|
|
2322
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
2323
|
+
}
|
|
2052
2324
|
}
|
|
2053
2325
|
}
|
|
2054
2326
|
}
|
|
2055
|
-
(0, when_ts_1.when)(lastResolution, () => outstanding--, (error) => {
|
|
2056
|
-
outstanding--;
|
|
2057
|
-
hadIndexingErrors = true;
|
|
2058
|
-
logger.error(error);
|
|
2059
|
-
});
|
|
2060
2327
|
if (worker_threads_1.workerData && worker_threads_1.workerData.restartNumber !== manageThreads.restartNumber) {
|
|
2061
2328
|
interrupted = true;
|
|
2062
2329
|
}
|
|
2063
|
-
if (
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2330
|
+
if (interrupted) {
|
|
2331
|
+
await drainMutations();
|
|
2332
|
+
await checkpointing;
|
|
2333
|
+
await persistCheckpoint(key);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
if (atInterval && indexed >= nextCheckpointRecord && performance.now() >= nextCheckpointAt) {
|
|
2337
|
+
nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
2338
|
+
nextCheckpointRecord = indexed + indexingCheckpointMinRecords;
|
|
2339
|
+
await checkpointing;
|
|
2340
|
+
checkpointing = persistCheckpoint(key);
|
|
2071
2341
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
await new Promise((resolve) => setImmediate(resolve)); // custom indexes (e.g. HNSW) index synchronously and never raise `outstanding`; without this yield a large backfill runs in a single event-loop turn, starving keepalive/replication and queries and never letting the isIndexing flag be observed
|
|
2342
|
+
// Checked once per record, so a record's own fan-out can overshoot before the bound applies.
|
|
2343
|
+
while (pendingMutations.size > MAX_OUTSTANDING_INDEXING)
|
|
2344
|
+
await nextSettlement();
|
|
2345
|
+
if (atInterval || didSynchronousIndexing || pendingMutations.size > MIN_OUTSTANDING_INDEXING)
|
|
2346
|
+
await yieldEventTurn();
|
|
2078
2347
|
}
|
|
2079
2348
|
}
|
|
2080
|
-
|
|
2081
|
-
//
|
|
2082
|
-
|
|
2083
|
-
//
|
|
2084
|
-
//
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2349
|
+
await checkpointing;
|
|
2350
|
+
// A mutation that rejects after completion is declared has no build left to park.
|
|
2351
|
+
await drainMutations();
|
|
2352
|
+
// the tail since the last checkpoint is not durable until flushed; announcing the index complete
|
|
2353
|
+
// before that would outlive a crash that loses it
|
|
2354
|
+
if (!hadIndexingErrors) {
|
|
2355
|
+
try {
|
|
2356
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
2357
|
+
}
|
|
2358
|
+
catch (error) {
|
|
2359
|
+
hadIndexingErrors = true;
|
|
2360
|
+
logger.error(`Could not flush the indexes of ${Table.tableName} before marking them complete`, error);
|
|
2361
|
+
}
|
|
2093
2362
|
}
|
|
2094
|
-
// Yield one more event turn so any queued when() error callbacks (which fire as
|
|
2095
|
-
// microtasks when their tracked promise settles) have a chance to set hadIndexingErrors
|
|
2096
|
-
// before we decide whether to mark indexing as complete.
|
|
2097
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
2098
2363
|
if (hadIndexingErrors) {
|
|
2099
2364
|
// Some records failed to index. Persist the failure marker in the descriptor so
|
|
2100
2365
|
// the next call to table() (including after a restart with a fresh PID) re-triggers
|
|
@@ -2123,9 +2388,15 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2123
2388
|
// update the attributes to indicate that we are finished
|
|
2124
2389
|
for (const attribute of attributes) {
|
|
2125
2390
|
delete attribute.lastIndexedKey;
|
|
2391
|
+
delete attribute.checkpointCertified;
|
|
2392
|
+
// Survives completion, unlike the checkpoint fields: without it an index built here is
|
|
2393
|
+
// indistinguishable from one a release that could skip a failed record declared complete.
|
|
2394
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
2126
2395
|
delete attribute.indexingPID;
|
|
2127
2396
|
delete attribute.indexingFailed;
|
|
2128
2397
|
delete attribute.restartNumber;
|
|
2398
|
+
delete attribute.indexingIncarnation;
|
|
2399
|
+
delete attribute.indexingBuildId;
|
|
2129
2400
|
attribute.dbi.isIndexing = false;
|
|
2130
2401
|
// Also clear isIndexing on the currently-active dbi in Table.indices, which may
|
|
2131
2402
|
// differ from attribute.dbi if a resetDatabases() call during this migration
|
|
@@ -2142,6 +2413,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2142
2413
|
}
|
|
2143
2414
|
}
|
|
2144
2415
|
catch (error) {
|
|
2416
|
+
await checkpointing;
|
|
2145
2417
|
// A worker shutting down closes its stores mid-backfill, so the range iterator or a
|
|
2146
2418
|
// put throws (e.g. "Database not open" / "Iterator not initialized"). This is an
|
|
2147
2419
|
// interruption, not a data error: the next worker generation re-runs the backfill via
|