@wazir-dev/cli 1.0.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/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
# Database Anti-Patterns
|
|
2
|
+
> A field guide to the most destructive database design, query, and operational mistakes — with real incident post-mortems, concrete fixes, and detection rules.
|
|
3
|
+
> **Domain:** Backend
|
|
4
|
+
> **Anti-patterns covered:** 21
|
|
5
|
+
> **Highest severity:** Critical
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why Database Anti-Patterns Matter
|
|
10
|
+
|
|
11
|
+
Database mistakes are uniquely dangerous because they compound silently. A missing index causes no harm at 1,000 rows but brings down production at 10 million. A floating-point money column works fine for months until a reconciliation audit reveals thousands of dollars in drift. Unlike application bugs that crash loudly, database anti-patterns degrade gradually — then fail catastrophically under load, during migrations, or at audit time. Every anti-pattern below has caused real production outages, data loss, or security breaches at known companies. The fixes are almost always straightforward; the hard part is recognizing the pattern before it bites.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Anti-Pattern Catalog
|
|
16
|
+
|
|
17
|
+
### AP-01: N+1 Query Problem
|
|
18
|
+
|
|
19
|
+
**Also known as:** Lazy loading trap, chatty queries, query waterfall
|
|
20
|
+
**Frequency:** Very Common
|
|
21
|
+
**Severity:** High
|
|
22
|
+
**Detection difficulty:** Moderate
|
|
23
|
+
|
|
24
|
+
**What it looks like:**
|
|
25
|
+
```python
|
|
26
|
+
# Fetch all orders, then fetch customer for each order
|
|
27
|
+
orders = Order.objects.all()
|
|
28
|
+
for order in orders:
|
|
29
|
+
print(order.customer.name) # Each access fires a separate query
|
|
30
|
+
```
|
|
31
|
+
This generates 1 query for orders + N queries for customers. With 500 orders, that is 501 database round-trips.
|
|
32
|
+
|
|
33
|
+
**Why developers do it:**
|
|
34
|
+
ORM lazy loading is the default in most frameworks (Django, Rails, Hibernate). The code reads naturally and works fine in development with small datasets. Developers often do not see the generated SQL.
|
|
35
|
+
|
|
36
|
+
**What goes wrong:**
|
|
37
|
+
A checkout service deployment at an e-commerce company introduced an N+1 query that increased per-request database connections from 2 to 11 under load. On November 14 at 23:47 UTC, the checkout service began returning errors to 34% of users. The database connection pool was exhausted within minutes. The 19-minute outage required a rollback. The N+1 was detectable with existing tooling but no query performance review existed in the deployment checklist. Sentry documents N+1 queries as one of the most common performance issues detected in production applications.
|
|
38
|
+
|
|
39
|
+
**The fix:**
|
|
40
|
+
```python
|
|
41
|
+
# Before: N+1 (501 queries for 500 orders)
|
|
42
|
+
orders = Order.objects.all()
|
|
43
|
+
for order in orders:
|
|
44
|
+
print(order.customer.name)
|
|
45
|
+
|
|
46
|
+
# After: Eager loading (2 queries total)
|
|
47
|
+
orders = Order.objects.select_related('customer').all()
|
|
48
|
+
for order in orders:
|
|
49
|
+
print(order.customer.name)
|
|
50
|
+
```
|
|
51
|
+
In SQL, replace the loop with a JOIN:
|
|
52
|
+
```sql
|
|
53
|
+
SELECT o.*, c.name
|
|
54
|
+
FROM orders o
|
|
55
|
+
JOIN customers c ON o.customer_id = c.id;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Detection rule:**
|
|
59
|
+
Flag any request that issues more than 10 queries of the same shape differing only in a WHERE parameter. Tools: django-debug-toolbar, Bullet gem (Rails), Hibernate statistics, Sentry performance monitoring.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### AP-02: Missing Indexes on Frequently Queried Columns
|
|
64
|
+
|
|
65
|
+
**Also known as:** The full table scan, index amnesia
|
|
66
|
+
**Frequency:** Very Common
|
|
67
|
+
**Severity:** Critical
|
|
68
|
+
**Detection difficulty:** Moderate
|
|
69
|
+
|
|
70
|
+
**What it looks like:**
|
|
71
|
+
```sql
|
|
72
|
+
-- No index on users.created_at
|
|
73
|
+
SELECT * FROM users WHERE created_at > '2025-01-01' ORDER BY created_at;
|
|
74
|
+
-- Full table scan on 50 million rows
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Why developers do it:**
|
|
78
|
+
Indexes are invisible to application code. Queries work identically with or without them at small scale. Developers add columns without considering query patterns, and ORMs do not auto-create indexes on filter/sort columns.
|
|
79
|
+
|
|
80
|
+
**What goes wrong:**
|
|
81
|
+
A documented API outage was caused by a missing index on `users.created_at`. Staging had 10x less traffic than production, so the issue was invisible in testing. The database connection pool was too small for the resulting traffic spike. The endpoint timed out under production load, cascading into a full service outage. The GitLab database outage of January 31, 2017 — while primarily caused by accidental data deletion — was compounded by replication lag from queries running without proper indexes under increased load.
|
|
82
|
+
|
|
83
|
+
**The fix:**
|
|
84
|
+
```sql
|
|
85
|
+
-- Add the index
|
|
86
|
+
CREATE INDEX idx_users_created_at ON users(created_at);
|
|
87
|
+
|
|
88
|
+
-- For composite queries, use composite indexes
|
|
89
|
+
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
|
|
90
|
+
```
|
|
91
|
+
Run `EXPLAIN ANALYZE` on every slow query and add indexes for any sequential scan on tables above 10,000 rows.
|
|
92
|
+
|
|
93
|
+
**Detection rule:**
|
|
94
|
+
Alert on any query with execution time > 100ms in production. Run `pg_stat_user_tables` (PostgreSQL) or `sys.dm_db_missing_index_details` (SQL Server) weekly.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### AP-03: Over-Indexing
|
|
99
|
+
|
|
100
|
+
**Also known as:** Index hoarding, write penalty creep
|
|
101
|
+
**Frequency:** Common
|
|
102
|
+
**Severity:** Medium
|
|
103
|
+
**Detection difficulty:** Hard
|
|
104
|
+
|
|
105
|
+
**What it looks like:**
|
|
106
|
+
A table with 12 columns and 39 non-clustered indexes, many of which overlap or are never used by the query planner.
|
|
107
|
+
|
|
108
|
+
**Why developers do it:**
|
|
109
|
+
Each slow query gets "fixed" by adding an index. Nobody removes old indexes. DBAs add indexes reactively without auditing existing ones. Over time, the table accumulates indexes that cover every possible column combination.
|
|
110
|
+
|
|
111
|
+
**What goes wrong:**
|
|
112
|
+
Percona benchmarked PostgreSQL and found that increasing indexes from 7 to 39 reduced write throughput to approximately 42% of the original. For a table with 9 non-clustered indexes, every INSERT requires 10 writes to the transaction log (one per index plus the clustered index). Index bloat consumes memory that could be used for data caching, and the query optimizer becomes confused when presented with many similar indexes, choosing suboptimal execution plans.
|
|
113
|
+
|
|
114
|
+
**The fix:**
|
|
115
|
+
```sql
|
|
116
|
+
-- PostgreSQL: find unused indexes
|
|
117
|
+
SELECT indexrelname, idx_scan, pg_size_pretty(pg_relation_size(i.indexrelid))
|
|
118
|
+
FROM pg_stat_user_indexes i
|
|
119
|
+
JOIN pg_index USING (indexrelid)
|
|
120
|
+
WHERE idx_scan = 0 AND NOT indisunique
|
|
121
|
+
ORDER BY pg_relation_size(i.indexrelid) DESC;
|
|
122
|
+
|
|
123
|
+
-- Drop unused indexes
|
|
124
|
+
DROP INDEX CONCURRENTLY idx_never_used;
|
|
125
|
+
```
|
|
126
|
+
Audit indexes quarterly. Remove any non-unique index with zero scans over a 30-day observation window.
|
|
127
|
+
|
|
128
|
+
**Detection rule:**
|
|
129
|
+
Flag tables where the number of indexes exceeds the number of columns, or where any index has had zero scans in 30+ days.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### AP-04: God Table
|
|
134
|
+
|
|
135
|
+
**Also known as:** The mega-table, kitchen sink table, one table to rule them all
|
|
136
|
+
**Frequency:** Common
|
|
137
|
+
**Severity:** High
|
|
138
|
+
**Detection difficulty:** Easy
|
|
139
|
+
|
|
140
|
+
**What it looks like:**
|
|
141
|
+
```sql
|
|
142
|
+
CREATE TABLE records (
|
|
143
|
+
id INT PRIMARY KEY,
|
|
144
|
+
type VARCHAR(50), -- 'user', 'order', 'product', 'log', 'event'
|
|
145
|
+
name VARCHAR(255),
|
|
146
|
+
email VARCHAR(255), -- only for type='user'
|
|
147
|
+
price DECIMAL(10,2), -- only for type='product'
|
|
148
|
+
quantity INT, -- only for type='order'
|
|
149
|
+
log_level VARCHAR(20), -- only for type='log'
|
|
150
|
+
payload TEXT,
|
|
151
|
+
-- ... 200 more columns, most NULL for any given row
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Why developers do it:**
|
|
156
|
+
Starts as a quick prototype. Adding a new entity type means just adding a column instead of a new table with migrations. Developers avoid the perceived overhead of schema changes. The table "works" at first.
|
|
157
|
+
|
|
158
|
+
**What goes wrong:**
|
|
159
|
+
A mid-sized e-commerce platform built a God Table for all transaction data. It started with 50 columns and grew to over 200. Queries became monstrosities scanning millions of rows for simple lookups. Performance degraded from milliseconds to seconds during peak hours. Row size exceeded page limits, forcing row-overflow storage. Indexes became enormous because every row was indexed regardless of type.
|
|
160
|
+
|
|
161
|
+
**The fix:**
|
|
162
|
+
Decompose into domain-specific tables:
|
|
163
|
+
```sql
|
|
164
|
+
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));
|
|
165
|
+
CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(255), price DECIMAL(10,2));
|
|
166
|
+
CREATE TABLE orders (id INT PRIMARY KEY, user_id INT REFERENCES users(id), quantity INT);
|
|
167
|
+
CREATE TABLE logs (id INT PRIMARY KEY, log_level VARCHAR(20), payload TEXT);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Detection rule:**
|
|
171
|
+
Flag any table with more than 30 columns, or any table where more than 40% of columns are NULL in the average row.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### AP-05: Entity-Attribute-Value (EAV) Abuse
|
|
176
|
+
|
|
177
|
+
**Also known as:** The schema-free relational table, property bag, open schema
|
|
178
|
+
**Frequency:** Common
|
|
179
|
+
**Severity:** High
|
|
180
|
+
**Detection difficulty:** Moderate
|
|
181
|
+
|
|
182
|
+
**What it looks like:**
|
|
183
|
+
```sql
|
|
184
|
+
CREATE TABLE attributes (
|
|
185
|
+
entity_id INT,
|
|
186
|
+
attribute_name VARCHAR(255),
|
|
187
|
+
attribute_value TEXT
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
-- Storing a product with name, price, color:
|
|
191
|
+
INSERT INTO attributes VALUES (1, 'name', 'Widget');
|
|
192
|
+
INSERT INTO attributes VALUES (1, 'price', '29.99');
|
|
193
|
+
INSERT INTO attributes VALUES (1, 'color', 'blue');
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Why developers do it:**
|
|
197
|
+
Provides maximum flexibility for dynamic schemas. Avoids migrations when adding new attributes. Commonly seen in CMS platforms, e-commerce (Magento famously uses EAV), and multi-tenant SaaS.
|
|
198
|
+
|
|
199
|
+
**What goes wrong:**
|
|
200
|
+
Magento's EAV model is well-documented: with 100,000 entities and 400 attributes, the attribute tables can reach 100,000,000 entries. Simple entity retrieval requires expensive multi-table JOINs (one per attribute). Data type enforcement is impossible — `price` stored as TEXT means no numeric validation. Typos silently create new attributes. You cannot declare foreign keys, CHECK constraints, or NOT NULL on individual attributes. Query complexity explodes: retrieving a single "row" of data requires pivoting across dozens of joins.
|
|
201
|
+
|
|
202
|
+
**The fix:**
|
|
203
|
+
For moderate schema variation, use a JSONB column:
|
|
204
|
+
```sql
|
|
205
|
+
CREATE TABLE products (
|
|
206
|
+
id INT PRIMARY KEY,
|
|
207
|
+
name VARCHAR(255) NOT NULL,
|
|
208
|
+
price DECIMAL(10,2) NOT NULL,
|
|
209
|
+
attributes JSONB -- for truly dynamic fields
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
-- Query with indexing support
|
|
213
|
+
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
|
|
214
|
+
SELECT * FROM products WHERE attributes->>'color' = 'blue';
|
|
215
|
+
```
|
|
216
|
+
For extreme variation, consider a document database (MongoDB, DynamoDB) instead of forcing EAV into a relational schema.
|
|
217
|
+
|
|
218
|
+
**Detection rule:**
|
|
219
|
+
Flag any table with columns named `attribute_name`/`attribute_value` or `key`/`value` where the table has more than 100,000 rows.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### AP-06: Implicit Columns (SELECT *)
|
|
224
|
+
|
|
225
|
+
**Also known as:** The wildcard query, kitchen sink select
|
|
226
|
+
**Frequency:** Very Common
|
|
227
|
+
**Severity:** Medium
|
|
228
|
+
**Detection difficulty:** Easy
|
|
229
|
+
|
|
230
|
+
**What it looks like:**
|
|
231
|
+
```sql
|
|
232
|
+
SELECT * FROM users WHERE id = 42;
|
|
233
|
+
```
|
|
234
|
+
Application only uses `id`, `name`, and `email` — but the table has 35 columns including a TEXT bio and a BYTEA avatar.
|
|
235
|
+
|
|
236
|
+
**Why developers do it:**
|
|
237
|
+
It is less typing. Works fine during initial development. ORMs default to loading full model objects. Developers do not think about which columns are actually needed.
|
|
238
|
+
|
|
239
|
+
**What goes wrong:**
|
|
240
|
+
SELECT * fetches all columns including large TEXT and BLOB fields, increasing I/O, network transfer, and memory usage. It defeats covering indexes (the query planner cannot use an index-only scan). When new columns are added to the table, existing code silently fetches them — potentially including sensitive data (SSN, tokens). On BigQuery and similar pay-per-scan services, SELECT * on a TB-sized table with dozens of columns can cost hundreds of dollars per query when only four columns are needed.
|
|
241
|
+
|
|
242
|
+
**The fix:**
|
|
243
|
+
```sql
|
|
244
|
+
-- Before
|
|
245
|
+
SELECT * FROM users WHERE id = 42;
|
|
246
|
+
|
|
247
|
+
-- After
|
|
248
|
+
SELECT id, name, email FROM users WHERE id = 42;
|
|
249
|
+
```
|
|
250
|
+
In ORMs:
|
|
251
|
+
```python
|
|
252
|
+
# Django: use .only() or .values()
|
|
253
|
+
User.objects.filter(id=42).only('id', 'name', 'email')
|
|
254
|
+
|
|
255
|
+
# Rails: use .select()
|
|
256
|
+
User.where(id: 42).select(:id, :name, :email)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Detection rule:**
|
|
260
|
+
Static analysis: grep for `SELECT *` in application code and queries. Exclude migrations and backup scripts.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### AP-07: Storing Delimited Lists in a Single Column
|
|
265
|
+
|
|
266
|
+
**Also known as:** Jaywalking, CSV column, comma-separated hell
|
|
267
|
+
**Frequency:** Common
|
|
268
|
+
**Severity:** High
|
|
269
|
+
**Detection difficulty:** Easy
|
|
270
|
+
|
|
271
|
+
**What it looks like:**
|
|
272
|
+
```sql
|
|
273
|
+
CREATE TABLE articles (
|
|
274
|
+
id INT PRIMARY KEY,
|
|
275
|
+
title VARCHAR(255),
|
|
276
|
+
tags VARCHAR(1000) -- stores "python,database,tutorial"
|
|
277
|
+
);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Why developers do it:**
|
|
281
|
+
Avoids creating a junction table. Simpler INSERT statements. Fewer tables in the schema. The application can easily split on commas. It is Chapter 1 of Bill Karwin's "SQL Antipatterns" because it is so pervasive.
|
|
282
|
+
|
|
283
|
+
**What goes wrong:**
|
|
284
|
+
You cannot enforce uniqueness within the list (duplicates like `"python,python,python"` are valid). You cannot enforce data types or referential integrity. Searching requires `LIKE '%python%'` which cannot use indexes and produces false positives (`LIKE '%java%'` matches `"javascript"`). JOINs are impossible. Sorting, counting, and aggregation require application-side parsing. Column length limits silently truncate data. A healthcare application stored diagnosis codes as comma-delimited lists; a truncated ICD code led to a misclassified billing record that triggered an insurance audit.
|
|
285
|
+
|
|
286
|
+
**The fix:**
|
|
287
|
+
```sql
|
|
288
|
+
-- Junction table
|
|
289
|
+
CREATE TABLE article_tags (
|
|
290
|
+
article_id INT REFERENCES articles(id),
|
|
291
|
+
tag_id INT REFERENCES tags(id),
|
|
292
|
+
PRIMARY KEY (article_id, tag_id)
|
|
293
|
+
);
|
|
294
|
+
```
|
|
295
|
+
If you need flexibility without a junction table, use native array types:
|
|
296
|
+
```sql
|
|
297
|
+
-- PostgreSQL array column
|
|
298
|
+
ALTER TABLE articles ADD COLUMN tags TEXT[];
|
|
299
|
+
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);
|
|
300
|
+
SELECT * FROM articles WHERE 'python' = ANY(tags);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Detection rule:**
|
|
304
|
+
Flag any VARCHAR/TEXT column that contains commas or semicolons in more than 50% of its rows.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### AP-08: Not Using Transactions Where Needed
|
|
309
|
+
|
|
310
|
+
**Also known as:** Implicit auto-commit, partial write, split-brain writes
|
|
311
|
+
**Frequency:** Common
|
|
312
|
+
**Severity:** Critical
|
|
313
|
+
**Detection difficulty:** Hard
|
|
314
|
+
|
|
315
|
+
**What it looks like:**
|
|
316
|
+
```python
|
|
317
|
+
# Transfer money without a transaction
|
|
318
|
+
def transfer(from_id, to_id, amount):
|
|
319
|
+
db.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (amount, from_id))
|
|
320
|
+
# Application crashes here -- money vanished
|
|
321
|
+
db.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (amount, to_id))
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Why developers do it:**
|
|
325
|
+
Many ORMs and database drivers default to auto-commit mode, so each statement commits immediately. Developers assume the code runs atomically because it is in the same function. Transaction management feels like boilerplate.
|
|
326
|
+
|
|
327
|
+
**What goes wrong:**
|
|
328
|
+
Money disappears from one account but never arrives in another. Inventory is decremented but the order is never created. In a widely reported incident, the Travis CI database outage of March 13, 2018, a query was accidentally run against the production database which truncated all tables — the query was blocked for around 10 minutes before finally executing. Proper transaction boundaries and rollback procedures could have prevented the data loss.
|
|
329
|
+
|
|
330
|
+
**The fix:**
|
|
331
|
+
```python
|
|
332
|
+
def transfer(from_id, to_id, amount):
|
|
333
|
+
with db.transaction():
|
|
334
|
+
db.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (amount, from_id))
|
|
335
|
+
db.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (amount, to_id))
|
|
336
|
+
# Both succeed or both roll back
|
|
337
|
+
```
|
|
338
|
+
Establish a rule: any operation that modifies more than one row or more than one table must be wrapped in an explicit transaction.
|
|
339
|
+
|
|
340
|
+
**Detection rule:**
|
|
341
|
+
Audit application code for sequences of write statements (`INSERT`, `UPDATE`, `DELETE`) without an enclosing `BEGIN`/`COMMIT` or ORM transaction context manager.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### AP-09: Premature Denormalization
|
|
346
|
+
|
|
347
|
+
**Also known as:** Speculative optimization, copy-paste schema
|
|
348
|
+
**Frequency:** Common
|
|
349
|
+
**Severity:** Medium
|
|
350
|
+
**Detection difficulty:** Hard
|
|
351
|
+
|
|
352
|
+
**What it looks like:**
|
|
353
|
+
```sql
|
|
354
|
+
CREATE TABLE orders (
|
|
355
|
+
id INT PRIMARY KEY,
|
|
356
|
+
user_id INT,
|
|
357
|
+
user_name VARCHAR(255), -- copied from users table
|
|
358
|
+
user_email VARCHAR(255), -- copied from users table
|
|
359
|
+
product_name VARCHAR(255), -- copied from products table
|
|
360
|
+
product_price DECIMAL(10,2) -- copied from products table
|
|
361
|
+
);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Why developers do it:**
|
|
365
|
+
Fear of JOIN performance. "We might need it to be fast later." Cargo-culting advice from high-scale systems (Facebook, Twitter) without having the same traffic. Premature denormalization is a specific case of premature optimization.
|
|
366
|
+
|
|
367
|
+
**What goes wrong:**
|
|
368
|
+
When a user changes their email, you must update every order row that copied it — or accept stale data. Write amplification increases as every INSERT must propagate to denormalized copies. A SaaS platform denormalized customer addresses into every invoice, shipment, and support ticket table. When a customer changed address, 4 out of 7 tables were updated but 3 were missed. The customer received a shipment at the old address three months after moving. Data inconsistency is the inevitable consequence of denormalization without automated synchronization.
|
|
369
|
+
|
|
370
|
+
**The fix:**
|
|
371
|
+
Normalize first. Denormalize only when:
|
|
372
|
+
1. You have measured an actual performance problem with `EXPLAIN ANALYZE`
|
|
373
|
+
2. You have exhausted index and query optimization
|
|
374
|
+
3. You implement automated synchronization (triggers, materialized views, or change-data-capture)
|
|
375
|
+
|
|
376
|
+
```sql
|
|
377
|
+
-- Use a materialized view instead of denormalizing the base tables
|
|
378
|
+
CREATE MATERIALIZED VIEW order_details AS
|
|
379
|
+
SELECT o.id, o.user_id, u.name, u.email, p.name AS product_name, p.price
|
|
380
|
+
FROM orders o
|
|
381
|
+
JOIN users u ON o.user_id = u.id
|
|
382
|
+
JOIN products p ON o.product_id = p.id;
|
|
383
|
+
|
|
384
|
+
-- Refresh on a schedule
|
|
385
|
+
REFRESH MATERIALIZED VIEW CONCURRENTLY order_details;
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Detection rule:**
|
|
389
|
+
Flag any column whose name matches `[table]_[column]` pattern (e.g., `user_name` in the `orders` table) where a foreign key to that table already exists.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### AP-10: Missing Foreign Key Constraints
|
|
394
|
+
|
|
395
|
+
**Also known as:** Orphan rows, referential anarchy, trust-the-app integrity
|
|
396
|
+
**Frequency:** Very Common
|
|
397
|
+
**Severity:** High
|
|
398
|
+
**Detection difficulty:** Moderate
|
|
399
|
+
|
|
400
|
+
**What it looks like:**
|
|
401
|
+
```sql
|
|
402
|
+
CREATE TABLE orders (
|
|
403
|
+
id INT PRIMARY KEY,
|
|
404
|
+
user_id INT, -- no REFERENCES clause
|
|
405
|
+
product_id INT -- no REFERENCES clause
|
|
406
|
+
);
|
|
407
|
+
-- Nothing prevents user_id = 999999 when no such user exists
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**Why developers do it:**
|
|
411
|
+
"The application handles validation." Foreign keys slow down bulk inserts. Microservice architectures discourage cross-service foreign keys. NoSQL migrations leave teams unfamiliar with constraints. A survey by Dataedo found that many production databases — some with over 200 tables — had zero foreign key constraints.
|
|
412
|
+
|
|
413
|
+
**What goes wrong:**
|
|
414
|
+
Orphan rows accumulate silently. An `orders` row references a deleted user, causing NullPointerExceptions when the application tries to display the user name. Reports produce incorrect counts. A MySQL bug (Bug #16039) documented that data integrity was not validated after foreign key constraints were re-enabled, allowing orphaned records to persist. SQLite does not enforce foreign keys by default (`PRAGMA foreign_keys = ON` must be set explicitly), leading to widespread orphan data in mobile applications.
|
|
415
|
+
|
|
416
|
+
**The fix:**
|
|
417
|
+
```sql
|
|
418
|
+
ALTER TABLE orders
|
|
419
|
+
ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id),
|
|
420
|
+
ADD CONSTRAINT fk_orders_product FOREIGN KEY (product_id) REFERENCES products(id);
|
|
421
|
+
```
|
|
422
|
+
For bulk load performance, disable constraints temporarily:
|
|
423
|
+
```sql
|
|
424
|
+
SET session_replication_role = 'replica'; -- PostgreSQL: disables FK checks
|
|
425
|
+
-- ... bulk load ...
|
|
426
|
+
SET session_replication_role = 'origin';
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Detection rule:**
|
|
430
|
+
Query `information_schema.columns` for any `_id` suffixed column that lacks a corresponding entry in `information_schema.table_constraints`.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### AP-11: Using OFFSET for Pagination
|
|
435
|
+
|
|
436
|
+
**Also known as:** Offset creep, page drift, skip-scan pagination
|
|
437
|
+
**Frequency:** Very Common
|
|
438
|
+
**Severity:** Medium
|
|
439
|
+
**Detection difficulty:** Easy
|
|
440
|
+
|
|
441
|
+
**What it looks like:**
|
|
442
|
+
```sql
|
|
443
|
+
-- Page 5000 of results
|
|
444
|
+
SELECT * FROM products ORDER BY created_at LIMIT 20 OFFSET 100000;
|
|
445
|
+
```
|
|
446
|
+
The database scans 100,020 rows, sorts them, then discards the first 100,000.
|
|
447
|
+
|
|
448
|
+
**Why developers do it:**
|
|
449
|
+
OFFSET/LIMIT is the simplest pagination API. Every SQL tutorial teaches it. ORMs generate it by default (`.page(5000)` in Rails/Django). It works fine for the first few pages.
|
|
450
|
+
|
|
451
|
+
**What goes wrong:**
|
|
452
|
+
Performance degrades linearly with offset size. Sentry documented that query times dropped from approximately 8 seconds to approximately 13 milliseconds when switching from OFFSET to cursor-based pagination on large datasets. GitLab invested significant engineering effort to optimize offset pagination across their application because it was causing measurable performance degradation. Additionally, if rows are inserted or deleted between page requests, users see duplicated or missing rows.
|
|
453
|
+
|
|
454
|
+
**The fix:**
|
|
455
|
+
```sql
|
|
456
|
+
-- Before: OFFSET pagination (slow at high offsets)
|
|
457
|
+
SELECT * FROM products ORDER BY created_at LIMIT 20 OFFSET 100000;
|
|
458
|
+
|
|
459
|
+
-- After: Keyset/cursor pagination (constant time)
|
|
460
|
+
SELECT * FROM products
|
|
461
|
+
WHERE created_at > '2025-06-15T10:30:00Z' -- cursor from previous page
|
|
462
|
+
ORDER BY created_at
|
|
463
|
+
LIMIT 20;
|
|
464
|
+
```
|
|
465
|
+
Return the cursor (last row's sort key) to the client with each response. The client passes it back for the next page.
|
|
466
|
+
|
|
467
|
+
**Detection rule:**
|
|
468
|
+
Flag any query where OFFSET exceeds 1,000, or any API endpoint that accepts a `page` parameter without a `cursor` alternative.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### AP-12: Storing Money as Floating Point
|
|
473
|
+
|
|
474
|
+
**Also known as:** IEEE 754 rounding trap, penny shaving, float finance
|
|
475
|
+
**Frequency:** Common
|
|
476
|
+
**Severity:** Critical
|
|
477
|
+
**Detection difficulty:** Moderate
|
|
478
|
+
|
|
479
|
+
**What it looks like:**
|
|
480
|
+
```sql
|
|
481
|
+
CREATE TABLE transactions (
|
|
482
|
+
id INT PRIMARY KEY,
|
|
483
|
+
amount FLOAT -- NEVER do this for money
|
|
484
|
+
);
|
|
485
|
+
```
|
|
486
|
+
```python
|
|
487
|
+
>>> 0.1 + 0.2
|
|
488
|
+
0.30000000000000004
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Why developers do it:**
|
|
492
|
+
FLOAT is the default numeric type developers reach for. It "works" in casual testing. The rounding errors are invisible in small transactions. Some ORMs map to FLOAT by default.
|
|
493
|
+
|
|
494
|
+
**What goes wrong:**
|
|
495
|
+
A German retail bank's mortgage calculation system used floating-point arithmetic for compound interest. Over 5 years, accumulated errors meant some customers overpaid by hundreds of euros while others underpaid, resulting in a EUR 12 million correction and regulatory fines. High-frequency trading algorithms at the London Stock Exchange generated thousands of erroneous trades when floating-point errors accumulated during rapid price calculations, forcing a 45-minute trading halt costing millions in lost volume. A cryptocurrency exchange lost $50,000 when floating-point rounding allowed attackers to exploit tiny fractional differences. Modern Treasury explicitly documents why they use 64-bit integers instead of floats for all monetary values.
|
|
496
|
+
|
|
497
|
+
**The fix:**
|
|
498
|
+
```sql
|
|
499
|
+
-- Use DECIMAL/NUMERIC with explicit precision
|
|
500
|
+
CREATE TABLE transactions (
|
|
501
|
+
id INT PRIMARY KEY,
|
|
502
|
+
amount DECIMAL(19, 4) NOT NULL -- 19 digits, 4 decimal places
|
|
503
|
+
);
|
|
504
|
+
```
|
|
505
|
+
Or store as integer cents/smallest currency unit:
|
|
506
|
+
```python
|
|
507
|
+
# Store $29.99 as 2999 (integer cents)
|
|
508
|
+
amount_cents = int(round(dollars * 100))
|
|
509
|
+
|
|
510
|
+
# Display
|
|
511
|
+
display_amount = f"${amount_cents / 100:.2f}"
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Detection rule:**
|
|
515
|
+
Flag any column of type FLOAT, DOUBLE, or REAL in tables with names containing `price`, `amount`, `cost`, `balance`, `payment`, or `salary`.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
### AP-13: Not Handling NULL Properly
|
|
520
|
+
|
|
521
|
+
**Also known as:** The billion-dollar NULL, three-valued logic trap
|
|
522
|
+
**Frequency:** Very Common
|
|
523
|
+
**Severity:** Medium
|
|
524
|
+
**Detection difficulty:** Hard
|
|
525
|
+
|
|
526
|
+
**What it looks like:**
|
|
527
|
+
```sql
|
|
528
|
+
-- This returns NO rows, even if discount is NULL
|
|
529
|
+
SELECT * FROM products WHERE discount != 10;
|
|
530
|
+
|
|
531
|
+
-- This is always UNKNOWN, not TRUE
|
|
532
|
+
SELECT * FROM products WHERE NULL = NULL;
|
|
533
|
+
|
|
534
|
+
-- Aggregation silently drops NULLs
|
|
535
|
+
SELECT AVG(rating) FROM reviews; -- ignores unrated reviews
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Why developers do it:**
|
|
539
|
+
Developers think in two-valued logic (true/false). SQL's three-valued logic (TRUE, FALSE, UNKNOWN) is counterintuitive. Most programming languages do not have an equivalent to SQL NULL semantics. `NULL != 10` evaluating to UNKNOWN (not TRUE) surprises everyone.
|
|
540
|
+
|
|
541
|
+
**What goes wrong:**
|
|
542
|
+
Queries silently return incorrect results. A reporting system calculated average customer satisfaction by running `AVG(rating)` — which silently excluded all customers who had not rated, inflating the score from 3.2 to 4.1 and misleading the executive team. NOT IN with NULLs returns empty result sets: `WHERE id NOT IN (1, 2, NULL)` returns zero rows because `id != NULL` is always UNKNOWN.
|
|
543
|
+
|
|
544
|
+
**The fix:**
|
|
545
|
+
```sql
|
|
546
|
+
-- Use IS NULL / IS NOT NULL
|
|
547
|
+
SELECT * FROM products WHERE discount IS DISTINCT FROM 10;
|
|
548
|
+
|
|
549
|
+
-- Handle NULLs in aggregation
|
|
550
|
+
SELECT AVG(COALESCE(rating, 0)) FROM reviews;
|
|
551
|
+
-- Or explicitly filter
|
|
552
|
+
SELECT AVG(rating) FROM reviews WHERE rating IS NOT NULL;
|
|
553
|
+
|
|
554
|
+
-- Use NOT EXISTS instead of NOT IN
|
|
555
|
+
SELECT * FROM users u
|
|
556
|
+
WHERE NOT EXISTS (SELECT 1 FROM blacklist b WHERE b.user_id = u.id);
|
|
557
|
+
```
|
|
558
|
+
Define columns as NOT NULL with sensible defaults wherever possible:
|
|
559
|
+
```sql
|
|
560
|
+
ALTER TABLE products ALTER COLUMN discount SET DEFAULT 0;
|
|
561
|
+
ALTER TABLE products ALTER COLUMN discount SET NOT NULL;
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**Detection rule:**
|
|
565
|
+
Flag any `WHERE col != value` or `WHERE col NOT IN (...)` where the column is nullable. Flag `NOT IN` subqueries on nullable columns.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
### AP-14: Soft Delete Everywhere
|
|
570
|
+
|
|
571
|
+
**Also known as:** Logical delete, is_deleted creep, the undead row
|
|
572
|
+
**Frequency:** Common
|
|
573
|
+
**Severity:** Medium
|
|
574
|
+
**Detection difficulty:** Moderate
|
|
575
|
+
|
|
576
|
+
**What it looks like:**
|
|
577
|
+
```sql
|
|
578
|
+
-- Every table gets this
|
|
579
|
+
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL;
|
|
580
|
+
ALTER TABLE orders ADD COLUMN deleted_at TIMESTAMP NULL;
|
|
581
|
+
ALTER TABLE products ADD COLUMN deleted_at TIMESTAMP NULL;
|
|
582
|
+
|
|
583
|
+
-- Every query must include this filter
|
|
584
|
+
SELECT * FROM users WHERE deleted_at IS NULL;
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Why developers do it:**
|
|
588
|
+
Fear of data loss. Audit requirements (perceived, not always real). "What if we need to restore it?" Undo functionality. It feels safer than actual deletion.
|
|
589
|
+
|
|
590
|
+
**What goes wrong:**
|
|
591
|
+
Every SELECT, JOIN, and subquery must include `WHERE deleted_at IS NULL` — a single omission leaks "deleted" data into results. Foreign keys become meaningless: a soft-deleted user's orders still reference a valid row, so the constraint does not fire. Unique constraints break: you cannot recreate a user with the same email because the soft-deleted row occupies the unique slot. GDPR requires actual deletion of personal data; soft deletes do not comply. Over time, tables bloat with ghost rows that slow queries and consume storage. Django's `django-model-utils` library has an open issue (#364) titled "SoftDelete is an anti-pattern."
|
|
592
|
+
|
|
593
|
+
**The fix:**
|
|
594
|
+
Use soft deletes only where legally required (audit trails, financial records). For everything else:
|
|
595
|
+
```sql
|
|
596
|
+
-- Archive to a separate table before hard delete
|
|
597
|
+
INSERT INTO users_archive SELECT * FROM users WHERE id = 42;
|
|
598
|
+
DELETE FROM users WHERE id = 42;
|
|
599
|
+
```
|
|
600
|
+
For databases that support it, use temporal tables:
|
|
601
|
+
```sql
|
|
602
|
+
-- SQL Server temporal table
|
|
603
|
+
CREATE TABLE users (
|
|
604
|
+
id INT PRIMARY KEY,
|
|
605
|
+
name VARCHAR(255),
|
|
606
|
+
email VARCHAR(255),
|
|
607
|
+
SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START,
|
|
608
|
+
SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END,
|
|
609
|
+
PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)
|
|
610
|
+
) WITH (SYSTEM_VERSIONING = ON);
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Detection rule:**
|
|
614
|
+
Flag any schema where more than 50% of tables have a `deleted_at`, `is_deleted`, or `deleted` column.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
### AP-15: Database as Message Queue
|
|
619
|
+
|
|
620
|
+
**Also known as:** Poll table, job queue table, database-as-IPC
|
|
621
|
+
**Frequency:** Common
|
|
622
|
+
**Severity:** High
|
|
623
|
+
**Detection difficulty:** Moderate
|
|
624
|
+
|
|
625
|
+
**What it looks like:**
|
|
626
|
+
```sql
|
|
627
|
+
CREATE TABLE job_queue (
|
|
628
|
+
id SERIAL PRIMARY KEY,
|
|
629
|
+
payload JSONB,
|
|
630
|
+
status VARCHAR(20) DEFAULT 'pending',
|
|
631
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
632
|
+
locked_by VARCHAR(255),
|
|
633
|
+
locked_at TIMESTAMP
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
-- Worker polls every second
|
|
637
|
+
SELECT * FROM job_queue WHERE status = 'pending' ORDER BY created_at LIMIT 1 FOR UPDATE SKIP LOCKED;
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Why developers do it:**
|
|
641
|
+
Avoids adding a new infrastructure component (RabbitMQ, Kafka, Redis). The database is already there. Transactional guarantees feel reassuring. Small-scale systems work fine with this pattern.
|
|
642
|
+
|
|
643
|
+
**What goes wrong:**
|
|
644
|
+
A financial service used a queue table for fraud checks. Multiple anti-fraud workers scanning simultaneously caused database locks, deadlocks, and timeout cascading. Polling every second across many workers saturated the database with read operations. A ride-sharing application using database queues for ride requests experienced CPU and memory contention between queue operations and normal transactional queries, degrading both. The fundamental problem: databases optimize for data storage and retrieval, not for the produce-consume-delete lifecycle of messages.
|
|
645
|
+
|
|
646
|
+
**The fix:**
|
|
647
|
+
Use a purpose-built message broker:
|
|
648
|
+
```python
|
|
649
|
+
# Before: database polling
|
|
650
|
+
while True:
|
|
651
|
+
job = db.query("SELECT * FROM job_queue WHERE status='pending' LIMIT 1 FOR UPDATE")
|
|
652
|
+
process(job)
|
|
653
|
+
db.query("UPDATE job_queue SET status='done' WHERE id=%s", job.id)
|
|
654
|
+
time.sleep(1)
|
|
655
|
+
|
|
656
|
+
# After: Redis/RabbitMQ
|
|
657
|
+
import redis
|
|
658
|
+
r = redis.Redis()
|
|
659
|
+
while True:
|
|
660
|
+
job = r.brpop('job_queue') # blocks until message available, no polling
|
|
661
|
+
process(job)
|
|
662
|
+
```
|
|
663
|
+
If you must use the database (small scale, strong consistency required), use PostgreSQL `LISTEN`/`NOTIFY` instead of polling.
|
|
664
|
+
|
|
665
|
+
**Detection rule:**
|
|
666
|
+
Flag any table with columns named `status`, `locked_by`, `locked_at` together, or any query pattern with `FOR UPDATE SKIP LOCKED` in a polling loop.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
### AP-16: Storing Files/BLOBs in the Database
|
|
671
|
+
|
|
672
|
+
**Also known as:** BLOB bloat, binary column trap
|
|
673
|
+
**Frequency:** Common
|
|
674
|
+
**Severity:** Medium
|
|
675
|
+
**Detection difficulty:** Easy
|
|
676
|
+
|
|
677
|
+
**What it looks like:**
|
|
678
|
+
```sql
|
|
679
|
+
CREATE TABLE documents (
|
|
680
|
+
id INT PRIMARY KEY,
|
|
681
|
+
filename VARCHAR(255),
|
|
682
|
+
content BYTEA, -- storing entire PDF/image in the database
|
|
683
|
+
uploaded_at TIMESTAMP
|
|
684
|
+
);
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**Why developers do it:**
|
|
688
|
+
Transactional consistency with metadata. Single backup includes everything. No external storage configuration needed. Simpler deployment for small applications.
|
|
689
|
+
|
|
690
|
+
**What goes wrong:**
|
|
691
|
+
A 100MB file stored as a BLOB kills query performance, backup times, and replication. SQL Server's 8K data pages force BLOBs into off-row storage, bloating filegroups. BLOBs consume buffer pool memory meant for hot row data. Every full and differential backup includes every file ever uploaded, regardless of whether it changed. Brent Ozar documented a case where moving BLOBs out of SQL Server freed enough resources to eliminate the need for a planned hardware upgrade. Replication lag increases because binary data must be shipped to every replica.
|
|
692
|
+
|
|
693
|
+
**The fix:**
|
|
694
|
+
```python
|
|
695
|
+
# Store file in object storage, reference in database
|
|
696
|
+
import boto3
|
|
697
|
+
|
|
698
|
+
s3 = boto3.client('s3')
|
|
699
|
+
s3.upload_file('report.pdf', 'my-bucket', f'documents/{doc_id}/report.pdf')
|
|
700
|
+
|
|
701
|
+
# Database stores only the reference
|
|
702
|
+
db.execute(
|
|
703
|
+
"INSERT INTO documents (id, filename, s3_key, uploaded_at) VALUES (%s, %s, %s, NOW())",
|
|
704
|
+
(doc_id, 'report.pdf', f'documents/{doc_id}/report.pdf')
|
|
705
|
+
)
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
**Detection rule:**
|
|
709
|
+
Flag any BYTEA, BLOB, LONGBLOB, or VARBINARY(MAX) column in a table with more than 10,000 rows or total table size exceeding 1 GB.
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
### AP-17: Missing Migration Rollback Plans
|
|
714
|
+
|
|
715
|
+
**Also known as:** One-way migration, deploy and pray, irreversible DDL
|
|
716
|
+
**Frequency:** Common
|
|
717
|
+
**Severity:** Critical
|
|
718
|
+
**Detection difficulty:** Hard
|
|
719
|
+
|
|
720
|
+
**What it looks like:**
|
|
721
|
+
```python
|
|
722
|
+
# Migration: drop a column
|
|
723
|
+
class Migration:
|
|
724
|
+
def up(self):
|
|
725
|
+
self.execute("ALTER TABLE users DROP COLUMN legacy_role;")
|
|
726
|
+
|
|
727
|
+
def down(self):
|
|
728
|
+
pass # "We'll never need to roll back"
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Why developers do it:**
|
|
732
|
+
Rollback migrations are tedious to write. "We tested in staging." Dropping columns and tables is easy; recreating them with data is hard. Time pressure skips the rollback step.
|
|
733
|
+
|
|
734
|
+
**What goes wrong:**
|
|
735
|
+
Val Town experienced a 12-minute outage when database migrations deployed successfully but application code deployment hung. The new migrations were incompatible with the old application code. When they attempted to roll back, the rollback was rejected because the migration had already succeeded and the system's rollback guard prevented re-entry. GitHub's June 2025 outage cascaded from a routine database migration into a platform-wide crisis affecting Git operations, GitHub Actions, Pages, Codespaces, and API endpoints. The engineering team had to execute emergency rollback procedures. A Rails application dropped a column, then when the code was rolled back, the old code crashed because the column was gone — and the migration was irreversible.
|
|
736
|
+
|
|
737
|
+
**The fix:**
|
|
738
|
+
```python
|
|
739
|
+
class Migration:
|
|
740
|
+
def up(self):
|
|
741
|
+
# Step 1: Stop reading the column in application code (deploy first)
|
|
742
|
+
# Step 2: Add column to ignore list
|
|
743
|
+
# Step 3: Drop column in a separate migration after confirming no reads
|
|
744
|
+
self.execute("ALTER TABLE users DROP COLUMN legacy_role;")
|
|
745
|
+
|
|
746
|
+
def down(self):
|
|
747
|
+
self.execute("ALTER TABLE users ADD COLUMN legacy_role VARCHAR(50);")
|
|
748
|
+
self.execute("UPDATE users SET legacy_role = 'member';") # safe default
|
|
749
|
+
```
|
|
750
|
+
Follow the expand-contract pattern: add new columns first, migrate data, update code, then remove old columns in a later migration.
|
|
751
|
+
|
|
752
|
+
**Detection rule:**
|
|
753
|
+
Flag any migration with `DROP COLUMN`, `DROP TABLE`, or `ALTER COLUMN TYPE` that has an empty or missing `down()` method.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### AP-18: Polymorphic Associations Without Constraints
|
|
758
|
+
|
|
759
|
+
**Also known as:** Dual-purpose foreign key, type-id pattern, Rails polymorphic
|
|
760
|
+
**Frequency:** Common
|
|
761
|
+
**Severity:** High
|
|
762
|
+
**Detection difficulty:** Moderate
|
|
763
|
+
|
|
764
|
+
**What it looks like:**
|
|
765
|
+
```sql
|
|
766
|
+
CREATE TABLE comments (
|
|
767
|
+
id INT PRIMARY KEY,
|
|
768
|
+
body TEXT,
|
|
769
|
+
commentable_type VARCHAR(50), -- 'Article', 'Video', 'Photo'
|
|
770
|
+
commentable_id INT -- could reference articles, videos, or photos
|
|
771
|
+
);
|
|
772
|
+
-- No foreign key possible: commentable_id could point to any table
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Why developers do it:**
|
|
776
|
+
Rails `belongs_to :commentable, polymorphic: true` makes it a one-liner. Avoids creating separate `article_comments`, `video_comments`, and `photo_comments` tables. Reduces table count.
|
|
777
|
+
|
|
778
|
+
**What goes wrong:**
|
|
779
|
+
No foreign key constraint can be declared because `commentable_id` references different tables depending on `commentable_type`. The database cannot enforce referential integrity — you can have a comment pointing to `Article #999` when no such article exists. Rails does not check referential integrity for polymorphic associations. Queries are slower because the database must check both `commentable_type` and `commentable_id`. You cannot use JOINs naturally; the application must construct different queries per type. GitLab's development guidelines explicitly warn against polymorphic associations in their database design documentation.
|
|
780
|
+
|
|
781
|
+
**The fix:**
|
|
782
|
+
Use exclusive belongs-to (separate nullable foreign keys):
|
|
783
|
+
```sql
|
|
784
|
+
CREATE TABLE comments (
|
|
785
|
+
id INT PRIMARY KEY,
|
|
786
|
+
body TEXT,
|
|
787
|
+
article_id INT REFERENCES articles(id),
|
|
788
|
+
video_id INT REFERENCES videos(id),
|
|
789
|
+
photo_id INT REFERENCES photos(id),
|
|
790
|
+
CONSTRAINT one_parent CHECK (
|
|
791
|
+
(article_id IS NOT NULL)::INT +
|
|
792
|
+
(video_id IS NOT NULL)::INT +
|
|
793
|
+
(photo_id IS NOT NULL)::INT = 1
|
|
794
|
+
)
|
|
795
|
+
);
|
|
796
|
+
```
|
|
797
|
+
Or use a shared base table with inheritance:
|
|
798
|
+
```sql
|
|
799
|
+
CREATE TABLE commentable_items (id INT PRIMARY KEY, type VARCHAR(50));
|
|
800
|
+
CREATE TABLE articles (id INT PRIMARY KEY REFERENCES commentable_items(id));
|
|
801
|
+
CREATE TABLE comments (
|
|
802
|
+
id INT PRIMARY KEY,
|
|
803
|
+
commentable_item_id INT REFERENCES commentable_items(id)
|
|
804
|
+
);
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
**Detection rule:**
|
|
808
|
+
Flag any column pair `*_type`/`*_id` where the `*_id` column has no foreign key constraint.
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
### AP-19: Over-Reliance on ORM (Ignoring SQL)
|
|
813
|
+
|
|
814
|
+
**Also known as:** ORM tunnel vision, abstraction ceiling, query generator abuse
|
|
815
|
+
**Frequency:** Very Common
|
|
816
|
+
**Severity:** Medium
|
|
817
|
+
**Detection difficulty:** Hard
|
|
818
|
+
|
|
819
|
+
**What it looks like:**
|
|
820
|
+
```python
|
|
821
|
+
# Django: fetching data for a report
|
|
822
|
+
users = User.objects.all()
|
|
823
|
+
result = []
|
|
824
|
+
for user in users:
|
|
825
|
+
order_count = Order.objects.filter(user=user).count()
|
|
826
|
+
total_spent = Order.objects.filter(user=user).aggregate(Sum('amount'))['amount__sum']
|
|
827
|
+
result.append({'user': user.name, 'orders': order_count, 'total': total_spent})
|
|
828
|
+
```
|
|
829
|
+
This generates 2N+1 queries and does aggregation in Python instead of the database.
|
|
830
|
+
|
|
831
|
+
**Why developers do it:**
|
|
832
|
+
ORMs abstract away SQL, so developers never learn it. The code looks clean and object-oriented. "We should not write raw SQL." The ORM generates valid SQL, so developers assume it generates efficient SQL. Teams ban raw SQL for consistency.
|
|
833
|
+
|
|
834
|
+
**What goes wrong:**
|
|
835
|
+
ORMs generate suboptimal queries that are invisible unless you inspect the SQL log. Developers have reported incidents where ORMs caused servers to run out of memory from materializing entire result sets. Complex reports that could be a single SQL query with GROUP BY and window functions become N+1 loops with application-side aggregation. An e-commerce platform rewrote a product recommendation query from ORM code to raw SQL, reducing response time from 4.2 seconds to 80 milliseconds — a 50x improvement.
|
|
836
|
+
|
|
837
|
+
**The fix:**
|
|
838
|
+
```python
|
|
839
|
+
# Before: ORM loop (2N+1 queries)
|
|
840
|
+
for user in User.objects.all():
|
|
841
|
+
count = Order.objects.filter(user=user).count()
|
|
842
|
+
|
|
843
|
+
# After: Single SQL query via ORM
|
|
844
|
+
from django.db.models import Count, Sum
|
|
845
|
+
users = User.objects.annotate(
|
|
846
|
+
order_count=Count('orders'),
|
|
847
|
+
total_spent=Sum('orders__amount')
|
|
848
|
+
).values('name', 'order_count', 'total_spent')
|
|
849
|
+
```
|
|
850
|
+
For complex analytics, use raw SQL:
|
|
851
|
+
```python
|
|
852
|
+
users = User.objects.raw('''
|
|
853
|
+
SELECT u.id, u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_spent
|
|
854
|
+
FROM users u
|
|
855
|
+
LEFT JOIN orders o ON u.id = o.user_id
|
|
856
|
+
GROUP BY u.id, u.name
|
|
857
|
+
HAVING SUM(o.amount) > 1000
|
|
858
|
+
''')
|
|
859
|
+
```
|
|
860
|
+
Rule of thumb: if you cannot express it in one ORM call, consider raw SQL.
|
|
861
|
+
|
|
862
|
+
**Detection rule:**
|
|
863
|
+
Flag any loop that calls ORM query methods (`.filter()`, `.get()`, `.count()`) inside a for-each over a queryset.
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
### AP-20: Not Using Connection Pooling
|
|
868
|
+
|
|
869
|
+
**Also known as:** Connection thrashing, connect-per-request, pool exhaustion
|
|
870
|
+
**Frequency:** Common
|
|
871
|
+
**Severity:** Critical
|
|
872
|
+
**Detection difficulty:** Moderate
|
|
873
|
+
|
|
874
|
+
**What it looks like:**
|
|
875
|
+
```python
|
|
876
|
+
# New connection for every request
|
|
877
|
+
def handle_request(request):
|
|
878
|
+
conn = psycopg2.connect(host='db', dbname='app') # TCP handshake + auth every time
|
|
879
|
+
cursor = conn.cursor()
|
|
880
|
+
cursor.execute("SELECT * FROM users WHERE id = %s", (request.user_id,))
|
|
881
|
+
result = cursor.fetchone()
|
|
882
|
+
conn.close() # connection destroyed
|
|
883
|
+
return result
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
**Why developers do it:**
|
|
887
|
+
The simplest database tutorial code opens and closes connections per-request. Connection pooling requires additional configuration or libraries. Serverless environments (Lambda, Cloud Functions) make pooling non-trivial. Developers do not realize that connection establishment takes 20-100ms of overhead per request.
|
|
888
|
+
|
|
889
|
+
**What goes wrong:**
|
|
890
|
+
LinkedIn experienced a 4-hour outage when a stored procedure became slow, holding connections open until the pool was exhausted. PostgreSQL's default `max_connections` (100) is easily exceeded by a moderately loaded web application — each connection consumes approximately 10MB of RAM on the server. Without pooling, a spike to 200 concurrent requests opens 200 connections, overwhelming the database. Connection pool exhaustion is described as "the most insidious failure in distributed systems because it looks invisible until it destroys everything."
|
|
891
|
+
|
|
892
|
+
**The fix:**
|
|
893
|
+
```python
|
|
894
|
+
# Use a connection pool
|
|
895
|
+
from psycopg2 import pool
|
|
896
|
+
|
|
897
|
+
connection_pool = pool.ThreadedConnectionPool(
|
|
898
|
+
minconn=5,
|
|
899
|
+
maxconn=20,
|
|
900
|
+
host='db',
|
|
901
|
+
dbname='app'
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
def handle_request(request):
|
|
905
|
+
conn = connection_pool.getconn()
|
|
906
|
+
try:
|
|
907
|
+
cursor = conn.cursor()
|
|
908
|
+
cursor.execute("SELECT * FROM users WHERE id = %s", (request.user_id,))
|
|
909
|
+
return cursor.fetchone()
|
|
910
|
+
finally:
|
|
911
|
+
connection_pool.putconn(conn) # return to pool, not destroyed
|
|
912
|
+
```
|
|
913
|
+
For serverless: use PgBouncer, RDS Proxy, or Supabase connection pooler as an external pool.
|
|
914
|
+
|
|
915
|
+
**Detection rule:**
|
|
916
|
+
Flag any code path that calls `connect()` without going through a pool. Monitor `pg_stat_activity` for connection count spikes correlated with request volume.
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
### AP-21: Lock Contention from Long Transactions
|
|
921
|
+
|
|
922
|
+
**Also known as:** Transaction scope creep, hold-and-wait, lock chain
|
|
923
|
+
**Frequency:** Common
|
|
924
|
+
**Severity:** Critical
|
|
925
|
+
**Detection difficulty:** Hard
|
|
926
|
+
|
|
927
|
+
**What it looks like:**
|
|
928
|
+
```python
|
|
929
|
+
with db.transaction():
|
|
930
|
+
order = db.query("SELECT * FROM orders WHERE id = %s FOR UPDATE", order_id)
|
|
931
|
+
# ... call external payment API (takes 2-30 seconds) ...
|
|
932
|
+
payment_result = payment_gateway.charge(order.amount)
|
|
933
|
+
# ... lock held the entire time ...
|
|
934
|
+
db.execute("UPDATE orders SET status = 'paid' WHERE id = %s", order_id)
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
**Why developers do it:**
|
|
938
|
+
Developers wrap everything in a transaction "for safety." External API calls, file I/O, and user-facing waits get included inside the transaction scope. The transaction feels like a safety net, so its scope expands to cover more operations.
|
|
939
|
+
|
|
940
|
+
**What goes wrong:**
|
|
941
|
+
A documented production incident showed that in a 45-minute window, hundreds of connections were stuck waiting for locked resources. A dangerous combination of concurrent queries, poorly tuned indexes, and long transactions caused severe slowness, API failures, and resource saturation across an entire environment. The classic lock chain: Transaction A locks row 1 and waits for row 2; Transaction B locks row 2 and waits for row 1 — deadlock. Even without deadlocks, long-held locks cause cascading waits where every subsequent request queues behind the blocked one.
|
|
942
|
+
|
|
943
|
+
**The fix:**
|
|
944
|
+
```python
|
|
945
|
+
# Before: External call inside transaction (lock held for seconds)
|
|
946
|
+
with db.transaction():
|
|
947
|
+
order = db.query("SELECT * FROM orders WHERE id = %s FOR UPDATE", order_id)
|
|
948
|
+
payment_result = payment_gateway.charge(order.amount)
|
|
949
|
+
db.execute("UPDATE orders SET status = 'paid' WHERE id = %s", order_id)
|
|
950
|
+
|
|
951
|
+
# After: Minimize transaction scope
|
|
952
|
+
order = db.query("SELECT * FROM orders WHERE id = %s", order_id)
|
|
953
|
+
payment_result = payment_gateway.charge(order.amount) # outside transaction
|
|
954
|
+
|
|
955
|
+
with db.transaction():
|
|
956
|
+
db.execute(
|
|
957
|
+
"UPDATE orders SET status = 'paid' WHERE id = %s AND status = 'pending'",
|
|
958
|
+
order_id
|
|
959
|
+
)
|
|
960
|
+
# Optimistic concurrency: check affected rows
|
|
961
|
+
if db.rowcount == 0:
|
|
962
|
+
raise ConcurrencyError("Order was modified concurrently")
|
|
963
|
+
```
|
|
964
|
+
Rules: never call external services inside a transaction, never allow user interaction inside a transaction, keep transactions under 100ms.
|
|
965
|
+
|
|
966
|
+
**Detection rule:**
|
|
967
|
+
Monitor for transactions exceeding 1 second in duration. Alert on rising `lock_wait_time` metrics. Flag any transaction that contains HTTP calls or sleep statements.
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## Root Cause Analysis
|
|
972
|
+
|
|
973
|
+
| Root Cause | Anti-Patterns Triggered | Frequency |
|
|
974
|
+
|---|---|---|
|
|
975
|
+
| ORM defaults hiding SQL behavior | AP-01, AP-06, AP-11, AP-19 | Very Common |
|
|
976
|
+
| "Works in dev" / small dataset blindness | AP-02, AP-03, AP-04, AP-11, AP-12 | Very Common |
|
|
977
|
+
| Skipping schema design for speed | AP-04, AP-05, AP-07, AP-09, AP-10, AP-18 | Common |
|
|
978
|
+
| Fear of data loss | AP-14, AP-16, AP-17 | Common |
|
|
979
|
+
| Premature optimization cargo cult | AP-03, AP-09, AP-15 | Common |
|
|
980
|
+
| Not understanding SQL semantics | AP-08, AP-12, AP-13 | Common |
|
|
981
|
+
| Infrastructure avoidance | AP-15, AP-20 | Common |
|
|
982
|
+
| Framework conventions over correctness | AP-01, AP-14, AP-18, AP-19 | Common |
|
|
983
|
+
| Missing deployment safety checks | AP-02, AP-17, AP-21 | Common |
|
|
984
|
+
| Transaction scope misunderstanding | AP-08, AP-21 | Common |
|
|
985
|
+
|
|
986
|
+
---
|
|
987
|
+
|
|
988
|
+
## Self-Check Questions
|
|
989
|
+
|
|
990
|
+
Before deploying database changes, ask:
|
|
991
|
+
|
|
992
|
+
1. **Have I run `EXPLAIN ANALYZE` on every new query against production-sized data?** (catches AP-02, AP-03, AP-11)
|
|
993
|
+
2. **Does any request issue more than 5 queries of the same shape?** (catches AP-01)
|
|
994
|
+
3. **Am I storing data in a column that belongs in a separate table?** (catches AP-04, AP-05, AP-07)
|
|
995
|
+
4. **Do all multi-statement writes use explicit transactions?** (catches AP-08)
|
|
996
|
+
5. **Is every `_id` column backed by a foreign key constraint?** (catches AP-10, AP-18)
|
|
997
|
+
6. **Can I write the rollback migration for this schema change?** (catches AP-17)
|
|
998
|
+
7. **Am I using FLOAT/DOUBLE for any monetary value?** (catches AP-12)
|
|
999
|
+
8. **Does my WHERE clause handle NULL correctly?** (catches AP-13)
|
|
1000
|
+
9. **Am I calling external services or APIs inside a database transaction?** (catches AP-21)
|
|
1001
|
+
10. **Does this query use SELECT * when only specific columns are needed?** (catches AP-06)
|
|
1002
|
+
11. **Is my pagination using OFFSET on a table that could exceed 100K rows?** (catches AP-11)
|
|
1003
|
+
12. **Am I adding soft delete to a table where hard delete plus an archive table would suffice?** (catches AP-14)
|
|
1004
|
+
13. **Am I storing binary files larger than 1MB directly in the database?** (catches AP-16)
|
|
1005
|
+
14. **Is my connection pool properly sized for peak traffic?** (catches AP-20)
|
|
1006
|
+
15. **Am I denormalizing data before proving a measured performance problem exists?** (catches AP-09)
|
|
1007
|
+
|
|
1008
|
+
---
|
|
1009
|
+
|
|
1010
|
+
## Code Smell Quick Reference
|
|
1011
|
+
|
|
1012
|
+
| Smell | Likely Anti-Pattern | Severity | First Check |
|
|
1013
|
+
|---|---|---|---|
|
|
1014
|
+
| Loop calling `.get()` / `.filter()` per item | AP-01: N+1 Query | High | Add `select_related` / `prefetch_related` |
|
|
1015
|
+
| Sequential scan on table > 10K rows | AP-02: Missing Index | Critical | Run `EXPLAIN ANALYZE` |
|
|
1016
|
+
| Table with more indexes than columns | AP-03: Over-Indexing | Medium | Audit unused indexes |
|
|
1017
|
+
| Table with 50+ columns, many nullable | AP-04: God Table | High | Decompose by domain entity |
|
|
1018
|
+
| `attribute_name` / `attribute_value` columns | AP-05: EAV Abuse | High | Use JSONB or separate tables |
|
|
1019
|
+
| `SELECT *` in application queries | AP-06: Implicit Columns | Medium | Specify column list |
|
|
1020
|
+
| Commas inside VARCHAR data | AP-07: Delimited Lists | High | Create junction table |
|
|
1021
|
+
| Multiple writes without `BEGIN`/`COMMIT` | AP-08: Missing Transaction | Critical | Wrap in transaction |
|
|
1022
|
+
| Duplicated columns across tables | AP-09: Premature Denormalization | Medium | Normalize, use views |
|
|
1023
|
+
| `_id` column without REFERENCES | AP-10: Missing FK | High | Add foreign key |
|
|
1024
|
+
| `OFFSET` > 1000 in any query | AP-11: OFFSET Pagination | Medium | Switch to keyset pagination |
|
|
1025
|
+
| FLOAT column for money | AP-12: Float Money | Critical | Use DECIMAL or integer cents |
|
|
1026
|
+
| `WHERE col != value` on nullable column | AP-13: NULL Mishandling | Medium | Use `IS DISTINCT FROM` |
|
|
1027
|
+
| `deleted_at` on most tables | AP-14: Soft Delete Everywhere | Medium | Archive table + hard delete |
|
|
1028
|
+
| `status`/`locked_by`/`locked_at` columns | AP-15: DB as Queue | High | Use a message broker |
|
|
1029
|
+
| BYTEA/BLOB columns on large tables | AP-16: BLOB Storage | Medium | Move to object storage |
|
|
1030
|
+
| Migration `down()` method is empty | AP-17: No Rollback Plan | Critical | Write the rollback migration |
|
|
1031
|
+
| `*_type` + `*_id` column pair, no FK | AP-18: Polymorphic Assoc | High | Use exclusive belongs-to |
|
|
1032
|
+
| ORM queries inside for-loops | AP-19: ORM Over-Reliance | Medium | Use annotations or raw SQL |
|
|
1033
|
+
| `connect()` call per request, no pool | AP-20: No Connection Pool | Critical | Add connection pooler |
|
|
1034
|
+
| External API call inside `BEGIN`/`COMMIT` | AP-21: Long Transactions | Critical | Move API call outside txn |
|
|
1035
|
+
|
|
1036
|
+
---
|
|
1037
|
+
|
|
1038
|
+
*Researched: 2026-03-08 | Sources: PlanetScale (N+1 queries), Sentry (N+1 detection), GitLab (Jan 2017 DB outage post-mortem, offset pagination optimization, polymorphic association guidelines), Travis CI (Mar 2018 DB truncation), Val Town (migration rollback post-mortem), GitHub (Jun 2025 migration outage), Percona (over-indexing benchmarks), Modern Treasury (integer money storage), Brent Ozar (BLOB removal case study), LinkedIn (connection pool exhaustion), Heartland Payment Systems / Equifax / Yahoo (SQL injection breaches), Magento (EAV architecture documentation), Bill Karwin "SQL Antipatterns Vol. 1", Mike Hadlow (database-as-queue), RavenDB (missed indexing reference post-mortem), CockroachDB (foreign key mistakes), Levitation.in (real-world DB design failures), dbsnOOp (lock contention incident)*
|