@harperfast/harper 5.2.8 → 5.2.10
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/config-root.schema.json +10 -0
- 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 +9 -0
- package/dist/resources/databases.js +315 -76
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/search.d.ts +1 -0
- package/dist/resources/search.js +41 -13
- 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/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/rocksMemoryConfig.js +7 -4
- package/dist/utility/rocksMemoryConfig.js.map +1 -1
- package/dist/validation/configValidator.js +2 -0
- package/dist/validation/configValidator.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 +296 -70
- package/resources/search.ts +43 -14
- package/server/storageReclamation.ts +10 -0
- package/server/threads/manageThreads.js +7 -0
- package/studio/web/assets/Chat-Br06zdMA.js +2267 -0
- package/studio/web/assets/FloatingChat-BWImX5fA.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-CrPb_98N.js → apiToken-DN0nmDsq.js} +1 -1
- package/studio/web/assets/applications-kSxVoyeU.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-D8ceA4av.js → cssMode-C1JeufH5.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-C-jlLd1G.js → editor-qoo9CrGO.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-BnuKFPjv.js → html-Dt4IIy04.js} +1 -1
- package/studio/web/assets/{htmlMode-DXM6YYUw.js → htmlMode-DXgKKr4C.js} +1 -1
- package/studio/web/assets/index-6onkYFOG.js +824 -0
- package/studio/web/assets/{index-Cxj2_wsl.css → index-7RMEgVG1.css} +1 -1
- package/studio/web/assets/index.lazy-BrCFnpNJ.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-CAuAkYxc.js → javascript-DNCQGUBc.js} +1 -1
- package/studio/web/assets/{journeyDiagram-EYS64GPL-B0ou8k0n.js → journeyDiagram-3NMN7TZE-CokIi6ll.js} +2 -2
- package/studio/web/assets/{jsonMode-Dny8hrU7.js → jsonMode-CR6HWruP.js} +1 -1
- package/studio/web/assets/{kanban-definition-3QL26DDD-uYg7iYzp.js → kanban-definition-UXKFOSKX-CukSFJfX.js} +1 -1
- package/studio/web/assets/{languageServices-D1uNqC6s.js → languageServices-BM4fI4rS.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-Bh-bvkyK.js → lspLanguageFeatures-DSa1ttcD.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-DsE0S3zT.js → notifications-BHXLnh6x.js} +1 -1
- package/studio/web/assets/notifications-CUmtIA6z.js +1 -0
- 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-BIeCZr-w.js → profile-8BeFSF3j.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-B5lGduv5.js → regions-C8qR0HhD.js} +1 -1
- package/studio/web/assets/{register-Cvc1TpmQ.js → register-B4n5i0SD.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-D8ZLDWhU.js → setComponentFile-g0_B0lgX.js} +1 -1
- package/studio/web/assets/{setup-D2az-Ue_.js → setup-B0CTj_Q5.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-C6Yib7-K.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-BdIEw2rK.js → toggleHighContrast-Dgta7bVi.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-CulNMwI9.js → tsMode-CH_jHvU-.js} +1 -1
- package/studio/web/assets/{typescript-BpRqjmvQ.js → typescript-Co9LCXd5.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-C7x-Yz9r.js → useEntityRestURL-wKC8NsC_.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-B2WpJDK9.js → workers-CWeLxCXA.js} +1 -1
- package/studio/web/assets/x-DIzaLEdK.js +1 -0
- package/studio/web/assets/{xml-CZnimVSX.js → xml-BadC-0Rk.js} +1 -1
- package/studio/web/assets/{xychartDiagram-S5SC5T6Z-CoKALMXr.js → xychartDiagram-S5SC5T6Z-Biok4GYV.js} +1 -1
- package/studio/web/assets/{yaml-BwNuyZS-.js → yaml-BiUfxPbC.js} +1 -1
- package/studio/web/index.html +14 -14
- package/utility/hdbTerms.ts +2 -0
- package/utility/rocksMemoryConfig.ts +7 -4
- package/validation/configValidator.ts +2 -0
- package/studio/web/assets/Chat-DDlysEi_.js +0 -2067
- package/studio/web/assets/FloatingChat-DdJ3DZJX.js +0 -23
- package/studio/web/assets/applications-C6Y4nL_X.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-BtTvoLf8.js +0 -824
- package/studio/web/assets/index.lazy-PVbC6xQh.js +0 -2
- package/studio/web/assets/notifications-DPTOF-SX.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-BJegPrWN.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.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");
|
|
@@ -321,6 +326,23 @@ const rocksdbDatabaseEnvs = new Map();
|
|
|
321
326
|
(0, globals_js_1._assignPackageExport)('databases', exports.databases);
|
|
322
327
|
(0, globals_js_1._assignPackageExport)('tables', exports.tables);
|
|
323
328
|
const NEXT_TABLE_ID = Symbol.for('next-table-id');
|
|
329
|
+
/**
|
|
330
|
+
* True when a descriptor claims an index build no live operation in this process can own. The PID and
|
|
331
|
+
* worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
|
|
332
|
+
* generation back at 1 while the persisted one is higher. A descriptor with no incarnation was written
|
|
333
|
+
* before the field existed, so it belongs to an earlier process; a thread started without one of its
|
|
334
|
+
* own cannot judge, and falls back rather than declaring a live build dead.
|
|
335
|
+
*/
|
|
336
|
+
function isAbandonedIndexBuild(descriptor, currentRestartGeneration) {
|
|
337
|
+
if (!descriptor)
|
|
338
|
+
return false;
|
|
339
|
+
if (descriptor.indexingPID && descriptor.indexingPID !== process.pid)
|
|
340
|
+
return true;
|
|
341
|
+
if (descriptor.restartNumber < currentRestartGeneration)
|
|
342
|
+
return true;
|
|
343
|
+
const incarnation = manageThreads.processIncarnation;
|
|
344
|
+
return !!descriptor.indexingPID && incarnation != null && descriptor.indexingIncarnation !== incarnation;
|
|
345
|
+
}
|
|
324
346
|
// How many times the schema load will try to finish a tombstoned drop before
|
|
325
347
|
// giving up for the rest of this process's lifetime. A drop that fails once
|
|
326
348
|
// almost always fails identically forever - the usual cause is a RocksDB
|
|
@@ -590,6 +612,7 @@ function readMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAME, au
|
|
|
590
612
|
rootStore = (0, lmdb_1.open)(envInit);
|
|
591
613
|
lmdbDatabaseEnvs.set(path, rootStore);
|
|
592
614
|
}
|
|
615
|
+
rootStore.dbisDb?.resetReadTxn();
|
|
593
616
|
return initStores(path, rootStore, databaseName, defaultTable, auditPath, isLegacy);
|
|
594
617
|
}
|
|
595
618
|
catch (error) {
|
|
@@ -835,6 +858,8 @@ function initStores(path, rootStore, databaseName, defaultTable, auditPath, isLe
|
|
|
835
858
|
indices[attribute.name] = dbi;
|
|
836
859
|
indices[attribute.name].indexNulls = attribute.indexNulls;
|
|
837
860
|
}
|
|
861
|
+
// the only way a thread that never declares the schema reaches Table.indices
|
|
862
|
+
indices[attribute.name].isIndexing = !!attribute.indexingPID;
|
|
838
863
|
const existingAttribute = existingAttributes.find((existingAttribute) => existingAttribute.name === attribute.name);
|
|
839
864
|
if (existingAttribute)
|
|
840
865
|
existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1, attribute);
|
|
@@ -1147,6 +1172,10 @@ async function dropDatabase(databaseName) {
|
|
|
1147
1172
|
delete exports.databases[databaseName];
|
|
1148
1173
|
exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
|
|
1149
1174
|
if (rootStore) {
|
|
1175
|
+
// awaited: retirement stops the loop admitting work, the barrier is what says the pass that was
|
|
1176
|
+
// already running has released the stores this is about to close and unlink
|
|
1177
|
+
await rootStore.auditStore?.stopAuditCleanup?.();
|
|
1178
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1150
1179
|
if (rootStore.status === 'open') {
|
|
1151
1180
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1152
1181
|
rootStore.close();
|
|
@@ -1164,6 +1193,8 @@ async function dropDatabase(databaseName) {
|
|
|
1164
1193
|
// the drop lock now (still before any destructive step)
|
|
1165
1194
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase)
|
|
1166
1195
|
lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
|
|
1196
|
+
await rootStore.auditStore?.stopAuditCleanup?.();
|
|
1197
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1167
1198
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1168
1199
|
rootStore.close();
|
|
1169
1200
|
rootStore.destroy();
|
|
@@ -1206,10 +1237,6 @@ function closeDatabase(databaseName) {
|
|
|
1206
1237
|
continue;
|
|
1207
1238
|
if (table.primaryStore.rootStore)
|
|
1208
1239
|
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
1240
|
}
|
|
1214
1241
|
// a database with no tables (an empty schema, or one whose tables were all dropped) still holds
|
|
1215
1242
|
// an open root store, tracked only on the defined-database entry rather than any table — include
|
|
@@ -1217,7 +1244,23 @@ function closeDatabase(databaseName) {
|
|
|
1217
1244
|
const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
|
|
1218
1245
|
if (definedRoot)
|
|
1219
1246
|
rootStores.add(definedRoot);
|
|
1247
|
+
// before any table store closes, so no further pass is admitted. This is synchronous, so it cannot
|
|
1248
|
+
// await the drain barrier stopAuditCleanup() returns; what covers it is the in-pass status checks,
|
|
1249
|
+
// plus the fact that its production callers reach it only for RocksDB databases, whose pass is one
|
|
1250
|
+
// synchronous purgeLogs() call with nothing suspended mid-removal.
|
|
1251
|
+
for (const rootStore of rootStores)
|
|
1252
|
+
rootStore.auditStore?.stopAuditCleanup?.();
|
|
1253
|
+
for (const tableName in dbTables) {
|
|
1254
|
+
const table = dbTables[tableName];
|
|
1255
|
+
if (!table?.primaryStore)
|
|
1256
|
+
continue;
|
|
1257
|
+
for (const indexName in table.indices || {}) {
|
|
1258
|
+
closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
|
|
1259
|
+
}
|
|
1260
|
+
closeStore(table.primaryStore, `table ${tableName}`);
|
|
1261
|
+
}
|
|
1220
1262
|
for (const rootStore of rootStores) {
|
|
1263
|
+
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1221
1264
|
closeStore(rootStore.dbisDb, 'attributes store');
|
|
1222
1265
|
closeStore(rootStore, 'root store');
|
|
1223
1266
|
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
@@ -1778,15 +1821,13 @@ function table(tableDefinition) {
|
|
|
1778
1821
|
if (changed ||
|
|
1779
1822
|
indexFormatNeedsPersist ||
|
|
1780
1823
|
attributeDescriptor?.indexingFailed ||
|
|
1781
|
-
(attributeDescriptor
|
|
1782
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
1824
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
1783
1825
|
hasChanges = true;
|
|
1784
1826
|
exclusiveLock();
|
|
1785
1827
|
attributeDescriptor = attributesDbi.getSync(dbiKey);
|
|
1786
1828
|
if (structurallyChanged ||
|
|
1787
1829
|
attributeDescriptor?.indexingFailed ||
|
|
1788
|
-
(attributeDescriptor
|
|
1789
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
1830
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
1790
1831
|
hasChanges = true;
|
|
1791
1832
|
if (attribute.indexNulls === undefined)
|
|
1792
1833
|
attribute.indexNulls = true;
|
|
@@ -1805,9 +1846,17 @@ function table(tableDefinition) {
|
|
|
1805
1846
|
// representation-only option difference — preserve the checkpoint so the backfill
|
|
1806
1847
|
// resumes rather than restarts. Canonicalized to match structurallyChanged above.
|
|
1807
1848
|
const indexOptionsChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1849
|
+
// Only a checkpoint runIndexing stamped with its own key resumes: earlier releases advanced
|
|
1850
|
+
// lastIndexedKey past failed and unflushed index writes, so any other is a full rebuild.
|
|
1851
|
+
const uncertifiedCheckpoint = attributeDescriptor?.lastIndexedKey !== undefined &&
|
|
1852
|
+
(attributeDescriptor.checkpointCertified === undefined ||
|
|
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;
|
|
1811
1860
|
// Explicit reindex is the upgrade path from a legacy (un-versioned) custom-index
|
|
1812
1861
|
// object store to the versioned, VT-cacheable format. A full rebuild from scratch
|
|
1813
1862
|
// (lastIndexedKey === undefined) clears the store and rewrites every node, so the
|
|
@@ -1829,6 +1878,9 @@ function table(tableDefinition) {
|
|
|
1829
1878
|
// the new process reuses the old PID. Cleared on clean completion; left in place
|
|
1830
1879
|
// on failure/crash so the next, higher-numbered restart re-triggers the backfill.
|
|
1831
1880
|
attribute.restartNumber = currentRestartGeneration;
|
|
1881
|
+
if (manageThreads.processIncarnation != null)
|
|
1882
|
+
attribute.indexingIncarnation = manageThreads.processIncarnation;
|
|
1883
|
+
attribute.indexingBuildId = (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
1832
1884
|
delete attribute.indexingFailed; // clear failure flag for the new run
|
|
1833
1885
|
dbi.isIndexing = true;
|
|
1834
1886
|
Object.defineProperty(attribute, 'dbi', { value: dbi, configurable: true, enumerable: false });
|
|
@@ -1844,6 +1896,12 @@ function table(tableDefinition) {
|
|
|
1844
1896
|
reindexReasons.push(`crash-recovery(pid=${attributeDescriptor.indexingPID})`);
|
|
1845
1897
|
if (attributeDescriptor?.restartNumber < currentRestartGeneration)
|
|
1846
1898
|
reindexReasons.push('restart-number');
|
|
1899
|
+
if (uncertifiedCheckpoint)
|
|
1900
|
+
reindexReasons.push('uncertified-checkpoint');
|
|
1901
|
+
if (attributeDescriptor?.indexingPID === process.pid &&
|
|
1902
|
+
manageThreads.processIncarnation != null &&
|
|
1903
|
+
attributeDescriptor.indexingIncarnation !== manageThreads.processIncarnation)
|
|
1904
|
+
reindexReasons.push('abandoned-build(previous process incarnation)');
|
|
1847
1905
|
logger.info(`reindex ${databaseName}.${tableName}.${attribute.name}: reason=${reindexReasons.join(',') || 'unknown'}`);
|
|
1848
1906
|
// we only set indexing nulls to true if new or reindexing, we can't have partial indexing of null
|
|
1849
1907
|
attributesToIndex.push(attribute);
|
|
@@ -1856,9 +1914,13 @@ function table(tableDefinition) {
|
|
|
1856
1914
|
// workers / a reload would treat the still-partial index as ready and return incomplete results.
|
|
1857
1915
|
attribute.indexingPID = attributeDescriptor.indexingPID;
|
|
1858
1916
|
attribute.lastIndexedKey = attributeDescriptor.lastIndexedKey;
|
|
1917
|
+
if (attributeDescriptor.checkpointCertified !== undefined)
|
|
1918
|
+
attribute.checkpointCertified = attributeDescriptor.checkpointCertified;
|
|
1859
1919
|
// Carry the in-progress restart generation too, so persisting this metadata-only
|
|
1860
1920
|
// change doesn't drop it and break the crash-recovery trigger for the running backfill.
|
|
1861
1921
|
attribute.restartNumber = attributeDescriptor.restartNumber;
|
|
1922
|
+
attribute.indexingIncarnation = attributeDescriptor.indexingIncarnation;
|
|
1923
|
+
attribute.indexingBuildId = attributeDescriptor.indexingBuildId;
|
|
1862
1924
|
if (attributeDescriptor.indexingFailed)
|
|
1863
1925
|
attribute.indexingFailed = attributeDescriptor.indexingFailed;
|
|
1864
1926
|
}
|
|
@@ -1892,7 +1954,10 @@ function table(tableDefinition) {
|
|
|
1892
1954
|
}
|
|
1893
1955
|
logger.trace(`${tableName} table loading, running index`);
|
|
1894
1956
|
if (attributesToIndex.length > 0 || indicesToRemove.length > 0) {
|
|
1895
|
-
|
|
1957
|
+
// captured before the backfill can rewrite the attributes
|
|
1958
|
+
const buildIds = new Map(attributesToIndex.map((attribute) => [attribute, attribute.indexingBuildId]));
|
|
1959
|
+
const markSettled = () => markAbandonedIndexBuild(Table, rootStore, buildIds);
|
|
1960
|
+
Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove).then(markSettled, markSettled);
|
|
1896
1961
|
}
|
|
1897
1962
|
else if (hasChanges)
|
|
1898
1963
|
signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
|
|
@@ -1966,28 +2031,163 @@ function canonicalizeIndexOptions(value) {
|
|
|
1966
2031
|
}
|
|
1967
2032
|
const MAX_OUTSTANDING_INDEXING = 1000;
|
|
1968
2033
|
const MIN_OUTSTANDING_INDEXING = 10;
|
|
2034
|
+
const INDEXING_YIELD_INTERVAL = 100;
|
|
2035
|
+
// A resumable checkpoint is written only after a flush (see flushIndexStores), at most once per period
|
|
2036
|
+
// and never before this many more records: the flush seals every column family in the database, so a
|
|
2037
|
+
// slow backfill must not impose the period's flush rate on unrelated tables.
|
|
2038
|
+
let indexingCheckpointPeriodMs = 5000;
|
|
2039
|
+
let indexingCheckpointMinRecords = 10000;
|
|
2040
|
+
function setIndexingCheckpointPeriod(ms, minRecords = indexingCheckpointMinRecords) {
|
|
2041
|
+
const previous = { ms: indexingCheckpointPeriodMs, minRecords: indexingCheckpointMinRecords };
|
|
2042
|
+
indexingCheckpointPeriodMs = ms;
|
|
2043
|
+
indexingCheckpointMinRecords = minRecords;
|
|
2044
|
+
return previous;
|
|
2045
|
+
}
|
|
2046
|
+
const yieldEventTurn = () => new Promise((resolve) => setImmediate(resolve));
|
|
2047
|
+
// RocksDB index stores have no WAL (openRocksDatabase defaults disableWAL), so a flush is what makes the
|
|
2048
|
+
// entries a checkpoint certifies durable. A flush only covers writes issued before it started, so a caller
|
|
2049
|
+
// never joins one in flight: it joins the next one, which every backfill on that database asking meanwhile
|
|
2050
|
+
// shares — at most one in flight and one queued.
|
|
2051
|
+
const indexingFlushes = new WeakMap();
|
|
2052
|
+
function flushIndexStores(rootStore) {
|
|
2053
|
+
if (!(rootStore instanceof rocksdb_js_1.RocksDatabase))
|
|
2054
|
+
return;
|
|
2055
|
+
let flushes = indexingFlushes.get(rootStore);
|
|
2056
|
+
if (!flushes)
|
|
2057
|
+
indexingFlushes.set(rootStore, (flushes = {}));
|
|
2058
|
+
if (flushes.queued)
|
|
2059
|
+
return flushes.queued;
|
|
2060
|
+
const start = () => {
|
|
2061
|
+
flushes.queued = undefined;
|
|
2062
|
+
const flush = rootStore.flush().finally(() => {
|
|
2063
|
+
if (flushes.inFlight === flush)
|
|
2064
|
+
flushes.inFlight = undefined;
|
|
2065
|
+
});
|
|
2066
|
+
flushes.inFlight = flush;
|
|
2067
|
+
return flush;
|
|
2068
|
+
};
|
|
2069
|
+
if (!flushes.inFlight)
|
|
2070
|
+
return start();
|
|
2071
|
+
return (flushes.queued = flushes.inFlight.then(start, start));
|
|
2072
|
+
}
|
|
2073
|
+
function resumeStartKey(attributes) {
|
|
2074
|
+
let start;
|
|
2075
|
+
for (const attribute of attributes) {
|
|
2076
|
+
if (attribute.lastIndexedKey == undefined)
|
|
2077
|
+
return undefined;
|
|
2078
|
+
if (start === undefined || (0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
|
|
2079
|
+
start = attribute.lastIndexedKey;
|
|
2080
|
+
}
|
|
2081
|
+
return start;
|
|
2082
|
+
}
|
|
2083
|
+
// Bounded, unlike the exclusiveLock() spin above: that one runs on the declaring path, where the
|
|
2084
|
+
// caller is waiting on the result and there is nothing useful to do without the lock. This one runs
|
|
2085
|
+
// after a backfill has already settled, so a holder that never releases would wedge the worker's
|
|
2086
|
+
// event loop for nothing. Giving up costs only the marker, and the next load of the table
|
|
2087
|
+
// re-triggers the build regardless.
|
|
2088
|
+
exports.ABANDONED_MARK_LOCK_TIMEOUT = 10000;
|
|
2089
|
+
const abandonedMarkLockWait = new Int32Array(new SharedArrayBuffer(4));
|
|
2090
|
+
// `timeout` is the test seam; production callers take the default.
|
|
2091
|
+
function tryAcquireUpdateAttributesLock(rootStore, timeout = exports.ABANDONED_MARK_LOCK_TIMEOUT) {
|
|
2092
|
+
if (rootStore.tryLock('update-attributes'))
|
|
2093
|
+
return true;
|
|
2094
|
+
const startTime = performance.now();
|
|
2095
|
+
let waitTime = 1;
|
|
2096
|
+
while (!rootStore.tryLock('update-attributes')) {
|
|
2097
|
+
const elapsed = performance.now() - startTime;
|
|
2098
|
+
if (elapsed >= timeout)
|
|
2099
|
+
return false;
|
|
2100
|
+
// Atomics.wait rather than a busy spin: the section this guards is synchronous, so the wait
|
|
2101
|
+
// blocks this thread either way, but sleeping does not burn a core while it does.
|
|
2102
|
+
if (elapsed >= 2) {
|
|
2103
|
+
Atomics.wait(abandonedMarkLockWait, 0, 0, Math.min(waitTime, timeout - elapsed));
|
|
2104
|
+
if (waitTime < 16)
|
|
2105
|
+
waitTime *= 2;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return true;
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Persists the failure marker for a build that ended without running one of runIndexing's own exit
|
|
2112
|
+
* paths, so something re-triggers it. Fenced on `indexingBuildId` inside the storage engine's catalog
|
|
2113
|
+
* serialization boundary, because a replacement generation (or another thread declaring different index
|
|
2114
|
+
* options) can claim the attribute before an outgoing build's promise settles, and marking that would fail
|
|
2115
|
+
* a live build. The fence read and write stay synchronous, and nothing here may throw because
|
|
2116
|
+
* `Table.indexingOperation` reaches operations-API callers.
|
|
2117
|
+
*/
|
|
2118
|
+
async function markAbandonedIndexBuild(Table, rootStore, buildIds) {
|
|
2119
|
+
for (const [attribute, buildId] of buildIds) {
|
|
2120
|
+
try {
|
|
2121
|
+
let marked;
|
|
2122
|
+
if (buildId == null || Table.dbisDB.getSync(attribute.key)?.indexingBuildId !== buildId)
|
|
2123
|
+
continue;
|
|
2124
|
+
const markIfOwned = () => {
|
|
2125
|
+
const descriptor = Table.dbisDB.getSync(attribute.key);
|
|
2126
|
+
if (descriptor?.indexingBuildId === buildId && !descriptor.indexingFailed) {
|
|
2127
|
+
Table.dbisDB.putSync(attribute.key, { ...descriptor, indexingFailed: true });
|
|
2128
|
+
marked = true;
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
2132
|
+
if (!tryAcquireUpdateAttributesLock(rootStore)) {
|
|
2133
|
+
logger.warn(`Could not mark the abandoned index build of ${Table.databaseName}.${Table.tableName}.${attribute.name}: ` +
|
|
2134
|
+
`timed out after ${exports.ABANDONED_MARK_LOCK_TIMEOUT}ms waiting for the exclusive 'update-attributes' lock. ` +
|
|
2135
|
+
`The index stays incomplete and the next load of the table re-triggers the backfill.`);
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
try {
|
|
2139
|
+
markIfOwned();
|
|
2140
|
+
}
|
|
2141
|
+
finally {
|
|
2142
|
+
rootStore.unlock('update-attributes');
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
else {
|
|
2146
|
+
rootStore.transactionSync(markIfOwned);
|
|
2147
|
+
}
|
|
2148
|
+
if (marked)
|
|
2149
|
+
logger.warn(`Indexing of ${Table.databaseName}.${Table.tableName}.${attribute.name} ended without completing. ` +
|
|
2150
|
+
`The index stays incomplete and every query on the attribute reports it as not indexed yet; ` +
|
|
2151
|
+
`the next load of the table retries the backfill from the last checkpoint (indexingFailed=true).`);
|
|
2152
|
+
}
|
|
2153
|
+
catch (error) {
|
|
2154
|
+
// A store closed by shutdown is the common case, and it cannot be written to at all.
|
|
2155
|
+
try {
|
|
2156
|
+
logger.debug(`Could not mark the abandoned index build of ${Table.tableName}.${attribute.name}`, error);
|
|
2157
|
+
}
|
|
2158
|
+
catch { }
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
1969
2162
|
async function runIndexing(Table, attributes, indicesToRemove) {
|
|
2163
|
+
let checkpointing;
|
|
2164
|
+
let hadIndexingErrors = false;
|
|
2165
|
+
const attributeErrorReported = {};
|
|
2166
|
+
const onIndexPutRejected = (property, error) => {
|
|
2167
|
+
hadIndexingErrors = true;
|
|
2168
|
+
if (attributeErrorReported[property])
|
|
2169
|
+
return;
|
|
2170
|
+
attributeErrorReported[property] = true;
|
|
2171
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
2172
|
+
};
|
|
2173
|
+
const putRejectionHandlers = attributes.map((attribute) => (error) => onIndexPutRejected(attribute.name, error));
|
|
1970
2174
|
try {
|
|
1971
2175
|
logger.info(`Indexing ${Table.tableName} attributes`, attributes);
|
|
1972
2176
|
await signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
|
|
1973
2177
|
let lastResolution;
|
|
1974
2178
|
for (const index of indicesToRemove) {
|
|
1975
2179
|
lastResolution = index.drop();
|
|
2180
|
+
if (lastResolution?.then)
|
|
2181
|
+
lastResolution.then(undefined, (error) => onIndexPutRejected(index.name, error));
|
|
1976
2182
|
}
|
|
1977
2183
|
let interrupted;
|
|
1978
|
-
let hadIndexingErrors = false;
|
|
1979
|
-
const attributeErrorReported = {};
|
|
1980
2184
|
let indexed = 0;
|
|
1981
2185
|
const attributesLength = attributes.length;
|
|
1982
2186
|
await new Promise((resolve) => setImmediate(resolve)); // yield event turn, indexing should consistently take at least one event turn
|
|
1983
2187
|
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
|
|
2188
|
+
const start = resumeStartKey(attributes);
|
|
2189
|
+
if (start === undefined) {
|
|
2190
|
+
for (const attribute of attributes) {
|
|
1991
2191
|
if (attribute.dbi.clearAsync) {
|
|
1992
2192
|
// LMDB, note that we don't need to wait for this to complete, just gets enqueued in front of the other writes
|
|
1993
2193
|
attribute.dbi.clearAsync();
|
|
@@ -1998,6 +2198,28 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
1998
2198
|
}
|
|
1999
2199
|
}
|
|
2000
2200
|
let outstanding = 0;
|
|
2201
|
+
// A resumed scan starts at the checkpoint, so it must only name a key whose every predecessor is
|
|
2202
|
+
// durably indexed: persisted once the writes it covers have settled and flushed, frozen after any
|
|
2203
|
+
// record fails so the retry re-covers it, and stamped with its own key (see the trigger in table()).
|
|
2204
|
+
const persistCheckpoint = async (key) => {
|
|
2205
|
+
if (hadIndexingErrors)
|
|
2206
|
+
return;
|
|
2207
|
+
try {
|
|
2208
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
2209
|
+
const puts = [];
|
|
2210
|
+
for (const attribute of attributes) {
|
|
2211
|
+
attribute.lastIndexedKey = key;
|
|
2212
|
+
attribute.checkpointCertified = key;
|
|
2213
|
+
puts.push(Table.dbisDB.put(attribute.key, attribute));
|
|
2214
|
+
}
|
|
2215
|
+
await Promise.all(puts);
|
|
2216
|
+
}
|
|
2217
|
+
catch (error) {
|
|
2218
|
+
logger.warn(`Could not persist the indexing checkpoint for ${Table.tableName}`, error);
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
let nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
2222
|
+
let nextCheckpointRecord = indexingCheckpointMinRecords;
|
|
2001
2223
|
// this means that a new attribute has been introduced that needs to be indexed
|
|
2002
2224
|
for (const { key, value: record } of Table.primaryStore.getRange({
|
|
2003
2225
|
start,
|
|
@@ -2005,8 +2227,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2005
2227
|
versions: true,
|
|
2006
2228
|
snapshot: false, // don't hold a read transaction this whole time
|
|
2007
2229
|
})) {
|
|
2008
|
-
|
|
2009
|
-
continue; // deletion entry
|
|
2230
|
+
const atInterval = ++indexed % INDEXING_YIELD_INTERVAL === 0;
|
|
2010
2231
|
// TODO: Do we ever need to interrupt due to a schema change that was not a restart?
|
|
2011
2232
|
//if (Table.schemaVersion !== schemaVersion) return; // break out if there are any schema changes and let someone else pick it up
|
|
2012
2233
|
outstanding++;
|
|
@@ -2018,72 +2239,75 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2018
2239
|
// we index, that's fine because indexing is idempotent, we can just put the same values again. If it changes
|
|
2019
2240
|
// during the indexing, the indexing here will fail. This is also fine because it means the other thread will have
|
|
2020
2241
|
// 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
|
-
|
|
2242
|
+
if (record) {
|
|
2243
|
+
for (let i = 0; i < attributesLength; i++) {
|
|
2244
|
+
const attribute = attributes[i];
|
|
2245
|
+
const property = attribute.name;
|
|
2246
|
+
const index = attribute.dbi;
|
|
2247
|
+
const onPutRejected = putRejectionHandlers[i];
|
|
2248
|
+
try {
|
|
2249
|
+
const resolver = attribute.resolve;
|
|
2250
|
+
const value = record && (resolver ? resolver(record) : record[property]);
|
|
2251
|
+
if (index.customIndex) {
|
|
2252
|
+
index.customIndex.index(key, value);
|
|
2253
|
+
didSynchronousIndexing = true;
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
|
|
2257
|
+
if (values) {
|
|
2258
|
+
for (let i = 0, l = values.length; i < l; i++) {
|
|
2259
|
+
lastResolution = index.put(values[i], key);
|
|
2260
|
+
if (lastResolution?.then)
|
|
2261
|
+
lastResolution.then(undefined, onPutRejected);
|
|
2262
|
+
}
|
|
2037
2263
|
}
|
|
2038
2264
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2265
|
+
catch (error) {
|
|
2266
|
+
hadIndexingErrors = true;
|
|
2267
|
+
if (!attributeErrorReported[property]) {
|
|
2268
|
+
// just report an indexing error once per attribute so we don't spam the logs.
|
|
2269
|
+
// A store closed by worker shutdown surfaces here as "Database not open"; that is
|
|
2270
|
+
// a benign interruption (the next generation re-runs the backfill), so don't log
|
|
2271
|
+
// it as an error — the outer catch returns quietly once the iterator also throws.
|
|
2272
|
+
attributeErrorReported[property] = true;
|
|
2273
|
+
if (Table.primaryStore?.rootStore?.status === 'closed')
|
|
2274
|
+
logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
|
|
2275
|
+
else
|
|
2276
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
2277
|
+
}
|
|
2052
2278
|
}
|
|
2053
2279
|
}
|
|
2054
2280
|
}
|
|
2055
|
-
(0, when_ts_1.when)(lastResolution, () => outstanding--, (
|
|
2056
|
-
outstanding--;
|
|
2057
|
-
hadIndexingErrors = true;
|
|
2058
|
-
logger.error(error);
|
|
2059
|
-
});
|
|
2281
|
+
(0, when_ts_1.when)(lastResolution, () => outstanding--, () => outstanding--);
|
|
2060
2282
|
if (worker_threads_1.workerData && worker_threads_1.workerData.restartNumber !== manageThreads.restartNumber) {
|
|
2061
2283
|
interrupted = true;
|
|
2062
2284
|
}
|
|
2063
|
-
if (
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
attribute.lastIndexedKey = key;
|
|
2067
|
-
Table.dbisDB.put(attribute.key, attribute);
|
|
2285
|
+
if (interrupted) {
|
|
2286
|
+
try {
|
|
2287
|
+
await lastResolution;
|
|
2068
2288
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2289
|
+
catch {
|
|
2290
|
+
// already counted and logged by the rejection handler above
|
|
2291
|
+
}
|
|
2292
|
+
await checkpointing;
|
|
2293
|
+
await persistCheckpoint(key);
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
if (atInterval && indexed >= nextCheckpointRecord && performance.now() >= nextCheckpointAt) {
|
|
2297
|
+
nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
2298
|
+
nextCheckpointRecord = indexed + indexingCheckpointMinRecords;
|
|
2299
|
+
await checkpointing;
|
|
2300
|
+
checkpointing = (0, when_ts_1.when)(lastResolution, () => persistCheckpoint(key), () => { });
|
|
2071
2301
|
}
|
|
2072
2302
|
if (outstanding > MAX_OUTSTANDING_INDEXING)
|
|
2073
2303
|
await lastResolution;
|
|
2074
|
-
|
|
2075
|
-
await
|
|
2076
|
-
else if (didSynchronousIndexing)
|
|
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
|
|
2304
|
+
if (atInterval || didSynchronousIndexing || outstanding > MIN_OUTSTANDING_INDEXING)
|
|
2305
|
+
await yieldEventTurn();
|
|
2078
2306
|
}
|
|
2079
2307
|
}
|
|
2080
|
-
|
|
2081
|
-
//
|
|
2082
|
-
//
|
|
2083
|
-
// itself rejects (i.e. the very last put in the loop failed) which would otherwise
|
|
2084
|
-
// throw past the hadIndexingErrors check to the outer catch. The broader issue of
|
|
2085
|
-
// unhandled rejections from non-last puts in multi-value attributes is pre-existing
|
|
2086
|
-
// and out of scope for this fix.
|
|
2308
|
+
await checkpointing;
|
|
2309
|
+
// Await the last pending put. If it rejects, that is also an indexing error (already counted by
|
|
2310
|
+
// onIndexPutRejected); catching it here keeps it from escaping to the outer catch.
|
|
2087
2311
|
try {
|
|
2088
2312
|
await lastResolution;
|
|
2089
2313
|
}
|
|
@@ -2095,6 +2319,17 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2095
2319
|
// microtasks when their tracked promise settles) have a chance to set hadIndexingErrors
|
|
2096
2320
|
// before we decide whether to mark indexing as complete.
|
|
2097
2321
|
await new Promise((resolve) => setImmediate(resolve));
|
|
2322
|
+
// the tail since the last checkpoint is not durable until flushed; announcing the index complete
|
|
2323
|
+
// before that would outlive a crash that loses it
|
|
2324
|
+
if (!hadIndexingErrors) {
|
|
2325
|
+
try {
|
|
2326
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
2327
|
+
}
|
|
2328
|
+
catch (error) {
|
|
2329
|
+
hadIndexingErrors = true;
|
|
2330
|
+
logger.error(`Could not flush the indexes of ${Table.tableName} before marking them complete`, error);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2098
2333
|
if (hadIndexingErrors) {
|
|
2099
2334
|
// Some records failed to index. Persist the failure marker in the descriptor so
|
|
2100
2335
|
// the next call to table() (including after a restart with a fresh PID) re-triggers
|
|
@@ -2123,9 +2358,12 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2123
2358
|
// update the attributes to indicate that we are finished
|
|
2124
2359
|
for (const attribute of attributes) {
|
|
2125
2360
|
delete attribute.lastIndexedKey;
|
|
2361
|
+
delete attribute.checkpointCertified;
|
|
2126
2362
|
delete attribute.indexingPID;
|
|
2127
2363
|
delete attribute.indexingFailed;
|
|
2128
2364
|
delete attribute.restartNumber;
|
|
2365
|
+
delete attribute.indexingIncarnation;
|
|
2366
|
+
delete attribute.indexingBuildId;
|
|
2129
2367
|
attribute.dbi.isIndexing = false;
|
|
2130
2368
|
// Also clear isIndexing on the currently-active dbi in Table.indices, which may
|
|
2131
2369
|
// differ from attribute.dbi if a resetDatabases() call during this migration
|
|
@@ -2142,6 +2380,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
|
|
|
2142
2380
|
}
|
|
2143
2381
|
}
|
|
2144
2382
|
catch (error) {
|
|
2383
|
+
await checkpointing;
|
|
2145
2384
|
// A worker shutting down closes its stores mid-backfill, so the range iterator or a
|
|
2146
2385
|
// put throws (e.g. "Database not open" / "Iterator not initialized"). This is an
|
|
2147
2386
|
// interruption, not a data error: the next worker generation re-runs the backfill via
|