kenaz 0.3.2 → 0.3.3
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/core/.agents/skills/typesafe-ai/LICENSE +21 -21
- package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
- package/core/.claude/commands/Ansuz.md +19 -30
- package/core/.claude/hooks/_ansuz-conversation.js +1 -0
- package/core/.claude/hooks/_ansuz-conversation.test.js +6 -0
- package/core/.claude/hooks/_run-user-prompt.js +0 -4
- package/core/.claude/hooks/_run-user-prompt.test.js +2 -3
- package/core/.claude/hooks/session-init.ps1 +49 -49
- package/core/.claude/settings.json +0 -37
- package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/core/.collaboration/core/task-api.js +3 -8
- package/core/.formation/tests/hello.txt +2 -2
- package/core/.formation/tools/prompt.js +58 -5
- package/core/.formation/tools/prompt.test.js +29 -0
- package/core/.kenaz/claude-hooks-manifest.json +16 -16
- package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
- package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
- package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
- package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
- package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
- package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
- package/core/.kenaz/muninn_episode_write.ps1 +17 -17
- package/core/.kenaz/specialist/field_report_watermark.json +1 -1
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
- package/core/.kenaz/specialist/t1777_probe.txt +1 -1
- package/core/.matrix/index.matrix +5 -5
- package/core/.specialist/Ansuz/RULES.md +6 -10
- package/core/.specialist/tools/__tests__/task-2218-boot-session-id.test.js +17 -1
- package/core/.specialist/tools/__tests__/task-2222-session-identity-interleave.test.js +17 -1
- package/core/.specialist/tools/__tests__/task-2254-handoff-archive-contract.test.js +6 -5
- package/core/.specialist/tools/ansuz-boot-fast.js +44 -100
- package/core/.specialist/tools/ansuz-boot-fast.test.js +45 -1
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
- package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
- package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
- package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
- package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
- package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
- package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
- package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
- package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
- package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
- package/core/dev/jev-eval/README.md +99 -99
- package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
- package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
- package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
- package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
- package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
- package/core/dev/pipeline/tasks/.gitkeep +1 -1
- package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
- package/core/dev/plans/ANSUZ_SLIM_PLAN.md +68 -0
- package/core/dev/scripts/init_git.sh +0 -0
- package/core/dev/scripts/init_project.sh +0 -0
- package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
- package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
- package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
- package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
- package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
- package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
- package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
- package/core/dev/scripts/run_once.sh +0 -0
- package/core/dev/scripts/setup_mcp.sh +0 -0
- package/core/dev/scripts/setup_rag.sh +0 -0
- package/core/dev/scripts/start_dev_agent.sh +0 -0
- package/core/dev/scripts/start_multi_agent.sh +0 -0
- package/core/dev/scripts/sync_task_queue.sh +0 -0
- package/core/k-cli/dist/alaya/run.js +91 -0
- package/core/k-cli/dist/core/output.js +24 -0
- package/core/k-cli/dist/core/process.js +38 -0
- package/core/k-cli/dist/core/project.js +80 -0
- package/core/k-cli/dist/core/spawn.js +268 -0
- package/core/k-cli/dist/core/types.js +2 -0
- package/core/k-cli/dist/dashboard/update.js +53 -0
- package/core/k-cli/dist/dashboard/version.js +20 -0
- package/core/k-cli/dist/dispatch/run.js +241 -0
- package/core/k-cli/dist/dispatch/status.js +103 -0
- package/core/k-cli/dist/index.js +283 -0
- package/core/k-cli/dist/project/initialize.js +301 -0
- package/core/k-cli/dist/project/install.js +89 -0
- package/core/k-cli/dist/project/status.js +138 -0
- package/core/k-cli/dist/specialist/list.js +100 -0
- package/core/k-cli/dist/specialist/run.js +93 -0
- package/core/k-cli/dist/task/create.js +194 -0
- package/core/k-cli/dist/task/delete.js +98 -0
- package/core/k-cli/dist/task/get.js +55 -0
- package/core/k-cli/dist/task/list.js +58 -0
- package/core/k-cli/dist/task/submit.js +156 -0
- package/core/k-cli/dist/ygg/run.js +51 -0
- package/core/k-cli/node_modules/.bin/yaml +16 -0
- package/core/k-cli/node_modules/.bin/yaml.cmd +17 -0
- package/core/k-cli/node_modules/.bin/yaml.ps1 +28 -0
- package/core/k-cli/node_modules/.package-lock.json +23 -0
- package/core/k-cli/node_modules/yaml/LICENSE +13 -0
- package/core/k-cli/node_modules/yaml/README.md +172 -0
- package/core/k-cli/node_modules/yaml/bin.mjs +11 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +217 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +223 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/core/k-cli/node_modules/yaml/browser/dist/errors.js +57 -0
- package/core/k-cli/node_modules/yaml/browser/dist/index.js +17 -0
- package/core/k-cli/node_modules/yaml/browser/dist/log.js +11 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +114 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +717 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +967 -0
- package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +64 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +24 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/core/k-cli/node_modules/yaml/browser/dist/util.js +11 -0
- package/core/k-cli/node_modules/yaml/browser/dist/visit.js +233 -0
- package/core/k-cli/node_modules/yaml/browser/index.js +5 -0
- package/core/k-cli/node_modules/yaml/browser/package.json +3 -0
- package/core/k-cli/node_modules/yaml/dist/cli.d.ts +8 -0
- package/core/k-cli/node_modules/yaml/dist/cli.mjs +201 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/core/k-cli/node_modules/yaml/dist/compose/composer.js +222 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +225 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/core/k-cli/node_modules/yaml/dist/doc/Document.js +337 -0
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/core/k-cli/node_modules/yaml/dist/doc/directives.js +178 -0
- package/core/k-cli/node_modules/yaml/dist/errors.d.ts +21 -0
- package/core/k-cli/node_modules/yaml/dist/errors.js +62 -0
- package/core/k-cli/node_modules/yaml/dist/index.d.ts +25 -0
- package/core/k-cli/node_modules/yaml/dist/index.js +50 -0
- package/core/k-cli/node_modules/yaml/dist/log.d.ts +3 -0
- package/core/k-cli/node_modules/yaml/dist/log.js +19 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +116 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +43 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/core/k-cli/node_modules/yaml/dist/options.d.ts +350 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/core/k-cli/node_modules/yaml/dist/parse/cst.js +112 -0
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +719 -0
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/core/k-cli/node_modules/yaml/dist/parse/parser.js +972 -0
- package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/core/k-cli/node_modules/yaml/dist/public-api.js +107 -0
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/core/k-cli/node_modules/yaml/dist/schema/tags.js +99 -0
- package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +68 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +26 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/core/k-cli/node_modules/yaml/dist/test-events.js +134 -0
- package/core/k-cli/node_modules/yaml/dist/util.d.ts +16 -0
- package/core/k-cli/node_modules/yaml/dist/util.js +28 -0
- package/core/k-cli/node_modules/yaml/dist/visit.d.ts +102 -0
- package/core/k-cli/node_modules/yaml/dist/visit.js +236 -0
- package/core/k-cli/node_modules/yaml/package.json +97 -0
- package/core/k-cli/node_modules/yaml/util.js +2 -0
- package/core/plugins/kenaz/commands/Ansuz.md +19 -30
- package/core/plugins/kenaz/hooks/hooks.json +1 -5
- package/core/plugins/kenaz/hooks/kenaz-hook.js +0 -4
- package/core/plugins/kenaz/scripts/kenaz.test.js +1 -1
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/lib/cli.js +36 -2
- package/lib/cli.test.js +24 -0
- package/lib/dashboard.js +10 -0
- package/package.json +6 -6
- package/core/.specialist/tools/__tests__/task-1863-boot-lease-reuse.test.js +0 -99
- package/core/.specialist/tools/__tests__/task-1977-banner-cap.test.js +0 -129
|
@@ -1,412 +1,412 @@
|
|
|
1
|
-
From 926a0ba10d221eb437add0885ff492e7576bb7c9 Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: kory-io <kory-io@users.noreply.github.com>
|
|
3
|
-
Date: Thu, 24 Sep 2026 18:56:55 -0600
|
|
4
|
-
Subject: [PATCH] fix(storage): keep segment on disk when an out-of-place
|
|
5
|
-
checkpoint fails
|
|
6
|
-
|
|
7
|
-
Column::checkpointColumnChunkOutOfPlace rewrote a segment by switching
|
|
8
|
-
the persistent ColumnChunkData itself to IN_MEMORY (setToInMemory) and
|
|
9
|
-
then resizing, scanning, writing, finalizing and flushing it. Any of
|
|
10
|
-
those steps can throw, for example "Unable to allocate memory! The
|
|
11
|
-
buffer pool is full" from the resize or the scan. The checkpoint is
|
|
12
|
-
then rolled back, but Checkpointer::rollback only discards pending page
|
|
13
|
-
frees and rolls back node-table indexes; it does not touch node group
|
|
14
|
-
state. The node group keeps the half-rewritten segment, which stays
|
|
15
|
-
IN_MEMORY, and with runtime checks enabled (Debug, or RUNTIME_CHECKS=1
|
|
16
|
-
as in CI) every later checkpoint of that table fails the
|
|
17
|
-
`segment->getResidencyState() == ResidencyState::ON_DISK` assertion in
|
|
18
|
-
ColumnChunk::checkpoint, so the database can no longer checkpoint.
|
|
19
|
-
|
|
20
|
-
Rewrite the segment into a new in-memory chunk of the same kind
|
|
21
|
-
instead, and return it once it has been flushed, the same way split
|
|
22
|
-
segments are already returned. The persistent segment object is not
|
|
23
|
-
touched until the rewrite has succeeded, and its pages are only freed
|
|
24
|
-
after that, so a failure leaves it on disk and unchanged and the next
|
|
25
|
-
checkpoint starts from it again. Segments rewritten earlier in the
|
|
26
|
-
same failed checkpoint stay replaced; their old pages are only
|
|
27
|
-
reclaimed after the database is reopened.
|
|
28
|
-
|
|
29
|
-
ColumnChunk::checkpoint already installs returned segments. The nested
|
|
30
|
-
null, list offset and list size chunks, which are checkpointed without
|
|
31
|
-
splitting, now install the returned chunk into their parent.
|
|
32
|
-
StringColumn and StructColumn also call syncNumValues on the returned
|
|
33
|
-
segments, as they already did on the persistent segment.
|
|
34
|
-
|
|
35
|
-
Add FlakyCheckpointerTest.RecoverFromFailureDuringOutOfPlaceCheckpoint.
|
|
36
|
-
It fails a checkpoint at the flush of an out-of-place rewrite, then
|
|
37
|
-
checks that the data is intact and that the next checkpoint succeeds.
|
|
38
|
-
Add an e2e case that checkpoints a list column in place while its
|
|
39
|
-
offsets and nulls are rewritten out of place.
|
|
40
|
-
---
|
|
41
|
-
src/include/storage/table/column.h | 4 +-
|
|
42
|
-
src/storage/table/column.cpp | 58 +++++++---
|
|
43
|
-
src/storage/table/column_chunk.cpp | 4 +-
|
|
44
|
-
src/storage/table/list_column.cpp | 15 ++-
|
|
45
|
-
src/storage/table/string_column.cpp | 3 +
|
|
46
|
-
src/storage/table/struct_column.cpp | 3 +
|
|
47
|
-
.../transaction/set_node/set_empty.test | 41 +++++++
|
|
48
|
-
test/transaction/checkpoint_test.cpp | 106 ++++++++++++++++++
|
|
49
|
-
8 files changed, 209 insertions(+), 25 deletions(-)
|
|
50
|
-
|
|
51
|
-
diff --git a/src/include/storage/table/column.h b/src/include/storage/table/column.h
|
|
52
|
-
index 16584cb027..d56fefbf5f 100644
|
|
53
|
-
--- a/src/include/storage/table/column.h
|
|
54
|
-
+++ b/src/include/storage/table/column.h
|
|
55
|
-
@@ -95,8 +95,8 @@ class Column {
|
|
56
|
-
return common::dynamic_cast_checked<TARGET&>(*this);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
- // Return value is the new segments if segment splitting occurs during an out of place
|
|
60
|
-
- // checkpoint
|
|
61
|
-
+ // Return value is the new segment(s) replacing the persistent segment if it was checkpointed
|
|
62
|
-
+ // out of place (more than one if it was split), or empty if it was checkpointed in place
|
|
63
|
-
virtual std::vector<std::unique_ptr<ColumnChunkData>> checkpointSegment(
|
|
64
|
-
ColumnCheckpointState&& checkpointState, PageAllocator& pageAllocator,
|
|
65
|
-
bool canSplitSegment = true) const;
|
|
66
|
-
diff --git a/src/storage/table/column.cpp b/src/storage/table/column.cpp
|
|
67
|
-
index 72b9a2f206..9d23776188 100644
|
|
68
|
-
--- a/src/storage/table/column.cpp
|
|
69
|
-
+++ b/src/storage/table/column.cpp
|
|
70
|
-
@@ -3,6 +3,7 @@
|
|
71
|
-
#include <algorithm>
|
|
72
|
-
#include <cstdint>
|
|
73
|
-
#include <memory>
|
|
74
|
-
+#include <tuple>
|
|
75
|
-
|
|
76
|
-
#include "common/assert.h"
|
|
77
|
-
#include "common/data_chunk/sel_vector.h"
|
|
78
|
-
@@ -439,42 +440,63 @@ void Column::checkpointNullData(const ColumnCheckpointState& checkpointState,
|
|
79
|
-
segmentCheckpointState.numRows);
|
|
80
|
-
}
|
|
81
|
-
DASSERT(checkpointState.persistentData.hasNullData());
|
|
82
|
-
- nullColumn->checkpointSegment(
|
|
83
|
-
+ auto newNullData = nullColumn->checkpointSegment(
|
|
84
|
-
ColumnCheckpointState(*checkpointState.persistentData.getNullData(),
|
|
85
|
-
std::move(nullSegmentCheckpointStates)),
|
|
86
|
-
pageAllocator, false);
|
|
87
|
-
+ if (!newNullData.empty()) {
|
|
88
|
-
+ DASSERT(newNullData.size() == 1);
|
|
89
|
-
+ auto* nullChunk = &newNullData[0]->cast<NullChunkData>();
|
|
90
|
-
+ std::ignore = newNullData[0].release();
|
|
91
|
-
+ checkpointState.persistentData.setNullData(std::unique_ptr<NullChunkData>(nullChunk));
|
|
92
|
-
+ }
|
|
93
|
-
+}
|
|
94
|
-
+
|
|
95
|
-
+static std::unique_ptr<ColumnChunkData> createInMemoryChunkLike(const ColumnChunkData& chunk,
|
|
96
|
-
+ uint64_t capacity) {
|
|
97
|
-
+ if (dynamic_cast<const NullChunkData*>(&chunk) != nullptr) {
|
|
98
|
-
+ return ColumnChunkFactory::createNullChunkData(chunk.getMemoryManager(),
|
|
99
|
-
+ chunk.isCompressionEnabled(), capacity, ResidencyState::IN_MEMORY);
|
|
100
|
-
+ }
|
|
101
|
-
+ return ColumnChunkFactory::createColumnChunkData(chunk.getMemoryManager(),
|
|
102
|
-
+ chunk.getDataType().copy(), chunk.isCompressionEnabled(), capacity,
|
|
103
|
-
+ ResidencyState::IN_MEMORY, chunk.hasNullData());
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
std::vector<std::unique_ptr<ColumnChunkData>> Column::checkpointColumnChunkOutOfPlace(
|
|
107
|
-
const SegmentState& state, const ColumnCheckpointState& checkpointState,
|
|
108
|
-
PageAllocator& pageAllocator, bool canSplitSegment) const {
|
|
109
|
-
const auto numRows = std::max(checkpointState.endRowIdxToWrite, state.metadata.numValues);
|
|
110
|
-
- checkpointState.persistentData.setToInMemory();
|
|
111
|
-
- checkpointState.persistentData.resize(numRows);
|
|
112
|
-
- DASSERT(checkpointState.persistentData.getNumValues() == 0);
|
|
113
|
-
- scanSegment(state, &checkpointState.persistentData, 0, state.metadata.numValues);
|
|
114
|
-
- state.reclaimAllocatedPages(pageAllocator);
|
|
115
|
-
+ // Rewrite the segment into a new chunk instead of converting persistentData to an in-memory
|
|
116
|
-
+ // chunk in place. If the rewrite fails part way (e.g. the buffer pool is full), the checkpoint
|
|
117
|
-
+ // is rolled back but the segment is kept, so it must still describe its on-disk pages for the
|
|
118
|
-
+ // data to stay readable and for the next checkpoint to be able to rewrite it.
|
|
119
|
-
+ auto newData = createInMemoryChunkLike(checkpointState.persistentData, numRows);
|
|
120
|
-
+ DASSERT(newData->getNumValues() == 0);
|
|
121
|
-
+ scanSegment(state, newData.get(), 0, state.metadata.numValues);
|
|
122
|
-
// TODO(bmwinger): for simple compression types, we can predict whether or not we will need to
|
|
123
|
-
// split the segment and avoid having to re-write it multiple times
|
|
124
|
-
for (auto& segmentCheckpointState : checkpointState.segmentCheckpointStates) {
|
|
125
|
-
- checkpointState.persistentData.write(&segmentCheckpointState.chunkData,
|
|
126
|
-
- segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
|
|
127
|
-
- segmentCheckpointState.numRows);
|
|
128
|
-
+ newData->write(&segmentCheckpointState.chunkData, segmentCheckpointState.startRowInData,
|
|
129
|
-
+ segmentCheckpointState.offsetInSegment, segmentCheckpointState.numRows);
|
|
130
|
-
}
|
|
131
|
-
// Finalize is necessary prior to splitting for strings and lists so that pruned values don't
|
|
132
|
-
// have an impact on the number/size of segments It should not be necessary after splitting
|
|
133
|
-
// since the function is used to prune unused values (or duplicated dictionary entries in the
|
|
134
|
-
// case of strings) and those will never be introduced when splitting.
|
|
135
|
-
- checkpointState.persistentData.finalize();
|
|
136
|
-
- if (canSplitSegment && checkpointState.persistentData.shouldSplit()) {
|
|
137
|
-
- auto newSegments = checkpointState.persistentData.split();
|
|
138
|
-
- for (auto& segment : newSegments) {
|
|
139
|
-
- segment->flush(pageAllocator);
|
|
140
|
-
- }
|
|
141
|
-
- return newSegments;
|
|
142
|
-
+ newData->finalize();
|
|
143
|
-
+ std::vector<std::unique_ptr<ColumnChunkData>> newSegments;
|
|
144
|
-
+ if (canSplitSegment && newData->shouldSplit()) {
|
|
145
|
-
+ newSegments = newData->split();
|
|
146
|
-
+ } else {
|
|
147
|
-
+ newSegments.push_back(std::move(newData));
|
|
148
|
-
}
|
|
149
|
-
- checkpointState.persistentData.flush(pageAllocator);
|
|
150
|
-
- return {};
|
|
151
|
-
+ for (auto& segment : newSegments) {
|
|
152
|
-
+ segment->flush(pageAllocator);
|
|
153
|
-
+ }
|
|
154
|
-
+ // Only free the old pages once the replacement is on disk.
|
|
155
|
-
+ state.reclaimAllocatedPages(pageAllocator);
|
|
156
|
-
+ return newSegments;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
bool Column::canCheckpointInPlace(const SegmentState& state,
|
|
160
|
-
diff --git a/src/storage/table/column_chunk.cpp b/src/storage/table/column_chunk.cpp
|
|
161
|
-
index 9adf90426d..4c0f277344 100644
|
|
162
|
-
--- a/src/storage/table/column_chunk.cpp
|
|
163
|
-
+++ b/src/storage/table/column_chunk.cpp
|
|
164
|
-
@@ -311,8 +311,8 @@ void ColumnChunk::checkpoint(Column& column,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
auto segmentEnd = segmentStart + segment->getNumValues();
|
|
168
|
-
- // If the segment was split during checkpointing we need to insert the new segments into the
|
|
169
|
-
- // ColumnChunk
|
|
170
|
-
+ // If the segment was rewritten out of place (and possibly split) during checkpointing we
|
|
171
|
-
+ // need to replace it with the new segments in the ColumnChunk
|
|
172
|
-
auto newSegments = column.checkpointSegment(
|
|
173
|
-
ColumnCheckpointState(*segment, std::move(segmentCheckpointStates)), pageAllocator);
|
|
174
|
-
if (!newSegments.empty()) {
|
|
175
|
-
diff --git a/src/storage/table/list_column.cpp b/src/storage/table/list_column.cpp
|
|
176
|
-
index 0ac0465896..e4a1ece4e2 100644
|
|
177
|
-
--- a/src/storage/table/list_column.cpp
|
|
178
|
-
+++ b/src/storage/table/list_column.cpp
|
|
179
|
-
@@ -403,10 +403,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// We do not allow nested splitting of offset/size segments
|
|
183
|
-
- offsetColumn->checkpointSegment(
|
|
184
|
-
+ auto newOffsetChunk = offsetColumn->checkpointSegment(
|
|
185
|
-
ColumnCheckpointState(*persistentListChunk.getOffsetColumnChunk(),
|
|
186
|
-
std::move(offsetChunkCheckpointStates)),
|
|
187
|
-
pageAllocator, false);
|
|
188
|
-
+ if (!newOffsetChunk.empty()) {
|
|
189
|
-
+ DASSERT(newOffsetChunk.size() == 1);
|
|
190
|
-
+ persistentListChunk.setOffsetColumnChunk(std::move(newOffsetChunk[0]));
|
|
191
|
-
+ }
|
|
192
|
-
|
|
193
|
-
// Checkpoint size data.
|
|
194
|
-
std::vector<SegmentCheckpointState> sizeChunkCheckpointStates;
|
|
195
|
-
@@ -416,9 +420,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
|
|
196
|
-
segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
|
|
197
|
-
segmentCheckpointState.numRows});
|
|
198
|
-
}
|
|
199
|
-
- sizeColumn->checkpointSegment(ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
|
|
200
|
-
- std::move(sizeChunkCheckpointStates)),
|
|
201
|
-
+ auto newSizeChunk = sizeColumn->checkpointSegment(
|
|
202
|
-
+ ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
|
|
203
|
-
+ std::move(sizeChunkCheckpointStates)),
|
|
204
|
-
pageAllocator, false);
|
|
205
|
-
+ if (!newSizeChunk.empty()) {
|
|
206
|
-
+ DASSERT(newSizeChunk.size() == 1);
|
|
207
|
-
+ persistentListChunk.setSizeColumnChunk(std::move(newSizeChunk[0]));
|
|
208
|
-
+ }
|
|
209
|
-
// Checkpoint null data.
|
|
210
|
-
Column::checkpointNullData(checkpointState, pageAllocator);
|
|
211
|
-
|
|
212
|
-
diff --git a/src/storage/table/string_column.cpp b/src/storage/table/string_column.cpp
|
|
213
|
-
index b7f94dc3ea..38b508765a 100644
|
|
214
|
-
--- a/src/storage/table/string_column.cpp
|
|
215
|
-
+++ b/src/storage/table/string_column.cpp
|
|
216
|
-
@@ -116,6 +116,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StringColumn::checkpointSegment(
|
|
217
|
-
auto result =
|
|
218
|
-
Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
|
|
219
|
-
persistentData.syncNumValues();
|
|
220
|
-
+ for (auto& segment : result) {
|
|
221
|
-
+ segment->syncNumValues();
|
|
222
|
-
+ }
|
|
223
|
-
return result;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
diff --git a/src/storage/table/struct_column.cpp b/src/storage/table/struct_column.cpp
|
|
227
|
-
index b16e3d83ff..673cb6c468 100644
|
|
228
|
-
--- a/src/storage/table/struct_column.cpp
|
|
229
|
-
+++ b/src/storage/table/struct_column.cpp
|
|
230
|
-
@@ -114,6 +114,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StructColumn::checkpointSegment(
|
|
231
|
-
auto result =
|
|
232
|
-
Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
|
|
233
|
-
persistentStructChunk.syncNumValues();
|
|
234
|
-
+ for (auto& segment : result) {
|
|
235
|
-
+ segment->syncNumValues();
|
|
236
|
-
+ }
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
diff --git a/test/test_files/transaction/set_node/set_empty.test b/test/test_files/transaction/set_node/set_empty.test
|
|
241
|
-
index 72dbeefd09..b6e1495e07 100644
|
|
242
|
-
--- a/test/test_files/transaction/set_node/set_empty.test
|
|
243
|
-
+++ b/test/test_files/transaction/set_node/set_empty.test
|
|
244
|
-
@@ -33,3 +33,44 @@
|
|
245
|
-
---- 2
|
|
246
|
-
0|1010101010.300000
|
|
247
|
-
1341|1010101010.200000
|
|
248
|
-
+
|
|
249
|
-
+-CASE ListInPlaceWithOutOfPlaceOffsetsAndNulls
|
|
250
|
-
+-STATEMENT CALL auto_checkpoint=false;
|
|
251
|
-
+---- ok
|
|
252
|
-
+-STATEMENT CREATE NODE TABLE t2(id INT64 PRIMARY KEY, l INT64[]);
|
|
253
|
-
+---- ok
|
|
254
|
-
+-STATEMENT UNWIND range(0, 4094) AS i CREATE (:t2 {id: i, l: [0, 1]});
|
|
255
|
-
+---- ok
|
|
256
|
-
+-STATEMENT CHECKPOINT;
|
|
257
|
-
+---- ok
|
|
258
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 SET a.l = [1,0,1,0,1,0,1,0,1,0];
|
|
259
|
-
+---- ok
|
|
260
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 7 SET a.l = NULL;
|
|
261
|
-
+---- ok
|
|
262
|
-
+-STATEMENT CHECKPOINT;
|
|
263
|
-
+---- ok
|
|
264
|
-
+-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
|
|
265
|
-
+---- 1
|
|
266
|
-
+4094
|
|
267
|
-
+-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
|
|
268
|
-
+---- 1
|
|
269
|
-
+8196|4098
|
|
270
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
271
|
-
+---- 1
|
|
272
|
-
+10|1|0
|
|
273
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 6 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
274
|
-
+---- 1
|
|
275
|
-
+2|0|1
|
|
276
|
-
+-RELOADDB
|
|
277
|
-
+-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
|
|
278
|
-
+---- 1
|
|
279
|
-
+4094
|
|
280
|
-
+-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
|
|
281
|
-
+---- 1
|
|
282
|
-
+8196|4098
|
|
283
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
284
|
-
+---- 1
|
|
285
|
-
+10|1|0
|
|
286
|
-
+-STATEMENT MATCH (a:t2) WHERE a.id = 4094 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
287
|
-
+---- 1
|
|
288
|
-
+2|0|1
|
|
289
|
-
diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
|
|
290
|
-
index 2777560616..2049d21d19 100644
|
|
291
|
-
--- a/test/transaction/checkpoint_test.cpp
|
|
292
|
-
+++ b/test/transaction/checkpoint_test.cpp
|
|
293
|
-
@@ -13,6 +13,8 @@
|
|
294
|
-
#include "catalog/catalog_entry/table_catalog_entry.h"
|
|
295
|
-
#include "common/exception/runtime.h"
|
|
296
|
-
#include "storage/checkpointer.h"
|
|
297
|
-
+#include "storage/page_allocator.h"
|
|
298
|
-
+#include "storage/page_manager.h"
|
|
299
|
-
#include "storage/storage_manager.h"
|
|
300
|
-
#include "storage/table/node_table.h"
|
|
301
|
-
#include "storage/table/string_chunk_data.h"
|
|
302
|
-
@@ -235,6 +237,110 @@ TEST_F(FlakyCheckpointerTest, RecoverFromCheckpointClearingFilesFailure) {
|
|
303
|
-
runTest(flakyCheckpointer);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
+// Page allocator that fails the first page allocation. In-place checkpoints do not allocate
|
|
307
|
-
+// pages, so during the storage phase of a checkpoint that only updates a node group with
|
|
308
|
-
+// persistent data, the first allocation is the flush of an out-of-place column chunk rewrite and
|
|
309
|
-
+// the failure lands in the middle of that rewrite.
|
|
310
|
-
+class FailFirstAllocationPageAllocator final : public PageAllocator {
|
|
311
|
-
+public:
|
|
312
|
-
+ FailFirstAllocationPageAllocator(PageAllocator& inner, bool& failed)
|
|
313
|
-
+ : PageAllocator(inner.getDataFH()), inner{inner}, failed{failed} {}
|
|
314
|
-
+
|
|
315
|
-
+ PageRange allocatePageRange(page_idx_t numPages) override {
|
|
316
|
-
+ if (!failed) {
|
|
317
|
-
+ failed = true;
|
|
318
|
-
+ throw RuntimeException("checkpoint failed.");
|
|
319
|
-
+ }
|
|
320
|
-
+ return inner.allocatePageRange(numPages);
|
|
321
|
-
+ }
|
|
322
|
-
+ void freePageRange(PageRange block) override { inner.freePageRange(block); }
|
|
323
|
-
+
|
|
324
|
-
+private:
|
|
325
|
-
+ PageAllocator& inner;
|
|
326
|
-
+ bool& failed;
|
|
327
|
-
+};
|
|
328
|
-
+
|
|
329
|
-
+class FlakyCheckpointerFailsDuringOutOfPlaceRewrite final : public Checkpointer {
|
|
330
|
-
+public:
|
|
331
|
-
+ FlakyCheckpointerFailsDuringOutOfPlaceRewrite(main::ClientContext& context, bool& failed)
|
|
332
|
-
+ : Checkpointer(context), failed{failed} {}
|
|
333
|
-
+
|
|
334
|
-
+ bool checkpointStorage() override {
|
|
335
|
-
+ for (const auto& target : checkpointTargets) {
|
|
336
|
-
+ FailFirstAllocationPageAllocator pageAllocator(
|
|
337
|
-
+ *target.storageManager->getDataFH()->getPageManager(), failed);
|
|
338
|
-
+ const Transaction snapshotTxn(TransactionType::CHECKPOINT,
|
|
339
|
-
+ Transaction::DUMMY_TRANSACTION_ID, snapshotTS);
|
|
340
|
-
+ target.storageManager->checkpoint(&clientContext, *target.catalog, snapshotTxn,
|
|
341
|
-
+ pageAllocator, tableEpochWatermarksByManager.at(target.storageManager));
|
|
342
|
-
+ }
|
|
343
|
-
+ throw RuntimeException("expected the injected page allocation failure to fire.");
|
|
344
|
-
+ }
|
|
345
|
-
+
|
|
346
|
-
+private:
|
|
347
|
-
+ bool& failed;
|
|
348
|
-
+};
|
|
349
|
-
+
|
|
350
|
-
+// A checkpoint that fails while a column chunk segment is being rewritten out of place must
|
|
351
|
-
+// leave that segment as it was, so that the data stays readable and later checkpoints succeed.
|
|
352
|
-
+TEST_F(FlakyCheckpointerTest, RecoverFromFailureDuringOutOfPlaceCheckpoint) {
|
|
353
|
-
+ if (inMemMode || systemConfig->checkpointThreshold == 0) {
|
|
354
|
-
+ GTEST_SKIP();
|
|
355
|
-
+ }
|
|
356
|
-
+ conn->query("CALL force_checkpoint_on_close=false;");
|
|
357
|
-
+ conn->query("CALL auto_checkpoint=false;");
|
|
358
|
-
+ ASSERT_TRUE(
|
|
359
|
-
+ conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
|
|
360
|
-
+ constexpr int64_t numInitialRows = 3000;
|
|
361
|
-
+ constexpr int64_t numRows = 6000;
|
|
362
|
-
+ constexpr std::string_view namePrefix = "a longer name so the pages fill up ";
|
|
363
|
-
+ auto nameOf = [&](int64_t i) { return std::format("{}{}", namePrefix, i); };
|
|
364
|
-
+ auto insertRows = [&](int64_t start, int64_t end) {
|
|
365
|
-
+ auto res = conn->query(std::format("UNWIND range({}, {}) AS i CREATE (a:test {{id: i, "
|
|
366
|
-
+ "name: concat('{}', CAST(i AS STRING))}});",
|
|
367
|
-
+ start, end - 1, namePrefix));
|
|
368
|
-
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
369
|
-
+ };
|
|
370
|
-
+ auto checkRows = [&]() {
|
|
371
|
-
+ auto res = conn->query("MATCH (a:test) RETURN COUNT(a), CAST(SUM(a.id) AS INT64), "
|
|
372
|
-
+ "CAST(SUM(SIZE(a.name)) AS INT64), MIN(a.name), MAX(a.name);");
|
|
373
|
-
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
374
|
-
+ auto row = res->getNext();
|
|
375
|
-
+ ASSERT_EQ(row->getValue(0)->getValue<int64_t>(), numRows);
|
|
376
|
-
+ ASSERT_EQ(row->getValue(1)->getValue<int64_t>(), numRows * (numRows - 1) / 2);
|
|
377
|
-
+ int64_t totalNameSize = 0;
|
|
378
|
-
+ for (auto i = 0; i < numRows; i++) {
|
|
379
|
-
+ totalNameSize += nameOf(i).size();
|
|
380
|
-
+ }
|
|
381
|
-
+ ASSERT_EQ(row->getValue(2)->getValue<int64_t>(), totalNameSize);
|
|
382
|
-
+ ASSERT_EQ(row->getValue(3)->getValue<std::string>(), nameOf(0));
|
|
383
|
-
+ ASSERT_EQ(row->getValue(4)->getValue<std::string>(), nameOf(999));
|
|
384
|
-
+ };
|
|
385
|
-
+ insertRows(0, numInitialRows);
|
|
386
|
-
+ ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
|
|
387
|
-
+ // Appending to the persistent node group outgrows the pages of its column chunks, so the
|
|
388
|
-
+ // next checkpoint has to rewrite them out of place.
|
|
389
|
-
+ insertRows(numInitialRows, numRows);
|
|
390
|
-
+
|
|
391
|
-
+ auto context = getClientContext(*conn);
|
|
392
|
-
+ bool failed = false;
|
|
393
|
-
+ FlakyCheckpointer flakyCheckpointer([&failed](main::ClientContext& ctx) {
|
|
394
|
-
+ return std::make_unique<FlakyCheckpointerFailsDuringOutOfPlaceRewrite>(ctx, failed);
|
|
395
|
-
+ });
|
|
396
|
-
+ flakyCheckpointer.setCheckpointer(*context);
|
|
397
|
-
+ auto res = conn->query("CHECKPOINT;");
|
|
398
|
-
+ ASSERT_FALSE(res->isSuccess());
|
|
399
|
-
+ ASSERT_TRUE(failed);
|
|
400
|
-
+ checkRows();
|
|
401
|
-
+
|
|
402
|
-
+ FlakyCheckpointer defaultCheckpointer(
|
|
403
|
-
+ [](main::ClientContext& ctx) { return std::make_unique<Checkpointer>(ctx); });
|
|
404
|
-
+ defaultCheckpointer.setCheckpointer(*context);
|
|
405
|
-
+ res = conn->query("CHECKPOINT;");
|
|
406
|
-
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
407
|
-
+ checkRows();
|
|
408
|
-
+}
|
|
409
|
-
+
|
|
410
|
-
// Simulates a situation where a database attempts to replay a shadow file from an older database
|
|
411
|
-
// with the same path
|
|
412
|
-
TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchExistingDB) {
|
|
1
|
+
From 926a0ba10d221eb437add0885ff492e7576bb7c9 Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: kory-io <kory-io@users.noreply.github.com>
|
|
3
|
+
Date: Thu, 24 Sep 2026 18:56:55 -0600
|
|
4
|
+
Subject: [PATCH] fix(storage): keep segment on disk when an out-of-place
|
|
5
|
+
checkpoint fails
|
|
6
|
+
|
|
7
|
+
Column::checkpointColumnChunkOutOfPlace rewrote a segment by switching
|
|
8
|
+
the persistent ColumnChunkData itself to IN_MEMORY (setToInMemory) and
|
|
9
|
+
then resizing, scanning, writing, finalizing and flushing it. Any of
|
|
10
|
+
those steps can throw, for example "Unable to allocate memory! The
|
|
11
|
+
buffer pool is full" from the resize or the scan. The checkpoint is
|
|
12
|
+
then rolled back, but Checkpointer::rollback only discards pending page
|
|
13
|
+
frees and rolls back node-table indexes; it does not touch node group
|
|
14
|
+
state. The node group keeps the half-rewritten segment, which stays
|
|
15
|
+
IN_MEMORY, and with runtime checks enabled (Debug, or RUNTIME_CHECKS=1
|
|
16
|
+
as in CI) every later checkpoint of that table fails the
|
|
17
|
+
`segment->getResidencyState() == ResidencyState::ON_DISK` assertion in
|
|
18
|
+
ColumnChunk::checkpoint, so the database can no longer checkpoint.
|
|
19
|
+
|
|
20
|
+
Rewrite the segment into a new in-memory chunk of the same kind
|
|
21
|
+
instead, and return it once it has been flushed, the same way split
|
|
22
|
+
segments are already returned. The persistent segment object is not
|
|
23
|
+
touched until the rewrite has succeeded, and its pages are only freed
|
|
24
|
+
after that, so a failure leaves it on disk and unchanged and the next
|
|
25
|
+
checkpoint starts from it again. Segments rewritten earlier in the
|
|
26
|
+
same failed checkpoint stay replaced; their old pages are only
|
|
27
|
+
reclaimed after the database is reopened.
|
|
28
|
+
|
|
29
|
+
ColumnChunk::checkpoint already installs returned segments. The nested
|
|
30
|
+
null, list offset and list size chunks, which are checkpointed without
|
|
31
|
+
splitting, now install the returned chunk into their parent.
|
|
32
|
+
StringColumn and StructColumn also call syncNumValues on the returned
|
|
33
|
+
segments, as they already did on the persistent segment.
|
|
34
|
+
|
|
35
|
+
Add FlakyCheckpointerTest.RecoverFromFailureDuringOutOfPlaceCheckpoint.
|
|
36
|
+
It fails a checkpoint at the flush of an out-of-place rewrite, then
|
|
37
|
+
checks that the data is intact and that the next checkpoint succeeds.
|
|
38
|
+
Add an e2e case that checkpoints a list column in place while its
|
|
39
|
+
offsets and nulls are rewritten out of place.
|
|
40
|
+
---
|
|
41
|
+
src/include/storage/table/column.h | 4 +-
|
|
42
|
+
src/storage/table/column.cpp | 58 +++++++---
|
|
43
|
+
src/storage/table/column_chunk.cpp | 4 +-
|
|
44
|
+
src/storage/table/list_column.cpp | 15 ++-
|
|
45
|
+
src/storage/table/string_column.cpp | 3 +
|
|
46
|
+
src/storage/table/struct_column.cpp | 3 +
|
|
47
|
+
.../transaction/set_node/set_empty.test | 41 +++++++
|
|
48
|
+
test/transaction/checkpoint_test.cpp | 106 ++++++++++++++++++
|
|
49
|
+
8 files changed, 209 insertions(+), 25 deletions(-)
|
|
50
|
+
|
|
51
|
+
diff --git a/src/include/storage/table/column.h b/src/include/storage/table/column.h
|
|
52
|
+
index 16584cb027..d56fefbf5f 100644
|
|
53
|
+
--- a/src/include/storage/table/column.h
|
|
54
|
+
+++ b/src/include/storage/table/column.h
|
|
55
|
+
@@ -95,8 +95,8 @@ class Column {
|
|
56
|
+
return common::dynamic_cast_checked<TARGET&>(*this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
- // Return value is the new segments if segment splitting occurs during an out of place
|
|
60
|
+
- // checkpoint
|
|
61
|
+
+ // Return value is the new segment(s) replacing the persistent segment if it was checkpointed
|
|
62
|
+
+ // out of place (more than one if it was split), or empty if it was checkpointed in place
|
|
63
|
+
virtual std::vector<std::unique_ptr<ColumnChunkData>> checkpointSegment(
|
|
64
|
+
ColumnCheckpointState&& checkpointState, PageAllocator& pageAllocator,
|
|
65
|
+
bool canSplitSegment = true) const;
|
|
66
|
+
diff --git a/src/storage/table/column.cpp b/src/storage/table/column.cpp
|
|
67
|
+
index 72b9a2f206..9d23776188 100644
|
|
68
|
+
--- a/src/storage/table/column.cpp
|
|
69
|
+
+++ b/src/storage/table/column.cpp
|
|
70
|
+
@@ -3,6 +3,7 @@
|
|
71
|
+
#include <algorithm>
|
|
72
|
+
#include <cstdint>
|
|
73
|
+
#include <memory>
|
|
74
|
+
+#include <tuple>
|
|
75
|
+
|
|
76
|
+
#include "common/assert.h"
|
|
77
|
+
#include "common/data_chunk/sel_vector.h"
|
|
78
|
+
@@ -439,42 +440,63 @@ void Column::checkpointNullData(const ColumnCheckpointState& checkpointState,
|
|
79
|
+
segmentCheckpointState.numRows);
|
|
80
|
+
}
|
|
81
|
+
DASSERT(checkpointState.persistentData.hasNullData());
|
|
82
|
+
- nullColumn->checkpointSegment(
|
|
83
|
+
+ auto newNullData = nullColumn->checkpointSegment(
|
|
84
|
+
ColumnCheckpointState(*checkpointState.persistentData.getNullData(),
|
|
85
|
+
std::move(nullSegmentCheckpointStates)),
|
|
86
|
+
pageAllocator, false);
|
|
87
|
+
+ if (!newNullData.empty()) {
|
|
88
|
+
+ DASSERT(newNullData.size() == 1);
|
|
89
|
+
+ auto* nullChunk = &newNullData[0]->cast<NullChunkData>();
|
|
90
|
+
+ std::ignore = newNullData[0].release();
|
|
91
|
+
+ checkpointState.persistentData.setNullData(std::unique_ptr<NullChunkData>(nullChunk));
|
|
92
|
+
+ }
|
|
93
|
+
+}
|
|
94
|
+
+
|
|
95
|
+
+static std::unique_ptr<ColumnChunkData> createInMemoryChunkLike(const ColumnChunkData& chunk,
|
|
96
|
+
+ uint64_t capacity) {
|
|
97
|
+
+ if (dynamic_cast<const NullChunkData*>(&chunk) != nullptr) {
|
|
98
|
+
+ return ColumnChunkFactory::createNullChunkData(chunk.getMemoryManager(),
|
|
99
|
+
+ chunk.isCompressionEnabled(), capacity, ResidencyState::IN_MEMORY);
|
|
100
|
+
+ }
|
|
101
|
+
+ return ColumnChunkFactory::createColumnChunkData(chunk.getMemoryManager(),
|
|
102
|
+
+ chunk.getDataType().copy(), chunk.isCompressionEnabled(), capacity,
|
|
103
|
+
+ ResidencyState::IN_MEMORY, chunk.hasNullData());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
std::vector<std::unique_ptr<ColumnChunkData>> Column::checkpointColumnChunkOutOfPlace(
|
|
107
|
+
const SegmentState& state, const ColumnCheckpointState& checkpointState,
|
|
108
|
+
PageAllocator& pageAllocator, bool canSplitSegment) const {
|
|
109
|
+
const auto numRows = std::max(checkpointState.endRowIdxToWrite, state.metadata.numValues);
|
|
110
|
+
- checkpointState.persistentData.setToInMemory();
|
|
111
|
+
- checkpointState.persistentData.resize(numRows);
|
|
112
|
+
- DASSERT(checkpointState.persistentData.getNumValues() == 0);
|
|
113
|
+
- scanSegment(state, &checkpointState.persistentData, 0, state.metadata.numValues);
|
|
114
|
+
- state.reclaimAllocatedPages(pageAllocator);
|
|
115
|
+
+ // Rewrite the segment into a new chunk instead of converting persistentData to an in-memory
|
|
116
|
+
+ // chunk in place. If the rewrite fails part way (e.g. the buffer pool is full), the checkpoint
|
|
117
|
+
+ // is rolled back but the segment is kept, so it must still describe its on-disk pages for the
|
|
118
|
+
+ // data to stay readable and for the next checkpoint to be able to rewrite it.
|
|
119
|
+
+ auto newData = createInMemoryChunkLike(checkpointState.persistentData, numRows);
|
|
120
|
+
+ DASSERT(newData->getNumValues() == 0);
|
|
121
|
+
+ scanSegment(state, newData.get(), 0, state.metadata.numValues);
|
|
122
|
+
// TODO(bmwinger): for simple compression types, we can predict whether or not we will need to
|
|
123
|
+
// split the segment and avoid having to re-write it multiple times
|
|
124
|
+
for (auto& segmentCheckpointState : checkpointState.segmentCheckpointStates) {
|
|
125
|
+
- checkpointState.persistentData.write(&segmentCheckpointState.chunkData,
|
|
126
|
+
- segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
|
|
127
|
+
- segmentCheckpointState.numRows);
|
|
128
|
+
+ newData->write(&segmentCheckpointState.chunkData, segmentCheckpointState.startRowInData,
|
|
129
|
+
+ segmentCheckpointState.offsetInSegment, segmentCheckpointState.numRows);
|
|
130
|
+
}
|
|
131
|
+
// Finalize is necessary prior to splitting for strings and lists so that pruned values don't
|
|
132
|
+
// have an impact on the number/size of segments It should not be necessary after splitting
|
|
133
|
+
// since the function is used to prune unused values (or duplicated dictionary entries in the
|
|
134
|
+
// case of strings) and those will never be introduced when splitting.
|
|
135
|
+
- checkpointState.persistentData.finalize();
|
|
136
|
+
- if (canSplitSegment && checkpointState.persistentData.shouldSplit()) {
|
|
137
|
+
- auto newSegments = checkpointState.persistentData.split();
|
|
138
|
+
- for (auto& segment : newSegments) {
|
|
139
|
+
- segment->flush(pageAllocator);
|
|
140
|
+
- }
|
|
141
|
+
- return newSegments;
|
|
142
|
+
+ newData->finalize();
|
|
143
|
+
+ std::vector<std::unique_ptr<ColumnChunkData>> newSegments;
|
|
144
|
+
+ if (canSplitSegment && newData->shouldSplit()) {
|
|
145
|
+
+ newSegments = newData->split();
|
|
146
|
+
+ } else {
|
|
147
|
+
+ newSegments.push_back(std::move(newData));
|
|
148
|
+
}
|
|
149
|
+
- checkpointState.persistentData.flush(pageAllocator);
|
|
150
|
+
- return {};
|
|
151
|
+
+ for (auto& segment : newSegments) {
|
|
152
|
+
+ segment->flush(pageAllocator);
|
|
153
|
+
+ }
|
|
154
|
+
+ // Only free the old pages once the replacement is on disk.
|
|
155
|
+
+ state.reclaimAllocatedPages(pageAllocator);
|
|
156
|
+
+ return newSegments;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
bool Column::canCheckpointInPlace(const SegmentState& state,
|
|
160
|
+
diff --git a/src/storage/table/column_chunk.cpp b/src/storage/table/column_chunk.cpp
|
|
161
|
+
index 9adf90426d..4c0f277344 100644
|
|
162
|
+
--- a/src/storage/table/column_chunk.cpp
|
|
163
|
+
+++ b/src/storage/table/column_chunk.cpp
|
|
164
|
+
@@ -311,8 +311,8 @@ void ColumnChunk::checkpoint(Column& column,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
auto segmentEnd = segmentStart + segment->getNumValues();
|
|
168
|
+
- // If the segment was split during checkpointing we need to insert the new segments into the
|
|
169
|
+
- // ColumnChunk
|
|
170
|
+
+ // If the segment was rewritten out of place (and possibly split) during checkpointing we
|
|
171
|
+
+ // need to replace it with the new segments in the ColumnChunk
|
|
172
|
+
auto newSegments = column.checkpointSegment(
|
|
173
|
+
ColumnCheckpointState(*segment, std::move(segmentCheckpointStates)), pageAllocator);
|
|
174
|
+
if (!newSegments.empty()) {
|
|
175
|
+
diff --git a/src/storage/table/list_column.cpp b/src/storage/table/list_column.cpp
|
|
176
|
+
index 0ac0465896..e4a1ece4e2 100644
|
|
177
|
+
--- a/src/storage/table/list_column.cpp
|
|
178
|
+
+++ b/src/storage/table/list_column.cpp
|
|
179
|
+
@@ -403,10 +403,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// We do not allow nested splitting of offset/size segments
|
|
183
|
+
- offsetColumn->checkpointSegment(
|
|
184
|
+
+ auto newOffsetChunk = offsetColumn->checkpointSegment(
|
|
185
|
+
ColumnCheckpointState(*persistentListChunk.getOffsetColumnChunk(),
|
|
186
|
+
std::move(offsetChunkCheckpointStates)),
|
|
187
|
+
pageAllocator, false);
|
|
188
|
+
+ if (!newOffsetChunk.empty()) {
|
|
189
|
+
+ DASSERT(newOffsetChunk.size() == 1);
|
|
190
|
+
+ persistentListChunk.setOffsetColumnChunk(std::move(newOffsetChunk[0]));
|
|
191
|
+
+ }
|
|
192
|
+
|
|
193
|
+
// Checkpoint size data.
|
|
194
|
+
std::vector<SegmentCheckpointState> sizeChunkCheckpointStates;
|
|
195
|
+
@@ -416,9 +420,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
|
|
196
|
+
segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
|
|
197
|
+
segmentCheckpointState.numRows});
|
|
198
|
+
}
|
|
199
|
+
- sizeColumn->checkpointSegment(ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
|
|
200
|
+
- std::move(sizeChunkCheckpointStates)),
|
|
201
|
+
+ auto newSizeChunk = sizeColumn->checkpointSegment(
|
|
202
|
+
+ ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
|
|
203
|
+
+ std::move(sizeChunkCheckpointStates)),
|
|
204
|
+
pageAllocator, false);
|
|
205
|
+
+ if (!newSizeChunk.empty()) {
|
|
206
|
+
+ DASSERT(newSizeChunk.size() == 1);
|
|
207
|
+
+ persistentListChunk.setSizeColumnChunk(std::move(newSizeChunk[0]));
|
|
208
|
+
+ }
|
|
209
|
+
// Checkpoint null data.
|
|
210
|
+
Column::checkpointNullData(checkpointState, pageAllocator);
|
|
211
|
+
|
|
212
|
+
diff --git a/src/storage/table/string_column.cpp b/src/storage/table/string_column.cpp
|
|
213
|
+
index b7f94dc3ea..38b508765a 100644
|
|
214
|
+
--- a/src/storage/table/string_column.cpp
|
|
215
|
+
+++ b/src/storage/table/string_column.cpp
|
|
216
|
+
@@ -116,6 +116,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StringColumn::checkpointSegment(
|
|
217
|
+
auto result =
|
|
218
|
+
Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
|
|
219
|
+
persistentData.syncNumValues();
|
|
220
|
+
+ for (auto& segment : result) {
|
|
221
|
+
+ segment->syncNumValues();
|
|
222
|
+
+ }
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
diff --git a/src/storage/table/struct_column.cpp b/src/storage/table/struct_column.cpp
|
|
227
|
+
index b16e3d83ff..673cb6c468 100644
|
|
228
|
+
--- a/src/storage/table/struct_column.cpp
|
|
229
|
+
+++ b/src/storage/table/struct_column.cpp
|
|
230
|
+
@@ -114,6 +114,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StructColumn::checkpointSegment(
|
|
231
|
+
auto result =
|
|
232
|
+
Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
|
|
233
|
+
persistentStructChunk.syncNumValues();
|
|
234
|
+
+ for (auto& segment : result) {
|
|
235
|
+
+ segment->syncNumValues();
|
|
236
|
+
+ }
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
diff --git a/test/test_files/transaction/set_node/set_empty.test b/test/test_files/transaction/set_node/set_empty.test
|
|
241
|
+
index 72dbeefd09..b6e1495e07 100644
|
|
242
|
+
--- a/test/test_files/transaction/set_node/set_empty.test
|
|
243
|
+
+++ b/test/test_files/transaction/set_node/set_empty.test
|
|
244
|
+
@@ -33,3 +33,44 @@
|
|
245
|
+
---- 2
|
|
246
|
+
0|1010101010.300000
|
|
247
|
+
1341|1010101010.200000
|
|
248
|
+
+
|
|
249
|
+
+-CASE ListInPlaceWithOutOfPlaceOffsetsAndNulls
|
|
250
|
+
+-STATEMENT CALL auto_checkpoint=false;
|
|
251
|
+
+---- ok
|
|
252
|
+
+-STATEMENT CREATE NODE TABLE t2(id INT64 PRIMARY KEY, l INT64[]);
|
|
253
|
+
+---- ok
|
|
254
|
+
+-STATEMENT UNWIND range(0, 4094) AS i CREATE (:t2 {id: i, l: [0, 1]});
|
|
255
|
+
+---- ok
|
|
256
|
+
+-STATEMENT CHECKPOINT;
|
|
257
|
+
+---- ok
|
|
258
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 SET a.l = [1,0,1,0,1,0,1,0,1,0];
|
|
259
|
+
+---- ok
|
|
260
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 7 SET a.l = NULL;
|
|
261
|
+
+---- ok
|
|
262
|
+
+-STATEMENT CHECKPOINT;
|
|
263
|
+
+---- ok
|
|
264
|
+
+-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
|
|
265
|
+
+---- 1
|
|
266
|
+
+4094
|
|
267
|
+
+-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
|
|
268
|
+
+---- 1
|
|
269
|
+
+8196|4098
|
|
270
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
271
|
+
+---- 1
|
|
272
|
+
+10|1|0
|
|
273
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 6 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
274
|
+
+---- 1
|
|
275
|
+
+2|0|1
|
|
276
|
+
+-RELOADDB
|
|
277
|
+
+-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
|
|
278
|
+
+---- 1
|
|
279
|
+
+4094
|
|
280
|
+
+-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
|
|
281
|
+
+---- 1
|
|
282
|
+
+8196|4098
|
|
283
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
284
|
+
+---- 1
|
|
285
|
+
+10|1|0
|
|
286
|
+
+-STATEMENT MATCH (a:t2) WHERE a.id = 4094 RETURN SIZE(a.l), a.l[1], a.l[2];
|
|
287
|
+
+---- 1
|
|
288
|
+
+2|0|1
|
|
289
|
+
diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
|
|
290
|
+
index 2777560616..2049d21d19 100644
|
|
291
|
+
--- a/test/transaction/checkpoint_test.cpp
|
|
292
|
+
+++ b/test/transaction/checkpoint_test.cpp
|
|
293
|
+
@@ -13,6 +13,8 @@
|
|
294
|
+
#include "catalog/catalog_entry/table_catalog_entry.h"
|
|
295
|
+
#include "common/exception/runtime.h"
|
|
296
|
+
#include "storage/checkpointer.h"
|
|
297
|
+
+#include "storage/page_allocator.h"
|
|
298
|
+
+#include "storage/page_manager.h"
|
|
299
|
+
#include "storage/storage_manager.h"
|
|
300
|
+
#include "storage/table/node_table.h"
|
|
301
|
+
#include "storage/table/string_chunk_data.h"
|
|
302
|
+
@@ -235,6 +237,110 @@ TEST_F(FlakyCheckpointerTest, RecoverFromCheckpointClearingFilesFailure) {
|
|
303
|
+
runTest(flakyCheckpointer);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
+// Page allocator that fails the first page allocation. In-place checkpoints do not allocate
|
|
307
|
+
+// pages, so during the storage phase of a checkpoint that only updates a node group with
|
|
308
|
+
+// persistent data, the first allocation is the flush of an out-of-place column chunk rewrite and
|
|
309
|
+
+// the failure lands in the middle of that rewrite.
|
|
310
|
+
+class FailFirstAllocationPageAllocator final : public PageAllocator {
|
|
311
|
+
+public:
|
|
312
|
+
+ FailFirstAllocationPageAllocator(PageAllocator& inner, bool& failed)
|
|
313
|
+
+ : PageAllocator(inner.getDataFH()), inner{inner}, failed{failed} {}
|
|
314
|
+
+
|
|
315
|
+
+ PageRange allocatePageRange(page_idx_t numPages) override {
|
|
316
|
+
+ if (!failed) {
|
|
317
|
+
+ failed = true;
|
|
318
|
+
+ throw RuntimeException("checkpoint failed.");
|
|
319
|
+
+ }
|
|
320
|
+
+ return inner.allocatePageRange(numPages);
|
|
321
|
+
+ }
|
|
322
|
+
+ void freePageRange(PageRange block) override { inner.freePageRange(block); }
|
|
323
|
+
+
|
|
324
|
+
+private:
|
|
325
|
+
+ PageAllocator& inner;
|
|
326
|
+
+ bool& failed;
|
|
327
|
+
+};
|
|
328
|
+
+
|
|
329
|
+
+class FlakyCheckpointerFailsDuringOutOfPlaceRewrite final : public Checkpointer {
|
|
330
|
+
+public:
|
|
331
|
+
+ FlakyCheckpointerFailsDuringOutOfPlaceRewrite(main::ClientContext& context, bool& failed)
|
|
332
|
+
+ : Checkpointer(context), failed{failed} {}
|
|
333
|
+
+
|
|
334
|
+
+ bool checkpointStorage() override {
|
|
335
|
+
+ for (const auto& target : checkpointTargets) {
|
|
336
|
+
+ FailFirstAllocationPageAllocator pageAllocator(
|
|
337
|
+
+ *target.storageManager->getDataFH()->getPageManager(), failed);
|
|
338
|
+
+ const Transaction snapshotTxn(TransactionType::CHECKPOINT,
|
|
339
|
+
+ Transaction::DUMMY_TRANSACTION_ID, snapshotTS);
|
|
340
|
+
+ target.storageManager->checkpoint(&clientContext, *target.catalog, snapshotTxn,
|
|
341
|
+
+ pageAllocator, tableEpochWatermarksByManager.at(target.storageManager));
|
|
342
|
+
+ }
|
|
343
|
+
+ throw RuntimeException("expected the injected page allocation failure to fire.");
|
|
344
|
+
+ }
|
|
345
|
+
+
|
|
346
|
+
+private:
|
|
347
|
+
+ bool& failed;
|
|
348
|
+
+};
|
|
349
|
+
+
|
|
350
|
+
+// A checkpoint that fails while a column chunk segment is being rewritten out of place must
|
|
351
|
+
+// leave that segment as it was, so that the data stays readable and later checkpoints succeed.
|
|
352
|
+
+TEST_F(FlakyCheckpointerTest, RecoverFromFailureDuringOutOfPlaceCheckpoint) {
|
|
353
|
+
+ if (inMemMode || systemConfig->checkpointThreshold == 0) {
|
|
354
|
+
+ GTEST_SKIP();
|
|
355
|
+
+ }
|
|
356
|
+
+ conn->query("CALL force_checkpoint_on_close=false;");
|
|
357
|
+
+ conn->query("CALL auto_checkpoint=false;");
|
|
358
|
+
+ ASSERT_TRUE(
|
|
359
|
+
+ conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
|
|
360
|
+
+ constexpr int64_t numInitialRows = 3000;
|
|
361
|
+
+ constexpr int64_t numRows = 6000;
|
|
362
|
+
+ constexpr std::string_view namePrefix = "a longer name so the pages fill up ";
|
|
363
|
+
+ auto nameOf = [&](int64_t i) { return std::format("{}{}", namePrefix, i); };
|
|
364
|
+
+ auto insertRows = [&](int64_t start, int64_t end) {
|
|
365
|
+
+ auto res = conn->query(std::format("UNWIND range({}, {}) AS i CREATE (a:test {{id: i, "
|
|
366
|
+
+ "name: concat('{}', CAST(i AS STRING))}});",
|
|
367
|
+
+ start, end - 1, namePrefix));
|
|
368
|
+
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
369
|
+
+ };
|
|
370
|
+
+ auto checkRows = [&]() {
|
|
371
|
+
+ auto res = conn->query("MATCH (a:test) RETURN COUNT(a), CAST(SUM(a.id) AS INT64), "
|
|
372
|
+
+ "CAST(SUM(SIZE(a.name)) AS INT64), MIN(a.name), MAX(a.name);");
|
|
373
|
+
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
374
|
+
+ auto row = res->getNext();
|
|
375
|
+
+ ASSERT_EQ(row->getValue(0)->getValue<int64_t>(), numRows);
|
|
376
|
+
+ ASSERT_EQ(row->getValue(1)->getValue<int64_t>(), numRows * (numRows - 1) / 2);
|
|
377
|
+
+ int64_t totalNameSize = 0;
|
|
378
|
+
+ for (auto i = 0; i < numRows; i++) {
|
|
379
|
+
+ totalNameSize += nameOf(i).size();
|
|
380
|
+
+ }
|
|
381
|
+
+ ASSERT_EQ(row->getValue(2)->getValue<int64_t>(), totalNameSize);
|
|
382
|
+
+ ASSERT_EQ(row->getValue(3)->getValue<std::string>(), nameOf(0));
|
|
383
|
+
+ ASSERT_EQ(row->getValue(4)->getValue<std::string>(), nameOf(999));
|
|
384
|
+
+ };
|
|
385
|
+
+ insertRows(0, numInitialRows);
|
|
386
|
+
+ ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
|
|
387
|
+
+ // Appending to the persistent node group outgrows the pages of its column chunks, so the
|
|
388
|
+
+ // next checkpoint has to rewrite them out of place.
|
|
389
|
+
+ insertRows(numInitialRows, numRows);
|
|
390
|
+
+
|
|
391
|
+
+ auto context = getClientContext(*conn);
|
|
392
|
+
+ bool failed = false;
|
|
393
|
+
+ FlakyCheckpointer flakyCheckpointer([&failed](main::ClientContext& ctx) {
|
|
394
|
+
+ return std::make_unique<FlakyCheckpointerFailsDuringOutOfPlaceRewrite>(ctx, failed);
|
|
395
|
+
+ });
|
|
396
|
+
+ flakyCheckpointer.setCheckpointer(*context);
|
|
397
|
+
+ auto res = conn->query("CHECKPOINT;");
|
|
398
|
+
+ ASSERT_FALSE(res->isSuccess());
|
|
399
|
+
+ ASSERT_TRUE(failed);
|
|
400
|
+
+ checkRows();
|
|
401
|
+
+
|
|
402
|
+
+ FlakyCheckpointer defaultCheckpointer(
|
|
403
|
+
+ [](main::ClientContext& ctx) { return std::make_unique<Checkpointer>(ctx); });
|
|
404
|
+
+ defaultCheckpointer.setCheckpointer(*context);
|
|
405
|
+
+ res = conn->query("CHECKPOINT;");
|
|
406
|
+
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
407
|
+
+ checkRows();
|
|
408
|
+
+}
|
|
409
|
+
+
|
|
410
|
+
// Simulates a situation where a database attempts to replay a shadow file from an older database
|
|
411
|
+
// with the same path
|
|
412
|
+
TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchExistingDB) {
|