cfsa-antigravity 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/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: redis
|
|
3
|
+
description: "Expert Redis development guide covering data structures (strings, hashes, lists, sets, sorted sets, streams, JSON), caching patterns (cache-aside, write-through, write-behind), pub/sub, Lua scripting, transactions (MULTI/EXEC), pipelining, Redis Stack (Search, JSON, TimeSeries, Bloom), key naming, TTL strategies, memory management, eviction policies, Redis Cluster, Sentinel, client libraries (ioredis, redis-py), and rate limiting."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Redis Expert
|
|
8
|
+
|
|
9
|
+
## 1. Data Structures
|
|
10
|
+
|
|
11
|
+
### Strings
|
|
12
|
+
|
|
13
|
+
The most basic type. Stores text, integers, floats, or binary data up to 512 MB.
|
|
14
|
+
|
|
15
|
+
```redis
|
|
16
|
+
# Basic key-value
|
|
17
|
+
SET user:1001:name "Alice Johnson"
|
|
18
|
+
GET user:1001:name
|
|
19
|
+
|
|
20
|
+
# With expiration
|
|
21
|
+
SET session:abc123 "{\"userId\":\"1001\"}" EX 3600 # Expires in 1 hour
|
|
22
|
+
SET session:abc123 "{\"userId\":\"1001\"}" PX 60000 # Expires in 60 seconds (ms)
|
|
23
|
+
|
|
24
|
+
# Set only if key does not exist (distributed lock primitive)
|
|
25
|
+
SET lock:order:5001 "worker-1" NX EX 30 # Acquire lock for 30 seconds
|
|
26
|
+
|
|
27
|
+
# Set only if key exists (update, not create)
|
|
28
|
+
SET user:1001:name "Alice Smith" XX
|
|
29
|
+
|
|
30
|
+
# Atomic counters
|
|
31
|
+
INCR page:views:home # Increment by 1
|
|
32
|
+
INCRBY user:1001:score 50 # Increment by 50
|
|
33
|
+
DECR inventory:product:301 # Decrement by 1
|
|
34
|
+
INCRBYFLOAT account:1001:balance 19.99
|
|
35
|
+
|
|
36
|
+
# Bit operations (compact boolean flags)
|
|
37
|
+
SETBIT user:1001:features 0 1 # Feature 0 enabled
|
|
38
|
+
SETBIT user:1001:features 1 0 # Feature 1 disabled
|
|
39
|
+
GETBIT user:1001:features 0 # Returns 1
|
|
40
|
+
BITCOUNT user:1001:features # Count enabled features
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Hashes
|
|
44
|
+
|
|
45
|
+
Field-value maps. Ideal for representing objects. More memory-efficient than separate string keys.
|
|
46
|
+
|
|
47
|
+
```redis
|
|
48
|
+
# Set fields
|
|
49
|
+
HSET user:1001 name "Alice" email "alice@example.com" age 28 active 1
|
|
50
|
+
HMSET user:1001 city "Portland" country "US"
|
|
51
|
+
|
|
52
|
+
# Get fields
|
|
53
|
+
HGET user:1001 name # "Alice"
|
|
54
|
+
HMGET user:1001 name email age # ["Alice", "alice@example.com", "28"]
|
|
55
|
+
HGETALL user:1001 # All fields and values
|
|
56
|
+
|
|
57
|
+
# Atomic field operations
|
|
58
|
+
HINCRBY user:1001 login_count 1
|
|
59
|
+
HINCRBYFLOAT user:1001 balance 19.99
|
|
60
|
+
|
|
61
|
+
# Check existence
|
|
62
|
+
HEXISTS user:1001 email # 1 (exists)
|
|
63
|
+
HLEN user:1001 # Number of fields
|
|
64
|
+
|
|
65
|
+
# Delete fields
|
|
66
|
+
HDEL user:1001 temporary_field
|
|
67
|
+
|
|
68
|
+
# Get only keys or values
|
|
69
|
+
HKEYS user:1001
|
|
70
|
+
HVALS user:1001
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Lists
|
|
74
|
+
|
|
75
|
+
Ordered sequences. Support push/pop from both ends. Good for queues, activity feeds, and recent items.
|
|
76
|
+
|
|
77
|
+
```redis
|
|
78
|
+
# Push items
|
|
79
|
+
LPUSH queue:emails "{\"to\":\"alice@example.com\",\"subject\":\"Welcome\"}"
|
|
80
|
+
RPUSH activity:user:1001 "logged_in" "viewed_product" "added_to_cart"
|
|
81
|
+
|
|
82
|
+
# Pop items (queue pattern: LPUSH + RPOP or RPUSH + LPOP)
|
|
83
|
+
RPOP queue:emails # Pop from right (FIFO with LPUSH)
|
|
84
|
+
LPOP queue:emails # Pop from left
|
|
85
|
+
|
|
86
|
+
# Blocking pop (waits for data, great for worker queues)
|
|
87
|
+
BRPOP queue:emails 30 # Block up to 30 seconds
|
|
88
|
+
BLPOP queue:high queue:low 10 # Priority queues: check high first
|
|
89
|
+
|
|
90
|
+
# Range queries
|
|
91
|
+
LRANGE activity:user:1001 0 9 # Last 10 items
|
|
92
|
+
LRANGE activity:user:1001 0 -1 # All items
|
|
93
|
+
|
|
94
|
+
# Trim (keep only recent items)
|
|
95
|
+
LTRIM activity:user:1001 0 99 # Keep only the 100 most recent
|
|
96
|
+
|
|
97
|
+
# Length
|
|
98
|
+
LLEN queue:emails
|
|
99
|
+
|
|
100
|
+
# Move between lists (atomic)
|
|
101
|
+
LMOVE queue:processing queue:completed LEFT RIGHT
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Sets
|
|
105
|
+
|
|
106
|
+
Unordered collections of unique strings. Ideal for tags, unique visitors, membership tracking.
|
|
107
|
+
|
|
108
|
+
```redis
|
|
109
|
+
# Add members
|
|
110
|
+
SADD tags:product:301 "electronics" "sale" "featured"
|
|
111
|
+
SADD online:users "user:1001" "user:1002" "user:1003"
|
|
112
|
+
|
|
113
|
+
# Check membership
|
|
114
|
+
SISMEMBER online:users "user:1001" # 1 (member)
|
|
115
|
+
SISMEMBER online:users "user:9999" # 0 (not a member)
|
|
116
|
+
|
|
117
|
+
# Get all members
|
|
118
|
+
SMEMBERS tags:product:301
|
|
119
|
+
|
|
120
|
+
# Set operations
|
|
121
|
+
SADD interests:alice "music" "movies" "coding" "hiking"
|
|
122
|
+
SADD interests:bob "movies" "gaming" "coding" "cooking"
|
|
123
|
+
|
|
124
|
+
SINTER interests:alice interests:bob # {"movies", "coding"}
|
|
125
|
+
SUNION interests:alice interests:bob # All unique interests
|
|
126
|
+
SDIFF interests:alice interests:bob # {"music", "hiking"} (in alice, not in bob)
|
|
127
|
+
|
|
128
|
+
# Random members
|
|
129
|
+
SRANDMEMBER tags:product:301 2 # 2 random tags
|
|
130
|
+
SPOP online:users # Remove and return random member
|
|
131
|
+
|
|
132
|
+
# Cardinality
|
|
133
|
+
SCARD online:users # Count of members
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Sorted Sets
|
|
137
|
+
|
|
138
|
+
Sets with a score for each member. Sorted by score. Perfect for leaderboards, ranking, time-based data.
|
|
139
|
+
|
|
140
|
+
```redis
|
|
141
|
+
# Add members with scores
|
|
142
|
+
ZADD leaderboard 1500 "alice" 1200 "bob" 1800 "carol" 900 "dave"
|
|
143
|
+
|
|
144
|
+
# Get by rank (ascending score)
|
|
145
|
+
ZRANGE leaderboard 0 9 # Top 10 (lowest scores)
|
|
146
|
+
ZRANGE leaderboard 0 9 REV # Top 10 (highest scores)
|
|
147
|
+
ZRANGE leaderboard 0 9 REV WITHSCORES # With scores
|
|
148
|
+
|
|
149
|
+
# Get by score range
|
|
150
|
+
ZRANGEBYSCORE leaderboard 1000 2000 # Scores between 1000 and 2000
|
|
151
|
+
ZRANGEBYSCORE leaderboard "-inf" "+inf" # All members
|
|
152
|
+
|
|
153
|
+
# Rank of a member
|
|
154
|
+
ZRANK leaderboard "alice" # Rank (0-indexed, ascending)
|
|
155
|
+
ZREVRANK leaderboard "alice" # Rank (0-indexed, descending)
|
|
156
|
+
|
|
157
|
+
# Score of a member
|
|
158
|
+
ZSCORE leaderboard "alice" # 1500
|
|
159
|
+
|
|
160
|
+
# Increment score
|
|
161
|
+
ZINCRBY leaderboard 100 "alice" # Alice now has 1600
|
|
162
|
+
|
|
163
|
+
# Count members in score range
|
|
164
|
+
ZCOUNT leaderboard 1000 2000
|
|
165
|
+
|
|
166
|
+
# Remove members
|
|
167
|
+
ZREM leaderboard "dave"
|
|
168
|
+
ZREMRANGEBYSCORE leaderboard "-inf" 500 # Remove low scorers
|
|
169
|
+
ZREMRANGEBYRANK leaderboard 0 -11 # Keep only top 10
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Streams
|
|
173
|
+
|
|
174
|
+
Append-only log data structure. Best for event sourcing, message queues, and real-time feeds.
|
|
175
|
+
|
|
176
|
+
```redis
|
|
177
|
+
# Add entries (auto-generated ID with *)
|
|
178
|
+
XADD events:orders * action "created" orderId "5001" userId "1001" total "59.99"
|
|
179
|
+
XADD events:orders * action "paid" orderId "5001" paymentMethod "card"
|
|
180
|
+
|
|
181
|
+
# Read entries
|
|
182
|
+
XRANGE events:orders - + # All entries
|
|
183
|
+
XRANGE events:orders - + COUNT 10 # First 10
|
|
184
|
+
XRANGE events:orders 1700000000000-0 + # From timestamp
|
|
185
|
+
|
|
186
|
+
# Read new entries (blocking, consumer pattern)
|
|
187
|
+
XREAD COUNT 10 BLOCK 5000 STREAMS events:orders $ # Wait for new entries
|
|
188
|
+
|
|
189
|
+
# Consumer groups (parallel processing with acknowledgment)
|
|
190
|
+
XGROUP CREATE events:orders order-processors $ MKSTREAM
|
|
191
|
+
|
|
192
|
+
# Read as consumer in group
|
|
193
|
+
XREADGROUP GROUP order-processors worker-1 COUNT 1 BLOCK 5000 STREAMS events:orders >
|
|
194
|
+
|
|
195
|
+
# Acknowledge processed entry
|
|
196
|
+
XACK events:orders order-processors 1700000000001-0
|
|
197
|
+
|
|
198
|
+
# Check pending entries (not yet acknowledged)
|
|
199
|
+
XPENDING events:orders order-processors - + 10
|
|
200
|
+
|
|
201
|
+
# Claim stuck entries (when a worker dies)
|
|
202
|
+
XCLAIM events:orders order-processors worker-2 60000 1700000000001-0
|
|
203
|
+
|
|
204
|
+
# Trim stream length
|
|
205
|
+
XTRIM events:orders MAXLEN ~ 10000 # Keep approximately 10000 entries
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 2. Caching Patterns
|
|
211
|
+
|
|
212
|
+
### Cache-Aside (Lazy Loading)
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import Redis from "ioredis";
|
|
216
|
+
|
|
217
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
218
|
+
|
|
219
|
+
async function getUser(userId: string): Promise<User> {
|
|
220
|
+
const cacheKey = `user:${userId}`;
|
|
221
|
+
|
|
222
|
+
// 1. Check cache
|
|
223
|
+
const cached = await redis.get(cacheKey);
|
|
224
|
+
if (cached !== null) {
|
|
225
|
+
return JSON.parse(cached) as User;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2. Cache miss: fetch from database
|
|
229
|
+
const user = await db.users.findById(userId);
|
|
230
|
+
if (!user) {
|
|
231
|
+
throw new Error("User not found");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 3. Populate cache with TTL
|
|
235
|
+
await redis.set(cacheKey, JSON.stringify(user), "EX", 3600);
|
|
236
|
+
|
|
237
|
+
return user;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Invalidate on write
|
|
241
|
+
async function updateUser(userId: string, data: Partial<User>): Promise<User> {
|
|
242
|
+
const user = await db.users.updateById(userId, data);
|
|
243
|
+
await redis.del(`user:${userId}`); // Invalidate cache
|
|
244
|
+
return user;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Write-Through
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// Write to cache AND database on every write
|
|
252
|
+
async function saveProduct(product: Product): Promise<void> {
|
|
253
|
+
// Write to database
|
|
254
|
+
await db.products.upsert(product);
|
|
255
|
+
|
|
256
|
+
// Write to cache (cache is always fresh)
|
|
257
|
+
await redis.set(
|
|
258
|
+
`product:${product.id}`,
|
|
259
|
+
JSON.stringify(product),
|
|
260
|
+
"EX",
|
|
261
|
+
7200
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Write-Behind (Write-Back)
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Write to cache immediately, flush to database asynchronously
|
|
270
|
+
async function recordPageView(pageId: string): Promise<void> {
|
|
271
|
+
// Increment in Redis (fast)
|
|
272
|
+
await redis.hincrby(`pageviews:pending`, pageId, 1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Periodic flush to database (run every 30 seconds)
|
|
276
|
+
async function flushPageViews(): Promise<void> {
|
|
277
|
+
const pending = await redis.hgetall("pageviews:pending");
|
|
278
|
+
if (Object.keys(pending).length === 0) return;
|
|
279
|
+
|
|
280
|
+
// Batch update database
|
|
281
|
+
const updates = Object.entries(pending).map(([pageId, count]) =>
|
|
282
|
+
db.pages.increment(pageId, "views", parseInt(count, 10))
|
|
283
|
+
);
|
|
284
|
+
await Promise.all(updates);
|
|
285
|
+
|
|
286
|
+
// Clear pending counts
|
|
287
|
+
await redis.del("pageviews:pending");
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Cache Stampede Prevention
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Problem: many requests arrive simultaneously for an expired key
|
|
295
|
+
// All of them miss cache and hit the database at once
|
|
296
|
+
|
|
297
|
+
// Solution 1: Locking (only one request rebuilds cache)
|
|
298
|
+
async function getWithLock(key: string, fetchFn: () => Promise<string>, ttl: number): Promise<string> {
|
|
299
|
+
const cached = await redis.get(key);
|
|
300
|
+
if (cached !== null) return cached;
|
|
301
|
+
|
|
302
|
+
const lockKey = `lock:${key}`;
|
|
303
|
+
const acquired = await redis.set(lockKey, "1", "NX", "EX", 10);
|
|
304
|
+
|
|
305
|
+
if (acquired) {
|
|
306
|
+
try {
|
|
307
|
+
const value = await fetchFn();
|
|
308
|
+
await redis.set(key, value, "EX", ttl);
|
|
309
|
+
return value;
|
|
310
|
+
} finally {
|
|
311
|
+
await redis.del(lockKey);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Wait briefly and retry
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
317
|
+
return getWithLock(key, fetchFn, ttl);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Solution 2: Stale-while-revalidate (serve stale, refresh in background)
|
|
321
|
+
async function getStaleWhileRevalidate(key: string, fetchFn: () => Promise<string>, ttl: number, staleTTL: number): Promise<string | null> {
|
|
322
|
+
const cached = await redis.get(key);
|
|
323
|
+
const staleMarker = await redis.get(`stale:${key}`);
|
|
324
|
+
|
|
325
|
+
if (cached !== null) {
|
|
326
|
+
if (staleMarker === null) {
|
|
327
|
+
// Data is stale, refresh in background
|
|
328
|
+
fetchFn().then(async (value) => {
|
|
329
|
+
await redis.set(key, value, "EX", ttl + staleTTL);
|
|
330
|
+
await redis.set(`stale:${key}`, "1", "EX", ttl);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return cached;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Full cache miss
|
|
337
|
+
const value = await fetchFn();
|
|
338
|
+
await redis.set(key, value, "EX", ttl + staleTTL);
|
|
339
|
+
await redis.set(`stale:${key}`, "1", "EX", ttl);
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 3. Pub/Sub
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import Redis from "ioredis";
|
|
350
|
+
|
|
351
|
+
const publisher = new Redis(process.env.REDIS_URL);
|
|
352
|
+
const subscriber = new Redis(process.env.REDIS_URL);
|
|
353
|
+
|
|
354
|
+
// Subscribe to channels
|
|
355
|
+
subscriber.subscribe("orders:created", "orders:updated", (err, count) => {
|
|
356
|
+
if (err) throw err;
|
|
357
|
+
console.log(`Subscribed to ${count} channels`);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Handle messages
|
|
361
|
+
subscriber.on("message", (channel, message) => {
|
|
362
|
+
const data = JSON.parse(message);
|
|
363
|
+
switch (channel) {
|
|
364
|
+
case "orders:created":
|
|
365
|
+
handleNewOrder(data);
|
|
366
|
+
break;
|
|
367
|
+
case "orders:updated":
|
|
368
|
+
handleOrderUpdate(data);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Pattern subscribe (wildcard)
|
|
374
|
+
subscriber.psubscribe("events:*", (err) => {
|
|
375
|
+
if (err) throw err;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
subscriber.on("pmessage", (pattern, channel, message) => {
|
|
379
|
+
console.log(`Pattern ${pattern}, channel ${channel}: ${message}`);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Publish
|
|
383
|
+
await publisher.publish("orders:created", JSON.stringify({
|
|
384
|
+
orderId: "5001",
|
|
385
|
+
userId: "1001",
|
|
386
|
+
total: 59.99,
|
|
387
|
+
}));
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Pub/Sub Limitations
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
- Messages are fire-and-forget: if no subscriber is listening, the message is lost
|
|
394
|
+
- No message persistence or replay
|
|
395
|
+
- No consumer groups (use Streams for this)
|
|
396
|
+
- Subscriber connections cannot run other commands
|
|
397
|
+
- All subscribers get all messages (no load balancing)
|
|
398
|
+
|
|
399
|
+
Use Streams instead when you need:
|
|
400
|
+
- Message durability
|
|
401
|
+
- Consumer groups (parallel processing)
|
|
402
|
+
- Message acknowledgment
|
|
403
|
+
- Replay from a specific point
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 4. Lua Scripting
|
|
409
|
+
|
|
410
|
+
Lua scripts execute atomically on the Redis server. No other command runs during script execution.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// Rate limiter using Lua (atomic check-and-increment)
|
|
414
|
+
const rateLimitScript = `
|
|
415
|
+
local key = KEYS[1]
|
|
416
|
+
local limit = tonumber(ARGV[1])
|
|
417
|
+
local window = tonumber(ARGV[2])
|
|
418
|
+
|
|
419
|
+
local current = redis.call('GET', key)
|
|
420
|
+
if current and tonumber(current) >= limit then
|
|
421
|
+
return 0
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
current = redis.call('INCR', key)
|
|
425
|
+
if tonumber(current) == 1 then
|
|
426
|
+
redis.call('EXPIRE', key, window)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
return 1
|
|
430
|
+
`;
|
|
431
|
+
|
|
432
|
+
// Usage
|
|
433
|
+
const allowed = await redis.eval(
|
|
434
|
+
rateLimitScript,
|
|
435
|
+
1, // Number of KEYS
|
|
436
|
+
`ratelimit:${userId}`, // KEYS[1]
|
|
437
|
+
"100", // ARGV[1]: max requests
|
|
438
|
+
"60" // ARGV[2]: window in seconds
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
if (!allowed) {
|
|
442
|
+
throw new Error("Rate limit exceeded");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Sliding window rate limiter with sorted sets
|
|
446
|
+
const slidingWindowScript = `
|
|
447
|
+
local key = KEYS[1]
|
|
448
|
+
local now = tonumber(ARGV[1])
|
|
449
|
+
local window = tonumber(ARGV[2])
|
|
450
|
+
local limit = tonumber(ARGV[3])
|
|
451
|
+
local member = ARGV[4]
|
|
452
|
+
|
|
453
|
+
local windowStart = now - window
|
|
454
|
+
redis.call('ZREMRANGEBYSCORE', key, '-inf', windowStart)
|
|
455
|
+
|
|
456
|
+
local count = redis.call('ZCARD', key)
|
|
457
|
+
if count >= limit then
|
|
458
|
+
return 0
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
redis.call('ZADD', key, now, member)
|
|
462
|
+
redis.call('EXPIRE', key, window)
|
|
463
|
+
return 1
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
// Load script for repeated use (avoids sending full script each time)
|
|
467
|
+
const sha = await redis.script("LOAD", rateLimitScript);
|
|
468
|
+
const result = await redis.evalsha(sha, 1, `ratelimit:${userId}`, "100", "60");
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 5. Transactions (MULTI/EXEC)
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
// MULTI/EXEC: all commands execute atomically
|
|
477
|
+
const pipeline = redis.multi();
|
|
478
|
+
pipeline.set("account:1001:balance", "500");
|
|
479
|
+
pipeline.set("account:1002:balance", "300");
|
|
480
|
+
pipeline.incrby("account:1001:balance", -100);
|
|
481
|
+
pipeline.incrby("account:1002:balance", 100);
|
|
482
|
+
const results = await pipeline.exec();
|
|
483
|
+
// results: [[null, "OK"], [null, "OK"], [null, 400], [null, 400]]
|
|
484
|
+
|
|
485
|
+
// WATCH for optimistic locking (CAS -- check-and-set)
|
|
486
|
+
async function transferFunds(fromId: string, toId: string, amount: number): Promise<boolean> {
|
|
487
|
+
const fromKey = `account:${fromId}:balance`;
|
|
488
|
+
const toKey = `account:${toId}:balance`;
|
|
489
|
+
|
|
490
|
+
// Watch keys for changes
|
|
491
|
+
await redis.watch(fromKey, toKey);
|
|
492
|
+
|
|
493
|
+
const balance = parseInt(await redis.get(fromKey) || "0", 10);
|
|
494
|
+
if (balance < amount) {
|
|
495
|
+
await redis.unwatch();
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// If any watched key changed since WATCH, EXEC returns null
|
|
500
|
+
const result = await redis.multi()
|
|
501
|
+
.decrby(fromKey, amount)
|
|
502
|
+
.incrby(toKey, amount)
|
|
503
|
+
.exec();
|
|
504
|
+
|
|
505
|
+
return result !== null; // null means a watched key was modified
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 6. Pipelining
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Pipelining: send multiple commands without waiting for individual responses
|
|
515
|
+
// Reduces round trips from N to 1
|
|
516
|
+
|
|
517
|
+
const pipeline = redis.pipeline();
|
|
518
|
+
for (let i = 0; i < 1000; i++) {
|
|
519
|
+
pipeline.set(`key:${i}`, `value:${i}`);
|
|
520
|
+
}
|
|
521
|
+
const results = await pipeline.exec();
|
|
522
|
+
// All 1000 commands sent in a single round trip
|
|
523
|
+
|
|
524
|
+
// Read pipeline
|
|
525
|
+
const readPipeline = redis.pipeline();
|
|
526
|
+
const userIds = ["1001", "1002", "1003", "1004", "1005"];
|
|
527
|
+
for (const id of userIds) {
|
|
528
|
+
readPipeline.hgetall(`user:${id}`);
|
|
529
|
+
}
|
|
530
|
+
const users = await readPipeline.exec();
|
|
531
|
+
// users: [[null, { name: "Alice", ... }], [null, { name: "Bob", ... }], ...]
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Pipeline vs MULTI
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
Pipeline:
|
|
538
|
+
- Commands sent in batch (1 round trip)
|
|
539
|
+
- Commands may interleave with other clients' commands
|
|
540
|
+
- No atomicity guarantee
|
|
541
|
+
- Use for performance when atomicity is not needed
|
|
542
|
+
|
|
543
|
+
MULTI/EXEC:
|
|
544
|
+
- Commands queued and executed atomically
|
|
545
|
+
- No other command runs between them
|
|
546
|
+
- Slightly more overhead
|
|
547
|
+
- Use when atomicity matters
|
|
548
|
+
|
|
549
|
+
Pipeline + MULTI: combine for atomic batch operations in 1 round trip
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 7. Redis Stack Modules
|
|
555
|
+
|
|
556
|
+
### RedisJSON
|
|
557
|
+
|
|
558
|
+
```redis
|
|
559
|
+
# Store JSON documents natively
|
|
560
|
+
JSON.SET user:1001 $ '{"name":"Alice","age":28,"address":{"city":"Portland","state":"OR"},"tags":["premium","early-adopter"]}'
|
|
561
|
+
|
|
562
|
+
# Get specific path
|
|
563
|
+
JSON.GET user:1001 $.name # "Alice"
|
|
564
|
+
JSON.GET user:1001 $.address.city # "Portland"
|
|
565
|
+
JSON.GET user:1001 $.tags[0] # "premium"
|
|
566
|
+
|
|
567
|
+
# Update nested value
|
|
568
|
+
JSON.SET user:1001 $.address.city '"Seattle"'
|
|
569
|
+
|
|
570
|
+
# Numeric operations
|
|
571
|
+
JSON.NUMINCRBY user:1001 $.age 1 # 29
|
|
572
|
+
|
|
573
|
+
# Array operations
|
|
574
|
+
JSON.ARRAPPEND user:1001 $.tags '"vip"' # Add to array
|
|
575
|
+
JSON.ARRLEN user:1001 $.tags # Array length
|
|
576
|
+
JSON.ARRPOP user:1001 $.tags # Pop last element
|
|
577
|
+
|
|
578
|
+
# Type checking
|
|
579
|
+
JSON.TYPE user:1001 $.name # string
|
|
580
|
+
JSON.TYPE user:1001 $.age # integer
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### RediSearch
|
|
584
|
+
|
|
585
|
+
```redis
|
|
586
|
+
# Create a search index on hashes
|
|
587
|
+
FT.CREATE idx:products ON HASH PREFIX 1 "product:"
|
|
588
|
+
SCHEMA
|
|
589
|
+
name TEXT WEIGHT 5.0
|
|
590
|
+
description TEXT
|
|
591
|
+
category TAG
|
|
592
|
+
price NUMERIC SORTABLE
|
|
593
|
+
in_stock TAG
|
|
594
|
+
|
|
595
|
+
# Search
|
|
596
|
+
FT.SEARCH idx:products "wireless headphones" LIMIT 0 10
|
|
597
|
+
|
|
598
|
+
# Filtered search
|
|
599
|
+
FT.SEARCH idx:products "@category:{electronics} @price:[50 200]"
|
|
600
|
+
|
|
601
|
+
# Autocomplete
|
|
602
|
+
FT.SUGADD autocomplete:products "Wireless Bluetooth Headphones" 100
|
|
603
|
+
FT.SUGGET autocomplete:products "wire" FUZZY MAX 5
|
|
604
|
+
|
|
605
|
+
# Aggregation
|
|
606
|
+
FT.AGGREGATE idx:products "*"
|
|
607
|
+
GROUPBY 1 @category
|
|
608
|
+
REDUCE COUNT 0 AS product_count
|
|
609
|
+
REDUCE AVG 1 @price AS avg_price
|
|
610
|
+
SORTBY 2 @product_count DESC
|
|
611
|
+
LIMIT 0 10
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### RedisTimeSeries
|
|
615
|
+
|
|
616
|
+
```redis
|
|
617
|
+
# Create a time series
|
|
618
|
+
TS.CREATE temperature:sensor-1 RETENTION 86400000 LABELS sensor_id "1" location "warehouse-a"
|
|
619
|
+
|
|
620
|
+
# Add data points
|
|
621
|
+
TS.ADD temperature:sensor-1 * 22.5 # Auto timestamp
|
|
622
|
+
TS.ADD temperature:sensor-1 1700000000 23.1 # Explicit timestamp
|
|
623
|
+
|
|
624
|
+
# Range query
|
|
625
|
+
TS.RANGE temperature:sensor-1 1700000000 1700003600
|
|
626
|
+
|
|
627
|
+
# Aggregation over time buckets
|
|
628
|
+
TS.RANGE temperature:sensor-1 - + AGGREGATION avg 3600000 # Hourly averages
|
|
629
|
+
TS.RANGE temperature:sensor-1 - + AGGREGATION max 86400000 # Daily maximums
|
|
630
|
+
|
|
631
|
+
# Multi-series query by labels
|
|
632
|
+
TS.MRANGE - + FILTER location="warehouse-a" AGGREGATION avg 3600000
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Bloom Filter
|
|
636
|
+
|
|
637
|
+
```redis
|
|
638
|
+
# Create and add to a Bloom filter (probabilistic: no false negatives, possible false positives)
|
|
639
|
+
BF.ADD seen:emails "alice@example.com"
|
|
640
|
+
BF.ADD seen:emails "bob@example.com"
|
|
641
|
+
|
|
642
|
+
# Check membership (fast, memory-efficient)
|
|
643
|
+
BF.EXISTS seen:emails "alice@example.com" # 1 (definitely exists)
|
|
644
|
+
BF.EXISTS seen:emails "unknown@example.com" # 0 (definitely does not exist)
|
|
645
|
+
|
|
646
|
+
# Reserve with specific error rate and capacity
|
|
647
|
+
BF.RESERVE seen:emails 0.001 1000000 # 0.1% false positive rate, 1M capacity
|
|
648
|
+
|
|
649
|
+
# Use case: prevent duplicate processing
|
|
650
|
+
# Before processing an event, check if we have seen it before
|
|
651
|
+
# If BF.EXISTS returns 0, we know for certain it is new
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 8. Key Naming Conventions
|
|
657
|
+
|
|
658
|
+
```
|
|
659
|
+
Pattern: object-type:id:field
|
|
660
|
+
|
|
661
|
+
Examples:
|
|
662
|
+
user:1001 -- Hash of user data
|
|
663
|
+
user:1001:sessions -- Set of active session IDs
|
|
664
|
+
user:1001:notifications -- List of notifications
|
|
665
|
+
session:abc123 -- Session data (string or hash)
|
|
666
|
+
cache:api:products:list -- Cached API response
|
|
667
|
+
lock:order:5001 -- Distributed lock
|
|
668
|
+
ratelimit:ip:192.168.1.1 -- Rate limit counter
|
|
669
|
+
queue:emails -- Email processing queue
|
|
670
|
+
leaderboard:global -- Sorted set leaderboard
|
|
671
|
+
counter:page:views:home -- Page view counter
|
|
672
|
+
temp:import:batch-42 -- Temporary data with TTL
|
|
673
|
+
|
|
674
|
+
Conventions:
|
|
675
|
+
- Use colons (:) as separators
|
|
676
|
+
- Keep keys short but descriptive
|
|
677
|
+
- Prefix by data domain or application name in shared environments
|
|
678
|
+
- Use consistent patterns across the codebase
|
|
679
|
+
- Avoid very long keys (network overhead) but do not sacrifice clarity
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## 9. TTL Strategies
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
// Tier-based TTL
|
|
688
|
+
const TTL = {
|
|
689
|
+
SESSION: 24 * 60 * 60, // 24 hours
|
|
690
|
+
CACHE_API: 5 * 60, // 5 minutes
|
|
691
|
+
CACHE_DB: 60 * 60, // 1 hour
|
|
692
|
+
CACHE_STATIC: 24 * 60 * 60, // 24 hours
|
|
693
|
+
RATE_LIMIT: 60, // 1 minute window
|
|
694
|
+
LOCK: 30, // 30 seconds
|
|
695
|
+
TEMP: 10 * 60, // 10 minutes
|
|
696
|
+
} as const;
|
|
697
|
+
|
|
698
|
+
// Jittered TTL (prevent cache stampede from synchronized expiry)
|
|
699
|
+
function jitteredTTL(baseTTL: number): number {
|
|
700
|
+
const jitter = Math.floor(Math.random() * baseTTL * 0.1); // +/- 10%
|
|
701
|
+
return baseTTL + jitter;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
await redis.set("cache:products:list", data, "EX", jitteredTTL(3600));
|
|
705
|
+
|
|
706
|
+
// Sliding expiration (reset TTL on access)
|
|
707
|
+
async function getWithSlidingExpiry(key: string, ttl: number): Promise<string | null> {
|
|
708
|
+
const value = await redis.get(key);
|
|
709
|
+
if (value !== null) {
|
|
710
|
+
await redis.expire(key, ttl); // Reset TTL on access
|
|
711
|
+
}
|
|
712
|
+
return value;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Check remaining TTL
|
|
716
|
+
const remaining = await redis.ttl("session:abc123");
|
|
717
|
+
// -2: key does not exist
|
|
718
|
+
// -1: key exists but has no expiration
|
|
719
|
+
// N: seconds until expiration
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## 10. Memory Management
|
|
725
|
+
|
|
726
|
+
### Eviction Policies
|
|
727
|
+
|
|
728
|
+
```
|
|
729
|
+
# Set in redis.conf or at runtime
|
|
730
|
+
CONFIG SET maxmemory 2gb
|
|
731
|
+
CONFIG SET maxmemory-policy allkeys-lru
|
|
732
|
+
|
|
733
|
+
Policies:
|
|
734
|
+
noeviction -- Return errors when memory limit is reached (default)
|
|
735
|
+
allkeys-lru -- Evict least recently used keys (best for general caching)
|
|
736
|
+
allkeys-lfu -- Evict least frequently used keys (better for skewed access)
|
|
737
|
+
volatile-lru -- Evict LRU keys that have a TTL set
|
|
738
|
+
volatile-lfu -- Evict LFU keys that have a TTL set
|
|
739
|
+
volatile-ttl -- Evict keys with the shortest TTL first
|
|
740
|
+
allkeys-random -- Evict random keys
|
|
741
|
+
volatile-random -- Evict random keys that have a TTL set
|
|
742
|
+
|
|
743
|
+
Recommendations:
|
|
744
|
+
- Cache-only workloads: allkeys-lru or allkeys-lfu
|
|
745
|
+
- Mixed workloads (cache + persistent): volatile-lru
|
|
746
|
+
- Session stores: volatile-ttl
|
|
747
|
+
- Never use noeviction for cache workloads (causes write failures)
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Memory Optimization
|
|
751
|
+
|
|
752
|
+
```redis
|
|
753
|
+
# Check memory usage
|
|
754
|
+
INFO memory
|
|
755
|
+
MEMORY USAGE user:1001 # Bytes used by a specific key
|
|
756
|
+
MEMORY DOCTOR # Diagnostic suggestions
|
|
757
|
+
|
|
758
|
+
# Optimize small hashes (ziplist encoding)
|
|
759
|
+
# When a hash has few fields and small values, Redis uses a compact encoding
|
|
760
|
+
CONFIG SET hash-max-ziplist-entries 128 # Max fields for ziplist
|
|
761
|
+
CONFIG SET hash-max-ziplist-value 64 # Max value size for ziplist
|
|
762
|
+
|
|
763
|
+
# Optimize small sorted sets
|
|
764
|
+
CONFIG SET zset-max-ziplist-entries 128
|
|
765
|
+
CONFIG SET zset-max-ziplist-value 64
|
|
766
|
+
|
|
767
|
+
# Optimize small lists
|
|
768
|
+
CONFIG SET list-max-ziplist-size -2 # 8 KB per node
|
|
769
|
+
|
|
770
|
+
# Scan for large keys
|
|
771
|
+
redis-cli --bigkeys
|
|
772
|
+
|
|
773
|
+
# Check key count and memory by pattern
|
|
774
|
+
redis-cli --memkeys --pattern "cache:*"
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## 11. Redis Cluster
|
|
780
|
+
|
|
781
|
+
### Cluster Architecture
|
|
782
|
+
|
|
783
|
+
```
|
|
784
|
+
- Data is split across 16384 hash slots
|
|
785
|
+
- Each primary node owns a range of hash slots
|
|
786
|
+
- Each primary can have one or more replicas
|
|
787
|
+
- Clients route commands to the correct node based on the key's hash slot
|
|
788
|
+
- Minimum: 3 primary nodes + 3 replicas (6 nodes total)
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Hash Tags (Force Keys to Same Slot)
|
|
792
|
+
|
|
793
|
+
```redis
|
|
794
|
+
# Keys with the same hash tag go to the same slot
|
|
795
|
+
# Hash tag is the content between the first { and first }
|
|
796
|
+
SET {user:1001}:profile "..."
|
|
797
|
+
SET {user:1001}:settings "..."
|
|
798
|
+
SET {user:1001}:cart "..."
|
|
799
|
+
|
|
800
|
+
# These all hash on "user:1001" and land on the same node
|
|
801
|
+
# Multi-key commands (MGET, transactions) require same-slot keys
|
|
802
|
+
|
|
803
|
+
# Without hash tags, these could be on different nodes:
|
|
804
|
+
SET user:1001:profile "..." # Hashes on "user:1001:profile"
|
|
805
|
+
SET user:1001:settings "..." # Hashes on "user:1001:settings"
|
|
806
|
+
# MGET on these would fail in cluster mode
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### ioredis Cluster Client
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
import Redis from "ioredis";
|
|
813
|
+
|
|
814
|
+
const cluster = new Redis.Cluster([
|
|
815
|
+
{ host: "node1.example.com", port: 6379 },
|
|
816
|
+
{ host: "node2.example.com", port: 6379 },
|
|
817
|
+
{ host: "node3.example.com", port: 6379 },
|
|
818
|
+
], {
|
|
819
|
+
redisOptions: {
|
|
820
|
+
password: process.env.REDIS_PASSWORD,
|
|
821
|
+
tls: {}, // Enable TLS for production
|
|
822
|
+
},
|
|
823
|
+
scaleReads: "slave", // Read from replicas
|
|
824
|
+
maxRedirections: 16, // Max MOVED/ASK redirections
|
|
825
|
+
retryDelayOnMoved: 100, // Delay on MOVED response (ms)
|
|
826
|
+
retryDelayOnCloverdown: 300, // Delay when cluster is down
|
|
827
|
+
enableReadyCheck: true,
|
|
828
|
+
natMap: {}, // NAT mapping for cloud environments
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
cluster.on("error", (err) => console.error("Cluster error:", err));
|
|
832
|
+
cluster.on("ready", () => console.log("Cluster ready"));
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## 12. Sentinel (High Availability)
|
|
838
|
+
|
|
839
|
+
```typescript
|
|
840
|
+
// Sentinel monitors primary nodes and performs automatic failover
|
|
841
|
+
const redis = new Redis({
|
|
842
|
+
sentinels: [
|
|
843
|
+
{ host: "sentinel1.example.com", port: 26379 },
|
|
844
|
+
{ host: "sentinel2.example.com", port: 26379 },
|
|
845
|
+
{ host: "sentinel3.example.com", port: 26379 },
|
|
846
|
+
],
|
|
847
|
+
name: "mymaster", // Sentinel master name
|
|
848
|
+
password: process.env.REDIS_PASSWORD,
|
|
849
|
+
sentinelPassword: process.env.SENTINEL_PASSWORD,
|
|
850
|
+
db: 0,
|
|
851
|
+
role: "master", // Connect to master
|
|
852
|
+
// role: "slave", // Connect to a replica for reads
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
redis.on("error", (err) => console.error("Redis error:", err));
|
|
856
|
+
redis.on("reconnecting", () => console.log("Reconnecting to Redis..."));
|
|
857
|
+
redis.on("ready", () => console.log("Redis ready"));
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Cluster vs Sentinel
|
|
861
|
+
|
|
862
|
+
```
|
|
863
|
+
Sentinel:
|
|
864
|
+
- Single primary with replicas
|
|
865
|
+
- Automatic failover on primary failure
|
|
866
|
+
- All data fits on one node
|
|
867
|
+
- Simpler setup and operations
|
|
868
|
+
- Use when data fits in a single node's memory
|
|
869
|
+
|
|
870
|
+
Cluster:
|
|
871
|
+
- Multiple primaries, each owning a slice of data
|
|
872
|
+
- Horizontal scaling (add nodes to add capacity)
|
|
873
|
+
- Data sharded across nodes
|
|
874
|
+
- More complex operations (resharding, rebalancing)
|
|
875
|
+
- Use when data exceeds single node memory or you need write scaling
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
---
|
|
879
|
+
|
|
880
|
+
## 13. Client Libraries
|
|
881
|
+
|
|
882
|
+
### ioredis (Node.js)
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
import Redis from "ioredis";
|
|
886
|
+
|
|
887
|
+
const redis = new Redis({
|
|
888
|
+
host: process.env.REDIS_HOST || "localhost",
|
|
889
|
+
port: parseInt(process.env.REDIS_PORT || "6379", 10),
|
|
890
|
+
password: process.env.REDIS_PASSWORD,
|
|
891
|
+
db: 0,
|
|
892
|
+
maxRetriesPerRequest: 3,
|
|
893
|
+
retryStrategy(times: number): number | null {
|
|
894
|
+
if (times > 10) return null; // Stop retrying after 10 attempts
|
|
895
|
+
return Math.min(times * 200, 2000); // Exponential backoff, max 2s
|
|
896
|
+
},
|
|
897
|
+
enableReadyCheck: true,
|
|
898
|
+
lazyConnect: true, // Connect on first command
|
|
899
|
+
keepAlive: 30000, // TCP keepalive (ms)
|
|
900
|
+
connectTimeout: 10000, // Connection timeout (ms)
|
|
901
|
+
commandTimeout: 5000, // Command timeout (ms)
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Error handling
|
|
905
|
+
redis.on("error", (err) => {
|
|
906
|
+
console.error("Redis connection error:", err);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
redis.on("connect", () => {
|
|
910
|
+
console.log("Connected to Redis");
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
redis.on("ready", () => {
|
|
914
|
+
console.log("Redis is ready");
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// Graceful shutdown
|
|
918
|
+
async function shutdown(): Promise<void> {
|
|
919
|
+
await redis.quit();
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### redis-py (Python)
|
|
924
|
+
|
|
925
|
+
```python
|
|
926
|
+
import redis
|
|
927
|
+
import json
|
|
928
|
+
from datetime import timedelta
|
|
929
|
+
|
|
930
|
+
# Connection pool (reuse connections across requests)
|
|
931
|
+
pool = redis.ConnectionPool(
|
|
932
|
+
host="localhost",
|
|
933
|
+
port=6379,
|
|
934
|
+
password="your_password",
|
|
935
|
+
db=0,
|
|
936
|
+
max_connections=50,
|
|
937
|
+
decode_responses=True, # Return strings instead of bytes
|
|
938
|
+
socket_timeout=5,
|
|
939
|
+
socket_connect_timeout=5,
|
|
940
|
+
retry_on_timeout=True,
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
r = redis.Redis(connection_pool=pool)
|
|
944
|
+
|
|
945
|
+
# Basic operations
|
|
946
|
+
r.set("key", "value", ex=3600)
|
|
947
|
+
value = r.get("key")
|
|
948
|
+
|
|
949
|
+
# Pipeline
|
|
950
|
+
pipe = r.pipeline(transaction=False) # Non-transactional pipeline
|
|
951
|
+
for i in range(1000):
|
|
952
|
+
pipe.set(f"key:{i}", f"value:{i}")
|
|
953
|
+
pipe.execute()
|
|
954
|
+
|
|
955
|
+
# Pub/Sub
|
|
956
|
+
pubsub = r.pubsub()
|
|
957
|
+
pubsub.subscribe("channel")
|
|
958
|
+
for message in pubsub.listen():
|
|
959
|
+
if message["type"] == "message":
|
|
960
|
+
data = json.loads(message["data"])
|
|
961
|
+
process(data)
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
## 14. Rate Limiting with Sorted Sets
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
// Sliding window rate limiter
|
|
970
|
+
async function isRateLimited(
|
|
971
|
+
identifier: string,
|
|
972
|
+
maxRequests: number,
|
|
973
|
+
windowSeconds: number
|
|
974
|
+
): Promise<{ limited: boolean; remaining: number; resetAt: number }> {
|
|
975
|
+
const key = `ratelimit:${identifier}`;
|
|
976
|
+
const now = Date.now();
|
|
977
|
+
const windowStart = now - windowSeconds * 1000;
|
|
978
|
+
|
|
979
|
+
// Atomic operation using pipeline
|
|
980
|
+
const pipeline = redis.pipeline();
|
|
981
|
+
pipeline.zremrangebyscore(key, "-inf", windowStart); // Remove old entries
|
|
982
|
+
pipeline.zadd(key, now, `${now}-${Math.random()}`); // Add current request
|
|
983
|
+
pipeline.zcard(key); // Count requests in window
|
|
984
|
+
pipeline.expire(key, windowSeconds); // Set key expiry
|
|
985
|
+
|
|
986
|
+
const results = await pipeline.exec();
|
|
987
|
+
const requestCount = results[2][1] as number;
|
|
988
|
+
|
|
989
|
+
return {
|
|
990
|
+
limited: requestCount > maxRequests,
|
|
991
|
+
remaining: Math.max(0, maxRequests - requestCount),
|
|
992
|
+
resetAt: now + windowSeconds * 1000,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Usage
|
|
997
|
+
const { limited, remaining } = await isRateLimited("user:1001", 100, 60);
|
|
998
|
+
if (limited) {
|
|
999
|
+
return new Response("Too Many Requests", {
|
|
1000
|
+
status: 429,
|
|
1001
|
+
headers: { "X-RateLimit-Remaining": String(remaining) },
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
---
|
|
1007
|
+
|
|
1008
|
+
## 15. Common Anti-Patterns
|
|
1009
|
+
|
|
1010
|
+
### Using KEYS in Production
|
|
1011
|
+
|
|
1012
|
+
```redis
|
|
1013
|
+
# BAD: KEYS blocks the server, scans all keys
|
|
1014
|
+
KEYS user:*
|
|
1015
|
+
|
|
1016
|
+
# GOOD: Use SCAN for iterating keys (non-blocking, cursor-based)
|
|
1017
|
+
SCAN 0 MATCH user:* COUNT 100
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Storing Large Values
|
|
1021
|
+
|
|
1022
|
+
```
|
|
1023
|
+
# BAD: Storing 10 MB JSON blobs in a single key
|
|
1024
|
+
# Causes network latency, memory fragmentation, slow serialization
|
|
1025
|
+
|
|
1026
|
+
# GOOD: Break into smaller pieces
|
|
1027
|
+
# Store summary in a hash, details in separate keys
|
|
1028
|
+
# Use RedisJSON for nested document access without full deserialization
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
### No TTL on Cache Keys
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
// BAD: Cache without expiration (memory leak)
|
|
1035
|
+
await redis.set("cache:data", value);
|
|
1036
|
+
|
|
1037
|
+
// GOOD: Always set TTL on cache entries
|
|
1038
|
+
await redis.set("cache:data", value, "EX", 3600);
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
### Hot Keys
|
|
1042
|
+
|
|
1043
|
+
```
|
|
1044
|
+
# BAD: Single key receiving massive traffic (e.g., global counter)
|
|
1045
|
+
INCR global:page_views
|
|
1046
|
+
|
|
1047
|
+
# GOOD: Shard the hot key
|
|
1048
|
+
INCR global:page_views:{shard_id} # shard_id = hash(request_id) % N
|
|
1049
|
+
# Periodically aggregate shards
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
---
|
|
1053
|
+
|
|
1054
|
+
## 16. Critical Reminders
|
|
1055
|
+
|
|
1056
|
+
### ALWAYS
|
|
1057
|
+
|
|
1058
|
+
- Set `maxmemory` and an eviction policy in production
|
|
1059
|
+
- Use connection pooling (never create a connection per request)
|
|
1060
|
+
- Set TTL on all cache keys
|
|
1061
|
+
- Use `SCAN` instead of `KEYS` for iteration
|
|
1062
|
+
- Use pipelining for batch operations
|
|
1063
|
+
- Handle connection errors and implement retry logic
|
|
1064
|
+
- Use Lua scripts for operations that must be atomic
|
|
1065
|
+
- Use hash tags in Cluster mode for multi-key operations
|
|
1066
|
+
- Monitor memory usage, hit rate, and slow log
|
|
1067
|
+
- Close connections gracefully on application shutdown
|
|
1068
|
+
|
|
1069
|
+
### NEVER
|
|
1070
|
+
|
|
1071
|
+
- Use `KEYS *` in production (blocks the server)
|
|
1072
|
+
- Store values larger than 1 MB in a single key without good reason
|
|
1073
|
+
- Use Redis as a primary database without persistence configuration
|
|
1074
|
+
- Forget to handle `MOVED` and `ASK` redirections in Cluster mode
|
|
1075
|
+
- Use `FLUSHALL` or `FLUSHDB` in production without extreme caution
|
|
1076
|
+
- Leave `maxmemory` unset in production (Redis will use all available RAM)
|
|
1077
|
+
- Assume pub/sub messages are durable (they are fire-and-forget)
|
|
1078
|
+
- Use blocking commands (`BLPOP`, `BRPOP`) without timeouts
|
|
1079
|
+
- Store secrets or sensitive data without encryption
|