@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,1366 @@
|
|
|
1
|
+
# PostgreSQL Performance Expertise Module
|
|
2
|
+
|
|
3
|
+
> Comprehensive guide to PostgreSQL performance tuning, covering configuration,
|
|
4
|
+
> indexing, query optimization, connection pooling, vacuum management, partitioning,
|
|
5
|
+
> monitoring, and anti-pattern avoidance. Every recommendation is backed by numbers
|
|
6
|
+
> from real benchmarks and production experience.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
1. [Configuration Tuning](#1-configuration-tuning)
|
|
13
|
+
2. [Index Strategies](#2-index-strategies)
|
|
14
|
+
3. [Query Optimization with EXPLAIN](#3-query-optimization-with-explain)
|
|
15
|
+
4. [Connection Pooling](#4-connection-pooling)
|
|
16
|
+
5. [VACUUM and Autovacuum Tuning](#5-vacuum-and-autovacuum-tuning)
|
|
17
|
+
6. [Table Partitioning Strategies](#6-table-partitioning-strategies)
|
|
18
|
+
7. [Common Bottlenecks](#7-common-bottlenecks)
|
|
19
|
+
8. [Anti-Patterns](#8-anti-patterns)
|
|
20
|
+
9. [Monitoring with pg_stat_statements and pg_stat_user_tables](#9-monitoring)
|
|
21
|
+
10. [Before/After Query Examples](#10-beforeafter-query-examples)
|
|
22
|
+
11. [Quick Reference Checklist](#11-quick-reference-checklist)
|
|
23
|
+
12. [Sources](#12-sources)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Configuration Tuning
|
|
28
|
+
|
|
29
|
+
PostgreSQL ships with conservative defaults designed to run on minimal hardware. Production
|
|
30
|
+
deployments require tuning to match available resources. The parameters below have the
|
|
31
|
+
highest impact on throughput and latency.
|
|
32
|
+
|
|
33
|
+
### 1.1 shared_buffers
|
|
34
|
+
|
|
35
|
+
**What it does:** Controls the amount of memory PostgreSQL dedicates to caching data pages
|
|
36
|
+
in its own buffer pool, separate from the OS page cache.
|
|
37
|
+
|
|
38
|
+
**Recommended values:**
|
|
39
|
+
|
|
40
|
+
| System RAM | shared_buffers | Notes |
|
|
41
|
+
|------------|---------------|-------|
|
|
42
|
+
| 4 GB | 1 GB (25%) | Minimum production server |
|
|
43
|
+
| 16 GB | 4-5 GB (25-33%) | Standard web application |
|
|
44
|
+
| 64 GB | 16-20 GB (25-33%) | Medium OLTP workload |
|
|
45
|
+
| 256 GB | 64-80 GB (25-33%) | Large analytics/OLTP |
|
|
46
|
+
|
|
47
|
+
- The general guideline is 25-33% of total system RAM. Setting it higher than 40% rarely
|
|
48
|
+
helps because PostgreSQL also relies on the OS page cache for double-buffering.
|
|
49
|
+
- Increasing shared_buffers requires a corresponding increase in max_wal_size to spread
|
|
50
|
+
checkpoint writes over a longer period, avoiding I/O spikes.
|
|
51
|
+
- Requires a server restart to change.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
# postgresql.conf - 64 GB RAM server
|
|
55
|
+
shared_buffers = '16GB'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 1.2 work_mem
|
|
59
|
+
|
|
60
|
+
**What it does:** Sets the maximum memory each query operation (sort, hash join, hash
|
|
61
|
+
aggregate) can use before spilling to temporary disk files. A single complex query can
|
|
62
|
+
allocate work_mem multiple times (once per sort/hash node).
|
|
63
|
+
|
|
64
|
+
**Formula:** `(Total RAM - shared_buffers) / (16 x CPU cores)`
|
|
65
|
+
|
|
66
|
+
| System RAM | CPU Cores | shared_buffers | work_mem |
|
|
67
|
+
|------------|-----------|---------------|----------|
|
|
68
|
+
| 16 GB | 4 | 4 GB | 192 MB |
|
|
69
|
+
| 64 GB | 16 | 16 GB | 187 MB |
|
|
70
|
+
| 256 GB | 32 | 64 GB | 375 MB |
|
|
71
|
+
|
|
72
|
+
- OLTP workloads: Keep lower (4-64 MB) since many concurrent connections each allocate
|
|
73
|
+
this amount. With 200 connections at 64 MB work_mem, worst case is 12.8 GB consumed
|
|
74
|
+
just by sort buffers.
|
|
75
|
+
- OLAP/reporting workloads: Can go higher (256 MB-1 GB) with fewer concurrent queries.
|
|
76
|
+
- Monitor temp file usage via `pg_stat_database.temp_bytes` -- if temp files are
|
|
77
|
+
generated frequently, increase work_mem.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
# OLTP server with 200 max connections
|
|
81
|
+
work_mem = '32MB'
|
|
82
|
+
|
|
83
|
+
# OLAP server with 20 max connections
|
|
84
|
+
work_mem = '512MB'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 1.3 effective_cache_size
|
|
88
|
+
|
|
89
|
+
**What it does:** Tells the query planner how much memory is available for disk caching
|
|
90
|
+
across both shared_buffers and the OS page cache. Does not allocate memory; only
|
|
91
|
+
influences cost estimates for index scans vs sequential scans.
|
|
92
|
+
|
|
93
|
+
**Recommended values:**
|
|
94
|
+
- Conservative: 50% of total RAM
|
|
95
|
+
- Typical: 75% of total RAM (most common production setting)
|
|
96
|
+
|
|
97
|
+
A 64 GB server should use `effective_cache_size = '48GB'`. Setting this too low causes
|
|
98
|
+
the planner to prefer sequential scans over index scans because it assumes data is
|
|
99
|
+
unlikely to be cached, artificially inflating the estimated cost of random I/O.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
# 64 GB RAM server
|
|
103
|
+
effective_cache_size = '48GB'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 1.4 random_page_cost
|
|
107
|
+
|
|
108
|
+
**What it does:** Tells the planner the relative cost of a non-sequential (random) disk
|
|
109
|
+
page fetch compared to a sequential one. Default is 4.0 (random I/O is 4x more
|
|
110
|
+
expensive than sequential).
|
|
111
|
+
|
|
112
|
+
**SSD tuning is critical:** On SSDs, random reads are nearly as fast as sequential reads.
|
|
113
|
+
|
|
114
|
+
| Storage Type | random_page_cost | Rationale |
|
|
115
|
+
|-------------|-----------------|-----------|
|
|
116
|
+
| HDD (7200 RPM) | 4.0 (default) | Random seeks are ~10ms vs ~0.1ms sequential |
|
|
117
|
+
| SSD (SATA) | 1.5-2.0 | Random latency ~0.1ms, sequential ~0.05ms |
|
|
118
|
+
| NVMe SSD | 1.1-1.5 | Near-uniform access latency |
|
|
119
|
+
| RAM disk | 1.0 | No mechanical seek penalty |
|
|
120
|
+
|
|
121
|
+
Leaving this at 4.0 on SSD storage causes PostgreSQL to avoid index scans in favor of
|
|
122
|
+
sequential scans, often resulting in 10-100x slower queries on large tables.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
# NVMe SSD storage
|
|
126
|
+
random_page_cost = 1.1
|
|
127
|
+
seq_page_cost = 1.0
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 1.5 WAL and Checkpoint Configuration
|
|
131
|
+
|
|
132
|
+
| Parameter | Default | Recommended | Why |
|
|
133
|
+
|-----------|---------|-------------|-----|
|
|
134
|
+
| wal_buffers | 64 KB (auto-tuned) | 16-64 MB | Buffers WAL records before disk flush; 16 MB minimum for write-heavy loads |
|
|
135
|
+
| max_wal_size | 1 GB | 2-4 GB | Reduces checkpoint frequency; more WAL between checkpoints |
|
|
136
|
+
| min_wal_size | 80 MB | 512 MB-1 GB | Keeps WAL files around to avoid recreation overhead |
|
|
137
|
+
| checkpoint_completion_target | 0.9 | 0.9 | Spreads checkpoint I/O over 90% of the interval; reduces spikes |
|
|
138
|
+
| checkpoint_timeout | 5 min | 10-15 min | Less frequent checkpoints; reduces write amplification |
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
# Write-heavy OLTP workload
|
|
142
|
+
wal_buffers = '64MB'
|
|
143
|
+
max_wal_size = '4GB'
|
|
144
|
+
min_wal_size = '1GB'
|
|
145
|
+
checkpoint_completion_target = 0.9
|
|
146
|
+
checkpoint_timeout = '15min'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 1.6 Parallelism Settings
|
|
150
|
+
|
|
151
|
+
| Parameter | Default | Recommended | Effect |
|
|
152
|
+
|-----------|---------|-------------|--------|
|
|
153
|
+
| max_parallel_workers_per_gather | 2 | 2-4 | Workers per parallel query node |
|
|
154
|
+
| max_parallel_workers | 8 | CPU cores / 2 | Total parallel workers system-wide |
|
|
155
|
+
| max_worker_processes | 8 | CPU cores | Total background workers |
|
|
156
|
+
| parallel_tuple_cost | 0.1 | 0.01-0.1 | Lower = planner uses parallelism more |
|
|
157
|
+
| min_parallel_table_scan_size | 8 MB | 8 MB | Minimum table size for parallel seq scan |
|
|
158
|
+
|
|
159
|
+
Parallelism is most effective for OLAP queries scanning large tables. For OLTP with many
|
|
160
|
+
short queries, keep defaults or reduce parallel workers to avoid contention.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 2. Index Strategies
|
|
165
|
+
|
|
166
|
+
Indexes are the single most impactful tool for query performance. PostgreSQL supports
|
|
167
|
+
seven index types, each optimized for different access patterns.
|
|
168
|
+
|
|
169
|
+
### 2.1 B-tree Indexes (Default)
|
|
170
|
+
|
|
171
|
+
**Best for:** Equality (=), range (<, >, BETWEEN), sorting (ORDER BY), and uniqueness.
|
|
172
|
+
|
|
173
|
+
B-tree is the default and handles ~90% of indexing needs. Supports the operators
|
|
174
|
+
`<`, `<=`, `=`, `>=`, `>`, `BETWEEN`, `IN`, `IS NULL`, and `IS NOT NULL`.
|
|
175
|
+
|
|
176
|
+
```sql
|
|
177
|
+
-- Standard B-tree index
|
|
178
|
+
CREATE INDEX idx_orders_created_at ON orders (created_at);
|
|
179
|
+
|
|
180
|
+
-- Multi-column B-tree (leftmost prefix rule applies)
|
|
181
|
+
CREATE INDEX idx_orders_user_status ON orders (user_id, status);
|
|
182
|
+
-- This index serves: WHERE user_id = X
|
|
183
|
+
-- WHERE user_id = X AND status = Y
|
|
184
|
+
-- But NOT: WHERE status = Y (alone)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Size impact:** A B-tree index on a 100M row table with an integer column is typically
|
|
188
|
+
200-400 MB. On a UUID column, expect 2-4 GB.
|
|
189
|
+
|
|
190
|
+
### 2.2 GIN Indexes (Generalized Inverted Index)
|
|
191
|
+
|
|
192
|
+
**Best for:** JSONB containment (@>), array overlap (&&), full-text search (@@),
|
|
193
|
+
trigram similarity (%).
|
|
194
|
+
|
|
195
|
+
GIN creates an entry for each element within a composite value, making lookups
|
|
196
|
+
extremely fast for "contains" queries. Write overhead is 3-10x higher than B-tree
|
|
197
|
+
because each INSERT may generate multiple index entries.
|
|
198
|
+
|
|
199
|
+
```sql
|
|
200
|
+
-- JSONB containment queries
|
|
201
|
+
CREATE INDEX idx_events_data ON events USING gin (data);
|
|
202
|
+
-- Enables: WHERE data @> '{"type": "purchase"}'
|
|
203
|
+
|
|
204
|
+
-- Full-text search
|
|
205
|
+
CREATE INDEX idx_articles_search ON articles USING gin (to_tsvector('english', body));
|
|
206
|
+
-- Enables: WHERE to_tsvector('english', body) @@ to_tsquery('postgresql & performance')
|
|
207
|
+
|
|
208
|
+
-- Trigram pattern matching (requires pg_trgm extension)
|
|
209
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
210
|
+
CREATE INDEX idx_users_name_trgm ON users USING gin (name gin_trgm_ops);
|
|
211
|
+
-- Enables: WHERE name LIKE '%john%' (leading wildcard!)
|
|
212
|
+
-- Enables: WHERE name ILIKE '%john%'
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Performance note:** GIN indexes are 3-5x slower to build than B-tree but provide
|
|
216
|
+
sub-millisecond lookups for containment queries that would otherwise require full
|
|
217
|
+
table scans.
|
|
218
|
+
|
|
219
|
+
### 2.3 GiST Indexes (Generalized Search Tree)
|
|
220
|
+
|
|
221
|
+
**Best for:** Geometric data, range types, nearest-neighbor searches, full-text search
|
|
222
|
+
(alternative to GIN with faster updates but slower reads).
|
|
223
|
+
|
|
224
|
+
```sql
|
|
225
|
+
-- Geometric proximity queries
|
|
226
|
+
CREATE INDEX idx_locations_point ON locations USING gist (coordinates);
|
|
227
|
+
-- Enables: WHERE coordinates <-> point(40.7128, -74.0060) < 0.01
|
|
228
|
+
|
|
229
|
+
-- Range overlap queries
|
|
230
|
+
CREATE INDEX idx_reservations_period ON reservations USING gist (
|
|
231
|
+
tsrange(check_in, check_out)
|
|
232
|
+
);
|
|
233
|
+
-- Enables: WHERE tsrange(check_in, check_out) && tsrange('2025-01-01', '2025-01-07')
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**GiST vs GIN for full-text search:** GIN is 2-3x faster for reads but 3x slower for
|
|
237
|
+
updates. Use GIN for read-heavy workloads; GiST for write-heavy ones.
|
|
238
|
+
|
|
239
|
+
### 2.4 BRIN Indexes (Block Range Indexes)
|
|
240
|
+
|
|
241
|
+
**Best for:** Very large tables (100M+ rows) where data is physically ordered by the
|
|
242
|
+
indexed column. Common for time-series, append-only logs, and IoT data.
|
|
243
|
+
|
|
244
|
+
BRIN stores min/max summaries per block range (default 128 pages = 1 MB) instead of
|
|
245
|
+
indexing every row. This makes BRIN indexes 100-1000x smaller than equivalent B-tree
|
|
246
|
+
indexes.
|
|
247
|
+
|
|
248
|
+
```sql
|
|
249
|
+
-- Time-series data (rows inserted in chronological order)
|
|
250
|
+
CREATE INDEX idx_sensor_readings_ts ON sensor_readings USING brin (recorded_at);
|
|
251
|
+
-- Size: ~100 KB for a 10 GB table (vs ~200 MB for B-tree)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
| Metric | B-tree (10 GB table) | BRIN (10 GB table) |
|
|
255
|
+
|--------|---------------------|-------------------|
|
|
256
|
+
| Index size | ~200 MB | ~100 KB |
|
|
257
|
+
| Build time | ~60 seconds | ~2 seconds |
|
|
258
|
+
| Point query speed | <1 ms | 5-50 ms |
|
|
259
|
+
| Range scan speed | <10 ms | 10-100 ms |
|
|
260
|
+
|
|
261
|
+
**Caveat:** BRIN is only effective when physical row order correlates with the indexed
|
|
262
|
+
column. Run `SELECT correlation FROM pg_stats WHERE tablename = 'your_table'` -- a
|
|
263
|
+
correlation near 1.0 or -1.0 indicates BRIN suitability.
|
|
264
|
+
|
|
265
|
+
### 2.5 Partial Indexes
|
|
266
|
+
|
|
267
|
+
**What:** Indexes that cover only a subset of rows, defined by a WHERE clause. Smaller,
|
|
268
|
+
faster to maintain, and more cache-friendly than full indexes.
|
|
269
|
+
|
|
270
|
+
```sql
|
|
271
|
+
-- Only index active users (5% of 10M rows = 500K entries)
|
|
272
|
+
CREATE INDEX idx_users_active_email ON users (email)
|
|
273
|
+
WHERE is_active = true;
|
|
274
|
+
-- Size: ~20 MB instead of ~400 MB for a full index
|
|
275
|
+
|
|
276
|
+
-- Only index unprocessed orders
|
|
277
|
+
CREATE INDEX idx_orders_pending ON orders (created_at)
|
|
278
|
+
WHERE status = 'pending';
|
|
279
|
+
-- If 2% of orders are pending, this index is 50x smaller than a full index
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Query must match the partial index predicate** for the planner to use it:
|
|
283
|
+
|
|
284
|
+
```sql
|
|
285
|
+
-- Uses the partial index:
|
|
286
|
+
SELECT * FROM users WHERE email = 'x@y.com' AND is_active = true;
|
|
287
|
+
|
|
288
|
+
-- Does NOT use the partial index:
|
|
289
|
+
SELECT * FROM users WHERE email = 'x@y.com';
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 2.6 Covering Indexes (INCLUDE)
|
|
293
|
+
|
|
294
|
+
**What:** B-tree indexes that store additional columns in the leaf pages, enabling
|
|
295
|
+
index-only scans without touching the heap (table data).
|
|
296
|
+
|
|
297
|
+
```sql
|
|
298
|
+
-- Covering index: the query never needs to read the table
|
|
299
|
+
CREATE INDEX idx_orders_user_covering ON orders (user_id)
|
|
300
|
+
INCLUDE (total_amount, status);
|
|
301
|
+
|
|
302
|
+
-- This query becomes an index-only scan:
|
|
303
|
+
SELECT total_amount, status FROM orders WHERE user_id = 42;
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Index-only scans avoid heap fetches entirely, which can improve performance by 2-10x
|
|
307
|
+
for queries that only need the included columns. The visibility map must be up to date
|
|
308
|
+
(run VACUUM) for index-only scans to be effective.
|
|
309
|
+
|
|
310
|
+
### 2.7 Expression Indexes
|
|
311
|
+
|
|
312
|
+
**What:** Indexes on the result of an expression or function, not just raw column values.
|
|
313
|
+
|
|
314
|
+
```sql
|
|
315
|
+
-- Case-insensitive email lookup
|
|
316
|
+
CREATE INDEX idx_users_email_lower ON users (lower(email));
|
|
317
|
+
-- Enables: WHERE lower(email) = 'user@example.com'
|
|
318
|
+
|
|
319
|
+
-- Extract year from timestamp
|
|
320
|
+
CREATE INDEX idx_orders_year ON orders (extract(year FROM created_at));
|
|
321
|
+
-- Enables: WHERE extract(year FROM created_at) = 2025
|
|
322
|
+
|
|
323
|
+
-- JSONB field extraction
|
|
324
|
+
CREATE INDEX idx_events_type ON events ((data->>'type'));
|
|
325
|
+
-- Enables: WHERE data->>'type' = 'click'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**The query must use the exact same expression** as the index definition for the planner
|
|
329
|
+
to recognize it.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 3. Query Optimization with EXPLAIN
|
|
334
|
+
|
|
335
|
+
EXPLAIN is the most important diagnostic tool in PostgreSQL. Always use it with ANALYZE
|
|
336
|
+
and BUFFERS to get actual execution statistics.
|
|
337
|
+
|
|
338
|
+
### 3.1 EXPLAIN Variants
|
|
339
|
+
|
|
340
|
+
```sql
|
|
341
|
+
-- Basic: shows the planner's estimated plan (no execution)
|
|
342
|
+
EXPLAIN SELECT ...;
|
|
343
|
+
|
|
344
|
+
-- With execution: runs the query and shows actual times
|
|
345
|
+
EXPLAIN ANALYZE SELECT ...;
|
|
346
|
+
|
|
347
|
+
-- With buffer statistics: shows cache hits vs disk reads
|
|
348
|
+
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
|
|
349
|
+
|
|
350
|
+
-- Machine-readable format for tooling
|
|
351
|
+
EXPLAIN (ANALYZE, BUFFERS, FORMAT YAML) SELECT ...;
|
|
352
|
+
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...;
|
|
353
|
+
|
|
354
|
+
-- PostgreSQL 18+: ANALYZE automatically includes BUFFERS
|
|
355
|
+
EXPLAIN ANALYZE SELECT ...; -- includes buffer stats in PG 18+
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 3.2 Reading EXPLAIN Output
|
|
359
|
+
|
|
360
|
+
Key fields to examine:
|
|
361
|
+
|
|
362
|
+
| Field | Meaning | Warning Sign |
|
|
363
|
+
|-------|---------|-------------|
|
|
364
|
+
| `Seq Scan` | Full table scan | On tables > 10K rows with a WHERE clause |
|
|
365
|
+
| `actual time=X..Y` | Startup..total time in ms | Total > 100ms for OLTP |
|
|
366
|
+
| `rows=N` | Actual rows returned by node | Large mismatch with `rows` estimate |
|
|
367
|
+
| `Buffers: shared hit=X` | Pages read from shared_buffers | Good: high hit ratio |
|
|
368
|
+
| `Buffers: shared read=Y` | Pages read from disk | Bad if Y >> X |
|
|
369
|
+
| `Buffers: temp read/written` | Spilled to disk temp files | work_mem too low |
|
|
370
|
+
| `Sort Method: external merge` | Sort spilled to disk | Increase work_mem |
|
|
371
|
+
| `Hash Batch` | Hash join spilled to disk | Increase work_mem |
|
|
372
|
+
|
|
373
|
+
### 3.3 Planner Estimate Mismatches
|
|
374
|
+
|
|
375
|
+
When the planner's estimated rows differ from actual rows by 10x or more, the chosen
|
|
376
|
+
plan is likely suboptimal. Common causes:
|
|
377
|
+
|
|
378
|
+
1. **Stale statistics** -- Run `ANALYZE tablename` to refresh.
|
|
379
|
+
2. **Correlated columns** -- The planner assumes column independence. Use extended
|
|
380
|
+
statistics: `CREATE STATISTICS stat_name (dependencies) ON col1, col2 FROM table;`
|
|
381
|
+
3. **Skewed data** -- Default histogram has 100 buckets. Increase with
|
|
382
|
+
`ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000;`
|
|
383
|
+
|
|
384
|
+
### 3.4 Common Plan Node Improvements
|
|
385
|
+
|
|
386
|
+
| Slow Node | Likely Fix | Expected Improvement |
|
|
387
|
+
|-----------|-----------|---------------------|
|
|
388
|
+
| Seq Scan on large table | Add appropriate index | 10-1000x |
|
|
389
|
+
| Nested Loop with inner Seq Scan | Add index on join column | 100-10000x |
|
|
390
|
+
| Sort (external merge Disk) | Increase work_mem | 2-5x |
|
|
391
|
+
| Hash Join (multiple batches) | Increase work_mem | 2-5x |
|
|
392
|
+
| Bitmap Heap Scan (lossy) | Increase work_mem | 1.5-3x |
|
|
393
|
+
| Index Scan with Filter (many rows filtered) | Use partial or covering index | 2-10x |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 4. Connection Pooling
|
|
398
|
+
|
|
399
|
+
PostgreSQL uses a process-per-connection model. Each connection forks a backend process
|
|
400
|
+
consuming ~10 MB of memory minimum. Beyond 200 active connections, performance degrades
|
|
401
|
+
sharply due to OS context switching, lock contention, and cache thrashing.
|
|
402
|
+
|
|
403
|
+
### 4.1 The Connection Problem
|
|
404
|
+
|
|
405
|
+
**Optimal active connections:** `(CPU cores * 2) + effective_spindle_count`
|
|
406
|
+
|
|
407
|
+
For a 16-core server with SSDs, the optimal number is approximately 33-40 active
|
|
408
|
+
connections. Having 500 connections with 40 active at any time means 460 idle connections
|
|
409
|
+
each consuming memory and causing context-switching overhead.
|
|
410
|
+
|
|
411
|
+
**Measured impact of idle connections:**
|
|
412
|
+
- 50 idle connections: ~1-2% throughput loss
|
|
413
|
+
- 200 idle connections: ~10-15% throughput loss
|
|
414
|
+
- 500 idle connections: ~25-35% throughput loss
|
|
415
|
+
- 1000+ idle connections: throughput can drop by 50%+ due to process table pressure
|
|
416
|
+
|
|
417
|
+
### 4.2 PgBouncer
|
|
418
|
+
|
|
419
|
+
The most widely deployed PostgreSQL connection pooler. Single-threaded, lightweight
|
|
420
|
+
(~2 KB per connection), and battle-tested.
|
|
421
|
+
|
|
422
|
+
**Pooling modes:**
|
|
423
|
+
|
|
424
|
+
| Mode | Description | Use Case |
|
|
425
|
+
|------|-------------|----------|
|
|
426
|
+
| Session | Client holds a server connection for its entire session | Legacy apps needing session state |
|
|
427
|
+
| Transaction | Client gets a server connection only during a transaction | Most web applications (recommended) |
|
|
428
|
+
| Statement | Client gets a connection per statement | Simple read-only queries only |
|
|
429
|
+
|
|
430
|
+
**Benchmark results (pgbench, 16-core server):**
|
|
431
|
+
|
|
432
|
+
| Metric | Direct PG (200 conn) | PgBouncer Transaction (200 conn, 40 pool) |
|
|
433
|
+
|--------|---------------------|------------------------------------------|
|
|
434
|
+
| TPS | ~8,000 | ~50,000 |
|
|
435
|
+
| Avg latency | 25 ms | 4 ms |
|
|
436
|
+
| p99 latency | 180 ms | 15 ms |
|
|
437
|
+
| Memory usage | 2 GB (backends) | 400 MB |
|
|
438
|
+
|
|
439
|
+
```ini
|
|
440
|
+
# pgbouncer.ini
|
|
441
|
+
[databases]
|
|
442
|
+
mydb = host=127.0.0.1 port=5432 dbname=mydb
|
|
443
|
+
|
|
444
|
+
[pgbouncer]
|
|
445
|
+
listen_port = 6432
|
|
446
|
+
listen_addr = 0.0.0.0
|
|
447
|
+
auth_type = md5
|
|
448
|
+
auth_file = /etc/pgbouncer/userlist.txt
|
|
449
|
+
pool_mode = transaction
|
|
450
|
+
max_client_conn = 1000
|
|
451
|
+
default_pool_size = 40
|
|
452
|
+
min_pool_size = 10
|
|
453
|
+
reserve_pool_size = 5
|
|
454
|
+
reserve_pool_timeout = 3
|
|
455
|
+
server_idle_timeout = 300
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Limitation:** Single-threaded. At very high concurrency (>5000 connections), the single
|
|
459
|
+
event loop becomes a bottleneck. PgBouncer peaks at ~44,000 TPS and degrades to
|
|
460
|
+
25,000-30,000 TPS beyond 75 concurrent active connections in benchmarks.
|
|
461
|
+
|
|
462
|
+
### 4.3 PgCat
|
|
463
|
+
|
|
464
|
+
Multi-threaded Rust-based pooler developed by PostgresML/Supabase. Scales better under
|
|
465
|
+
high concurrency.
|
|
466
|
+
|
|
467
|
+
**Benchmark comparison with PgBouncer (1000 concurrent clients):**
|
|
468
|
+
|
|
469
|
+
| Metric | PgBouncer | PgCat |
|
|
470
|
+
|--------|-----------|-------|
|
|
471
|
+
| Peak TPS | 44,096 | 59,000 |
|
|
472
|
+
| Latency at 100 clients | Lower by ~50us | Slightly higher |
|
|
473
|
+
| Latency at 500 clients | Higher | Lower |
|
|
474
|
+
| CPU usage | Single core maxed | Distributed across cores |
|
|
475
|
+
| Features | Mature, stable | Query routing, load balancing, sharding |
|
|
476
|
+
|
|
477
|
+
**When to choose PgCat over PgBouncer:**
|
|
478
|
+
- More than 5000 client connections
|
|
479
|
+
- Need for query routing across read replicas
|
|
480
|
+
- Need for built-in load balancing
|
|
481
|
+
- Multi-tenant architectures requiring query-level routing
|
|
482
|
+
|
|
483
|
+
### 4.4 Application-Level Pooling
|
|
484
|
+
|
|
485
|
+
Most application frameworks provide connection pools that sit between the application
|
|
486
|
+
and PgBouncer/PostgreSQL:
|
|
487
|
+
|
|
488
|
+
- **HikariCP (Java):** Set `maximumPoolSize` to CPU cores * 2. Default idle timeout
|
|
489
|
+
of 600 seconds works well.
|
|
490
|
+
- **SQLAlchemy (Python):** `pool_size=20, max_overflow=10, pool_pre_ping=True`
|
|
491
|
+
- **node-postgres (Node.js):** `max: 20, idleTimeoutMillis: 30000`
|
|
492
|
+
|
|
493
|
+
Layer pooling: Application pool (20-50) -> PgBouncer (40-100 server connections) ->
|
|
494
|
+
PostgreSQL (max_connections = 150).
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## 5. VACUUM and Autovacuum Tuning
|
|
499
|
+
|
|
500
|
+
PostgreSQL's MVCC implementation creates dead tuples on every UPDATE and DELETE. Without
|
|
501
|
+
VACUUM, tables and indexes bloat, causing sequential scans to read increasingly more
|
|
502
|
+
dead data.
|
|
503
|
+
|
|
504
|
+
### 5.1 How MVCC Creates Bloat
|
|
505
|
+
|
|
506
|
+
1. An UPDATE creates a new tuple version; the old one is marked dead.
|
|
507
|
+
2. A DELETE marks the existing tuple as dead.
|
|
508
|
+
3. Dead tuples remain until VACUUM removes them.
|
|
509
|
+
4. Dead tuples are only removable when no active transaction can see them.
|
|
510
|
+
|
|
511
|
+
**Impact of bloat:** A table with 50% dead tuples requires 2x the I/O for sequential
|
|
512
|
+
scans, 2x the buffer cache, and 2x the backup storage.
|
|
513
|
+
|
|
514
|
+
### 5.2 Autovacuum Default Behavior
|
|
515
|
+
|
|
516
|
+
Autovacuum triggers when:
|
|
517
|
+
`dead_tuples > autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor * table_rows)`
|
|
518
|
+
|
|
519
|
+
Default: `50 + (0.20 * table_rows)` -- VACUUM starts when 20% of rows are dead.
|
|
520
|
+
|
|
521
|
+
**Problem for large tables:** A 100M row table won't be vacuumed until 20,000,050 dead
|
|
522
|
+
tuples accumulate. This is far too late -- the table will be severely bloated.
|
|
523
|
+
|
|
524
|
+
### 5.3 Recommended Autovacuum Settings
|
|
525
|
+
|
|
526
|
+
**Global settings (postgresql.conf):**
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
# Reduce scale factor so VACUUM triggers sooner
|
|
530
|
+
autovacuum_vacuum_scale_factor = 0.05 # 5% instead of 20%
|
|
531
|
+
autovacuum_analyze_scale_factor = 0.02 # 2% instead of 10%
|
|
532
|
+
|
|
533
|
+
# Make VACUUM faster by increasing I/O budget
|
|
534
|
+
autovacuum_vacuum_cost_limit = 1000 # default: 200
|
|
535
|
+
autovacuum_vacuum_cost_delay = 2ms # default: 2ms (PG 12+)
|
|
536
|
+
|
|
537
|
+
# Allow more concurrent autovacuum workers
|
|
538
|
+
autovacuum_max_workers = 5 # default: 3
|
|
539
|
+
|
|
540
|
+
# Run more frequently
|
|
541
|
+
autovacuum_naptime = 15s # default: 1min
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Per-table overrides for high-churn tables:**
|
|
545
|
+
|
|
546
|
+
```sql
|
|
547
|
+
-- Orders table: 50M rows, 100K updates/hour
|
|
548
|
+
ALTER TABLE orders SET (
|
|
549
|
+
autovacuum_vacuum_scale_factor = 0.01, -- 1% = 500K dead tuples
|
|
550
|
+
autovacuum_vacuum_threshold = 1000,
|
|
551
|
+
autovacuum_analyze_scale_factor = 0.005,
|
|
552
|
+
autovacuum_vacuum_cost_limit = 2000
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
-- Sessions table: 10M rows, constant insert/delete
|
|
556
|
+
ALTER TABLE sessions SET (
|
|
557
|
+
autovacuum_vacuum_scale_factor = 0.02,
|
|
558
|
+
autovacuum_vacuum_threshold = 500,
|
|
559
|
+
autovacuum_vacuum_cost_limit = 3000
|
|
560
|
+
);
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### 5.4 Monitoring VACUUM Health
|
|
564
|
+
|
|
565
|
+
```sql
|
|
566
|
+
-- Check dead tuple counts and last vacuum time per table
|
|
567
|
+
SELECT
|
|
568
|
+
schemaname, relname,
|
|
569
|
+
n_live_tup,
|
|
570
|
+
n_dead_tup,
|
|
571
|
+
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
|
|
572
|
+
last_vacuum,
|
|
573
|
+
last_autovacuum,
|
|
574
|
+
last_analyze,
|
|
575
|
+
last_autoanalyze
|
|
576
|
+
FROM pg_stat_user_tables
|
|
577
|
+
WHERE n_dead_tup > 10000
|
|
578
|
+
ORDER BY n_dead_tup DESC;
|
|
579
|
+
|
|
580
|
+
-- Check for long-running transactions blocking VACUUM
|
|
581
|
+
SELECT pid, age(backend_xid) AS xid_age,
|
|
582
|
+
now() - xact_start AS duration,
|
|
583
|
+
query
|
|
584
|
+
FROM pg_stat_activity
|
|
585
|
+
WHERE backend_xid IS NOT NULL
|
|
586
|
+
ORDER BY age(backend_xid) DESC
|
|
587
|
+
LIMIT 5;
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 5.5 The Long-Running Transaction Trap
|
|
591
|
+
|
|
592
|
+
Autovacuum cannot reclaim dead tuples that are still visible to any open transaction.
|
|
593
|
+
A single forgotten `BEGIN` without a `COMMIT` can hold back VACUUM across the entire
|
|
594
|
+
database, causing unbounded bloat.
|
|
595
|
+
|
|
596
|
+
**Prevention:**
|
|
597
|
+
- Set `idle_in_transaction_session_timeout = '5min'` to kill abandoned transactions
|
|
598
|
+
- Set `statement_timeout = '30s'` for web applications
|
|
599
|
+
- Monitor `pg_stat_activity` for long-running `idle in transaction` sessions
|
|
600
|
+
|
|
601
|
+
### 5.6 Visibility Map and Index-Only Scans
|
|
602
|
+
|
|
603
|
+
VACUUM maintains a visibility map that tracks which heap pages contain only tuples
|
|
604
|
+
visible to all transactions. This enables:
|
|
605
|
+
|
|
606
|
+
1. **Index-only scans:** If a page is marked "all-visible," the executor skips the heap
|
|
607
|
+
fetch, reading only from the index. This can improve query performance by 2-10x.
|
|
608
|
+
2. **Faster future VACUUMs:** Pages already marked all-visible are skipped, making
|
|
609
|
+
subsequent VACUUM runs much faster on stable data.
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## 6. Table Partitioning Strategies
|
|
614
|
+
|
|
615
|
+
Partitioning splits a large logical table into smaller physical tables. PostgreSQL 10+
|
|
616
|
+
supports declarative partitioning with range, list, and hash strategies.
|
|
617
|
+
|
|
618
|
+
### 6.1 When to Partition
|
|
619
|
+
|
|
620
|
+
Partition when:
|
|
621
|
+
- Table exceeds 50-100 GB or 100M+ rows
|
|
622
|
+
- Queries naturally filter on the partition key (date ranges, tenant IDs)
|
|
623
|
+
- Maintenance operations (VACUUM, REINDEX) on the full table are too slow
|
|
624
|
+
- Data lifecycle requires dropping old data (detach partition vs DELETE)
|
|
625
|
+
|
|
626
|
+
Do NOT partition when:
|
|
627
|
+
- Table is under 10 GB and queries are well-indexed
|
|
628
|
+
- Queries do not include the partition key in WHERE clauses
|
|
629
|
+
- You would create more than 1000 partitions (planner overhead)
|
|
630
|
+
- Individual partitions would have fewer than 10,000 rows
|
|
631
|
+
|
|
632
|
+
### 6.2 Range Partitioning (Time-Series)
|
|
633
|
+
|
|
634
|
+
The most common strategy. Partitions by continuous intervals, typically dates.
|
|
635
|
+
|
|
636
|
+
```sql
|
|
637
|
+
CREATE TABLE events (
|
|
638
|
+
id bigint GENERATED ALWAYS AS IDENTITY,
|
|
639
|
+
event_type text NOT NULL,
|
|
640
|
+
payload jsonb,
|
|
641
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
642
|
+
) PARTITION BY RANGE (created_at);
|
|
643
|
+
|
|
644
|
+
-- Monthly partitions
|
|
645
|
+
CREATE TABLE events_2025_01 PARTITION OF events
|
|
646
|
+
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
647
|
+
CREATE TABLE events_2025_02 PARTITION OF events
|
|
648
|
+
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
|
|
649
|
+
-- ... additional months
|
|
650
|
+
|
|
651
|
+
-- Indexes are defined on the parent; PostgreSQL creates them on each partition
|
|
652
|
+
CREATE INDEX idx_events_type ON events (event_type);
|
|
653
|
+
CREATE INDEX idx_events_created ON events (created_at);
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Performance benefit:** A query with `WHERE created_at BETWEEN '2025-01-15' AND
|
|
657
|
+
'2025-01-20'` only scans the January partition, skipping all others (partition pruning).
|
|
658
|
+
On a table with 24 monthly partitions, this means scanning ~4% of the data.
|
|
659
|
+
|
|
660
|
+
### 6.3 List Partitioning (Categorical)
|
|
661
|
+
|
|
662
|
+
Partitions by discrete values. Common for multi-tenant or status-based partitioning.
|
|
663
|
+
|
|
664
|
+
```sql
|
|
665
|
+
CREATE TABLE orders (
|
|
666
|
+
id bigint GENERATED ALWAYS AS IDENTITY,
|
|
667
|
+
tenant_id int NOT NULL,
|
|
668
|
+
total numeric(12,2),
|
|
669
|
+
created_at timestamptz NOT NULL
|
|
670
|
+
) PARTITION BY LIST (tenant_id);
|
|
671
|
+
|
|
672
|
+
CREATE TABLE orders_tenant_1 PARTITION OF orders FOR VALUES IN (1);
|
|
673
|
+
CREATE TABLE orders_tenant_2 PARTITION OF orders FOR VALUES IN (2);
|
|
674
|
+
CREATE TABLE orders_tenant_3 PARTITION OF orders FOR VALUES IN (3);
|
|
675
|
+
-- Default partition for new tenants
|
|
676
|
+
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### 6.4 Hash Partitioning (Uniform Distribution)
|
|
680
|
+
|
|
681
|
+
Distributes rows evenly across a fixed number of partitions using a hash function.
|
|
682
|
+
Best for uniform access patterns without natural range boundaries.
|
|
683
|
+
|
|
684
|
+
```sql
|
|
685
|
+
CREATE TABLE user_activities (
|
|
686
|
+
id bigint GENERATED ALWAYS AS IDENTITY,
|
|
687
|
+
user_id bigint NOT NULL,
|
|
688
|
+
action text,
|
|
689
|
+
created_at timestamptz NOT NULL
|
|
690
|
+
) PARTITION BY HASH (user_id);
|
|
691
|
+
|
|
692
|
+
CREATE TABLE user_activities_0 PARTITION OF user_activities
|
|
693
|
+
FOR VALUES WITH (MODULUS 8, REMAINDER 0);
|
|
694
|
+
CREATE TABLE user_activities_1 PARTITION OF user_activities
|
|
695
|
+
FOR VALUES WITH (MODULUS 8, REMAINDER 1);
|
|
696
|
+
-- ... partitions 2-7
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### 6.5 Partition Maintenance
|
|
700
|
+
|
|
701
|
+
```sql
|
|
702
|
+
-- Drop old data by detaching and dropping the partition (instant vs DELETE)
|
|
703
|
+
ALTER TABLE events DETACH PARTITION events_2023_01;
|
|
704
|
+
DROP TABLE events_2023_01; -- instant; no VACUUM needed
|
|
705
|
+
|
|
706
|
+
-- Add future partitions (automate with pg_partman or cron)
|
|
707
|
+
CREATE TABLE events_2026_01 PARTITION OF events
|
|
708
|
+
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
|
|
709
|
+
|
|
710
|
+
-- VACUUM/ANALYZE individual partitions independently
|
|
711
|
+
VACUUM ANALYZE events_2025_01;
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Guidelines:** Keep 50-200 partitions maximum. Each partition should hold at least
|
|
715
|
+
10,000-100,000 rows. The planner must evaluate all partitions during planning, so
|
|
716
|
+
excessive partitions add planning overhead (~0.5-1 ms per 100 partitions).
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## 7. Common Bottlenecks
|
|
721
|
+
|
|
722
|
+
### 7.1 Sequential Scans on Large Tables
|
|
723
|
+
|
|
724
|
+
**Symptom:** `Seq Scan on large_table` in EXPLAIN with `rows=50000000`.
|
|
725
|
+
|
|
726
|
+
**Cause:** Missing index, or planner chooses sequential scan because:
|
|
727
|
+
- `random_page_cost` is too high for your storage (SSD but set to 4.0)
|
|
728
|
+
- `effective_cache_size` is too low (planner assumes cache misses)
|
|
729
|
+
- Table statistics are stale (run ANALYZE)
|
|
730
|
+
|
|
731
|
+
**Fix:** Add an appropriate index. If the index exists but isn't used, check the above
|
|
732
|
+
configuration parameters.
|
|
733
|
+
|
|
734
|
+
```sql
|
|
735
|
+
-- Detect tables with high sequential scan counts
|
|
736
|
+
SELECT schemaname, relname,
|
|
737
|
+
seq_scan, seq_tup_read,
|
|
738
|
+
idx_scan, idx_tup_fetch,
|
|
739
|
+
CASE WHEN seq_scan > 0
|
|
740
|
+
THEN round(seq_tup_read::numeric / seq_scan)
|
|
741
|
+
ELSE 0 END AS avg_rows_per_seq_scan
|
|
742
|
+
FROM pg_stat_user_tables
|
|
743
|
+
WHERE seq_scan > 100
|
|
744
|
+
AND seq_tup_read > 100000
|
|
745
|
+
ORDER BY seq_tup_read DESC
|
|
746
|
+
LIMIT 20;
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### 7.2 Missing Indexes
|
|
750
|
+
|
|
751
|
+
**Symptom:** Queries with WHERE clauses on non-indexed columns show sequential scans.
|
|
752
|
+
|
|
753
|
+
```sql
|
|
754
|
+
-- Find tables that might benefit from indexes
|
|
755
|
+
-- High seq_scan count + low or zero idx_scan = likely missing index
|
|
756
|
+
SELECT relname, seq_scan, idx_scan,
|
|
757
|
+
seq_tup_read, idx_tup_fetch,
|
|
758
|
+
n_live_tup
|
|
759
|
+
FROM pg_stat_user_tables
|
|
760
|
+
WHERE seq_scan > idx_scan
|
|
761
|
+
AND n_live_tup > 10000
|
|
762
|
+
ORDER BY seq_tup_read DESC;
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### 7.3 Bloated Tables and Indexes
|
|
766
|
+
|
|
767
|
+
**Symptom:** Table size much larger than expected for row count. Slow sequential scans
|
|
768
|
+
even with appropriate WHERE clauses.
|
|
769
|
+
|
|
770
|
+
```sql
|
|
771
|
+
-- Estimate table bloat using pgstattuple extension
|
|
772
|
+
CREATE EXTENSION IF NOT EXISTS pgstattuple;
|
|
773
|
+
SELECT * FROM pgstattuple('orders');
|
|
774
|
+
-- Look at dead_tuple_percent; > 20% indicates significant bloat
|
|
775
|
+
|
|
776
|
+
-- Estimate index bloat
|
|
777
|
+
SELECT * FROM pgstatindex('idx_orders_user_id');
|
|
778
|
+
-- Look at avg_leaf_density; < 50% indicates index bloat needing REINDEX
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Fix:**
|
|
782
|
+
- For moderate bloat: Let autovacuum catch up (tune settings per Section 5)
|
|
783
|
+
- For severe bloat: `VACUUM FULL tablename` (takes an exclusive lock) or use
|
|
784
|
+
`pg_repack` for online table compaction without locking
|
|
785
|
+
|
|
786
|
+
### 7.4 Lock Contention
|
|
787
|
+
|
|
788
|
+
**Symptom:** Queries blocked waiting for locks. Throughput drops. `wait_event_type =
|
|
789
|
+
'Lock'` in pg_stat_activity.
|
|
790
|
+
|
|
791
|
+
PostgreSQL uses 16 fast-path lock slots per backend. When these are exhausted, the
|
|
792
|
+
system falls back to the shared lock table, and throughput can drop by up to 34%.
|
|
793
|
+
|
|
794
|
+
```sql
|
|
795
|
+
-- Find blocked queries
|
|
796
|
+
SELECT blocked.pid AS blocked_pid,
|
|
797
|
+
blocked.query AS blocked_query,
|
|
798
|
+
blocking.pid AS blocking_pid,
|
|
799
|
+
blocking.query AS blocking_query,
|
|
800
|
+
now() - blocked.query_start AS blocked_duration
|
|
801
|
+
FROM pg_stat_activity blocked
|
|
802
|
+
JOIN pg_locks bl ON bl.pid = blocked.pid
|
|
803
|
+
JOIN pg_locks kl ON kl.locktype = bl.locktype
|
|
804
|
+
AND kl.database IS NOT DISTINCT FROM bl.database
|
|
805
|
+
AND kl.relation IS NOT DISTINCT FROM bl.relation
|
|
806
|
+
AND kl.page IS NOT DISTINCT FROM bl.page
|
|
807
|
+
AND kl.tuple IS NOT DISTINCT FROM bl.tuple
|
|
808
|
+
AND kl.transactionid IS NOT DISTINCT FROM bl.transactionid
|
|
809
|
+
AND kl.pid != bl.pid
|
|
810
|
+
AND kl.granted
|
|
811
|
+
JOIN pg_stat_activity blocking ON blocking.pid = kl.pid
|
|
812
|
+
WHERE NOT bl.granted;
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
**Prevention:**
|
|
816
|
+
- Keep transactions short (< 100ms for OLTP)
|
|
817
|
+
- Avoid long-running DDL during peak hours
|
|
818
|
+
- Use `CONCURRENTLY` for index creation: `CREATE INDEX CONCURRENTLY ...`
|
|
819
|
+
- Lock timeout: `SET lock_timeout = '5s';`
|
|
820
|
+
|
|
821
|
+
### 7.5 Connection Exhaustion
|
|
822
|
+
|
|
823
|
+
**Symptom:** `FATAL: too many connections for role "..."` or `FATAL: sorry, too many
|
|
824
|
+
clients already`.
|
|
825
|
+
|
|
826
|
+
Each PostgreSQL backend process consumes ~10 MB minimum. 500 connections = 5 GB of
|
|
827
|
+
memory just for backend processes, before any query execution memory.
|
|
828
|
+
|
|
829
|
+
**Fix:** Deploy PgBouncer in transaction pooling mode (see Section 4). Set
|
|
830
|
+
`max_connections` to a reasonable ceiling (100-200 for most workloads) and let the
|
|
831
|
+
pooler handle thousands of application connections.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## 8. Anti-Patterns
|
|
836
|
+
|
|
837
|
+
### 8.1 Over-Indexing
|
|
838
|
+
|
|
839
|
+
**Problem:** Every index on a table must be updated on each INSERT, UPDATE, and DELETE.
|
|
840
|
+
Five indexes on a table means 5 additional write operations per row modification.
|
|
841
|
+
|
|
842
|
+
**Measured impact:**
|
|
843
|
+
|
|
844
|
+
| Indexes on table | INSERT throughput (relative) | Storage overhead |
|
|
845
|
+
|-----------------|---------------------------|-----------------|
|
|
846
|
+
| 0 | 1.0x (baseline) | 0% |
|
|
847
|
+
| 1 | 0.85x | +15-25% |
|
|
848
|
+
| 3 | 0.65x | +40-60% |
|
|
849
|
+
| 5 | 0.50x | +70-100% |
|
|
850
|
+
| 10 | 0.30x | +150-200% |
|
|
851
|
+
|
|
852
|
+
**Detection:**
|
|
853
|
+
|
|
854
|
+
```sql
|
|
855
|
+
-- Find unused indexes (candidates for removal)
|
|
856
|
+
SELECT schemaname, relname, indexrelname,
|
|
857
|
+
idx_scan, idx_tup_read, idx_tup_fetch,
|
|
858
|
+
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
|
859
|
+
FROM pg_stat_user_indexes
|
|
860
|
+
WHERE idx_scan = 0
|
|
861
|
+
AND schemaname = 'public'
|
|
862
|
+
ORDER BY pg_relation_size(indexrelid) DESC;
|
|
863
|
+
|
|
864
|
+
-- Find duplicate/redundant indexes
|
|
865
|
+
SELECT a.indexrelid::regclass AS index_a,
|
|
866
|
+
b.indexrelid::regclass AS index_b,
|
|
867
|
+
pg_size_pretty(pg_relation_size(a.indexrelid)) AS size_a,
|
|
868
|
+
pg_size_pretty(pg_relation_size(b.indexrelid)) AS size_b
|
|
869
|
+
FROM pg_index a
|
|
870
|
+
JOIN pg_index b ON a.indrelid = b.indrelid
|
|
871
|
+
AND a.indexrelid != b.indexrelid
|
|
872
|
+
AND a.indkey::text LIKE b.indkey::text || '%'
|
|
873
|
+
WHERE a.indisvalid AND b.indisvalid;
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
**Rule of thumb:** Audit indexes monthly. Remove any index with 0 scans over 30+ days
|
|
877
|
+
(after confirming it isn't needed for unique constraints or infrequent batch jobs).
|
|
878
|
+
|
|
879
|
+
### 8.2 SELECT *
|
|
880
|
+
|
|
881
|
+
**Problem:** Fetches all columns, preventing index-only scans and transferring unnecessary
|
|
882
|
+
data over the network.
|
|
883
|
+
|
|
884
|
+
```sql
|
|
885
|
+
-- Anti-pattern: fetches all 30 columns when only 3 are needed
|
|
886
|
+
SELECT * FROM orders WHERE user_id = 42;
|
|
887
|
+
|
|
888
|
+
-- Correct: enables index-only scan if covering index exists
|
|
889
|
+
SELECT id, total_amount, status FROM orders WHERE user_id = 42;
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
**Impact:** On a table with 30 columns averaging 200 bytes per row, `SELECT *` transfers
|
|
893
|
+
6 KB per row. Selecting 3 columns transfers ~60 bytes per row -- a 100x reduction in
|
|
894
|
+
network I/O for large result sets.
|
|
895
|
+
|
|
896
|
+
### 8.3 LIKE '%pattern%' Without Trigram Index
|
|
897
|
+
|
|
898
|
+
**Problem:** Leading wildcard patterns (`LIKE '%pattern%'`) cannot use standard B-tree
|
|
899
|
+
indexes, forcing a sequential scan on every row.
|
|
900
|
+
|
|
901
|
+
```sql
|
|
902
|
+
-- Anti-pattern: always a Seq Scan without pg_trgm
|
|
903
|
+
SELECT * FROM products WHERE name LIKE '%wireless%';
|
|
904
|
+
-- Seq Scan on products: 12,000 ms on 5M rows
|
|
905
|
+
|
|
906
|
+
-- Solution: GIN trigram index
|
|
907
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
908
|
+
CREATE INDEX idx_products_name_trgm ON products USING gin (name gin_trgm_ops);
|
|
909
|
+
-- Now: Bitmap Index Scan: 3 ms on 5M rows
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
### 8.4 NOT IN vs NOT EXISTS
|
|
913
|
+
|
|
914
|
+
**Problem:** `NOT IN` has dangerous NULL semantics and often produces worse query plans.
|
|
915
|
+
|
|
916
|
+
```sql
|
|
917
|
+
-- Anti-pattern: NOT IN with a subquery
|
|
918
|
+
-- If ANY row in excluded_ids has a NULL value, the entire NOT IN returns
|
|
919
|
+
-- no rows (three-valued logic). Also produces a hashed SubPlan.
|
|
920
|
+
SELECT * FROM orders
|
|
921
|
+
WHERE user_id NOT IN (SELECT user_id FROM blocked_users);
|
|
922
|
+
|
|
923
|
+
-- Correct: NOT EXISTS handles NULLs correctly and often uses Anti Join
|
|
924
|
+
SELECT * FROM orders o
|
|
925
|
+
WHERE NOT EXISTS (
|
|
926
|
+
SELECT 1 FROM blocked_users b WHERE b.user_id = o.user_id
|
|
927
|
+
);
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
**Performance difference:** On tables with 1M orders and 10K blocked_users, NOT EXISTS
|
|
931
|
+
with an indexed join column runs in ~50ms. NOT IN can take 500ms-5s depending on the
|
|
932
|
+
subquery size because the planner may materialize and hash the entire subquery result.
|
|
933
|
+
|
|
934
|
+
### 8.5 Missing LIMIT on Exploratory Queries
|
|
935
|
+
|
|
936
|
+
```sql
|
|
937
|
+
-- Anti-pattern: fetches and transfers all 50M rows to the client
|
|
938
|
+
SELECT * FROM events WHERE event_type = 'page_view';
|
|
939
|
+
|
|
940
|
+
-- Correct: limit to needed rows
|
|
941
|
+
SELECT id, created_at FROM events
|
|
942
|
+
WHERE event_type = 'page_view'
|
|
943
|
+
ORDER BY created_at DESC
|
|
944
|
+
LIMIT 100;
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### 8.6 Implicit Type Casting in WHERE Clauses
|
|
948
|
+
|
|
949
|
+
```sql
|
|
950
|
+
-- Anti-pattern: index on user_id (integer) is not used because of text comparison
|
|
951
|
+
SELECT * FROM orders WHERE user_id = '42'; -- implicit cast; may skip index
|
|
952
|
+
|
|
953
|
+
-- Correct: match the column type
|
|
954
|
+
SELECT * FROM orders WHERE user_id = 42;
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### 8.7 Using OFFSET for Pagination on Large Tables
|
|
958
|
+
|
|
959
|
+
```sql
|
|
960
|
+
-- Anti-pattern: OFFSET 1000000 still scans and discards 1M rows
|
|
961
|
+
SELECT * FROM events ORDER BY id LIMIT 20 OFFSET 1000000;
|
|
962
|
+
-- Execution time grows linearly with OFFSET value
|
|
963
|
+
|
|
964
|
+
-- Correct: keyset/cursor pagination
|
|
965
|
+
SELECT * FROM events
|
|
966
|
+
WHERE id > 1000000 -- last seen id
|
|
967
|
+
ORDER BY id
|
|
968
|
+
LIMIT 20;
|
|
969
|
+
-- Constant execution time regardless of page depth
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
---
|
|
973
|
+
|
|
974
|
+
## 9. Monitoring
|
|
975
|
+
|
|
976
|
+
### 9.1 pg_stat_statements
|
|
977
|
+
|
|
978
|
+
The single most important extension for query performance monitoring. It normalizes
|
|
979
|
+
queries by replacing literal values with `$1`, `$2`, etc., and tracks cumulative
|
|
980
|
+
execution statistics.
|
|
981
|
+
|
|
982
|
+
**Setup:**
|
|
983
|
+
|
|
984
|
+
```sql
|
|
985
|
+
-- Add to postgresql.conf
|
|
986
|
+
-- shared_preload_libraries = 'pg_stat_statements'
|
|
987
|
+
-- pg_stat_statements.track = all
|
|
988
|
+
|
|
989
|
+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
**Top queries by total execution time:**
|
|
993
|
+
|
|
994
|
+
```sql
|
|
995
|
+
SELECT
|
|
996
|
+
substring(query, 1, 80) AS short_query,
|
|
997
|
+
calls,
|
|
998
|
+
round(total_exec_time::numeric, 2) AS total_ms,
|
|
999
|
+
round(mean_exec_time::numeric, 2) AS avg_ms,
|
|
1000
|
+
round((100.0 * total_exec_time / sum(total_exec_time) OVER ()), 2) AS pct_total,
|
|
1001
|
+
rows
|
|
1002
|
+
FROM pg_stat_statements
|
|
1003
|
+
ORDER BY total_exec_time DESC
|
|
1004
|
+
LIMIT 20;
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Key insight:** A moderately slow query (50ms) called 10,000 times per hour (total:
|
|
1008
|
+
500 seconds/hour) is a higher priority to optimize than a slow query (5s) called twice
|
|
1009
|
+
per day (total: 10 seconds/day). Always prioritize by `total_exec_time`, not `mean_exec_time`.
|
|
1010
|
+
|
|
1011
|
+
**Queries with the most I/O:**
|
|
1012
|
+
|
|
1013
|
+
```sql
|
|
1014
|
+
SELECT
|
|
1015
|
+
substring(query, 1, 80) AS short_query,
|
|
1016
|
+
calls,
|
|
1017
|
+
shared_blks_hit,
|
|
1018
|
+
shared_blks_read,
|
|
1019
|
+
round(100.0 * shared_blks_hit /
|
|
1020
|
+
NULLIF(shared_blks_hit + shared_blks_read, 0), 2) AS cache_hit_pct,
|
|
1021
|
+
temp_blks_read + temp_blks_written AS temp_blks
|
|
1022
|
+
FROM pg_stat_statements
|
|
1023
|
+
WHERE shared_blks_read > 1000
|
|
1024
|
+
ORDER BY shared_blks_read DESC
|
|
1025
|
+
LIMIT 20;
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**Target:** Cache hit ratio above 99% for OLTP workloads. Below 95% indicates
|
|
1029
|
+
shared_buffers may be too small or the working set has grown beyond available memory.
|
|
1030
|
+
|
|
1031
|
+
### 9.2 pg_stat_user_tables
|
|
1032
|
+
|
|
1033
|
+
Provides aggregate I/O and maintenance statistics per table.
|
|
1034
|
+
|
|
1035
|
+
```sql
|
|
1036
|
+
SELECT
|
|
1037
|
+
relname,
|
|
1038
|
+
seq_scan,
|
|
1039
|
+
seq_tup_read,
|
|
1040
|
+
idx_scan,
|
|
1041
|
+
idx_tup_fetch,
|
|
1042
|
+
n_live_tup,
|
|
1043
|
+
n_dead_tup,
|
|
1044
|
+
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
|
|
1045
|
+
last_autovacuum,
|
|
1046
|
+
last_autoanalyze
|
|
1047
|
+
FROM pg_stat_user_tables
|
|
1048
|
+
ORDER BY n_dead_tup DESC
|
|
1049
|
+
LIMIT 20;
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
**What to look for:**
|
|
1053
|
+
- `seq_scan` >> `idx_scan`: Missing indexes on frequently queried tables
|
|
1054
|
+
- `dead_pct` > 10%: Autovacuum cannot keep up; tune per-table settings
|
|
1055
|
+
- `last_autovacuum` is NULL or very old: Autovacuum may not be running
|
|
1056
|
+
|
|
1057
|
+
### 9.3 pg_stat_user_indexes
|
|
1058
|
+
|
|
1059
|
+
```sql
|
|
1060
|
+
-- Index usage statistics: find unused indexes
|
|
1061
|
+
SELECT
|
|
1062
|
+
schemaname || '.' || relname AS table,
|
|
1063
|
+
indexrelname AS index,
|
|
1064
|
+
idx_scan AS scans,
|
|
1065
|
+
pg_size_pretty(pg_relation_size(indexrelid)) AS size
|
|
1066
|
+
FROM pg_stat_user_indexes
|
|
1067
|
+
WHERE idx_scan < 10
|
|
1068
|
+
ORDER BY pg_relation_size(indexrelid) DESC
|
|
1069
|
+
LIMIT 20;
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### 9.4 Real-Time Activity Monitoring
|
|
1073
|
+
|
|
1074
|
+
```sql
|
|
1075
|
+
-- Active queries right now
|
|
1076
|
+
SELECT pid, now() - query_start AS duration,
|
|
1077
|
+
state, wait_event_type, wait_event,
|
|
1078
|
+
substring(query, 1, 100) AS query
|
|
1079
|
+
FROM pg_stat_activity
|
|
1080
|
+
WHERE state != 'idle'
|
|
1081
|
+
AND pid != pg_backend_pid()
|
|
1082
|
+
ORDER BY duration DESC;
|
|
1083
|
+
|
|
1084
|
+
-- Overall cache hit ratio
|
|
1085
|
+
SELECT
|
|
1086
|
+
sum(blks_hit) AS cache_hits,
|
|
1087
|
+
sum(blks_read) AS disk_reads,
|
|
1088
|
+
round(100.0 * sum(blks_hit) / NULLIF(sum(blks_hit) + sum(blks_read), 0), 2)
|
|
1089
|
+
AS cache_hit_ratio
|
|
1090
|
+
FROM pg_stat_database;
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
---
|
|
1094
|
+
|
|
1095
|
+
## 10. Before/After Query Examples
|
|
1096
|
+
|
|
1097
|
+
### Example 1: Missing Index on Foreign Key
|
|
1098
|
+
|
|
1099
|
+
**Before (Seq Scan, 2.3 seconds):**
|
|
1100
|
+
|
|
1101
|
+
```sql
|
|
1102
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1103
|
+
SELECT o.id, o.total, o.created_at
|
|
1104
|
+
FROM orders o
|
|
1105
|
+
WHERE o.user_id = 12345;
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
```
|
|
1109
|
+
Seq Scan on orders (cost=0.00..458932.00 rows=42 width=28)
|
|
1110
|
+
(actual time=1847.234..2291.561 rows=38 loops=1)
|
|
1111
|
+
Filter: (user_id = 12345)
|
|
1112
|
+
Rows Removed by Filter: 9999962
|
|
1113
|
+
Buffers: shared hit=12034 read=196498
|
|
1114
|
+
Planning Time: 0.089 ms
|
|
1115
|
+
Execution Time: 2291.612 ms
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
**After (Index Scan, 0.1 ms):**
|
|
1119
|
+
|
|
1120
|
+
```sql
|
|
1121
|
+
CREATE INDEX idx_orders_user_id ON orders (user_id);
|
|
1122
|
+
|
|
1123
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1124
|
+
SELECT o.id, o.total, o.created_at
|
|
1125
|
+
FROM orders o
|
|
1126
|
+
WHERE o.user_id = 12345;
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
Index Scan using idx_orders_user_id on orders
|
|
1131
|
+
(cost=0.43..164.50 rows=42 width=28)
|
|
1132
|
+
(actual time=0.031..0.089 rows=38 loops=1)
|
|
1133
|
+
Index Cond: (user_id = 12345)
|
|
1134
|
+
Buffers: shared hit=42
|
|
1135
|
+
Planning Time: 0.104 ms
|
|
1136
|
+
Execution Time: 0.112 ms
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
**Improvement:** 2291ms to 0.1ms (20,000x faster). Buffer reads dropped from 208,532
|
|
1140
|
+
to 42 (4,960x less I/O).
|
|
1141
|
+
|
|
1142
|
+
### Example 2: Covering Index Enabling Index-Only Scan
|
|
1143
|
+
|
|
1144
|
+
**Before (Index Scan + Heap Fetch, 45 ms):**
|
|
1145
|
+
|
|
1146
|
+
```sql
|
|
1147
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1148
|
+
SELECT status, total_amount
|
|
1149
|
+
FROM orders
|
|
1150
|
+
WHERE user_id = 42;
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
```
|
|
1154
|
+
Index Scan using idx_orders_user_id on orders
|
|
1155
|
+
(cost=0.43..1250.33 rows=310 width=18)
|
|
1156
|
+
(actual time=0.028..44.912 rows=305 loops=1)
|
|
1157
|
+
Index Cond: (user_id = 42)
|
|
1158
|
+
Buffers: shared hit=285 read=127
|
|
1159
|
+
Planning Time: 0.095 ms
|
|
1160
|
+
Execution Time: 45.001 ms
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
**After (Index-Only Scan, 0.3 ms):**
|
|
1164
|
+
|
|
1165
|
+
```sql
|
|
1166
|
+
CREATE INDEX idx_orders_user_covering ON orders (user_id)
|
|
1167
|
+
INCLUDE (status, total_amount);
|
|
1168
|
+
VACUUM orders; -- update visibility map
|
|
1169
|
+
|
|
1170
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1171
|
+
SELECT status, total_amount
|
|
1172
|
+
FROM orders
|
|
1173
|
+
WHERE user_id = 42;
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
```
|
|
1177
|
+
Index Only Scan using idx_orders_user_covering on orders
|
|
1178
|
+
(cost=0.43..12.80 rows=310 width=18)
|
|
1179
|
+
(actual time=0.024..0.298 rows=305 loops=1)
|
|
1180
|
+
Index Cond: (user_id = 42)
|
|
1181
|
+
Heap Fetches: 0
|
|
1182
|
+
Buffers: shared hit=5
|
|
1183
|
+
Planning Time: 0.088 ms
|
|
1184
|
+
Execution Time: 0.341 ms
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
**Improvement:** 45ms to 0.3ms (150x faster). Buffer accesses dropped from 412 to 5
|
|
1188
|
+
(82x less I/O). Zero heap fetches because all data comes from the index.
|
|
1189
|
+
|
|
1190
|
+
### Example 3: Trigram Index for LIKE '%pattern%'
|
|
1191
|
+
|
|
1192
|
+
**Before (Seq Scan, 8.4 seconds):**
|
|
1193
|
+
|
|
1194
|
+
```sql
|
|
1195
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1196
|
+
SELECT id, name, email
|
|
1197
|
+
FROM customers
|
|
1198
|
+
WHERE name ILIKE '%garcia%';
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
```
|
|
1202
|
+
Seq Scan on customers (cost=0.00..385421.00 rows=523 width=68)
|
|
1203
|
+
(actual time=234.112..8412.445 rows=487 loops=1)
|
|
1204
|
+
Filter: (name ~~* '%garcia%')
|
|
1205
|
+
Rows Removed by Filter: 4999513
|
|
1206
|
+
Buffers: shared hit=8432 read=148221
|
|
1207
|
+
Planning Time: 0.067 ms
|
|
1208
|
+
Execution Time: 8412.534 ms
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
**After (Bitmap Index Scan, 2.8 ms):**
|
|
1212
|
+
|
|
1213
|
+
```sql
|
|
1214
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
1215
|
+
CREATE INDEX idx_customers_name_trgm ON customers USING gin (name gin_trgm_ops);
|
|
1216
|
+
|
|
1217
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1218
|
+
SELECT id, name, email
|
|
1219
|
+
FROM customers
|
|
1220
|
+
WHERE name ILIKE '%garcia%';
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
```
|
|
1224
|
+
Bitmap Heap Scan on customers (cost=52.08..2104.33 rows=523 width=68)
|
|
1225
|
+
(actual time=1.245..2.801 rows=487 loops=1)
|
|
1226
|
+
Recheck Cond: (name ~~* '%garcia%')
|
|
1227
|
+
Rows Removed by Index Recheck: 12
|
|
1228
|
+
Heap Blocks: exact=478
|
|
1229
|
+
Buffers: shared hit=492
|
|
1230
|
+
-> Bitmap Index Scan on idx_customers_name_trgm
|
|
1231
|
+
(cost=0.00..51.95 rows=523 width=0)
|
|
1232
|
+
(actual time=1.102..1.103 rows=499 loops=1)
|
|
1233
|
+
Buffers: shared hit=14
|
|
1234
|
+
Planning Time: 0.234 ms
|
|
1235
|
+
Execution Time: 2.856 ms
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
**Improvement:** 8412ms to 2.8ms (3,000x faster). Buffer accesses dropped from 156,653
|
|
1239
|
+
to 492 (318x less I/O).
|
|
1240
|
+
|
|
1241
|
+
### Example 4: Partition Pruning on Time-Series Table
|
|
1242
|
+
|
|
1243
|
+
**Before (Scan all data, 3.2 seconds):**
|
|
1244
|
+
|
|
1245
|
+
```sql
|
|
1246
|
+
-- Unpartitioned 500M row table
|
|
1247
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1248
|
+
SELECT count(*), event_type
|
|
1249
|
+
FROM events
|
|
1250
|
+
WHERE created_at BETWEEN '2025-01-15' AND '2025-01-20'
|
|
1251
|
+
GROUP BY event_type;
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
```
|
|
1255
|
+
HashAggregate (cost=12458932.00..12458942.00 rows=10 width=40)
|
|
1256
|
+
(actual time=3201.445..3201.501 rows=8 loops=1)
|
|
1257
|
+
Group Key: event_type
|
|
1258
|
+
Buffers: shared hit=45023 read=1245889
|
|
1259
|
+
-> Seq Scan on events (cost=0.00..12358932.00 rows=20000000 width=32)
|
|
1260
|
+
(actual time=0.045..2845.112 rows=2741823 loops=1)
|
|
1261
|
+
Filter: (created_at >= '...' AND created_at <= '...')
|
|
1262
|
+
Rows Removed by Filter: 497258177
|
|
1263
|
+
Buffers: shared hit=45023 read=1245889
|
|
1264
|
+
Planning Time: 0.112 ms
|
|
1265
|
+
Execution Time: 3201.589 ms
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
**After (Partition pruning, 285 ms):**
|
|
1269
|
+
|
|
1270
|
+
```sql
|
|
1271
|
+
-- Same query on partitioned table (monthly partitions)
|
|
1272
|
+
EXPLAIN (ANALYZE, BUFFERS)
|
|
1273
|
+
SELECT count(*), event_type
|
|
1274
|
+
FROM events
|
|
1275
|
+
WHERE created_at BETWEEN '2025-01-15' AND '2025-01-20'
|
|
1276
|
+
GROUP BY event_type;
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
```
|
|
1280
|
+
HashAggregate (cost=548932.00..548942.00 rows=10 width=40)
|
|
1281
|
+
(actual time=285.112..285.168 rows=8 loops=1)
|
|
1282
|
+
Group Key: event_type
|
|
1283
|
+
Buffers: shared hit=42018 read=52344
|
|
1284
|
+
-> Append (cost=0.43..498932.00 rows=2741823 width=32)
|
|
1285
|
+
(actual time=0.034..198.445 rows=2741823 loops=1)
|
|
1286
|
+
Subplans Removed: 23 <-- 23 of 24 partitions pruned!
|
|
1287
|
+
-> Index Scan using events_2025_01_created_at_idx on events_2025_01
|
|
1288
|
+
(cost=0.43..498932.00 rows=2741823 width=32)
|
|
1289
|
+
(actual time=0.033..198.401 rows=2741823 loops=1)
|
|
1290
|
+
Index Cond: (created_at >= '...' AND created_at <= '...')
|
|
1291
|
+
Buffers: shared hit=42018 read=52344
|
|
1292
|
+
Planning Time: 1.245 ms
|
|
1293
|
+
Execution Time: 285.234 ms
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
**Improvement:** 3201ms to 285ms (11x faster). The planner pruned 23 of 24 partitions,
|
|
1297
|
+
scanning only January data. Buffer reads dropped from 1,290,912 to 94,362 (13x less I/O).
|
|
1298
|
+
|
|
1299
|
+
---
|
|
1300
|
+
|
|
1301
|
+
## 11. Quick Reference Checklist
|
|
1302
|
+
|
|
1303
|
+
### Initial Setup (Day 1)
|
|
1304
|
+
|
|
1305
|
+
- [ ] Set `shared_buffers` to 25-33% of RAM
|
|
1306
|
+
- [ ] Set `effective_cache_size` to 75% of RAM
|
|
1307
|
+
- [ ] Set `work_mem` using formula: `(RAM - shared_buffers) / (16 * CPU cores)`
|
|
1308
|
+
- [ ] Set `random_page_cost` to 1.1 for NVMe, 1.5 for SSD, 4.0 for HDD
|
|
1309
|
+
- [ ] Set `max_wal_size` to 2-4 GB
|
|
1310
|
+
- [ ] Set `checkpoint_completion_target` to 0.9
|
|
1311
|
+
- [ ] Enable `pg_stat_statements` in shared_preload_libraries
|
|
1312
|
+
- [ ] Deploy PgBouncer in transaction pooling mode
|
|
1313
|
+
- [ ] Set `idle_in_transaction_session_timeout` to 5 minutes
|
|
1314
|
+
|
|
1315
|
+
### Weekly Audit
|
|
1316
|
+
|
|
1317
|
+
- [ ] Review top 20 queries by total_exec_time in pg_stat_statements
|
|
1318
|
+
- [ ] Check for tables with > 10% dead tuples in pg_stat_user_tables
|
|
1319
|
+
- [ ] Identify unused indexes (idx_scan = 0) for potential removal
|
|
1320
|
+
- [ ] Check cache hit ratio (target > 99% for OLTP)
|
|
1321
|
+
- [ ] Review sequential scan counts on large tables
|
|
1322
|
+
|
|
1323
|
+
### Monthly Audit
|
|
1324
|
+
|
|
1325
|
+
- [ ] Run REINDEX CONCURRENTLY on indexes with > 30% bloat
|
|
1326
|
+
- [ ] Review and remove confirmed unused indexes
|
|
1327
|
+
- [ ] Check for duplicate/redundant indexes
|
|
1328
|
+
- [ ] Evaluate partitioning for tables that grew past 50 GB
|
|
1329
|
+
- [ ] Reset pg_stat_statements: `SELECT pg_stat_statements_reset();`
|
|
1330
|
+
- [ ] Review and tune per-table autovacuum settings for high-churn tables
|
|
1331
|
+
|
|
1332
|
+
---
|
|
1333
|
+
|
|
1334
|
+
## 12. Sources
|
|
1335
|
+
|
|
1336
|
+
- [PostgreSQL Official Documentation: Resource Consumption](https://www.postgresql.org/docs/current/runtime-config-resource.html)
|
|
1337
|
+
- [PostgreSQL Wiki: Tuning Your PostgreSQL Server](https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server)
|
|
1338
|
+
- [PostgreSQL Performance Tuning Best Practices 2025 - Mydbops](https://www.mydbops.com/blog/postgresql-parameter-tuning-best-practices)
|
|
1339
|
+
- [Crunchy Data: Optimize PostgreSQL Server Performance](https://www.crunchydata.com/blog/optimize-postgresql-server-performance)
|
|
1340
|
+
- [EDB: How to Tune PostgreSQL Memory](https://www.enterprisedb.com/postgres-tutorials/how-tune-postgresql-memory)
|
|
1341
|
+
- [OneUpTime: PostgreSQL shared_buffers and work_mem Tuning](https://oneuptime.com/blog/post/2026-01-25-postgresql-shared-buffers-work-mem-tuning/view)
|
|
1342
|
+
- [DBA Dataverse: PostgreSQL Configuration Parameters](https://dbadataverse.com/tech/postgresql/2025/02/postgresql-configuration-parameters-best-practices-for-performance-tuning)
|
|
1343
|
+
- [PostgreSQL Official Documentation: Index Types](https://www.postgresql.org/docs/current/indexes-types.html)
|
|
1344
|
+
- [Mydbops: PostgreSQL Index Best Practices](https://www.mydbops.com/blog/postgresql-indexing-best-practices-guide)
|
|
1345
|
+
- [Percona: A Practical Guide to PostgreSQL Indexes](https://www.percona.com/blog/a-practical-guide-to-postgresql-indexes/)
|
|
1346
|
+
- [Percona: How PostgreSQL Indexes Can Negatively Impact Performance](https://www.percona.com/blog/postgresql-indexes-can-hurt-you-negative-effects-and-the-costs-involved/)
|
|
1347
|
+
- [pganalyze: EXPLAIN ANALYZE BUFFERS and Nested Loops](https://pganalyze.com/blog/5mins-explain-analyze-buffers-nested-loops)
|
|
1348
|
+
- [PostgresAI: EXPLAIN ANALYZE Needs BUFFERS](https://postgres.ai/blog/20220106-explain-analyze-needs-buffers-to-improve-the-postgres-query-optimization-process)
|
|
1349
|
+
- [pgMustard: Using BUFFERS for Query Optimization](https://www.pgmustard.com/blog/using-postgres-buffers-for-query-optimization)
|
|
1350
|
+
- [Neon: PostgreSQL 18 Enhanced EXPLAIN with Automatic Buffers](https://neon.com/postgresql/postgresql-18/enhanced-explain)
|
|
1351
|
+
- [Onidel: PgBouncer vs Pgcat vs Odyssey on VPS in 2025](https://onidel.com/blog/postgresql-proxy-comparison-2025)
|
|
1352
|
+
- [Tembo: Benchmarking PostgreSQL Connection Poolers](https://legacy.tembo.io/blog/postgres-connection-poolers/)
|
|
1353
|
+
- [Percona: PgBouncer for PostgreSQL](https://www.percona.com/blog/pgbouncer-for-postgresql-how-connection-pooling-solves-enterprise-slowdowns/)
|
|
1354
|
+
- [pganalyze: PgCat vs PgBouncer](https://pganalyze.com/blog/5mins-postgres-pgcat-vs-pgbouncer)
|
|
1355
|
+
- [AWS: Understanding Autovacuum in RDS for PostgreSQL](https://aws.amazon.com/blogs/database/understanding-autovacuum-in-amazon-rds-for-postgresql-environments/)
|
|
1356
|
+
- [EDB: Autovacuum Tuning Basics](https://www.enterprisedb.com/blog/autovacuum-tuning-basics)
|
|
1357
|
+
- [Cybertec: Tuning Autovacuum for PostgreSQL](https://www.cybertec-postgresql.com/en/tuning-autovacuum-postgresql/)
|
|
1358
|
+
- [PostgreSQL Official Documentation: Table Partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html)
|
|
1359
|
+
- [EDB: How to Use Table Partitioning to Scale PostgreSQL](https://www.enterprisedb.com/postgres-tutorials/how-use-table-partitioning-scale-postgresql)
|
|
1360
|
+
- [AWS: Diagnose and Mitigate Lock Manager Contention](https://aws.amazon.com/blogs/database/improve-postgresql-performance-diagnose-and-mitigate-lock-manager-contention/)
|
|
1361
|
+
- [PostgreSQL Wiki: Number of Database Connections](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections)
|
|
1362
|
+
- [Last9: PostgreSQL Performance Tuning](https://last9.io/blog/postgresql-performance/)
|
|
1363
|
+
- [Supabase: pg_stat_statements Documentation](https://supabase.com/docs/guides/database/extensions/pg_stat_statements)
|
|
1364
|
+
- [Tiger Data: Identify Performance Bottlenecks with pg_stat_statements](https://www.tigerdata.com/blog/using-pg-stat-statements-to-optimize-queries)
|
|
1365
|
+
- [PostgresAI: Why Keep Your Index Set Lean](https://postgres.ai/blog/20251110-postgres-marathon-2-013-why-keep-your-index-set-lean)
|
|
1366
|
+
- [QuerySharp: How to Optimize LIKE Queries in PostgreSQL](https://querysharp.com/blog/how-to-optimize-like-queries-postgresql)
|