specweave 0.18.0 → 0.20.0
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/CLAUDE.md +229 -1817
- package/README.md +68 -0
- package/bin/specweave.js +62 -6
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +3 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts +21 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js +162 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts +112 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js +301 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +3 -3
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +70 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +258 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +14 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +51 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +2 -2
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +20 -5
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +87 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +412 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +64 -13
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts +78 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js +237 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js +301 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +386 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +28 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +156 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.d.ts +57 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.js +248 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.d.ts +82 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.js +13 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.d.ts +49 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.js +316 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.d.ts +70 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.js +23 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.d.ts +85 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.js +281 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.d.ts +75 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.js +238 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/commands/import-docs.js +4 -4
- package/dist/src/cli/commands/import-docs.js.map +1 -1
- package/dist/src/cli/commands/init-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/init-multiproject.js +17 -18
- package/dist/src/cli/commands/init-multiproject.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +107 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.js +8 -4
- package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
- package/dist/src/cli/commands/switch-project.d.ts.map +1 -1
- package/dist/src/cli/commands/switch-project.js +9 -26
- package/dist/src/cli/commands/switch-project.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-content.js +3 -0
- package/dist/src/cli/commands/sync-spec-content.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/increment/active-increment-manager.d.ts +42 -15
- package/dist/src/core/increment/active-increment-manager.d.ts.map +1 -1
- package/dist/src/core/increment/active-increment-manager.js +113 -46
- package/dist/src/core/increment/active-increment-manager.js.map +1 -1
- package/dist/src/core/increment/conflict-resolver.d.ts +40 -0
- package/dist/src/core/increment/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/increment/conflict-resolver.js +219 -0
- package/dist/src/core/increment/conflict-resolver.js.map +1 -0
- package/dist/src/core/increment/discipline-checker.d.ts.map +1 -1
- package/dist/src/core/increment/discipline-checker.js +7 -1
- package/dist/src/core/increment/discipline-checker.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.d.ts +52 -0
- package/dist/src/core/increment/duplicate-detector.d.ts.map +1 -0
- package/dist/src/core/increment/duplicate-detector.js +276 -0
- package/dist/src/core/increment/duplicate-detector.js.map +1 -0
- package/dist/src/core/increment/increment-archiver.d.ts +90 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -0
- package/dist/src/core/increment/increment-archiver.js +368 -0
- package/dist/src/core/increment/increment-archiver.js.map +1 -0
- package/dist/src/core/increment/increment-reopener.d.ts +165 -0
- package/dist/src/core/increment/increment-reopener.d.ts.map +1 -0
- package/dist/src/core/increment/increment-reopener.js +390 -0
- package/dist/src/core/increment/increment-reopener.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +26 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +143 -5
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/recent-work-scanner.d.ts +121 -0
- package/dist/src/core/increment/recent-work-scanner.d.ts.map +1 -0
- package/dist/src/core/increment/recent-work-scanner.js +303 -0
- package/dist/src/core/increment/recent-work-scanner.js.map +1 -0
- package/dist/src/core/increment/types.d.ts +1 -0
- package/dist/src/core/increment/types.d.ts.map +1 -1
- package/dist/src/core/increment-utils.d.ts +112 -0
- package/dist/src/core/increment-utils.d.ts.map +1 -0
- package/dist/src/core/increment-utils.js +210 -0
- package/dist/src/core/increment-utils.js.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts +65 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js +175 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts +130 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.js +549 -0
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts +81 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.js +339 -0
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +144 -83
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +488 -270
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/index.d.ts +6 -0
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +6 -0
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/project-detector.d.ts +6 -0
- package/dist/src/core/living-docs/project-detector.d.ts.map +1 -1
- package/dist/src/core/living-docs/project-detector.js +35 -1
- package/dist/src/core/living-docs/project-detector.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +100 -26
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.js +1275 -258
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -1
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts +109 -0
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js +221 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +143 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/project-manager.d.ts +2 -17
- package/dist/src/core/project-manager.d.ts.map +1 -1
- package/dist/src/core/project-manager.js +68 -48
- package/dist/src/core/project-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +1 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.d.ts +32 -54
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.js +142 -138
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -1
- package/dist/src/core/sync/performance-optimizer.d.ts +153 -0
- package/dist/src/core/sync/performance-optimizer.d.ts.map +1 -0
- package/dist/src/core/sync/performance-optimizer.js +220 -0
- package/dist/src/core/sync/performance-optimizer.js.map +1 -0
- package/dist/src/core/sync/retry-handler.d.ts +98 -0
- package/dist/src/core/sync/retry-handler.d.ts.map +1 -0
- package/dist/src/core/sync/retry-handler.js +196 -0
- package/dist/src/core/sync/retry-handler.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/types.d.ts +52 -0
- package/dist/src/core/sync/types.d.ts.map +1 -0
- package/dist/src/core/sync/types.js +5 -0
- package/dist/src/core/sync/types.js.map +1 -0
- package/dist/src/core/types/config.d.ts +125 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +25 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +10 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js +10 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-incremental-mapper.js.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-mapper.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/COMMANDS.md +13 -4
- package/plugins/specweave/agents/pm/AGENT.md +159 -12
- package/plugins/specweave/commands/specweave-abandon.md +22 -20
- package/plugins/specweave/commands/specweave-archive-features.md +121 -0
- package/plugins/specweave/commands/specweave-archive-increments.md +82 -0
- package/plugins/specweave/commands/specweave-archive.md +363 -0
- package/plugins/specweave/commands/specweave-backlog.md +211 -0
- package/plugins/specweave/commands/specweave-fix-duplicates.md +517 -0
- package/plugins/specweave/commands/specweave-increment.md +4 -3
- package/plugins/specweave/commands/specweave-progress.md +176 -27
- package/plugins/specweave/commands/specweave-reopen.md +391 -0
- package/plugins/specweave/commands/specweave-restore-feature.md +90 -0
- package/plugins/specweave/commands/specweave-restore.md +309 -0
- package/plugins/specweave/commands/specweave-resume.md +51 -23
- package/plugins/specweave/commands/specweave-status.md +41 -7
- package/plugins/specweave/commands/specweave-sync-specs.md +425 -0
- package/plugins/specweave/commands/specweave.md +70 -405
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/post-increment-planning.sh +26 -2
- package/plugins/specweave/hooks/post-task-completion.sh +39 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +83 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/lib/hooks/sync-living-docs.js +2 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +4 -0
- package/plugins/specweave/lib/hooks/update-ac-status.js +102 -0
- package/plugins/specweave/lib/hooks/update-ac-status.ts +192 -0
- package/plugins/specweave/skills/archive-increments/SKILL.md +198 -0
- package/plugins/specweave/skills/increment-planner/scripts/feature-utils.js +14 -0
- package/plugins/specweave/skills/smart-reopen-detector/SKILL.md +244 -0
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-confluent/README.md +375 -0
- package/plugins/specweave-confluent/agents/confluent-architect/AGENT.md +306 -0
- package/plugins/specweave-confluent/skills/confluent-kafka-connect/SKILL.md +453 -0
- package/plugins/specweave-confluent/skills/confluent-ksqldb/SKILL.md +470 -0
- package/plugins/specweave-confluent/skills/confluent-schema-registry/SKILL.md +316 -0
- package/plugins/specweave-github/agents/github-task-splitter/AGENT.md +2 -2
- package/plugins/specweave-github/agents/user-story-updater/AGENT.md +148 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +1 -1
- package/plugins/specweave-github/commands/specweave-github-update-user-story.md +156 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh +42 -9
- package/plugins/specweave-github/lib/completion-calculator.js +262 -0
- package/plugins/specweave-github/lib/completion-calculator.ts +434 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/plugins/specweave-github/lib/duplicate-detector.ts +4 -4
- package/plugins/specweave-github/lib/epic-content-builder.js +265 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +376 -0
- package/plugins/specweave-github/lib/github-client-v2.js +49 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +59 -0
- package/plugins/specweave-github/lib/github-epic-sync.js +23 -24
- package/plugins/specweave-github/lib/github-epic-sync.ts +30 -5
- package/plugins/specweave-github/lib/github-feature-sync.js +381 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +568 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.js +40 -10
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +82 -14
- package/plugins/specweave-github/lib/progress-comment-builder.js +229 -0
- package/plugins/specweave-github/lib/progress-comment-builder.ts +324 -0
- package/plugins/specweave-github/lib/user-story-content-builder.js +299 -0
- package/plugins/specweave-github/lib/user-story-content-builder.ts +413 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.js +344 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +543 -0
- package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +189 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/{enhanced-jira-sync.ts.disabled → enhanced-jira-sync.ts} +26 -52
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +26 -0
- package/plugins/specweave-kafka/IMPLEMENTATION-COMPLETE.md +483 -0
- package/plugins/specweave-kafka/README.md +242 -0
- package/plugins/specweave-kafka/agents/kafka-architect/AGENT.md +235 -0
- package/plugins/specweave-kafka/agents/kafka-devops/AGENT.md +209 -0
- package/plugins/specweave-kafka/agents/kafka-observability/AGENT.md +266 -0
- package/plugins/specweave-kafka/commands/deploy.md +99 -0
- package/plugins/specweave-kafka/commands/dev-env.md +176 -0
- package/plugins/specweave-kafka/commands/mcp-configure.md +101 -0
- package/plugins/specweave-kafka/commands/monitor-setup.md +96 -0
- package/plugins/specweave-kafka/docker/kafka-local/docker-compose.yml +187 -0
- package/plugins/specweave-kafka/docker/redpanda/docker-compose.yml +199 -0
- package/plugins/specweave-kafka/docker/templates/consumer-nodejs.js +225 -0
- package/plugins/specweave-kafka/docker/templates/consumer-python.py +220 -0
- package/plugins/specweave-kafka/docker/templates/producer-nodejs.js +168 -0
- package/plugins/specweave-kafka/docker/templates/producer-python.py +167 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.js +438 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.ts +541 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.js +47 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.ts +343 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.js +258 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.ts +298 -0
- package/plugins/specweave-kafka/lib/cli/types.js +10 -0
- package/plugins/specweave-kafka/lib/cli/types.ts +92 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.js +305 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.ts +528 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.js +114 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.ts +195 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.js +210 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.ts +338 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.js +60 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.ts +130 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.js +143 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.ts +290 -0
- package/plugins/specweave-kafka/lib/mcp/detector.js +298 -0
- package/plugins/specweave-kafka/lib/mcp/detector.ts +352 -0
- package/plugins/specweave-kafka/lib/mcp/types.js +21 -0
- package/plugins/specweave-kafka/lib/mcp/types.ts +77 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.js +193 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.ts +362 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.js +188 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.ts +359 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.js +195 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.ts +380 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.js +209 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.ts +358 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.js +354 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.ts +563 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.js +259 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.ts +516 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.js +233 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.ts +423 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.js +266 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.ts +445 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.js +312 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.ts +561 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.js +289 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.ts +607 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.js +264 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.ts +498 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.js +263 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.ts +549 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.js +205 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.ts +399 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.js +249 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.ts +427 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.js +252 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.ts +494 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.js +203 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.ts +469 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.js +419 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.ts +564 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.js +329 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.ts +473 -0
- package/plugins/specweave-kafka/lib/utils/sizing.js +221 -0
- package/plugins/specweave-kafka/lib/utils/sizing.ts +374 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-broker-metrics.json +628 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-cluster-overview.json +564 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-consumer-lag.json +509 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-jvm-metrics.json +674 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-topic-metrics.json +578 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/dashboards/kafka.yml +17 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/datasources/prometheus.yml +17 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-alerts.yml +415 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-jmx-exporter.yml +256 -0
- package/plugins/specweave-kafka/package.json +41 -0
- package/plugins/specweave-kafka/skills/kafka-architecture/SKILL.md +647 -0
- package/plugins/specweave-kafka/skills/kafka-cli-tools/SKILL.md +433 -0
- package/plugins/specweave-kafka/skills/kafka-iac-deployment/SKILL.md +449 -0
- package/plugins/specweave-kafka/skills/kafka-kubernetes/SKILL.md +667 -0
- package/plugins/specweave-kafka/skills/kafka-mcp-integration/SKILL.md +273 -0
- package/plugins/specweave-kafka/skills/kafka-observability/SKILL.md +576 -0
- package/plugins/specweave-kafka/templates/config/broker-production.properties +254 -0
- package/plugins/specweave-kafka/templates/config/consumer-low-latency.properties +112 -0
- package/plugins/specweave-kafka/templates/config/producer-high-throughput.properties +120 -0
- package/plugins/specweave-kafka/templates/migration/mirrormaker2-config.properties +234 -0
- package/plugins/specweave-kafka/templates/monitoring/grafana/multi-cluster-dashboard.json +686 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/main.tf +347 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/outputs.tf +107 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/templates/kafka-broker-init.sh.tpl +216 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/variables.tf +156 -0
- package/plugins/specweave-kafka/terraform/aws-msk/main.tf +362 -0
- package/plugins/specweave-kafka/terraform/aws-msk/outputs.tf +93 -0
- package/plugins/specweave-kafka/terraform/aws-msk/templates/server.properties.tpl +32 -0
- package/plugins/specweave-kafka/terraform/aws-msk/variables.tf +235 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/main.tf +281 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/outputs.tf +118 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/variables.tf +148 -0
- package/plugins/specweave-kafka/tsconfig.json +21 -0
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-kafka-streams/README.md +310 -0
- package/plugins/specweave-kafka-streams/skills/kafka-streams-topology/SKILL.md +539 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-n8n/README.md +354 -0
- package/plugins/specweave-n8n/skills/n8n-kafka-workflows/SKILL.md +504 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +601 -7
- package/src/templates/CLAUDE.md.template +188 -88
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +0 -255
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +0 -248
- package/plugins/specweave-github/commands/specweave-github-sync-from.md +0 -147
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +0 -208
- package/plugins/specweave-github/commands/specweave-github-sync-tasks.md +0 -530
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +0 -267
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +0 -240
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partitioning Strategy Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes partition key distribution and provides recommendations
|
|
5
|
+
* for optimal Kafka topic partitioning strategies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface PartitionKeyAnalysis {
|
|
9
|
+
key: string;
|
|
10
|
+
sampleValues: string[];
|
|
11
|
+
estimatedCardinality: number;
|
|
12
|
+
distribution: 'uniform' | 'skewed' | 'severely-skewed';
|
|
13
|
+
hotspotRisk: 'low' | 'medium' | 'high';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PartitioningRecommendation {
|
|
17
|
+
recommendedPartitions: number;
|
|
18
|
+
keyStrategy: 'simple-hash' | 'compound-hash' | 'custom-partitioner' | 'round-robin';
|
|
19
|
+
keyField: string | null;
|
|
20
|
+
reasoning: string;
|
|
21
|
+
warnings: string[];
|
|
22
|
+
examples: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PartitionAnalysisRequest {
|
|
26
|
+
// Topic metadata
|
|
27
|
+
topicName: string;
|
|
28
|
+
currentPartitionCount?: number;
|
|
29
|
+
|
|
30
|
+
// Workload characteristics
|
|
31
|
+
expectedMessagesPerSecond: number;
|
|
32
|
+
peakMultiplier?: number; // Default: 3x (peak = 3x average)
|
|
33
|
+
|
|
34
|
+
// Key characteristics
|
|
35
|
+
potentialKeys: Array<{
|
|
36
|
+
fieldName: string;
|
|
37
|
+
sampleValues: string[];
|
|
38
|
+
estimatedUniqueCount: number;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
// Business requirements
|
|
42
|
+
orderingRequired: boolean;
|
|
43
|
+
targetLatencyMs?: number; // Default: 100ms
|
|
44
|
+
targetThroughputMBps?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class PartitioningStrategyAnalyzer {
|
|
48
|
+
private readonly MAX_PARTITION_THROUGHPUT_MBPS = 20;
|
|
49
|
+
private readonly MAX_PARTITION_MSG_PER_SEC = 50000;
|
|
50
|
+
private readonly IDEAL_PARTITIONS_PER_CONSUMER = 2;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Analyze partition key candidates and recommend strategy
|
|
54
|
+
*/
|
|
55
|
+
analyze(req: PartitionAnalysisRequest): PartitioningRecommendation {
|
|
56
|
+
const peakMultiplier = req.peakMultiplier ?? 3;
|
|
57
|
+
const targetLatency = req.targetLatencyMs ?? 100;
|
|
58
|
+
const peakMessagesPerSec = req.expectedMessagesPerSecond * peakMultiplier;
|
|
59
|
+
|
|
60
|
+
// Analyze each potential key
|
|
61
|
+
const keyAnalyses = req.potentialKeys.map(key => this.analyzeKey(key));
|
|
62
|
+
|
|
63
|
+
// Find best key based on distribution
|
|
64
|
+
const bestKey = this.selectBestKey(keyAnalyses, req.orderingRequired);
|
|
65
|
+
|
|
66
|
+
// Calculate partitions needed based on throughput
|
|
67
|
+
let partitionsForThroughput = 1;
|
|
68
|
+
if (req.targetThroughputMBps) {
|
|
69
|
+
partitionsForThroughput = Math.ceil(
|
|
70
|
+
req.targetThroughputMBps / this.MAX_PARTITION_THROUGHPUT_MBPS
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Calculate partitions needed based on message rate
|
|
75
|
+
const partitionsForMsgRate = Math.ceil(
|
|
76
|
+
peakMessagesPerSec / this.MAX_PARTITION_MSG_PER_SEC
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Calculate partitions for consumer parallelism
|
|
80
|
+
const partitionsForParallelism = Math.max(4, partitionsForMsgRate * this.IDEAL_PARTITIONS_PER_CONSUMER);
|
|
81
|
+
|
|
82
|
+
// Take maximum (most constraining)
|
|
83
|
+
let recommendedPartitions = Math.max(
|
|
84
|
+
partitionsForThroughput,
|
|
85
|
+
partitionsForMsgRate,
|
|
86
|
+
partitionsForParallelism
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Round up to nearest power of 2 or multiple of 3 (for rack awareness)
|
|
90
|
+
if (recommendedPartitions <= 16) {
|
|
91
|
+
recommendedPartitions = this.nextPowerOfTwo(recommendedPartitions);
|
|
92
|
+
} else {
|
|
93
|
+
recommendedPartitions = Math.ceil(recommendedPartitions / 12) * 12; // Multiple of 12 (3 racks × 4)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Ensure minimum partitions
|
|
97
|
+
recommendedPartitions = Math.max(recommendedPartitions, 3);
|
|
98
|
+
|
|
99
|
+
// Determine key strategy
|
|
100
|
+
const { keyStrategy, keyField, reasoning } = this.determineKeyStrategy(
|
|
101
|
+
bestKey,
|
|
102
|
+
req.orderingRequired,
|
|
103
|
+
recommendedPartitions
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Generate warnings
|
|
107
|
+
const warnings = this.generateWarnings({
|
|
108
|
+
bestKey,
|
|
109
|
+
recommendedPartitions,
|
|
110
|
+
currentPartitionCount: req.currentPartitionCount,
|
|
111
|
+
req
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Generate usage examples
|
|
115
|
+
const examples = this.generateExamples(keyStrategy, keyField, req.topicName);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
recommendedPartitions,
|
|
119
|
+
keyStrategy,
|
|
120
|
+
keyField,
|
|
121
|
+
reasoning,
|
|
122
|
+
warnings,
|
|
123
|
+
examples
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Analyze a single partition key candidate
|
|
129
|
+
*/
|
|
130
|
+
private analyzeKey(key: { fieldName: string; sampleValues: string[]; estimatedUniqueCount: number }): PartitionKeyAnalysis {
|
|
131
|
+
const cardinality = key.estimatedUniqueCount;
|
|
132
|
+
const sampleSize = key.sampleValues.length;
|
|
133
|
+
|
|
134
|
+
// Estimate distribution from sample
|
|
135
|
+
const uniqueInSample = new Set(key.sampleValues).size;
|
|
136
|
+
const uniquenessRatio = uniqueInSample / sampleSize;
|
|
137
|
+
|
|
138
|
+
// Classify distribution
|
|
139
|
+
let distribution: 'uniform' | 'skewed' | 'severely-skewed';
|
|
140
|
+
if (uniquenessRatio > 0.9) {
|
|
141
|
+
distribution = 'uniform'; // Most values are unique
|
|
142
|
+
} else if (uniquenessRatio > 0.5) {
|
|
143
|
+
distribution = 'skewed'; // Some duplicate values
|
|
144
|
+
} else {
|
|
145
|
+
distribution = 'severely-skewed'; // Many duplicate values
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Assess hotspot risk
|
|
149
|
+
let hotspotRisk: 'low' | 'medium' | 'high';
|
|
150
|
+
if (cardinality > 10000 && distribution === 'uniform') {
|
|
151
|
+
hotspotRisk = 'low';
|
|
152
|
+
} else if (cardinality > 1000 && distribution !== 'severely-skewed') {
|
|
153
|
+
hotspotRisk = 'medium';
|
|
154
|
+
} else {
|
|
155
|
+
hotspotRisk = 'high';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
key: key.fieldName,
|
|
160
|
+
sampleValues: key.sampleValues.slice(0, 5), // Top 5 examples
|
|
161
|
+
estimatedCardinality: cardinality,
|
|
162
|
+
distribution,
|
|
163
|
+
hotspotRisk
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Select best partition key from candidates
|
|
169
|
+
*/
|
|
170
|
+
private selectBestKey(
|
|
171
|
+
analyses: PartitionKeyAnalysis[],
|
|
172
|
+
orderingRequired: boolean
|
|
173
|
+
): PartitionKeyAnalysis {
|
|
174
|
+
// Sort by hotspot risk (low is best) and cardinality (high is best)
|
|
175
|
+
const sorted = [...analyses].sort((a, b) => {
|
|
176
|
+
const riskScore = { low: 0, medium: 1, high: 2 };
|
|
177
|
+
const riskDiff = riskScore[a.hotspotRisk] - riskScore[b.hotspotRisk];
|
|
178
|
+
|
|
179
|
+
if (riskDiff !== 0) return riskDiff; // Lower risk wins
|
|
180
|
+
|
|
181
|
+
// If risks equal, higher cardinality wins
|
|
182
|
+
return b.estimatedCardinality - a.estimatedCardinality;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return sorted[0];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Determine optimal partitioning key strategy
|
|
190
|
+
*/
|
|
191
|
+
private determineKeyStrategy(
|
|
192
|
+
bestKey: PartitionKeyAnalysis | null,
|
|
193
|
+
orderingRequired: boolean,
|
|
194
|
+
partitionCount: number
|
|
195
|
+
): { keyStrategy: string; keyField: string | null; reasoning: string } {
|
|
196
|
+
// No ordering required and no good key → round-robin
|
|
197
|
+
if (!orderingRequired && (!bestKey || bestKey.hotspotRisk === 'high')) {
|
|
198
|
+
return {
|
|
199
|
+
keyStrategy: 'round-robin',
|
|
200
|
+
keyField: null,
|
|
201
|
+
reasoning: 'No ordering requirement and no suitable key field. Round-robin provides best load distribution.'
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Ordering required but bad key → need compound key or custom partitioner
|
|
206
|
+
if (orderingRequired && bestKey && bestKey.hotspotRisk === 'high') {
|
|
207
|
+
return {
|
|
208
|
+
keyStrategy: 'compound-hash',
|
|
209
|
+
keyField: bestKey.key,
|
|
210
|
+
reasoning: `Ordering required but ${bestKey.key} has high hotspot risk. Use compound key (e.g., ${bestKey.key} + timestamp) to distribute load.`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Good key with ordering → simple hash
|
|
215
|
+
if (orderingRequired && bestKey && bestKey.hotspotRisk !== 'high') {
|
|
216
|
+
return {
|
|
217
|
+
keyStrategy: 'simple-hash',
|
|
218
|
+
keyField: bestKey.key,
|
|
219
|
+
reasoning: `${bestKey.key} has ${bestKey.distribution} distribution with ${bestKey.hotspotRisk} hotspot risk. Use simple hash partitioning.`
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// No ordering, but good key available → can use for locality
|
|
224
|
+
if (!orderingRequired && bestKey && bestKey.hotspotRisk === 'low') {
|
|
225
|
+
return {
|
|
226
|
+
keyStrategy: 'simple-hash',
|
|
227
|
+
keyField: bestKey.key,
|
|
228
|
+
reasoning: `${bestKey.key} has excellent distribution. Use hash partitioning for data locality even though ordering not required.`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Fallback: custom partitioner for complex cases
|
|
233
|
+
return {
|
|
234
|
+
keyStrategy: 'custom-partitioner',
|
|
235
|
+
keyField: bestKey?.key ?? null,
|
|
236
|
+
reasoning: 'Complex partitioning requirements. Implement custom partitioner for optimal distribution.'
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Generate warnings for partitioning strategy
|
|
242
|
+
*/
|
|
243
|
+
private generateWarnings(params: {
|
|
244
|
+
bestKey: PartitionKeyAnalysis | null;
|
|
245
|
+
recommendedPartitions: number;
|
|
246
|
+
currentPartitionCount?: number;
|
|
247
|
+
req: PartitionAnalysisRequest;
|
|
248
|
+
}): string[] {
|
|
249
|
+
const warnings: string[] = [];
|
|
250
|
+
|
|
251
|
+
// Hotspot warnings
|
|
252
|
+
if (params.bestKey && params.bestKey.hotspotRisk === 'high') {
|
|
253
|
+
warnings.push(
|
|
254
|
+
`⚠️ Best key (${params.bestKey.key}) has HIGH hotspot risk with cardinality ${params.bestKey.estimatedCardinality}. ` +
|
|
255
|
+
`Consider compound key or custom partitioner.`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (params.bestKey && params.bestKey.hotspotRisk === 'medium') {
|
|
260
|
+
warnings.push(
|
|
261
|
+
`⚠️ Key (${params.bestKey.key}) has MEDIUM hotspot risk. Monitor partition distribution in production.`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Partition count warnings
|
|
266
|
+
if (params.currentPartitionCount && params.recommendedPartitions > params.currentPartitionCount) {
|
|
267
|
+
warnings.push(
|
|
268
|
+
`⚠️ Current partition count (${params.currentPartitionCount}) is below recommended (${params.recommendedPartitions}). ` +
|
|
269
|
+
`Increasing partitions requires re-creating topic or using partition expansion (Kafka 2.4+).`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (params.recommendedPartitions > 100) {
|
|
274
|
+
warnings.push(
|
|
275
|
+
`⚠️ Recommended ${params.recommendedPartitions} partitions is high. Ensure cluster can handle this (max ~4000 per broker).`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Ordering warnings
|
|
280
|
+
if (params.req.orderingRequired && !params.bestKey) {
|
|
281
|
+
warnings.push(
|
|
282
|
+
`🚨 CRITICAL: Ordering required but no suitable partition key found. ALL messages will be unordered!`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Throughput warnings
|
|
287
|
+
if (params.req.targetThroughputMBps && params.req.targetThroughputMBps > params.recommendedPartitions * this.MAX_PARTITION_THROUGHPUT_MBPS) {
|
|
288
|
+
const neededPartitions = Math.ceil(params.req.targetThroughputMBps / this.MAX_PARTITION_THROUGHPUT_MBPS);
|
|
289
|
+
warnings.push(
|
|
290
|
+
`⚠️ Target throughput (${params.req.targetThroughputMBps} MB/s) requires ${neededPartitions} partitions, ` +
|
|
291
|
+
`but recommended only ${params.recommendedPartitions}. Increase partition count.`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return warnings;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Generate usage examples for the recommended strategy
|
|
300
|
+
*/
|
|
301
|
+
private generateExamples(
|
|
302
|
+
keyStrategy: string,
|
|
303
|
+
keyField: string | null,
|
|
304
|
+
topicName: string
|
|
305
|
+
): string[] {
|
|
306
|
+
const examples: string[] = [];
|
|
307
|
+
|
|
308
|
+
switch (keyStrategy) {
|
|
309
|
+
case 'simple-hash':
|
|
310
|
+
examples.push(
|
|
311
|
+
`// Producer (JavaScript/Node.js)`,
|
|
312
|
+
`await producer.send({`,
|
|
313
|
+
` topic: '${topicName}',`,
|
|
314
|
+
` messages: [{`,
|
|
315
|
+
` key: record.${keyField}, // Hash this key`,
|
|
316
|
+
` value: JSON.stringify(record)`,
|
|
317
|
+
` }]`,
|
|
318
|
+
`});`,
|
|
319
|
+
``,
|
|
320
|
+
`// Producer (Java)`,
|
|
321
|
+
`ProducerRecord<String, String> record = new ProducerRecord<>(`,
|
|
322
|
+
` "${topicName}",`,
|
|
323
|
+
` record.get${this.capitalize(keyField || 'id')}(), // Partition key`,
|
|
324
|
+
` jsonValue`,
|
|
325
|
+
`);`,
|
|
326
|
+
`producer.send(record);`
|
|
327
|
+
);
|
|
328
|
+
break;
|
|
329
|
+
|
|
330
|
+
case 'compound-hash':
|
|
331
|
+
examples.push(
|
|
332
|
+
`// Producer (JavaScript/Node.js) - Compound Key`,
|
|
333
|
+
`const compoundKey = \`\${record.${keyField}}-\${Date.now() % 100}\`; // Add temporal component`,
|
|
334
|
+
`await producer.send({`,
|
|
335
|
+
` topic: '${topicName}',`,
|
|
336
|
+
` messages: [{`,
|
|
337
|
+
` key: compoundKey,`,
|
|
338
|
+
` value: JSON.stringify(record)`,
|
|
339
|
+
` }]`,
|
|
340
|
+
`});`,
|
|
341
|
+
``,
|
|
342
|
+
`// Alternative: Geographic + Entity ID`,
|
|
343
|
+
`const compoundKey = \`\${record.region}-\${record.${keyField}}\`;`
|
|
344
|
+
);
|
|
345
|
+
break;
|
|
346
|
+
|
|
347
|
+
case 'custom-partitioner':
|
|
348
|
+
examples.push(
|
|
349
|
+
`// Custom Partitioner (Java)`,
|
|
350
|
+
`public class CustomPartitioner implements Partitioner {`,
|
|
351
|
+
` @Override`,
|
|
352
|
+
` public int partition(String topic, Object key, byte[] keyBytes,`,
|
|
353
|
+
` Object value, byte[] valueBytes, Cluster cluster) {`,
|
|
354
|
+
` int partitionCount = cluster.partitionCountForTopic(topic);`,
|
|
355
|
+
` `,
|
|
356
|
+
` // Custom logic here`,
|
|
357
|
+
` String keyStr = (String) key;`,
|
|
358
|
+
` if (keyStr.startsWith("priority-")) {`,
|
|
359
|
+
` return 0; // High-priority partition`,
|
|
360
|
+
` }`,
|
|
361
|
+
` `,
|
|
362
|
+
` // Default hashing for others`,
|
|
363
|
+
` return Math.abs(keyStr.hashCode()) % (partitionCount - 1) + 1;`,
|
|
364
|
+
` }`,
|
|
365
|
+
`}`,
|
|
366
|
+
``,
|
|
367
|
+
`// Configure producer`,
|
|
368
|
+
`props.put("partitioner.class", "com.example.CustomPartitioner");`
|
|
369
|
+
);
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
case 'round-robin':
|
|
373
|
+
examples.push(
|
|
374
|
+
`// Producer (JavaScript/Node.js) - No Key`,
|
|
375
|
+
`await producer.send({`,
|
|
376
|
+
` topic: '${topicName}',`,
|
|
377
|
+
` messages: [{`,
|
|
378
|
+
` value: JSON.stringify(record) // No key = round-robin`,
|
|
379
|
+
` }]`,
|
|
380
|
+
`});`,
|
|
381
|
+
``,
|
|
382
|
+
`// Producer (Java)`,
|
|
383
|
+
`ProducerRecord<String, String> record = new ProducerRecord<>(`,
|
|
384
|
+
` "${topicName}",`,
|
|
385
|
+
` null, // No key`,
|
|
386
|
+
` jsonValue`,
|
|
387
|
+
`);`,
|
|
388
|
+
`producer.send(record);`
|
|
389
|
+
);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return examples;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Helper: Next power of 2
|
|
398
|
+
*/
|
|
399
|
+
private nextPowerOfTwo(n: number): number {
|
|
400
|
+
return Math.pow(2, Math.ceil(Math.log2(n)));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Helper: Capitalize first letter
|
|
405
|
+
*/
|
|
406
|
+
private capitalize(str: string): string {
|
|
407
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Calculate partition distribution for a given key
|
|
412
|
+
*/
|
|
413
|
+
calculateDistribution(
|
|
414
|
+
sampleValues: string[],
|
|
415
|
+
partitionCount: number
|
|
416
|
+
): { partition: number; count: number; percentage: number }[] {
|
|
417
|
+
// Simulate Kafka's DefaultPartitioner
|
|
418
|
+
const partitionCounts = new Map<number, number>();
|
|
419
|
+
|
|
420
|
+
for (const value of sampleValues) {
|
|
421
|
+
const hash = this.murmur2Hash(value);
|
|
422
|
+
const partition = Math.abs(hash) % partitionCount;
|
|
423
|
+
partitionCounts.set(partition, (partitionCounts.get(partition) || 0) + 1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Convert to array and calculate percentages
|
|
427
|
+
const distribution = Array.from(partitionCounts.entries())
|
|
428
|
+
.map(([partition, count]) => ({
|
|
429
|
+
partition,
|
|
430
|
+
count,
|
|
431
|
+
percentage: (count / sampleValues.length) * 100
|
|
432
|
+
}))
|
|
433
|
+
.sort((a, b) => b.count - a.count);
|
|
434
|
+
|
|
435
|
+
return distribution;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Simple MurmurHash2 implementation (matches Kafka's DefaultPartitioner)
|
|
440
|
+
*/
|
|
441
|
+
private murmur2Hash(data: string): number {
|
|
442
|
+
const bytes = Buffer.from(data, 'utf-8');
|
|
443
|
+
const length = bytes.length;
|
|
444
|
+
const seed = 0x9747b28c;
|
|
445
|
+
|
|
446
|
+
const m = 0x5bd1e995;
|
|
447
|
+
const r = 24;
|
|
448
|
+
let h = seed ^ length;
|
|
449
|
+
|
|
450
|
+
for (let i = 0; i + 4 <= length; i += 4) {
|
|
451
|
+
let k = bytes.readInt32LE(i);
|
|
452
|
+
k = Math.imul(k, m);
|
|
453
|
+
k ^= k >>> r;
|
|
454
|
+
k = Math.imul(k, m);
|
|
455
|
+
h = Math.imul(h, m);
|
|
456
|
+
h ^= k;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const remaining = length % 4;
|
|
460
|
+
if (remaining >= 3) h ^= bytes[length - 3] << 16;
|
|
461
|
+
if (remaining >= 2) h ^= bytes[length - 2] << 8;
|
|
462
|
+
if (remaining >= 1) {
|
|
463
|
+
h ^= bytes[length - 1];
|
|
464
|
+
h = Math.imul(h, m);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
h ^= h >>> 13;
|
|
468
|
+
h = Math.imul(h, m);
|
|
469
|
+
h ^= h >>> 15;
|
|
470
|
+
|
|
471
|
+
return h;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
class ClusterSizingCalculator {
|
|
2
|
+
constructor() {
|
|
3
|
+
// Constants (empirical limits from production deployments)
|
|
4
|
+
this.MAX_PARTITIONS_PER_BROKER = 4e3;
|
|
5
|
+
this.SINGLE_PARTITION_WRITE_MBPS = 20;
|
|
6
|
+
// Conservative estimate
|
|
7
|
+
this.SINGLE_PARTITION_READ_MBPS = 40;
|
|
8
|
+
this.NETWORK_OVERHEAD = 1.3;
|
|
9
|
+
// 30% overhead for protocol, replication
|
|
10
|
+
this.BASE_RAM_GB = 8;
|
|
11
|
+
// Minimum OS + Kafka overhead
|
|
12
|
+
this.RAM_PER_PARTITION_MB = 1;
|
|
13
|
+
// Page cache per partition
|
|
14
|
+
this.RAM_PER_REPLICATION_MB = 2;
|
|
15
|
+
}
|
|
16
|
+
// Replica fetcher overhead
|
|
17
|
+
/**
|
|
18
|
+
* Calculate recommended cluster size
|
|
19
|
+
*/
|
|
20
|
+
calculate(req) {
|
|
21
|
+
const growthFactor = req.growthFactor ?? 2;
|
|
22
|
+
const targetCPU = req.targetCPUUtilization ?? 0.6;
|
|
23
|
+
const targetDisk = req.targetDiskUtilization ?? 0.7;
|
|
24
|
+
const writeThru = req.writeThroughputMBps * growthFactor;
|
|
25
|
+
const readThru = req.readThroughputMBps * growthFactor;
|
|
26
|
+
const totalPartitions = req.topicCount * req.avgPartitionsPerTopic;
|
|
27
|
+
const brokersForWrite = Math.ceil(
|
|
28
|
+
writeThru * this.NETWORK_OVERHEAD / (this.SINGLE_PARTITION_WRITE_MBPS * this.MAX_PARTITIONS_PER_BROKER / 10)
|
|
29
|
+
);
|
|
30
|
+
const brokersForRead = Math.ceil(
|
|
31
|
+
readThru * this.NETWORK_OVERHEAD / (this.SINGLE_PARTITION_READ_MBPS * this.MAX_PARTITIONS_PER_BROKER / 10)
|
|
32
|
+
);
|
|
33
|
+
const brokersForPartitions = Math.ceil(totalPartitions / this.MAX_PARTITIONS_PER_BROKER);
|
|
34
|
+
let brokerCount = Math.max(brokersForWrite, brokersForRead, brokersForPartitions);
|
|
35
|
+
brokerCount = Math.max(brokerCount, req.replicationFactor);
|
|
36
|
+
if (brokerCount > 3 && brokerCount % 3 !== 0) {
|
|
37
|
+
brokerCount = Math.ceil(brokerCount / 3) * 3;
|
|
38
|
+
}
|
|
39
|
+
const partitionsPerBroker = Math.ceil(totalPartitions / brokerCount);
|
|
40
|
+
const dailyDataVolumeMB = req.messagesPerDay * req.avgMessageSizeKB / 1024;
|
|
41
|
+
const totalStorageGB = dailyDataVolumeMB * req.retentionDays / 1024;
|
|
42
|
+
const storageWithReplicationGB = totalStorageGB * req.replicationFactor;
|
|
43
|
+
const diskPerBrokerGB = Math.ceil(
|
|
44
|
+
storageWithReplicationGB / brokerCount / targetDisk
|
|
45
|
+
);
|
|
46
|
+
const ramForPartitionsGB = partitionsPerBroker * this.RAM_PER_PARTITION_MB / 1024;
|
|
47
|
+
const ramForReplicationGB = partitionsPerBroker * req.replicationFactor * this.RAM_PER_REPLICATION_MB / 1024;
|
|
48
|
+
const ramGB = Math.ceil(this.BASE_RAM_GB + ramForPartitionsGB + ramForReplicationGB);
|
|
49
|
+
const cpuCores = Math.ceil(partitionsPerBroker / 500) + 8;
|
|
50
|
+
const networkGbps = this.calculateNetworkBandwidth(writeThru, readThru, brokerCount);
|
|
51
|
+
const estimatedWriteThroughputMBps = brokerCount * this.SINGLE_PARTITION_WRITE_MBPS * (partitionsPerBroker / 10);
|
|
52
|
+
const estimatedReadThroughputMBps = brokerCount * this.SINGLE_PARTITION_READ_MBPS * (partitionsPerBroker / 10);
|
|
53
|
+
const estimatedLatencyP99Ms = this.estimateLatency(partitionsPerBroker, diskPerBrokerGB);
|
|
54
|
+
const warnings = this.generateWarnings({
|
|
55
|
+
partitionsPerBroker,
|
|
56
|
+
brokerCount,
|
|
57
|
+
req,
|
|
58
|
+
diskPerBrokerGB
|
|
59
|
+
});
|
|
60
|
+
const recommendations = this.generateRecommendations({
|
|
61
|
+
partitionsPerBroker,
|
|
62
|
+
brokerCount,
|
|
63
|
+
req,
|
|
64
|
+
ramGB,
|
|
65
|
+
diskPerBrokerGB
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
brokerCount,
|
|
69
|
+
totalPartitions,
|
|
70
|
+
partitionsPerBroker,
|
|
71
|
+
cpuCores,
|
|
72
|
+
ramGB,
|
|
73
|
+
diskGB: diskPerBrokerGB,
|
|
74
|
+
networkGbps,
|
|
75
|
+
estimatedWriteThroughputMBps,
|
|
76
|
+
estimatedReadThroughputMBps,
|
|
77
|
+
estimatedLatencyP99Ms,
|
|
78
|
+
dailyDataVolumeMB,
|
|
79
|
+
totalStorageRequiredGB: totalStorageGB,
|
|
80
|
+
storageWithReplicationGB,
|
|
81
|
+
warnings,
|
|
82
|
+
recommendations
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Calculate network bandwidth requirements
|
|
87
|
+
*/
|
|
88
|
+
calculateNetworkBandwidth(writeMBps, readMBps, brokerCount) {
|
|
89
|
+
const replicationTrafficMBps = writeMBps * 2;
|
|
90
|
+
const totalTrafficMBps = writeMBps + readMBps + replicationTrafficMBps;
|
|
91
|
+
const perBrokerMBps = totalTrafficMBps / brokerCount;
|
|
92
|
+
const gbps = Math.ceil(perBrokerMBps * 8 / 1e3);
|
|
93
|
+
if (gbps <= 1) return 1;
|
|
94
|
+
if (gbps <= 10) return 10;
|
|
95
|
+
if (gbps <= 25) return 25;
|
|
96
|
+
if (gbps <= 40) return 40;
|
|
97
|
+
return 100;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Estimate p99 latency based on partition count and disk size
|
|
101
|
+
*/
|
|
102
|
+
estimateLatency(partitionsPerBroker, diskGB) {
|
|
103
|
+
let latencyMs = 5;
|
|
104
|
+
if (partitionsPerBroker > 2e3) {
|
|
105
|
+
latencyMs += 5;
|
|
106
|
+
} else if (partitionsPerBroker > 1e3) {
|
|
107
|
+
latencyMs += 2;
|
|
108
|
+
}
|
|
109
|
+
if (diskGB > 1e4) {
|
|
110
|
+
latencyMs += 10;
|
|
111
|
+
} else if (diskGB > 5e3) {
|
|
112
|
+
latencyMs += 5;
|
|
113
|
+
}
|
|
114
|
+
return latencyMs;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generate warnings for potential issues
|
|
118
|
+
*/
|
|
119
|
+
generateWarnings(params) {
|
|
120
|
+
const warnings = [];
|
|
121
|
+
if (params.partitionsPerBroker > 4e3) {
|
|
122
|
+
warnings.push(
|
|
123
|
+
`\u26A0\uFE0F ${params.partitionsPerBroker} partitions per broker exceeds recommended limit (4000). Consider increasing broker count to ${Math.ceil(params.req.topicCount * params.req.avgPartitionsPerTopic / 4e3)}.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (params.partitionsPerBroker < 100) {
|
|
127
|
+
warnings.push(
|
|
128
|
+
`\u26A0\uFE0F ${params.partitionsPerBroker} partitions per broker is very low. You may be over-provisioned. Consider reducing to ${Math.max(3, Math.ceil(params.brokerCount / 2))} brokers.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (params.brokerCount < params.req.replicationFactor) {
|
|
132
|
+
warnings.push(
|
|
133
|
+
`\u{1F6A8} CRITICAL: ${params.brokerCount} brokers < replication factor (${params.req.replicationFactor}). Minimum required: ${params.req.replicationFactor} brokers.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (params.brokerCount === params.req.replicationFactor) {
|
|
137
|
+
warnings.push(
|
|
138
|
+
`\u26A0\uFE0F Broker count equals replication factor. No fault tolerance! Increase to at least ${params.req.replicationFactor + 1} brokers.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (params.diskPerBrokerGB > 1e4) {
|
|
142
|
+
warnings.push(
|
|
143
|
+
`\u26A0\uFE0F ${params.diskPerBrokerGB} GB per broker is very large. Consider using HDD for cost savings or increasing broker count to reduce disk per broker.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (params.req.minInsyncReplicas && params.req.minInsyncReplicas >= params.req.replicationFactor) {
|
|
147
|
+
warnings.push(
|
|
148
|
+
`\u{1F6A8} CRITICAL: min.insync.replicas (${params.req.minInsyncReplicas}) >= replication.factor (${params.req.replicationFactor}). This will cause writes to fail! Set min.insync.replicas to ${params.req.replicationFactor - 1}.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return warnings;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Generate optimization recommendations
|
|
155
|
+
*/
|
|
156
|
+
generateRecommendations(params) {
|
|
157
|
+
const recommendations = [];
|
|
158
|
+
if (params.req.avgPartitionsPerTopic < 10) {
|
|
159
|
+
recommendations.push(
|
|
160
|
+
`\u{1F4A1} Consider increasing partitions per topic to 12-24 for better parallelism and future growth.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (params.req.avgPartitionsPerTopic > 100) {
|
|
164
|
+
recommendations.push(
|
|
165
|
+
`\u{1F4A1} ${params.req.avgPartitionsPerTopic} partitions per topic is high. Consider splitting into multiple topics or reducing partition count unless you need extreme parallelism.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (params.diskPerBrokerGB > 5e3) {
|
|
169
|
+
recommendations.push(
|
|
170
|
+
`\u{1F4A1} Large disk requirement (${params.diskPerBrokerGB} GB). Consider using tiered storage (Kafka 2.8+) to offload old data to S3/Azure Blob/GCS.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (params.ramGB > 128) {
|
|
174
|
+
recommendations.push(
|
|
175
|
+
`\u{1F4A1} High RAM requirement (${params.ramGB} GB). Ensure your infrastructure supports this. Consider AWS i3en/i4i instances or equivalent.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (!params.req.minInsyncReplicas) {
|
|
179
|
+
recommendations.push(
|
|
180
|
+
`\u{1F4A1} Set min.insync.replicas=2 for production (currently not configured). This prevents data loss if a broker fails.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (params.req.replicationFactor < 3) {
|
|
184
|
+
recommendations.push(
|
|
185
|
+
`\u{1F4A1} Increase replication factor to 3 for production durability (currently ${params.req.replicationFactor}).`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (params.brokerCount % 3 !== 0 && params.brokerCount > 3) {
|
|
189
|
+
recommendations.push(
|
|
190
|
+
`\u{1F4A1} Broker count (${params.brokerCount}) is not a multiple of 3. Consider using ${Math.ceil(params.brokerCount / 3) * 3} brokers for better rack awareness.`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return recommendations;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Calculate disk IOPS requirements
|
|
197
|
+
*/
|
|
198
|
+
calculateDiskIOPS(writeThroughputMBps, replicationFactor) {
|
|
199
|
+
const writeIOPS = writeThroughputMBps * 1024 / 4;
|
|
200
|
+
const replicationWriteIOPS = writeIOPS * (replicationFactor - 1);
|
|
201
|
+
const readIOPS = writeIOPS * 0.5;
|
|
202
|
+
const totalWriteIOPS = writeIOPS + replicationWriteIOPS;
|
|
203
|
+
const totalReadIOPS = readIOPS;
|
|
204
|
+
let recommendation = "";
|
|
205
|
+
if (totalWriteIOPS > 5e4) {
|
|
206
|
+
recommendation = "Use NVMe SSDs (500K+ IOPS)";
|
|
207
|
+
} else if (totalWriteIOPS > 1e4) {
|
|
208
|
+
recommendation = "Use Premium SSD (20K-64K IOPS)";
|
|
209
|
+
} else {
|
|
210
|
+
recommendation = "Standard SSD (3K-6K IOPS) sufficient";
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
readIOPS: Math.ceil(totalReadIOPS),
|
|
214
|
+
writeIOPS: Math.ceil(totalWriteIOPS),
|
|
215
|
+
recommendation
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
ClusterSizingCalculator
|
|
221
|
+
};
|