@dotdo/postgres 0.1.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/README.md +868 -0
- package/dist/cdc/change-stream.d.ts +44 -0
- package/dist/cdc/change-stream.d.ts.map +1 -0
- package/dist/cdc/change-stream.js +95 -0
- package/dist/cdc/change-stream.js.map +1 -0
- package/dist/cdc/filter.d.ts +58 -0
- package/dist/cdc/filter.d.ts.map +1 -0
- package/dist/cdc/filter.js +520 -0
- package/dist/cdc/filter.js.map +1 -0
- package/dist/cdc/index.d.ts +47 -0
- package/dist/cdc/index.d.ts.map +1 -0
- package/dist/cdc/index.js +50 -0
- package/dist/cdc/index.js.map +1 -0
- package/dist/cdc/resume-token.d.ts +60 -0
- package/dist/cdc/resume-token.d.ts.map +1 -0
- package/dist/cdc/resume-token.js +228 -0
- package/dist/cdc/resume-token.js.map +1 -0
- package/dist/cdc/transport/index.d.ts +7 -0
- package/dist/cdc/transport/index.d.ts.map +1 -0
- package/dist/cdc/transport/index.js +7 -0
- package/dist/cdc/transport/index.js.map +1 -0
- package/dist/cdc/transport/sse.d.ts +120 -0
- package/dist/cdc/transport/sse.d.ts.map +1 -0
- package/dist/cdc/transport/sse.js +590 -0
- package/dist/cdc/transport/sse.js.map +1 -0
- package/dist/cdc/transport/websocket.d.ts +130 -0
- package/dist/cdc/transport/websocket.d.ts.map +1 -0
- package/dist/cdc/transport/websocket.js +688 -0
- package/dist/cdc/transport/websocket.js.map +1 -0
- package/dist/cdc/types.d.ts +306 -0
- package/dist/cdc/types.d.ts.map +1 -0
- package/dist/cdc/types.js +8 -0
- package/dist/cdc/types.js.map +1 -0
- package/dist/config/index.d.ts +25 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +25 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/memory.d.ts +139 -0
- package/dist/config/memory.d.ts.map +1 -0
- package/dist/config/memory.js +157 -0
- package/dist/config/memory.js.map +1 -0
- package/dist/config/storage.d.ts +157 -0
- package/dist/config/storage.d.ts.map +1 -0
- package/dist/config/storage.js +178 -0
- package/dist/config/storage.js.map +1 -0
- package/dist/config/streaming.d.ts +117 -0
- package/dist/config/streaming.d.ts.map +1 -0
- package/dist/config/streaming.js +132 -0
- package/dist/config/streaming.js.map +1 -0
- package/dist/config/timeouts.d.ts +168 -0
- package/dist/config/timeouts.d.ts.map +1 -0
- package/dist/config/timeouts.js +192 -0
- package/dist/config/timeouts.js.map +1 -0
- package/dist/extensions/config.d.ts +89 -0
- package/dist/extensions/config.d.ts.map +1 -0
- package/dist/extensions/config.js +216 -0
- package/dist/extensions/config.js.map +1 -0
- package/dist/extensions/geo.d.ts +452 -0
- package/dist/extensions/geo.d.ts.map +1 -0
- package/dist/extensions/geo.js +583 -0
- package/dist/extensions/geo.js.map +1 -0
- package/dist/extensions/index.d.ts +167 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +99 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/loader.d.ts +226 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +456 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/pgmq-lite.d.ts +330 -0
- package/dist/extensions/pgmq-lite.d.ts.map +1 -0
- package/dist/extensions/pgmq-lite.js +648 -0
- package/dist/extensions/pgmq-lite.js.map +1 -0
- package/dist/extensions/plugins.d.ts +260 -0
- package/dist/extensions/plugins.d.ts.map +1 -0
- package/dist/extensions/plugins.js +535 -0
- package/dist/extensions/plugins.js.map +1 -0
- package/dist/extensions/registry.d.ts +93 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +182 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/vector.d.ts +106 -0
- package/dist/extensions/vector.d.ts.map +1 -0
- package/dist/extensions/vector.js +129 -0
- package/dist/extensions/vector.js.map +1 -0
- package/dist/iceberg/analytics.d.ts +279 -0
- package/dist/iceberg/analytics.d.ts.map +1 -0
- package/dist/iceberg/analytics.js +448 -0
- package/dist/iceberg/analytics.js.map +1 -0
- package/dist/iceberg/catalog-api.d.ts +39 -0
- package/dist/iceberg/catalog-api.d.ts.map +1 -0
- package/dist/iceberg/catalog-api.js +388 -0
- package/dist/iceberg/catalog-api.js.map +1 -0
- package/dist/iceberg/catalog.d.ts +401 -0
- package/dist/iceberg/catalog.d.ts.map +1 -0
- package/dist/iceberg/catalog.js +677 -0
- package/dist/iceberg/catalog.js.map +1 -0
- package/dist/iceberg/duckdb-wasm.d.ts +447 -0
- package/dist/iceberg/duckdb-wasm.d.ts.map +1 -0
- package/dist/iceberg/duckdb-wasm.js +600 -0
- package/dist/iceberg/duckdb-wasm.js.map +1 -0
- package/dist/iceberg/index.d.ts +92 -0
- package/dist/iceberg/index.d.ts.map +1 -0
- package/dist/iceberg/index.js +119 -0
- package/dist/iceberg/index.js.map +1 -0
- package/dist/iceberg/metadata.d.ts +214 -0
- package/dist/iceberg/metadata.d.ts.map +1 -0
- package/dist/iceberg/metadata.js +535 -0
- package/dist/iceberg/metadata.js.map +1 -0
- package/dist/iceberg/optimizer.d.ts +296 -0
- package/dist/iceberg/optimizer.d.ts.map +1 -0
- package/dist/iceberg/optimizer.js +889 -0
- package/dist/iceberg/optimizer.js.map +1 -0
- package/dist/iceberg/parquet.d.ts +447 -0
- package/dist/iceberg/parquet.d.ts.map +1 -0
- package/dist/iceberg/parquet.js +1225 -0
- package/dist/iceberg/parquet.js.map +1 -0
- package/dist/iceberg/r2-organization.d.ts +422 -0
- package/dist/iceberg/r2-organization.d.ts.map +1 -0
- package/dist/iceberg/r2-organization.js +672 -0
- package/dist/iceberg/r2-organization.js.map +1 -0
- package/dist/iceberg/scheduler-do-example.d.ts +158 -0
- package/dist/iceberg/scheduler-do-example.d.ts.map +1 -0
- package/dist/iceberg/scheduler-do-example.js +261 -0
- package/dist/iceberg/scheduler-do-example.js.map +1 -0
- package/dist/iceberg/scheduler.d.ts +434 -0
- package/dist/iceberg/scheduler.d.ts.map +1 -0
- package/dist/iceberg/scheduler.js +818 -0
- package/dist/iceberg/scheduler.js.map +1 -0
- package/dist/iceberg/schema.d.ts +149 -0
- package/dist/iceberg/schema.d.ts.map +1 -0
- package/dist/iceberg/schema.js +525 -0
- package/dist/iceberg/schema.js.map +1 -0
- package/dist/iceberg/snapshot-manager.d.ts +406 -0
- package/dist/iceberg/snapshot-manager.d.ts.map +1 -0
- package/dist/iceberg/snapshot-manager.js +934 -0
- package/dist/iceberg/snapshot-manager.js.map +1 -0
- package/dist/iceberg/sql-router.d.ts +194 -0
- package/dist/iceberg/sql-router.d.ts.map +1 -0
- package/dist/iceberg/sql-router.js +180 -0
- package/dist/iceberg/sql-router.js.map +1 -0
- package/dist/iceberg/test-fixtures.d.ts +151 -0
- package/dist/iceberg/test-fixtures.d.ts.map +1 -0
- package/dist/iceberg/test-fixtures.js +446 -0
- package/dist/iceberg/test-fixtures.js.map +1 -0
- package/dist/iceberg/time-travel-api.d.ts +102 -0
- package/dist/iceberg/time-travel-api.d.ts.map +1 -0
- package/dist/iceberg/time-travel-api.js +437 -0
- package/dist/iceberg/time-travel-api.js.map +1 -0
- package/dist/iceberg/time-travel.d.ts +293 -0
- package/dist/iceberg/time-travel.d.ts.map +1 -0
- package/dist/iceberg/time-travel.js +689 -0
- package/dist/iceberg/time-travel.js.map +1 -0
- package/dist/iceberg/transformer.d.ts +356 -0
- package/dist/iceberg/transformer.d.ts.map +1 -0
- package/dist/iceberg/transformer.js +770 -0
- package/dist/iceberg/transformer.js.map +1 -0
- package/dist/iceberg/types.d.ts +318 -0
- package/dist/iceberg/types.d.ts.map +1 -0
- package/dist/iceberg/types.js +9 -0
- package/dist/iceberg/types.js.map +1 -0
- package/dist/iceberg/writer.d.ts +144 -0
- package/dist/iceberg/writer.d.ts.map +1 -0
- package/dist/iceberg/writer.js +452 -0
- package/dist/iceberg/writer.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/lineage/index.d.ts +11 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +11 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/integration.d.ts +134 -0
- package/dist/lineage/integration.d.ts.map +1 -0
- package/dist/lineage/integration.js +258 -0
- package/dist/lineage/integration.js.map +1 -0
- package/dist/lineage/tracker.d.ts +189 -0
- package/dist/lineage/tracker.d.ts.map +1 -0
- package/dist/lineage/tracker.js +1352 -0
- package/dist/lineage/tracker.js.map +1 -0
- package/dist/lineage/types.d.ts +318 -0
- package/dist/lineage/types.d.ts.map +1 -0
- package/dist/lineage/types.js +9 -0
- package/dist/lineage/types.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +16 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +397 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +507 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/migration-tooling/external-migration.d.ts +601 -0
- package/dist/migration-tooling/external-migration.d.ts.map +1 -0
- package/dist/migration-tooling/external-migration.js +1612 -0
- package/dist/migration-tooling/external-migration.js.map +1 -0
- package/dist/migration-tooling/index.d.ts +19 -0
- package/dist/migration-tooling/index.d.ts.map +1 -0
- package/dist/migration-tooling/index.js +19 -0
- package/dist/migration-tooling/index.js.map +1 -0
- package/dist/migrations/auto-migrator.d.ts +289 -0
- package/dist/migrations/auto-migrator.d.ts.map +1 -0
- package/dist/migrations/auto-migrator.js +396 -0
- package/dist/migrations/auto-migrator.js.map +1 -0
- package/dist/migrations/bulk-orchestrator.d.ts +403 -0
- package/dist/migrations/bulk-orchestrator.d.ts.map +1 -0
- package/dist/migrations/bulk-orchestrator.js +646 -0
- package/dist/migrations/bulk-orchestrator.js.map +1 -0
- package/dist/migrations/compatibility.d.ts +216 -0
- package/dist/migrations/compatibility.d.ts.map +1 -0
- package/dist/migrations/compatibility.js +651 -0
- package/dist/migrations/compatibility.js.map +1 -0
- package/dist/migrations/do-migrations.d.ts +101 -0
- package/dist/migrations/do-migrations.d.ts.map +1 -0
- package/dist/migrations/do-migrations.js +1060 -0
- package/dist/migrations/do-migrations.js.map +1 -0
- package/dist/migrations/do-migrations.types.d.ts +550 -0
- package/dist/migrations/do-migrations.types.d.ts.map +1 -0
- package/dist/migrations/do-migrations.types.js +15 -0
- package/dist/migrations/do-migrations.types.js.map +1 -0
- package/dist/migrations/drizzle-compat.d.ts +163 -0
- package/dist/migrations/drizzle-compat.d.ts.map +1 -0
- package/dist/migrations/drizzle-compat.js +273 -0
- package/dist/migrations/drizzle-compat.js.map +1 -0
- package/dist/migrations/index.d.ts +109 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +127 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-api.d.ts +161 -0
- package/dist/migrations/migration-api.d.ts.map +1 -0
- package/dist/migrations/migration-api.js +499 -0
- package/dist/migrations/migration-api.js.map +1 -0
- package/dist/migrations/progress-tracker-do.d.ts +195 -0
- package/dist/migrations/progress-tracker-do.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-do.js +339 -0
- package/dist/migrations/progress-tracker-do.js.map +1 -0
- package/dist/migrations/progress-tracker-kv.d.ts +103 -0
- package/dist/migrations/progress-tracker-kv.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-kv.js +231 -0
- package/dist/migrations/progress-tracker-kv.js.map +1 -0
- package/dist/migrations/progress-tracker.d.ts +320 -0
- package/dist/migrations/progress-tracker.d.ts.map +1 -0
- package/dist/migrations/progress-tracker.js +443 -0
- package/dist/migrations/progress-tracker.js.map +1 -0
- package/dist/migrations/registry.d.ts +231 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +376 -0
- package/dist/migrations/registry.js.map +1 -0
- package/dist/migrations/runner.d.ts +197 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +1167 -0
- package/dist/migrations/runner.js.map +1 -0
- package/dist/migrations/schema-generator.d.ts +111 -0
- package/dist/migrations/schema-generator.d.ts.map +1 -0
- package/dist/migrations/schema-generator.js +335 -0
- package/dist/migrations/schema-generator.js.map +1 -0
- package/dist/migrations/testing.d.ts +321 -0
- package/dist/migrations/testing.d.ts.map +1 -0
- package/dist/migrations/testing.js +645 -0
- package/dist/migrations/testing.js.map +1 -0
- package/dist/migrations/types.d.ts +503 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +11 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/migrations/validator.d.ts +215 -0
- package/dist/migrations/validator.d.ts.map +1 -0
- package/dist/migrations/validator.js +494 -0
- package/dist/migrations/validator.js.map +1 -0
- package/dist/observability/alerting.d.ts +116 -0
- package/dist/observability/alerting.d.ts.map +1 -0
- package/dist/observability/alerting.js +353 -0
- package/dist/observability/alerting.js.map +1 -0
- package/dist/observability/analytics-engine.d.ts +357 -0
- package/dist/observability/analytics-engine.d.ts.map +1 -0
- package/dist/observability/analytics-engine.js +430 -0
- package/dist/observability/analytics-engine.js.map +1 -0
- package/dist/observability/cost-metrics.d.ts +269 -0
- package/dist/observability/cost-metrics.d.ts.map +1 -0
- package/dist/observability/cost-metrics.js +560 -0
- package/dist/observability/cost-metrics.js.map +1 -0
- package/dist/observability/cross-do-tracing.d.ts +305 -0
- package/dist/observability/cross-do-tracing.d.ts.map +1 -0
- package/dist/observability/cross-do-tracing.js +431 -0
- package/dist/observability/cross-do-tracing.js.map +1 -0
- package/dist/observability/error-rate-collector.d.ts +163 -0
- package/dist/observability/error-rate-collector.d.ts.map +1 -0
- package/dist/observability/error-rate-collector.js +306 -0
- package/dist/observability/error-rate-collector.js.map +1 -0
- package/dist/observability/exporters.d.ts +231 -0
- package/dist/observability/exporters.d.ts.map +1 -0
- package/dist/observability/exporters.js +479 -0
- package/dist/observability/exporters.js.map +1 -0
- package/dist/observability/health-check.d.ts +106 -0
- package/dist/observability/health-check.d.ts.map +1 -0
- package/dist/observability/health-check.js +243 -0
- package/dist/observability/health-check.js.map +1 -0
- package/dist/observability/index.d.ts +297 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +455 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/instrumentation.d.ts +222 -0
- package/dist/observability/instrumentation.d.ts.map +1 -0
- package/dist/observability/instrumentation.js +532 -0
- package/dist/observability/instrumentation.js.map +1 -0
- package/dist/observability/memory-metrics.d.ts +227 -0
- package/dist/observability/memory-metrics.d.ts.map +1 -0
- package/dist/observability/memory-metrics.js +688 -0
- package/dist/observability/memory-metrics.js.map +1 -0
- package/dist/observability/metrics-endpoint.d.ts +91 -0
- package/dist/observability/metrics-endpoint.d.ts.map +1 -0
- package/dist/observability/metrics-endpoint.js +246 -0
- package/dist/observability/metrics-endpoint.js.map +1 -0
- package/dist/observability/metrics.d.ts +88 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +253 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/observability-features.d.ts +488 -0
- package/dist/observability/observability-features.d.ts.map +1 -0
- package/dist/observability/observability-features.js +773 -0
- package/dist/observability/observability-features.js.map +1 -0
- package/dist/observability/prometheus.d.ts +39 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/prometheus.js +120 -0
- package/dist/observability/prometheus.js.map +1 -0
- package/dist/observability/propagation.d.ts +126 -0
- package/dist/observability/propagation.d.ts.map +1 -0
- package/dist/observability/propagation.js +234 -0
- package/dist/observability/propagation.js.map +1 -0
- package/dist/observability/query-latency.d.ts +243 -0
- package/dist/observability/query-latency.d.ts.map +1 -0
- package/dist/observability/query-latency.js +292 -0
- package/dist/observability/query-latency.js.map +1 -0
- package/dist/observability/query-performance.d.ts +169 -0
- package/dist/observability/query-performance.d.ts.map +1 -0
- package/dist/observability/query-performance.js +290 -0
- package/dist/observability/query-performance.js.map +1 -0
- package/dist/observability/storage-tier-metrics.d.ts +174 -0
- package/dist/observability/storage-tier-metrics.d.ts.map +1 -0
- package/dist/observability/storage-tier-metrics.js +306 -0
- package/dist/observability/storage-tier-metrics.js.map +1 -0
- package/dist/observability/tier-cost-optimizer.d.ts +155 -0
- package/dist/observability/tier-cost-optimizer.d.ts.map +1 -0
- package/dist/observability/tier-cost-optimizer.js +536 -0
- package/dist/observability/tier-cost-optimizer.js.map +1 -0
- package/dist/observability/tracer.d.ts +149 -0
- package/dist/observability/tracer.d.ts.map +1 -0
- package/dist/observability/tracer.js +435 -0
- package/dist/observability/tracer.js.map +1 -0
- package/dist/observability/types.d.ts +402 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +103 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/pglite/workers-pglite.d.ts +138 -0
- package/dist/pglite/workers-pglite.d.ts.map +1 -0
- package/dist/pglite/workers-pglite.js +143 -0
- package/dist/pglite/workers-pglite.js.map +1 -0
- package/dist/pglite-assets/pglite.data +0 -0
- package/dist/pglite-assets/pglite.wasm +0 -0
- package/dist/playground/index.d.ts +52 -0
- package/dist/playground/index.d.ts.map +1 -0
- package/dist/playground/index.js +55 -0
- package/dist/playground/index.js.map +1 -0
- package/dist/playground/keyboard-shortcuts.d.ts +116 -0
- package/dist/playground/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/playground/keyboard-shortcuts.js +588 -0
- package/dist/playground/keyboard-shortcuts.js.map +1 -0
- package/dist/playground/playground.d.ts +82 -0
- package/dist/playground/playground.d.ts.map +1 -0
- package/dist/playground/playground.js +271 -0
- package/dist/playground/playground.js.map +1 -0
- package/dist/playground/query-executor.d.ts +115 -0
- package/dist/playground/query-executor.d.ts.map +1 -0
- package/dist/playground/query-executor.js +558 -0
- package/dist/playground/query-executor.js.map +1 -0
- package/dist/playground/query-history.d.ts +92 -0
- package/dist/playground/query-history.d.ts.map +1 -0
- package/dist/playground/query-history.js +259 -0
- package/dist/playground/query-history.js.map +1 -0
- package/dist/playground/result-formatter.d.ts +59 -0
- package/dist/playground/result-formatter.d.ts.map +1 -0
- package/dist/playground/result-formatter.js +341 -0
- package/dist/playground/result-formatter.js.map +1 -0
- package/dist/playground/sample-datasets.d.ts +77 -0
- package/dist/playground/sample-datasets.d.ts.map +1 -0
- package/dist/playground/sample-datasets.js +641 -0
- package/dist/playground/sample-datasets.js.map +1 -0
- package/dist/playground/sample-queries.d.ts +73 -0
- package/dist/playground/sample-queries.d.ts.map +1 -0
- package/dist/playground/sample-queries.js +1095 -0
- package/dist/playground/sample-queries.js.map +1 -0
- package/dist/playground/schema-explorer.d.ts +55 -0
- package/dist/playground/schema-explorer.d.ts.map +1 -0
- package/dist/playground/schema-explorer.js +473 -0
- package/dist/playground/schema-explorer.js.map +1 -0
- package/dist/playground/types.d.ts +430 -0
- package/dist/playground/types.d.ts.map +1 -0
- package/dist/playground/types.js +10 -0
- package/dist/playground/types.js.map +1 -0
- package/dist/readonly/cache-reader.d.ts +145 -0
- package/dist/readonly/cache-reader.d.ts.map +1 -0
- package/dist/readonly/cache-reader.js +198 -0
- package/dist/readonly/cache-reader.js.map +1 -0
- package/dist/readonly/config.d.ts +74 -0
- package/dist/readonly/config.d.ts.map +1 -0
- package/dist/readonly/config.js +67 -0
- package/dist/readonly/config.js.map +1 -0
- package/dist/readonly/index.d.ts +22 -0
- package/dist/readonly/index.d.ts.map +1 -0
- package/dist/readonly/index.js +17 -0
- package/dist/readonly/index.js.map +1 -0
- package/dist/readonly/pglite-wrapper.d.ts +82 -0
- package/dist/readonly/pglite-wrapper.d.ts.map +1 -0
- package/dist/readonly/pglite-wrapper.js +123 -0
- package/dist/readonly/pglite-wrapper.js.map +1 -0
- package/dist/readonly/worker.d.ts +142 -0
- package/dist/readonly/worker.d.ts.map +1 -0
- package/dist/readonly/worker.js +187 -0
- package/dist/readonly/worker.js.map +1 -0
- package/dist/readonly/write-blocker.d.ts +47 -0
- package/dist/readonly/write-blocker.d.ts.map +1 -0
- package/dist/readonly/write-blocker.js +136 -0
- package/dist/readonly/write-blocker.js.map +1 -0
- package/dist/recovery/disaster-recovery.d.ts +326 -0
- package/dist/recovery/disaster-recovery.d.ts.map +1 -0
- package/dist/recovery/disaster-recovery.js +799 -0
- package/dist/recovery/disaster-recovery.js.map +1 -0
- package/dist/recovery/index.d.ts +12 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +12 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/parquet-parser.d.ts +321 -0
- package/dist/recovery/parquet-parser.d.ts.map +1 -0
- package/dist/recovery/parquet-parser.js +797 -0
- package/dist/recovery/parquet-parser.js.map +1 -0
- package/dist/retention/index.d.ts +50 -0
- package/dist/retention/index.d.ts.map +1 -0
- package/dist/retention/index.js +50 -0
- package/dist/retention/index.js.map +1 -0
- package/dist/retention/policy.d.ts +344 -0
- package/dist/retention/policy.d.ts.map +1 -0
- package/dist/retention/policy.js +472 -0
- package/dist/retention/policy.js.map +1 -0
- package/dist/retention/purger.d.ts +187 -0
- package/dist/retention/purger.d.ts.map +1 -0
- package/dist/retention/purger.js +411 -0
- package/dist/retention/purger.js.map +1 -0
- package/dist/rls/auth-integration.d.ts +280 -0
- package/dist/rls/auth-integration.d.ts.map +1 -0
- package/dist/rls/auth-integration.js +399 -0
- package/dist/rls/auth-integration.js.map +1 -0
- package/dist/rls/generator.d.ts +249 -0
- package/dist/rls/generator.d.ts.map +1 -0
- package/dist/rls/generator.js +495 -0
- package/dist/rls/generator.js.map +1 -0
- package/dist/rls/index.d.ts +26 -0
- package/dist/rls/index.d.ts.map +1 -0
- package/dist/rls/index.js +58 -0
- package/dist/rls/index.js.map +1 -0
- package/dist/rls/policy.d.ts +116 -0
- package/dist/rls/policy.d.ts.map +1 -0
- package/dist/rls/policy.js +77 -0
- package/dist/rls/policy.js.map +1 -0
- package/dist/rls/validator.d.ts +155 -0
- package/dist/rls/validator.d.ts.map +1 -0
- package/dist/rls/validator.js +792 -0
- package/dist/rls/validator.js.map +1 -0
- package/dist/routing/adaptive-router.d.ts +317 -0
- package/dist/routing/adaptive-router.d.ts.map +1 -0
- package/dist/routing/adaptive-router.js +554 -0
- package/dist/routing/adaptive-router.js.map +1 -0
- package/dist/routing/circuit-breaker.d.ts +339 -0
- package/dist/routing/circuit-breaker.d.ts.map +1 -0
- package/dist/routing/circuit-breaker.js +620 -0
- package/dist/routing/circuit-breaker.js.map +1 -0
- package/dist/routing/cost-metrics.d.ts +133 -0
- package/dist/routing/cost-metrics.d.ts.map +1 -0
- package/dist/routing/cost-metrics.js +259 -0
- package/dist/routing/cost-metrics.js.map +1 -0
- package/dist/routing/do-connection-pool.d.ts +243 -0
- package/dist/routing/do-connection-pool.d.ts.map +1 -0
- package/dist/routing/do-connection-pool.js +572 -0
- package/dist/routing/do-connection-pool.js.map +1 -0
- package/dist/routing/index.d.ts +59 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +59 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/query-complexity-estimator.d.ts +73 -0
- package/dist/routing/query-complexity-estimator.d.ts.map +1 -0
- package/dist/routing/query-complexity-estimator.js +327 -0
- package/dist/routing/query-complexity-estimator.js.map +1 -0
- package/dist/routing/request-coalescing.d.ts +178 -0
- package/dist/routing/request-coalescing.d.ts.map +1 -0
- package/dist/routing/request-coalescing.js +325 -0
- package/dist/routing/request-coalescing.js.map +1 -0
- package/dist/routing/runtime-router.d.ts +107 -0
- package/dist/routing/runtime-router.d.ts.map +1 -0
- package/dist/routing/runtime-router.js +246 -0
- package/dist/routing/runtime-router.js.map +1 -0
- package/dist/routing/tenant-router.d.ts +848 -0
- package/dist/routing/tenant-router.d.ts.map +1 -0
- package/dist/routing/tenant-router.js +1056 -0
- package/dist/routing/tenant-router.js.map +1 -0
- package/dist/routing/websocket-pool.d.ts +119 -0
- package/dist/routing/websocket-pool.d.ts.map +1 -0
- package/dist/routing/websocket-pool.js +436 -0
- package/dist/routing/websocket-pool.js.map +1 -0
- package/dist/storage/cache-layer.d.ts +159 -0
- package/dist/storage/cache-layer.d.ts.map +1 -0
- package/dist/storage/cache-layer.js +245 -0
- package/dist/storage/cache-layer.js.map +1 -0
- package/dist/storage/cost-aware-tiering.d.ts +258 -0
- package/dist/storage/cost-aware-tiering.d.ts.map +1 -0
- package/dist/storage/cost-aware-tiering.js +526 -0
- package/dist/storage/cost-aware-tiering.js.map +1 -0
- package/dist/storage/index.d.ts +87 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +78 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interfaces.d.ts +856 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +69 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/r2-layer.d.ts +226 -0
- package/dist/storage/r2-layer.d.ts.map +1 -0
- package/dist/storage/r2-layer.js +307 -0
- package/dist/storage/r2-layer.js.map +1 -0
- package/dist/storage/r2-overflow.d.ts +344 -0
- package/dist/storage/r2-overflow.d.ts.map +1 -0
- package/dist/storage/r2-overflow.js +730 -0
- package/dist/storage/r2-overflow.js.map +1 -0
- package/dist/storage/r2-page-vfs.d.ts +374 -0
- package/dist/storage/r2-page-vfs.d.ts.map +1 -0
- package/dist/storage/r2-page-vfs.js +754 -0
- package/dist/storage/r2-page-vfs.js.map +1 -0
- package/dist/storage/swr-cache.d.ts +181 -0
- package/dist/storage/swr-cache.d.ts.map +1 -0
- package/dist/storage/swr-cache.js +295 -0
- package/dist/storage/swr-cache.js.map +1 -0
- package/dist/storage/tiered-orchestrator.d.ts +951 -0
- package/dist/storage/tiered-orchestrator.d.ts.map +1 -0
- package/dist/storage/tiered-orchestrator.js +1731 -0
- package/dist/storage/tiered-orchestrator.js.map +1 -0
- package/dist/storage/tiered-vfs-swr.d.ts +279 -0
- package/dist/storage/tiered-vfs-swr.d.ts.map +1 -0
- package/dist/storage/tiered-vfs-swr.js +584 -0
- package/dist/storage/tiered-vfs-swr.js.map +1 -0
- package/dist/storage/tiered-vfs.d.ts +405 -0
- package/dist/storage/tiered-vfs.d.ts.map +1 -0
- package/dist/storage/tiered-vfs.js +833 -0
- package/dist/storage/tiered-vfs.js.map +1 -0
- package/dist/streaming/backpressure-controller.d.ts +173 -0
- package/dist/streaming/backpressure-controller.d.ts.map +1 -0
- package/dist/streaming/backpressure-controller.js +344 -0
- package/dist/streaming/backpressure-controller.js.map +1 -0
- package/dist/streaming/buffer-pool.d.ts +241 -0
- package/dist/streaming/buffer-pool.d.ts.map +1 -0
- package/dist/streaming/buffer-pool.js +381 -0
- package/dist/streaming/buffer-pool.js.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts +272 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.js +408 -0
- package/dist/streaming/cdc-iceberg-connector.js.map +1 -0
- package/dist/streaming/index.d.ts +111 -0
- package/dist/streaming/index.d.ts.map +1 -0
- package/dist/streaming/index.js +128 -0
- package/dist/streaming/index.js.map +1 -0
- package/dist/streaming/live-cdc-stream.d.ts +400 -0
- package/dist/streaming/live-cdc-stream.d.ts.map +1 -0
- package/dist/streaming/live-cdc-stream.js +703 -0
- package/dist/streaming/live-cdc-stream.js.map +1 -0
- package/dist/streaming/memory-bounded-stream.d.ts +207 -0
- package/dist/streaming/memory-bounded-stream.d.ts.map +1 -0
- package/dist/streaming/memory-bounded-stream.js +340 -0
- package/dist/streaming/memory-bounded-stream.js.map +1 -0
- package/dist/streaming/query-streamer.d.ts +379 -0
- package/dist/streaming/query-streamer.d.ts.map +1 -0
- package/dist/streaming/query-streamer.js +495 -0
- package/dist/streaming/query-streamer.js.map +1 -0
- package/dist/streaming/response-streaming.d.ts +203 -0
- package/dist/streaming/response-streaming.d.ts.map +1 -0
- package/dist/streaming/response-streaming.js +449 -0
- package/dist/streaming/response-streaming.js.map +1 -0
- package/dist/types/branded.d.ts +859 -0
- package/dist/types/branded.d.ts.map +1 -0
- package/dist/types/branded.js +891 -0
- package/dist/types/branded.js.map +1 -0
- package/dist/types/utilities.d.ts +757 -0
- package/dist/types/utilities.d.ts.map +1 -0
- package/dist/types/utilities.js +447 -0
- package/dist/types/utilities.js.map +1 -0
- package/dist/wal/replay-engine.d.ts +344 -0
- package/dist/wal/replay-engine.d.ts.map +1 -0
- package/dist/wal/replay-engine.js +975 -0
- package/dist/wal/replay-engine.js.map +1 -0
- package/dist/worker/__mocks__/capnweb.d.ts +13 -0
- package/dist/worker/__mocks__/capnweb.d.ts.map +1 -0
- package/dist/worker/__mocks__/capnweb.js +15 -0
- package/dist/worker/__mocks__/capnweb.js.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts +31 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.js +33 -0
- package/dist/worker/__mocks__/cloudflare-workers.js.map +1 -0
- package/dist/worker/__mocks__/pglite.data.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.data.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.data.js +20 -0
- package/dist/worker/__mocks__/pglite.data.js.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.js +30 -0
- package/dist/worker/__mocks__/pglite.wasm.js.map +1 -0
- package/dist/worker/auth-rate-limiter.d.ts +270 -0
- package/dist/worker/auth-rate-limiter.d.ts.map +1 -0
- package/dist/worker/auth-rate-limiter.js +332 -0
- package/dist/worker/auth-rate-limiter.js.map +1 -0
- package/dist/worker/auth.d.ts +345 -0
- package/dist/worker/auth.d.ts.map +1 -0
- package/dist/worker/auth.js +837 -0
- package/dist/worker/auth.js.map +1 -0
- package/dist/worker/cdc-backpressure.d.ts +338 -0
- package/dist/worker/cdc-backpressure.d.ts.map +1 -0
- package/dist/worker/cdc-backpressure.js +619 -0
- package/dist/worker/cdc-backpressure.js.map +1 -0
- package/dist/worker/cdc-sse.d.ts +277 -0
- package/dist/worker/cdc-sse.d.ts.map +1 -0
- package/dist/worker/cdc-sse.js +528 -0
- package/dist/worker/cdc-sse.js.map +1 -0
- package/dist/worker/cdc-websocket.d.ts +252 -0
- package/dist/worker/cdc-websocket.d.ts.map +1 -0
- package/dist/worker/cdc-websocket.js +940 -0
- package/dist/worker/cdc-websocket.js.map +1 -0
- package/dist/worker/cdc.d.ts +95 -0
- package/dist/worker/cdc.d.ts.map +1 -0
- package/dist/worker/cdc.js +211 -0
- package/dist/worker/cdc.js.map +1 -0
- package/dist/worker/concerns/auth-concern.d.ts +50 -0
- package/dist/worker/concerns/auth-concern.d.ts.map +1 -0
- package/dist/worker/concerns/auth-concern.js +131 -0
- package/dist/worker/concerns/auth-concern.js.map +1 -0
- package/dist/worker/concerns/cdc-concern.d.ts +99 -0
- package/dist/worker/concerns/cdc-concern.d.ts.map +1 -0
- package/dist/worker/concerns/cdc-concern.js +137 -0
- package/dist/worker/concerns/cdc-concern.js.map +1 -0
- package/dist/worker/concerns/index.d.ts +22 -0
- package/dist/worker/concerns/index.d.ts.map +1 -0
- package/dist/worker/concerns/index.js +13 -0
- package/dist/worker/concerns/index.js.map +1 -0
- package/dist/worker/concerns/query-execution-concern.d.ts +104 -0
- package/dist/worker/concerns/query-execution-concern.d.ts.map +1 -0
- package/dist/worker/concerns/query-execution-concern.js +95 -0
- package/dist/worker/concerns/query-execution-concern.js.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts +78 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.js +240 -0
- package/dist/worker/concerns/storage-orchestration-concern.js.map +1 -0
- package/dist/worker/do-auth-manager.d.ts +108 -0
- package/dist/worker/do-auth-manager.d.ts.map +1 -0
- package/dist/worker/do-auth-manager.js +212 -0
- package/dist/worker/do-auth-manager.js.map +1 -0
- package/dist/worker/do-pglite-manager.d.ts +137 -0
- package/dist/worker/do-pglite-manager.d.ts.map +1 -0
- package/dist/worker/do-pglite-manager.js +228 -0
- package/dist/worker/do-pglite-manager.js.map +1 -0
- package/dist/worker/do.d.ts +556 -0
- package/dist/worker/do.d.ts.map +1 -0
- package/dist/worker/do.js +1441 -0
- package/dist/worker/do.js.map +1 -0
- package/dist/worker/entry.d.ts +23 -0
- package/dist/worker/entry.d.ts.map +1 -0
- package/dist/worker/entry.js +362 -0
- package/dist/worker/entry.js.map +1 -0
- package/dist/worker/errors.d.ts +106 -0
- package/dist/worker/errors.d.ts.map +1 -0
- package/dist/worker/errors.js +178 -0
- package/dist/worker/errors.js.map +1 -0
- package/dist/worker/health-check-manager.d.ts +141 -0
- package/dist/worker/health-check-manager.d.ts.map +1 -0
- package/dist/worker/health-check-manager.js +145 -0
- package/dist/worker/health-check-manager.js.map +1 -0
- package/dist/worker/index.d.ts +60 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +67 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/memory-pressure.d.ts +892 -0
- package/dist/worker/memory-pressure.d.ts.map +1 -0
- package/dist/worker/memory-pressure.js +1990 -0
- package/dist/worker/memory-pressure.js.map +1 -0
- package/dist/worker/migration-manager.d.ts +153 -0
- package/dist/worker/migration-manager.d.ts.map +1 -0
- package/dist/worker/migration-manager.js +461 -0
- package/dist/worker/migration-manager.js.map +1 -0
- package/dist/worker/plugin-manager.d.ts +147 -0
- package/dist/worker/plugin-manager.d.ts.map +1 -0
- package/dist/worker/plugin-manager.js +408 -0
- package/dist/worker/plugin-manager.js.map +1 -0
- package/dist/worker/proxy.d.ts +330 -0
- package/dist/worker/proxy.d.ts.map +1 -0
- package/dist/worker/proxy.js +504 -0
- package/dist/worker/proxy.js.map +1 -0
- package/dist/worker/query-execution-manager.d.ts +107 -0
- package/dist/worker/query-execution-manager.d.ts.map +1 -0
- package/dist/worker/query-execution-manager.js +155 -0
- package/dist/worker/query-execution-manager.js.map +1 -0
- package/dist/worker/query-executor.d.ts +163 -0
- package/dist/worker/query-executor.d.ts.map +1 -0
- package/dist/worker/query-executor.js +413 -0
- package/dist/worker/query-executor.js.map +1 -0
- package/dist/worker/query-stats-manager.d.ts +117 -0
- package/dist/worker/query-stats-manager.d.ts.map +1 -0
- package/dist/worker/query-stats-manager.js +162 -0
- package/dist/worker/query-stats-manager.js.map +1 -0
- package/dist/worker/result-handler.d.ts +192 -0
- package/dist/worker/result-handler.d.ts.map +1 -0
- package/dist/worker/result-handler.js +346 -0
- package/dist/worker/result-handler.js.map +1 -0
- package/dist/worker/routes.d.ts +135 -0
- package/dist/worker/routes.d.ts.map +1 -0
- package/dist/worker/routes.js +460 -0
- package/dist/worker/routes.js.map +1 -0
- package/dist/worker/rpc-methods-manager.d.ts +142 -0
- package/dist/worker/rpc-methods-manager.d.ts.map +1 -0
- package/dist/worker/rpc-methods-manager.js +195 -0
- package/dist/worker/rpc-methods-manager.js.map +1 -0
- package/dist/worker/rpc.d.ts +259 -0
- package/dist/worker/rpc.d.ts.map +1 -0
- package/dist/worker/rpc.js +398 -0
- package/dist/worker/rpc.js.map +1 -0
- package/dist/worker/schema-version.d.ts +209 -0
- package/dist/worker/schema-version.d.ts.map +1 -0
- package/dist/worker/schema-version.js +450 -0
- package/dist/worker/schema-version.js.map +1 -0
- package/dist/worker/session-manager.d.ts +282 -0
- package/dist/worker/session-manager.d.ts.map +1 -0
- package/dist/worker/session-manager.js +523 -0
- package/dist/worker/session-manager.js.map +1 -0
- package/dist/worker/shutdown-manager.d.ts +188 -0
- package/dist/worker/shutdown-manager.d.ts.map +1 -0
- package/dist/worker/shutdown-manager.js +347 -0
- package/dist/worker/shutdown-manager.js.map +1 -0
- package/dist/worker/sql-transform.d.ts +61 -0
- package/dist/worker/sql-transform.d.ts.map +1 -0
- package/dist/worker/sql-transform.js +312 -0
- package/dist/worker/sql-transform.js.map +1 -0
- package/dist/worker/types.d.ts +738 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +6 -0
- package/dist/worker/types.js.map +1 -0
- package/dist/worker/user-routes.d.ts +76 -0
- package/dist/worker/user-routes.d.ts.map +1 -0
- package/dist/worker/user-routes.js +188 -0
- package/dist/worker/user-routes.js.map +1 -0
- package/dist/worker/wal-facade.d.ts +138 -0
- package/dist/worker/wal-facade.d.ts.map +1 -0
- package/dist/worker/wal-facade.js +184 -0
- package/dist/worker/wal-facade.js.map +1 -0
- package/dist/worker/wal-r2.d.ts +271 -0
- package/dist/worker/wal-r2.d.ts.map +1 -0
- package/dist/worker/wal-r2.js +689 -0
- package/dist/worker/wal-r2.js.map +1 -0
- package/dist/worker/wal-replay.d.ts +361 -0
- package/dist/worker/wal-replay.d.ts.map +1 -0
- package/dist/worker/wal-replay.js +628 -0
- package/dist/worker/wal-replay.js.map +1 -0
- package/dist/worker/wal-retention.d.ts +389 -0
- package/dist/worker/wal-retention.d.ts.map +1 -0
- package/dist/worker/wal-retention.js +763 -0
- package/dist/worker/wal-retention.js.map +1 -0
- package/dist/worker/wal.d.ts +278 -0
- package/dist/worker/wal.d.ts.map +1 -0
- package/dist/worker/wal.js +467 -0
- package/dist/worker/wal.js.map +1 -0
- package/dist/worker/websocket.d.ts +85 -0
- package/dist/worker/websocket.d.ts.map +1 -0
- package/dist/worker/websocket.js +227 -0
- package/dist/worker/websocket.js.map +1 -0
- package/package.json +108 -0
- package/src/cdc/change-stream.ts +137 -0
- package/src/cdc/filter.ts +646 -0
- package/src/cdc/index.ts +112 -0
- package/src/cdc/resume-token.ts +280 -0
- package/src/cdc/transport/index.ts +7 -0
- package/src/cdc/transport/sse.ts +723 -0
- package/src/cdc/transport/websocket.ts +873 -0
- package/src/cdc/types.ts +346 -0
- package/src/config/index.ts +25 -0
- package/src/config/memory.ts +177 -0
- package/src/config/storage.ts +204 -0
- package/src/config/streaming.ts +147 -0
- package/src/config/timeouts.ts +221 -0
- package/src/extensions/config.test.ts +187 -0
- package/src/extensions/config.ts +278 -0
- package/src/extensions/geo.test.ts +455 -0
- package/src/extensions/geo.ts +858 -0
- package/src/extensions/index.test.ts +259 -0
- package/src/extensions/index.ts +227 -0
- package/src/extensions/loader.test.ts +555 -0
- package/src/extensions/loader.ts +588 -0
- package/src/extensions/pgmq-lite.test.ts +727 -0
- package/src/extensions/pgmq-lite.ts +770 -0
- package/src/extensions/plugins.test.ts +528 -0
- package/src/extensions/plugins.ts +718 -0
- package/src/extensions/registry.test.ts +202 -0
- package/src/extensions/registry.ts +267 -0
- package/src/extensions/vector.test.ts +195 -0
- package/src/extensions/vector.ts +217 -0
- package/src/iceberg/SCHEDULER.md +580 -0
- package/src/iceberg/analytics.test.ts +703 -0
- package/src/iceberg/analytics.ts +727 -0
- package/src/iceberg/catalog-api.test.ts +838 -0
- package/src/iceberg/catalog-api.ts +520 -0
- package/src/iceberg/catalog.test.ts +680 -0
- package/src/iceberg/catalog.ts +1007 -0
- package/src/iceberg/iceberg.test.ts +705 -0
- package/src/iceberg/index.ts +406 -0
- package/src/iceberg/metadata.test.ts +632 -0
- package/src/iceberg/metadata.ts +649 -0
- package/src/iceberg/optimizer.test.ts +868 -0
- package/src/iceberg/optimizer.ts +1287 -0
- package/src/iceberg/parquet.test.ts +899 -0
- package/src/iceberg/parquet.ts +1640 -0
- package/src/iceberg/r2-organization.test.ts +615 -0
- package/src/iceberg/r2-organization.ts +951 -0
- package/src/iceberg/scheduler-do-example.ts +364 -0
- package/src/iceberg/scheduler.test.ts +861 -0
- package/src/iceberg/scheduler.ts +1201 -0
- package/src/iceberg/schema.test.ts +547 -0
- package/src/iceberg/schema.ts +616 -0
- package/src/iceberg/snapshot-manager.test.ts +919 -0
- package/src/iceberg/snapshot-manager.ts +1369 -0
- package/src/iceberg/sql-router.test.ts +334 -0
- package/src/iceberg/sql-router.ts +337 -0
- package/src/iceberg/test-fixtures.ts +605 -0
- package/src/iceberg/time-travel-api.test.ts +1029 -0
- package/src/iceberg/time-travel-api.ts +731 -0
- package/src/iceberg/time-travel.test.ts +1218 -0
- package/src/iceberg/time-travel.ts +1052 -0
- package/src/iceberg/transformer.test.ts +689 -0
- package/src/iceberg/transformer.ts +1029 -0
- package/src/iceberg/types.ts +373 -0
- package/src/iceberg/writer.test.ts +716 -0
- package/src/iceberg/writer.ts +590 -0
- package/src/index.ts +212 -0
- package/src/lineage/index.ts +42 -0
- package/src/lineage/integration.ts +334 -0
- package/src/lineage/tracker.ts +1618 -0
- package/src/lineage/types.ts +354 -0
- package/src/middleware/index.ts +36 -0
- package/src/middleware/rate-limit-concurrent.test.ts +794 -0
- package/src/middleware/rate-limit.test.ts +1568 -0
- package/src/middleware/rate-limit.ts +840 -0
- package/src/migration-tooling/external-migration.test.ts +1864 -0
- package/src/migration-tooling/external-migration.ts +2355 -0
- package/src/migration-tooling/index.ts +19 -0
- package/src/migrations/ARCHITECTURE.md +474 -0
- package/src/migrations/PROGRESS_TRACKING.md +485 -0
- package/src/migrations/auto-migrator.test.ts +732 -0
- package/src/migrations/auto-migrator.ts +531 -0
- package/src/migrations/bulk-orchestrator.test.ts +801 -0
- package/src/migrations/bulk-orchestrator.ts +1039 -0
- package/src/migrations/compatibility.test.ts +958 -0
- package/src/migrations/compatibility.ts +902 -0
- package/src/migrations/do-migrations.test.ts +2620 -0
- package/src/migrations/do-migrations.ts +1289 -0
- package/src/migrations/do-migrations.types.ts +715 -0
- package/src/migrations/drizzle-compat.test.ts +210 -0
- package/src/migrations/drizzle-compat.ts +337 -0
- package/src/migrations/index.ts +334 -0
- package/src/migrations/migration-api.test.ts +438 -0
- package/src/migrations/migration-api.ts +704 -0
- package/src/migrations/progress-tracker-do.ts +518 -0
- package/src/migrations/progress-tracker-kv.ts +305 -0
- package/src/migrations/progress-tracker.test.ts +937 -0
- package/src/migrations/progress-tracker.ts +665 -0
- package/src/migrations/registry.test.ts +331 -0
- package/src/migrations/registry.ts +468 -0
- package/src/migrations/rollback.test.ts +644 -0
- package/src/migrations/runner.test.ts +807 -0
- package/src/migrations/runner.test.ts.backup +759 -0
- package/src/migrations/runner.ts +1459 -0
- package/src/migrations/schema-generator.test.ts +649 -0
- package/src/migrations/schema-generator.ts +513 -0
- package/src/migrations/testing.ts +1037 -0
- package/src/migrations/types.ts +573 -0
- package/src/migrations/validator.test.ts +660 -0
- package/src/migrations/validator.ts +741 -0
- package/src/observability/alerting.test.ts +1133 -0
- package/src/observability/alerting.ts +455 -0
- package/src/observability/analytics-engine.ts +733 -0
- package/src/observability/cost-metrics.ts +804 -0
- package/src/observability/cross-do-tracing.test.ts +516 -0
- package/src/observability/cross-do-tracing.ts +588 -0
- package/src/observability/dashboards/postgres-do-overview.json +1656 -0
- package/src/observability/error-rate-collector.test.ts +977 -0
- package/src/observability/error-rate-collector.ts +518 -0
- package/src/observability/exporters.test.ts +365 -0
- package/src/observability/exporters.ts +650 -0
- package/src/observability/health-check.test.ts +353 -0
- package/src/observability/health-check.ts +341 -0
- package/src/observability/index.test.ts +298 -0
- package/src/observability/index.ts +885 -0
- package/src/observability/instrumentation.test.ts +428 -0
- package/src/observability/instrumentation.ts +788 -0
- package/src/observability/memory-metrics.test.ts +355 -0
- package/src/observability/memory-metrics.ts +990 -0
- package/src/observability/metrics-endpoint.test.ts +402 -0
- package/src/observability/metrics-endpoint.ts +374 -0
- package/src/observability/metrics.test.ts +291 -0
- package/src/observability/metrics.ts +315 -0
- package/src/observability/observability-features.ts +1296 -0
- package/src/observability/prometheus.test.ts +292 -0
- package/src/observability/prometheus.ts +170 -0
- package/src/observability/propagation.test.ts +417 -0
- package/src/observability/propagation.ts +294 -0
- package/src/observability/query-latency.ts +586 -0
- package/src/observability/query-performance.test.ts +406 -0
- package/src/observability/query-performance.ts +491 -0
- package/src/observability/storage-tier-metrics.test.ts +633 -0
- package/src/observability/storage-tier-metrics.ts +570 -0
- package/src/observability/tier-cost-optimizer.ts +740 -0
- package/src/observability/tracer.test.ts +346 -0
- package/src/observability/tracer.ts +585 -0
- package/src/observability/types.test.ts +726 -0
- package/src/observability/types.ts +434 -0
- package/src/pglite/auto-demotion.test.ts +477 -0
- package/src/pglite/auto-demotion.ts +385 -0
- package/src/pglite/auto-promotion.test.ts +824 -0
- package/src/pglite/auto-promotion.ts +547 -0
- package/src/pglite/cache-layer.test.ts +469 -0
- package/src/pglite/cache-layer.ts +271 -0
- package/src/pglite/cold-start-manager.ts +1260 -0
- package/src/pglite/cold-start-optimizer.test.ts +937 -0
- package/src/pglite/cold-start-optimizer.ts +1895 -0
- package/src/pglite/dovfs-adapter.ts +1122 -0
- package/src/pglite/dovfs.ts +1258 -0
- package/src/pglite/etag-cache.test.ts +844 -0
- package/src/pglite/etag-cache.ts +526 -0
- package/src/pglite/index.ts +442 -0
- package/src/pglite/init.test.ts +455 -0
- package/src/pglite/init.ts +574 -0
- package/src/pglite/lifecycle.test.ts +599 -0
- package/src/pglite/lifecycle.ts +704 -0
- package/src/pglite/parallel-loader.test.ts +586 -0
- package/src/pglite/parallel-loader.ts +481 -0
- package/src/pglite/production-pglite.test.ts +666 -0
- package/src/pglite/production-pglite.ts +537 -0
- package/src/pglite/query-executor.ts +614 -0
- package/src/pglite/r2-layer.test.ts +501 -0
- package/src/pglite/r2-layer.ts +322 -0
- package/src/pglite/tiered-init.test.ts +725 -0
- package/src/pglite/tiered-init.ts +556 -0
- package/src/pglite/tiered-vfs.test.ts +726 -0
- package/src/pglite/tiered-vfs.ts +33 -0
- package/src/pglite/tiering-stats.test.ts +531 -0
- package/src/pglite/tiering-stats.ts +407 -0
- package/src/pglite/transaction-hooks.ts +343 -0
- package/src/pglite/warm-loader.test.ts +1701 -0
- package/src/pglite/warm-loader.ts +528 -0
- package/src/pglite/workers-pglite.ts +224 -0
- package/src/pglite-assets/pglite.data +0 -0
- package/src/pglite-assets/pglite.wasm +0 -0
- package/src/pglite.d.ts +47 -0
- package/src/playground/index.ts +137 -0
- package/src/playground/keyboard-shortcuts.ts +677 -0
- package/src/playground/playground.ts +323 -0
- package/src/playground/query-executor.ts +669 -0
- package/src/playground/query-history.ts +328 -0
- package/src/playground/result-formatter.ts +420 -0
- package/src/playground/sample-datasets.ts +674 -0
- package/src/playground/sample-queries.ts +1168 -0
- package/src/playground/schema-explorer.ts +558 -0
- package/src/playground/types.ts +518 -0
- package/src/readonly/cache-reader.test.ts +460 -0
- package/src/readonly/cache-reader.ts +313 -0
- package/src/readonly/config.test.ts +187 -0
- package/src/readonly/config.ts +128 -0
- package/src/readonly/index.ts +50 -0
- package/src/readonly/pglite-wrapper.test.ts +278 -0
- package/src/readonly/pglite-wrapper.ts +184 -0
- package/src/readonly/worker.test.ts +533 -0
- package/src/readonly/worker.ts +341 -0
- package/src/readonly/write-blocker.test.ts +459 -0
- package/src/readonly/write-blocker.ts +175 -0
- package/src/recovery/disaster-recovery.test.ts +618 -0
- package/src/recovery/disaster-recovery.ts +1181 -0
- package/src/recovery/index.ts +43 -0
- package/src/recovery/parquet-parser.ts +974 -0
- package/src/retention/index.ts +74 -0
- package/src/retention/policy.test.ts +571 -0
- package/src/retention/policy.ts +774 -0
- package/src/retention/purger.test.ts +465 -0
- package/src/retention/purger.ts +558 -0
- package/src/rls/auth-integration.test.ts +752 -0
- package/src/rls/auth-integration.ts +533 -0
- package/src/rls/generator.test.ts +829 -0
- package/src/rls/generator.ts +573 -0
- package/src/rls/index.ts +128 -0
- package/src/rls/policy.ts +208 -0
- package/src/rls/rls.test.ts +1071 -0
- package/src/rls/validator.test.ts +930 -0
- package/src/rls/validator.ts +895 -0
- package/src/routing/adaptive-router.test.ts +884 -0
- package/src/routing/adaptive-router.ts +845 -0
- package/src/routing/circuit-breaker.test.ts +1505 -0
- package/src/routing/circuit-breaker.ts +852 -0
- package/src/routing/cost-metrics.test.ts +565 -0
- package/src/routing/cost-metrics.ts +408 -0
- package/src/routing/do-connection-pool.test.ts +1109 -0
- package/src/routing/do-connection-pool.ts +828 -0
- package/src/routing/index.ts +158 -0
- package/src/routing/query-complexity-estimator.test.ts +356 -0
- package/src/routing/query-complexity-estimator.ts +444 -0
- package/src/routing/request-coalescing.test.ts +738 -0
- package/src/routing/request-coalescing.ts +475 -0
- package/src/routing/runtime-router.test.ts +436 -0
- package/src/routing/runtime-router.ts +357 -0
- package/src/routing/tenant-router.test.ts +2493 -0
- package/src/routing/tenant-router.ts +1908 -0
- package/src/routing/websocket-pool.test.ts +551 -0
- package/src/routing/websocket-pool.ts +577 -0
- package/src/storage/access-pattern-tracker.test.ts +874 -0
- package/src/storage/cache-layer.test.ts +560 -0
- package/src/storage/cache-layer.ts +328 -0
- package/src/storage/cost-aware-tiering.test.ts +652 -0
- package/src/storage/cost-aware-tiering.ts +794 -0
- package/src/storage/do-sqlite-blobs.test.ts +937 -0
- package/src/storage/index.ts +272 -0
- package/src/storage/interfaces.ts +974 -0
- package/src/storage/r2-layer.test.ts +653 -0
- package/src/storage/r2-layer.ts +434 -0
- package/src/storage/r2-overflow.ts +920 -0
- package/src/storage/r2-page-vfs.test.ts +2348 -0
- package/src/storage/r2-page-vfs.ts +1054 -0
- package/src/storage/swr-cache.test.ts +832 -0
- package/src/storage/swr-cache.ts +398 -0
- package/src/storage/swr-tiered-integration.test.ts +617 -0
- package/src/storage/tiered-orchestrator.test.ts +2441 -0
- package/src/storage/tiered-orchestrator.ts +2081 -0
- package/src/storage/tiered-vfs-swr.test.ts +736 -0
- package/src/storage/tiered-vfs-swr.ts +735 -0
- package/src/storage/tiered-vfs.test.ts +793 -0
- package/src/storage/tiered-vfs.ts +1082 -0
- package/src/streaming/backpressure-controller.ts +452 -0
- package/src/streaming/buffer-pool.ts +484 -0
- package/src/streaming/cdc-iceberg-connector.ts +605 -0
- package/src/streaming/index.ts +225 -0
- package/src/streaming/live-cdc-stream.ts +985 -0
- package/src/streaming/memory-bounded-stream.ts +443 -0
- package/src/streaming/query-streamer.ts +662 -0
- package/src/streaming/response-streaming.ts +557 -0
- package/src/types/branded.ts +1075 -0
- package/src/types/branded.ts.backup +273 -0
- package/src/types/utilities.ts +1023 -0
- package/src/types/wasm.d.ts +30 -0
- package/src/validation/typed-errors.test.ts +420 -0
- package/src/wal/replay-engine.ts +1264 -0
- package/src/worker/__mocks__/capnweb.ts +15 -0
- package/src/worker/__mocks__/pglite.data.ts +22 -0
- package/src/worker/__mocks__/pglite.wasm.ts +33 -0
- package/src/worker/auth-rate-limiter.test.ts +272 -0
- package/src/worker/auth-rate-limiter.ts +448 -0
- package/src/worker/auth.security-red.test.ts +1236 -0
- package/src/worker/auth.security.test.ts +822 -0
- package/src/worker/auth.test.ts +469 -0
- package/src/worker/auth.ts +1104 -0
- package/src/worker/cdc-backpressure.test.ts +726 -0
- package/src/worker/cdc-backpressure.ts +866 -0
- package/src/worker/cdc-sse.test.ts +780 -0
- package/src/worker/cdc-sse.ts +728 -0
- package/src/worker/cdc-websocket.ts +1229 -0
- package/src/worker/cdc-ws.test.ts +1009 -0
- package/src/worker/cdc.test.ts +327 -0
- package/src/worker/cdc.ts +289 -0
- package/src/worker/concerns/auth-concern.ts +179 -0
- package/src/worker/concerns/cdc-concern.ts +247 -0
- package/src/worker/concerns/index.ts +58 -0
- package/src/worker/concerns/query-execution-concern.ts +194 -0
- package/src/worker/concerns/storage-orchestration-concern.ts +373 -0
- package/src/worker/discriminated-types.test.ts +280 -0
- package/src/worker/do-auth-manager.ts +257 -0
- package/src/worker/do-decomposition.test.ts +1236 -0
- package/src/worker/do-pglite-manager.ts +302 -0
- package/src/worker/do.test.ts +2254 -0
- package/src/worker/do.ts +1878 -0
- package/src/worker/entry.ts +417 -0
- package/src/worker/errors.ts +285 -0
- package/src/worker/health-check-manager.test.ts +261 -0
- package/src/worker/health-check-manager.ts +231 -0
- package/src/worker/index.ts +389 -0
- package/src/worker/memory-pressure.test.ts +1460 -0
- package/src/worker/memory-pressure.ts +2650 -0
- package/src/worker/migration-manager.ts +582 -0
- package/src/worker/neon-compat.test.ts +332 -0
- package/src/worker/plugin-manager.ts +485 -0
- package/src/worker/postgres.do-rpc.d.ts +76 -0
- package/src/worker/proxy.ts +694 -0
- package/src/worker/query-execution-manager.test.ts +303 -0
- package/src/worker/query-execution-manager.ts +219 -0
- package/src/worker/query-executor.test.ts +282 -0
- package/src/worker/query-executor.ts +560 -0
- package/src/worker/query-stats-manager.ts +229 -0
- package/src/worker/result-handler.test.ts +364 -0
- package/src/worker/result-handler.ts +510 -0
- package/src/worker/routes.test.ts +795 -0
- package/src/worker/routes.ts +650 -0
- package/src/worker/rpc-methods-manager.test.ts +326 -0
- package/src/worker/rpc-methods-manager.ts +276 -0
- package/src/worker/rpc.ts +524 -0
- package/src/worker/schema-version.ts +605 -0
- package/src/worker/session-manager.test.ts +506 -0
- package/src/worker/session-manager.ts +732 -0
- package/src/worker/shutdown-manager.ts +469 -0
- package/src/worker/sql-transform.test.ts +286 -0
- package/src/worker/sql-transform.ts +368 -0
- package/src/worker/supabase-compat.test.ts +621 -0
- package/src/worker/types.test.ts +292 -0
- package/src/worker/types.ts +873 -0
- package/src/worker/user-routes.test.ts +703 -0
- package/src/worker/user-routes.ts +303 -0
- package/src/worker/wal-facade.ts +235 -0
- package/src/worker/wal-r2.test.ts +570 -0
- package/src/worker/wal-r2.ts +930 -0
- package/src/worker/wal-replay.test.ts +845 -0
- package/src/worker/wal-replay.ts +897 -0
- package/src/worker/wal-retention.test.ts +758 -0
- package/src/worker/wal-retention.ts +1075 -0
- package/src/worker/wal.test.ts +618 -0
- package/src/worker/wal.ts +697 -0
- package/src/worker/websocket.test.ts +296 -0
- package/src/worker/websocket.ts +284 -0
|
@@ -0,0 +1,1731 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TieredStorageOrchestrator - Unified Tiering System
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the complete tiered storage system with:
|
|
5
|
+
* - HOT tier: Cloudflare Cache API (FREE, sync-fast)
|
|
6
|
+
* - WARM tier: Durable Object SQLite blobs (persistent, sync)
|
|
7
|
+
* - COLD tier: R2 object storage (cheap, async)
|
|
8
|
+
*
|
|
9
|
+
* ## Features
|
|
10
|
+
* - Unified read/write interface across all tiers
|
|
11
|
+
* - Automatic promotion based on access patterns
|
|
12
|
+
* - Automatic demotion based on TTL/LRU policies
|
|
13
|
+
* - Cost-optimized tier selection
|
|
14
|
+
* - Comprehensive statistics and monitoring
|
|
15
|
+
* - Storage index for fast tier location lookups
|
|
16
|
+
* - DO lifecycle hooks (hibernate/wake)
|
|
17
|
+
* - Circuit breaker pattern for tier failures
|
|
18
|
+
* - Integrated metrics aggregation
|
|
19
|
+
* - Bounded storage index with LRU/LFU eviction
|
|
20
|
+
* - Detailed performance metrics and memory tracking
|
|
21
|
+
*
|
|
22
|
+
* ## Performance Characteristics
|
|
23
|
+
*
|
|
24
|
+
* ### Storage Index Operations
|
|
25
|
+
*
|
|
26
|
+
* | Operation | Time Complexity | Typical Latency |
|
|
27
|
+
* |---------------|-----------------|-----------------|
|
|
28
|
+
* | Lookup (get) | O(1) average | 50-100ns |
|
|
29
|
+
* | Update (set) | O(1) amortized | 100-200ns |
|
|
30
|
+
* | Delete | O(1) | 50-100ns |
|
|
31
|
+
* | Eviction | O(n*k) | 1-10ms/batch |
|
|
32
|
+
* | Maintenance | O(n * storage) | 100ms-10s |
|
|
33
|
+
*
|
|
34
|
+
* Where:
|
|
35
|
+
* - n = total index entries
|
|
36
|
+
* - k = eviction batch size (typically 10-20)
|
|
37
|
+
*
|
|
38
|
+
* ### Memory Usage
|
|
39
|
+
*
|
|
40
|
+
* The storage index uses approximately:
|
|
41
|
+
* - 120 bytes per entry (base overhead)
|
|
42
|
+
* - 2 bytes per character in key (JS UTF-16 strings)
|
|
43
|
+
* - ~10% additional for Map internal structures
|
|
44
|
+
*
|
|
45
|
+
* **Example Memory Usage:**
|
|
46
|
+
* | Entries | Avg Key Length | Estimated Memory |
|
|
47
|
+
* |---------|----------------|------------------|
|
|
48
|
+
* | 1,000 | 20 chars | ~160 KB |
|
|
49
|
+
* | 10,000 | 20 chars | ~1.6 MB |
|
|
50
|
+
* | 100,000 | 20 chars | ~16 MB |
|
|
51
|
+
* | 10,000 | 100 chars | ~3.2 MB |
|
|
52
|
+
*
|
|
53
|
+
* ### Eviction Policies
|
|
54
|
+
*
|
|
55
|
+
* - **LRU (default)**: Evicts least recently accessed entries first.
|
|
56
|
+
* Best for: Most workloads with temporal locality.
|
|
57
|
+
*
|
|
58
|
+
* - **LFU**: Evicts least frequently accessed entries first.
|
|
59
|
+
* Best for: Workloads with stable hot sets.
|
|
60
|
+
*
|
|
61
|
+
* - **FIFO**: Evicts oldest entries by creation time.
|
|
62
|
+
* Best for: Predictable eviction patterns, debugging.
|
|
63
|
+
*
|
|
64
|
+
* ### Hot Path Optimizations
|
|
65
|
+
*
|
|
66
|
+
* 1. **Single-entry lookup cache**: Avoids redundant Map lookups when
|
|
67
|
+
* the same key is accessed multiple times in quick succession
|
|
68
|
+
* (e.g., read -> check promotion -> update index).
|
|
69
|
+
*
|
|
70
|
+
* 2. **Partial selection for eviction**: Uses O(n*k) partial selection
|
|
71
|
+
* instead of O(n log n) full sort when evicting k entries from n.
|
|
72
|
+
*
|
|
73
|
+
* 3. **Batch eviction**: Amortizes eviction overhead across multiple
|
|
74
|
+
* entries (configurable via indexEvictionBatchSize).
|
|
75
|
+
*
|
|
76
|
+
* ### Configuration Guidelines
|
|
77
|
+
*
|
|
78
|
+
* For Cloudflare Workers (128MB limit):
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const orchestrator = createTieredStorageOrchestrator({
|
|
81
|
+
* // ... other config
|
|
82
|
+
* maxIndexEntries: 50000, // ~8MB for 20-char keys
|
|
83
|
+
* maxIndexMemoryBytes: 8 * 1024 * 1024, // 8MB hard limit
|
|
84
|
+
* indexEvictionPolicy: 'lru',
|
|
85
|
+
* indexEvictionBatchSize: 100, // Evict 100 at a time
|
|
86
|
+
* indexMemoryWarningThreshold: 0.8, // Warn at 80%
|
|
87
|
+
* })
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @module storage/tiered-orchestrator
|
|
91
|
+
*/
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Imported Configuration Constants (from centralized config)
|
|
94
|
+
// =============================================================================
|
|
95
|
+
import { DEFAULT_PAGE_SIZE, DEFAULT_COLD_TO_WARM_THRESHOLD, DEFAULT_WARM_TO_HOT_THRESHOLD, DEFAULT_PROMOTION_WINDOW_MS, DEFAULT_HOT_TTL_MS, DEFAULT_WARM_TTL_MS, DEFAULT_MAX_HOT_PAGES, DEFAULT_DO_PREFIX, DEFAULT_INDEX_EVICTION_BATCH_SIZE, DEFAULT_CIRCUIT_BREAKER_FAILURE_THRESHOLD, DEFAULT_CIRCUIT_BREAKER_RESET_TIMEOUT_MS, } from '../config/storage';
|
|
96
|
+
import { INDEX_ENTRY_MEMORY_OVERHEAD_BYTES, MAP_OVERHEAD_ESTIMATE_PERCENT, DEFAULT_INDEX_MEMORY_WARNING_THRESHOLD, INDEX_MEMORY_EVICTION_TARGET, DEFAULT_AVG_KEY_LENGTH_CHARS, } from '../config/memory';
|
|
97
|
+
import { assertNever } from '../types/utilities';
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Local Constant Aliases (for internal use)
|
|
100
|
+
// =============================================================================
|
|
101
|
+
/** Estimated bytes per index entry */
|
|
102
|
+
const ENTRY_MEMORY_OVERHEAD_BYTES = INDEX_ENTRY_MEMORY_OVERHEAD_BYTES;
|
|
103
|
+
// =============================================================================
|
|
104
|
+
// Timing Constants (local)
|
|
105
|
+
// =============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Nanoseconds per millisecond for performance timing conversions.
|
|
108
|
+
*/
|
|
109
|
+
const NS_PER_MS = 1e6;
|
|
110
|
+
/**
|
|
111
|
+
* TieredStorageOrchestrator - Unified Tiering System
|
|
112
|
+
*
|
|
113
|
+
* ## Performance Characteristics
|
|
114
|
+
*
|
|
115
|
+
* ### Index Operations
|
|
116
|
+
* - **Lookup (get)**: O(1) average via Map, ~50-100ns typical
|
|
117
|
+
* - **Update (set/modify)**: O(1) average for existing keys, O(1) amortized for new keys
|
|
118
|
+
* - **Eviction**: O(n*k) where n=index size, k=batch size; optimized for k << n
|
|
119
|
+
*
|
|
120
|
+
* ### Memory Usage
|
|
121
|
+
* - **Per-entry overhead**: ~120 bytes base + 2 bytes per character in key
|
|
122
|
+
* - **Example**: 10,000 entries with 20-char keys = ~1.6MB
|
|
123
|
+
* - **Warning threshold**: Configurable, default 80% of maxIndexMemoryBytes
|
|
124
|
+
*
|
|
125
|
+
* ### Eviction Policies
|
|
126
|
+
* - **LRU (default)**: Evicts least recently accessed entries
|
|
127
|
+
* - **LFU**: Evicts least frequently accessed entries
|
|
128
|
+
* - **FIFO**: Evicts oldest entries by creation time
|
|
129
|
+
*
|
|
130
|
+
* ### Hot Path Optimizations
|
|
131
|
+
* - Index entry caching for repeated lookups within promotion checks
|
|
132
|
+
* - Batch eviction to amortize overhead across multiple entries
|
|
133
|
+
* - Partial selection algorithm for eviction (avoids full sort when k << n)
|
|
134
|
+
*
|
|
135
|
+
* @class TieredStorageOrchestrator
|
|
136
|
+
*/
|
|
137
|
+
export class TieredStorageOrchestrator {
|
|
138
|
+
cacheLayer;
|
|
139
|
+
swrCacheLayer;
|
|
140
|
+
doStorage;
|
|
141
|
+
r2Layer;
|
|
142
|
+
config;
|
|
143
|
+
storageIndex = new Map();
|
|
144
|
+
indexMemoryBytes = 0;
|
|
145
|
+
maintenanceTimer;
|
|
146
|
+
memoryWarningEmitted = false;
|
|
147
|
+
/**
|
|
148
|
+
* Estimated memory per index entry (key string avg ~20 chars + StorageIndexEntry object)
|
|
149
|
+
*
|
|
150
|
+
* Breakdown:
|
|
151
|
+
* - Map entry overhead: ~40 bytes (key reference + value reference + internal structure)
|
|
152
|
+
* - StorageIndexEntry object: ~80 bytes (8 bytes per field * 9 fields + object overhead)
|
|
153
|
+
* - Total base: ~120 bytes per entry
|
|
154
|
+
* - Key string: 2 bytes per character (JS strings are UTF-16)
|
|
155
|
+
*/
|
|
156
|
+
static ENTRY_MEMORY_OVERHEAD = ENTRY_MEMORY_OVERHEAD_BYTES;
|
|
157
|
+
// Performance metrics tracking
|
|
158
|
+
performanceMetrics = {
|
|
159
|
+
lookupCount: 0,
|
|
160
|
+
updateCount: 0,
|
|
161
|
+
evictionCount: 0,
|
|
162
|
+
totalLookupTimeNs: 0,
|
|
163
|
+
totalUpdateTimeNs: 0,
|
|
164
|
+
totalEvictionTimeNs: 0,
|
|
165
|
+
totalMaintenanceTimeNs: 0,
|
|
166
|
+
peakEntries: 0,
|
|
167
|
+
peakMemoryBytes: 0,
|
|
168
|
+
evictionBatches: 0,
|
|
169
|
+
cacheHitCount: 0,
|
|
170
|
+
cacheMissCount: 0,
|
|
171
|
+
};
|
|
172
|
+
// Cache for hot path optimization - stores recently looked up entry for promotion check
|
|
173
|
+
lastLookupKey = null;
|
|
174
|
+
lastLookupEntry = null;
|
|
175
|
+
warmStats = { reads: 0, writes: 0, deletes: 0, bytesRead: 0, bytesWritten: 0 };
|
|
176
|
+
promotionStats = { coldToWarm: 0, warmToHot: 0, failed: 0 };
|
|
177
|
+
demotionStats = { hotToWarm: 0, warmToCold: 0, failed: 0 };
|
|
178
|
+
tierHealth = { hot: 'healthy', warm: 'healthy', cold: 'healthy' };
|
|
179
|
+
circuitBreakers = {
|
|
180
|
+
hot: { state: 'closed', failures: 0 },
|
|
181
|
+
warm: { state: 'closed', failures: 0 },
|
|
182
|
+
cold: { state: 'closed', failures: 0 },
|
|
183
|
+
};
|
|
184
|
+
latencyMeasurements = [];
|
|
185
|
+
demotionStrategies = {};
|
|
186
|
+
autoPromoterConfig = { coldToWarm: { accessThreshold: DEFAULT_COLD_TO_WARM_THRESHOLD }, warmToHot: { accessThreshold: DEFAULT_WARM_TO_HOT_THRESHOLD } };
|
|
187
|
+
autoDemoterConfig = { hotToWarm: { ttlMs: DEFAULT_HOT_TTL_MS }, warmToCold: { ttlMs: DEFAULT_WARM_TTL_MS } };
|
|
188
|
+
migrationCallbacks = [];
|
|
189
|
+
errorObservers = [];
|
|
190
|
+
circuitBreakerObservers = [];
|
|
191
|
+
errorCounts = { hot: 0, warm: 0, cold: 0 };
|
|
192
|
+
keyRetryCounters = new Map();
|
|
193
|
+
correlationCounter = 0;
|
|
194
|
+
eventSubscribers = [];
|
|
195
|
+
entryPriorities = new Map();
|
|
196
|
+
pinnedEntries = new Set();
|
|
197
|
+
memoryLimits = {};
|
|
198
|
+
constructor(config) {
|
|
199
|
+
this.cacheLayer = config.cacheLayer;
|
|
200
|
+
this.swrCacheLayer = config.swrCacheLayer;
|
|
201
|
+
this.doStorage = config.doStorage;
|
|
202
|
+
this.r2Layer = config.r2Layer;
|
|
203
|
+
this.config = {
|
|
204
|
+
pageSize: config.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
205
|
+
coldToWarmThreshold: config.coldToWarmThreshold ?? DEFAULT_COLD_TO_WARM_THRESHOLD,
|
|
206
|
+
warmToHotThreshold: config.warmToHotThreshold ?? DEFAULT_WARM_TO_HOT_THRESHOLD,
|
|
207
|
+
promotionWindowMs: config.promotionWindowMs ?? DEFAULT_PROMOTION_WINDOW_MS,
|
|
208
|
+
hotTtlMs: config.hotTtlMs ?? DEFAULT_HOT_TTL_MS,
|
|
209
|
+
warmTtlMs: config.warmTtlMs ?? DEFAULT_WARM_TTL_MS,
|
|
210
|
+
maxHotPages: config.maxHotPages ?? DEFAULT_MAX_HOT_PAGES,
|
|
211
|
+
autoPromote: config.autoPromote ?? true,
|
|
212
|
+
autoDemote: config.autoDemote ?? true,
|
|
213
|
+
writeThrough: config.writeThrough ?? false,
|
|
214
|
+
doPrefix: config.doPrefix ?? DEFAULT_DO_PREFIX,
|
|
215
|
+
circuitBreaker: config.circuitBreaker,
|
|
216
|
+
metrics: config.metrics,
|
|
217
|
+
logger: config.logger,
|
|
218
|
+
// Index bounds configuration
|
|
219
|
+
maxIndexEntries: config.maxIndexEntries,
|
|
220
|
+
maxIndexMemoryBytes: config.maxIndexMemoryBytes,
|
|
221
|
+
indexEvictionPolicy: config.indexEvictionPolicy ?? 'lru',
|
|
222
|
+
indexEvictionBatchSize: config.indexEvictionBatchSize ?? DEFAULT_INDEX_EVICTION_BATCH_SIZE,
|
|
223
|
+
indexMemoryWarningThreshold: config.indexMemoryWarningThreshold ?? DEFAULT_INDEX_MEMORY_WARNING_THRESHOLD,
|
|
224
|
+
indexMaintenanceIntervalMs: config.indexMaintenanceIntervalMs,
|
|
225
|
+
onIndexEviction: config.onIndexEviction,
|
|
226
|
+
};
|
|
227
|
+
this.autoPromoterConfig = {
|
|
228
|
+
coldToWarm: { accessThreshold: this.config.coldToWarmThreshold },
|
|
229
|
+
warmToHot: { accessThreshold: this.config.warmToHotThreshold, timeWindowMs: this.config.promotionWindowMs },
|
|
230
|
+
};
|
|
231
|
+
this.autoDemoterConfig = {
|
|
232
|
+
hotToWarm: { ttlMs: this.config.hotTtlMs },
|
|
233
|
+
warmToCold: { ttlMs: this.config.warmTtlMs },
|
|
234
|
+
};
|
|
235
|
+
// Start maintenance timer if configured
|
|
236
|
+
if (this.config.indexMaintenanceIntervalMs) {
|
|
237
|
+
this.startIndexMaintenance();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
onMigration(callback) { this.migrationCallbacks.push(callback); }
|
|
241
|
+
onError(callback) { this.errorObservers.push(callback); }
|
|
242
|
+
onCircuitBreakerChange(callback) { this.circuitBreakerObservers.push(callback); }
|
|
243
|
+
notifyError(error) {
|
|
244
|
+
if (error.tier)
|
|
245
|
+
this.errorCounts[error.tier]++;
|
|
246
|
+
if (this.config.logger) {
|
|
247
|
+
if (error.context === 'tier_read_failure' && error.tier) {
|
|
248
|
+
this.config.logger.warn(`${error.tier} tier read failed: ${error.error.message}${error.key ? ` (key: ${error.key})` : ''}`);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this.config.logger.warn(`${error.context}: ${error.error.message}${error.key ? ` (key: ${error.key})` : ''}${error.tier ? ` (tier: ${error.tier})` : ''}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const callback of this.errorObservers) {
|
|
255
|
+
try {
|
|
256
|
+
callback(error);
|
|
257
|
+
}
|
|
258
|
+
catch { /* ignore observer errors */ }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
notifyCircuitBreakerChange(event) {
|
|
262
|
+
this.config.logger?.warn(`Circuit breaker ${event.tier} changed to ${event.state} (failures: ${event.failures})`);
|
|
263
|
+
for (const callback of this.circuitBreakerObservers) {
|
|
264
|
+
try {
|
|
265
|
+
callback(event);
|
|
266
|
+
}
|
|
267
|
+
catch { /* ignore observer errors */ }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
notifyMigration(event) {
|
|
271
|
+
for (const callback of this.migrationCallbacks) {
|
|
272
|
+
try {
|
|
273
|
+
callback(event);
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
277
|
+
this.notifyError({
|
|
278
|
+
context: 'migration_callback',
|
|
279
|
+
error,
|
|
280
|
+
key: event.key,
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Also notify event subscribers
|
|
286
|
+
if (this.eventSubscribers && this.eventSubscribers.length > 0) {
|
|
287
|
+
const isPromotion = event.toTier === 'hot' || (event.fromTier === 'cold' && event.toTier === 'warm');
|
|
288
|
+
for (const { callback, filter } of this.eventSubscribers) {
|
|
289
|
+
if (filter?.type === 'promotion' && !isPromotion)
|
|
290
|
+
continue;
|
|
291
|
+
if (filter?.type === 'demotion' && isPromotion)
|
|
292
|
+
continue;
|
|
293
|
+
try {
|
|
294
|
+
callback(event);
|
|
295
|
+
}
|
|
296
|
+
catch { /* ignore */ }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
doKey(key) { return `${this.config.doPrefix}${key}`; }
|
|
301
|
+
/**
|
|
302
|
+
* High-resolution timer for performance measurement
|
|
303
|
+
* Uses performance.now() when available, falls back to Date.now() * 1e6
|
|
304
|
+
*/
|
|
305
|
+
getNowNs() {
|
|
306
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
307
|
+
return performance.now() * NS_PER_MS; // Convert ms to ns
|
|
308
|
+
}
|
|
309
|
+
return Date.now() * NS_PER_MS;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Update the storage index with entry information
|
|
313
|
+
*
|
|
314
|
+
* Performance characteristics:
|
|
315
|
+
* - O(1) for existing keys (direct Map update)
|
|
316
|
+
* - O(1) amortized for new keys (may trigger eviction)
|
|
317
|
+
* - Tracks timing for performance metrics
|
|
318
|
+
*
|
|
319
|
+
* @param key - The storage key
|
|
320
|
+
* @param tier - The storage tier where data resides
|
|
321
|
+
* @param size - Size of the data in bytes
|
|
322
|
+
* @param dirty - Whether the entry has uncommitted changes
|
|
323
|
+
*/
|
|
324
|
+
updateIndex(key, tier, size, dirty = false) {
|
|
325
|
+
const startNs = this.getNowNs();
|
|
326
|
+
this.performanceMetrics.updateCount++;
|
|
327
|
+
const existing = this.storageIndex.get(key);
|
|
328
|
+
const now = Date.now();
|
|
329
|
+
if (existing) {
|
|
330
|
+
existing.tier = tier;
|
|
331
|
+
existing.size = size;
|
|
332
|
+
existing.lastAccess = now;
|
|
333
|
+
existing.accessCount++;
|
|
334
|
+
existing.modified = now;
|
|
335
|
+
existing.dirty = dirty;
|
|
336
|
+
// Invalidate lookup cache if this was the cached entry
|
|
337
|
+
if (this.lastLookupKey === key) {
|
|
338
|
+
this.lastLookupEntry = existing;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Check if we need to evict before adding
|
|
343
|
+
this.enforceIndexBounds();
|
|
344
|
+
const newEntry = { key, tier, size, lastAccess: now, accessCount: 1, created: now, modified: now, dirty };
|
|
345
|
+
this.storageIndex.set(key, newEntry);
|
|
346
|
+
// Update memory tracking
|
|
347
|
+
this.indexMemoryBytes += TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD + key.length * 2;
|
|
348
|
+
// Track peak values
|
|
349
|
+
if (this.storageIndex.size > this.performanceMetrics.peakEntries) {
|
|
350
|
+
this.performanceMetrics.peakEntries = this.storageIndex.size;
|
|
351
|
+
}
|
|
352
|
+
if (this.indexMemoryBytes > this.performanceMetrics.peakMemoryBytes) {
|
|
353
|
+
this.performanceMetrics.peakMemoryBytes = this.indexMemoryBytes;
|
|
354
|
+
}
|
|
355
|
+
// Check memory warning threshold
|
|
356
|
+
this.checkMemoryWarning();
|
|
357
|
+
}
|
|
358
|
+
this.performanceMetrics.totalUpdateTimeNs += this.getNowNs() - startNs;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Enforce index bounds by evicting entries if necessary
|
|
362
|
+
*/
|
|
363
|
+
enforceIndexBounds() {
|
|
364
|
+
// Check entry count limit
|
|
365
|
+
if (this.config.maxIndexEntries && this.storageIndex.size >= this.config.maxIndexEntries) {
|
|
366
|
+
this.evictIndexEntries(this.config.indexEvictionBatchSize);
|
|
367
|
+
}
|
|
368
|
+
// Check memory limit
|
|
369
|
+
if (this.config.maxIndexMemoryBytes && this.indexMemoryBytes >= this.config.maxIndexMemoryBytes) {
|
|
370
|
+
// Estimate how many entries to evict based on average entry size
|
|
371
|
+
const avgEntrySize = this.indexMemoryBytes / Math.max(1, this.storageIndex.size);
|
|
372
|
+
const targetBytes = this.config.maxIndexMemoryBytes * INDEX_MEMORY_EVICTION_TARGET; // Target 80% of limit
|
|
373
|
+
const bytesToFree = this.indexMemoryBytes - targetBytes;
|
|
374
|
+
const entriesToEvict = Math.max(1, Math.ceil(bytesToFree / avgEntrySize));
|
|
375
|
+
this.evictIndexEntries(entriesToEvict);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Select the k entries with the smallest scores (partial selection)
|
|
380
|
+
*
|
|
381
|
+
* Uses a simple O(n*k) algorithm which is efficient when k << n.
|
|
382
|
+
* For large k values, this degrades gracefully but still avoids
|
|
383
|
+
* the memory overhead of creating and sorting a full array copy.
|
|
384
|
+
*/
|
|
385
|
+
selectSmallestEntries(k, getScore) {
|
|
386
|
+
const result = [];
|
|
387
|
+
const resultScores = [];
|
|
388
|
+
for (const entry of this.storageIndex.values()) {
|
|
389
|
+
const score = getScore(entry);
|
|
390
|
+
if (result.length < k) {
|
|
391
|
+
// Still building up the result set
|
|
392
|
+
result.push(entry);
|
|
393
|
+
resultScores.push(score);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// Find the entry with the highest score in our result (the one to potentially replace)
|
|
397
|
+
let maxIdx = 0;
|
|
398
|
+
let maxScore = resultScores[0] ?? 0;
|
|
399
|
+
for (let i = 1; i < resultScores.length; i++) {
|
|
400
|
+
const currentScore = resultScores[i] ?? 0;
|
|
401
|
+
if (currentScore > maxScore) {
|
|
402
|
+
maxIdx = i;
|
|
403
|
+
maxScore = currentScore;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// If this entry has a lower score, replace the max
|
|
407
|
+
if (score < maxScore) {
|
|
408
|
+
result[maxIdx] = entry;
|
|
409
|
+
resultScores[maxIdx] = score;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Evict entries from the index based on configured policy
|
|
417
|
+
*
|
|
418
|
+
* ## Performance Characteristics
|
|
419
|
+
*
|
|
420
|
+
* **Time Complexity**: O(n*k) where n=index size, k=entries to evict
|
|
421
|
+
* - Uses partial selection instead of full sort O(n log n)
|
|
422
|
+
* - Efficient when k << n (typical case: k=10-20, n=10000+)
|
|
423
|
+
*
|
|
424
|
+
* **Space Complexity**: O(k) for the result buffer
|
|
425
|
+
* - Does not create intermediate arrays of size n
|
|
426
|
+
*
|
|
427
|
+
* **Memory optimization**: Uses partial selection instead of full sort.
|
|
428
|
+
* This is more efficient when evicting a small batch from a large index.
|
|
429
|
+
*
|
|
430
|
+
* @param count - Number of entries to evict
|
|
431
|
+
*/
|
|
432
|
+
evictIndexEntries(count) {
|
|
433
|
+
if (this.storageIndex.size === 0 || count <= 0)
|
|
434
|
+
return;
|
|
435
|
+
const startNs = this.getNowNs();
|
|
436
|
+
// Select comparison function based on policy
|
|
437
|
+
let getScore;
|
|
438
|
+
switch (this.config.indexEvictionPolicy) {
|
|
439
|
+
case 'lfu':
|
|
440
|
+
// Least Frequently Used - lower accessCount = evict first
|
|
441
|
+
getScore = (e) => e.accessCount;
|
|
442
|
+
break;
|
|
443
|
+
case 'fifo':
|
|
444
|
+
// First In First Out - older created = evict first
|
|
445
|
+
getScore = (e) => e.created;
|
|
446
|
+
break;
|
|
447
|
+
case 'lru':
|
|
448
|
+
default:
|
|
449
|
+
// Least Recently Used - older lastAccess = evict first
|
|
450
|
+
getScore = (e) => e.lastAccess;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
// Use partial selection: find the k entries with smallest scores
|
|
454
|
+
// This avoids full sort when count << size
|
|
455
|
+
const toEvict = this.selectSmallestEntries(count, getScore);
|
|
456
|
+
const evictedKeys = [];
|
|
457
|
+
for (const entry of toEvict) {
|
|
458
|
+
this.storageIndex.delete(entry.key);
|
|
459
|
+
this.indexMemoryBytes -= TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD + entry.key.length * 2;
|
|
460
|
+
evictedKeys.push(entry.key);
|
|
461
|
+
// Invalidate lookup cache if evicted
|
|
462
|
+
if (this.lastLookupKey === entry.key) {
|
|
463
|
+
this.lastLookupKey = null;
|
|
464
|
+
this.lastLookupEntry = null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Update performance metrics
|
|
468
|
+
this.performanceMetrics.evictionCount += evictedKeys.length;
|
|
469
|
+
this.performanceMetrics.evictionBatches++;
|
|
470
|
+
this.performanceMetrics.totalEvictionTimeNs += this.getNowNs() - startNs;
|
|
471
|
+
// Ensure memory tracking doesn't go negative
|
|
472
|
+
if (this.indexMemoryBytes < 0)
|
|
473
|
+
this.indexMemoryBytes = 0;
|
|
474
|
+
// Notify callback if configured
|
|
475
|
+
if (this.config.onIndexEviction && evictedKeys.length > 0) {
|
|
476
|
+
try {
|
|
477
|
+
this.config.onIndexEviction(evictedKeys);
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
// Ignore callback errors
|
|
481
|
+
this.config.logger?.warn(`Index eviction callback error: ${e instanceof Error ? e.message : String(e)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Check and emit memory warning if threshold exceeded
|
|
487
|
+
*/
|
|
488
|
+
checkMemoryWarning() {
|
|
489
|
+
if (!this.config.maxIndexMemoryBytes || this.memoryWarningEmitted)
|
|
490
|
+
return;
|
|
491
|
+
const threshold = this.config.maxIndexMemoryBytes * this.config.indexMemoryWarningThreshold;
|
|
492
|
+
if (this.indexMemoryBytes >= threshold) {
|
|
493
|
+
this.memoryWarningEmitted = true;
|
|
494
|
+
this.config.logger?.warn(`Index memory approaching limit: ${this.indexMemoryBytes} bytes (${Math.round(this.indexMemoryBytes / this.config.maxIndexMemoryBytes * 100)}% of ${this.config.maxIndexMemoryBytes} bytes)`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Start periodic index maintenance
|
|
499
|
+
*/
|
|
500
|
+
startIndexMaintenance() {
|
|
501
|
+
if (this.maintenanceTimer)
|
|
502
|
+
return;
|
|
503
|
+
this.maintenanceTimer = setInterval(async () => {
|
|
504
|
+
try {
|
|
505
|
+
await this.runIndexMaintenance();
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
this.config.logger?.warn(`Index maintenance error: ${e instanceof Error ? e.message : String(e)}`);
|
|
509
|
+
}
|
|
510
|
+
}, this.config.indexMaintenanceIntervalMs);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Stop periodic index maintenance
|
|
514
|
+
*/
|
|
515
|
+
stopIndexMaintenance() {
|
|
516
|
+
if (this.maintenanceTimer) {
|
|
517
|
+
clearInterval(this.maintenanceTimer);
|
|
518
|
+
this.maintenanceTimer = undefined;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Run index maintenance - clean up stale entries
|
|
523
|
+
*
|
|
524
|
+
* ## Performance Characteristics
|
|
525
|
+
*
|
|
526
|
+
* **Time Complexity**: O(n * storage_ops) where n = index entries
|
|
527
|
+
* - Each entry may require 1-2 storage lookups
|
|
528
|
+
* - Storage ops are async and may have network latency
|
|
529
|
+
*
|
|
530
|
+
* **When to Run**:
|
|
531
|
+
* - Periodically via indexMaintenanceIntervalMs config
|
|
532
|
+
* - After suspected data loss or corruption
|
|
533
|
+
* - During low-traffic periods to avoid impacting hot path
|
|
534
|
+
*
|
|
535
|
+
* @returns Object with count of removed stale entries and timing info
|
|
536
|
+
*/
|
|
537
|
+
async runIndexMaintenance() {
|
|
538
|
+
const startNs = this.getNowNs();
|
|
539
|
+
let removed = 0;
|
|
540
|
+
const keysToRemove = [];
|
|
541
|
+
for (const [key, entry] of this.storageIndex) {
|
|
542
|
+
// Check if data still exists in the indicated tier
|
|
543
|
+
let exists = false;
|
|
544
|
+
if (entry.tier === 'hot') {
|
|
545
|
+
// Hot tier - assume it might have been evicted, check warm/cold
|
|
546
|
+
const warmData = await this.doStorage.get(this.doKey(key));
|
|
547
|
+
if (warmData) {
|
|
548
|
+
entry.tier = 'warm';
|
|
549
|
+
exists = true;
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
const coldData = await this.r2Layer.get(key);
|
|
553
|
+
if (coldData) {
|
|
554
|
+
entry.tier = 'cold';
|
|
555
|
+
exists = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else if (entry.tier === 'warm') {
|
|
560
|
+
const warmData = await this.doStorage.get(this.doKey(key));
|
|
561
|
+
if (warmData) {
|
|
562
|
+
exists = true;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const coldData = await this.r2Layer.get(key);
|
|
566
|
+
if (coldData) {
|
|
567
|
+
entry.tier = 'cold';
|
|
568
|
+
exists = true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const coldData = await this.r2Layer.get(key);
|
|
574
|
+
exists = !!coldData;
|
|
575
|
+
}
|
|
576
|
+
if (!exists) {
|
|
577
|
+
keysToRemove.push(key);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const key of keysToRemove) {
|
|
581
|
+
const entry = this.storageIndex.get(key);
|
|
582
|
+
if (entry) {
|
|
583
|
+
this.storageIndex.delete(key);
|
|
584
|
+
this.indexMemoryBytes -= TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD + key.length * 2;
|
|
585
|
+
removed++;
|
|
586
|
+
// Invalidate cache if needed
|
|
587
|
+
if (this.lastLookupKey === key) {
|
|
588
|
+
this.lastLookupKey = null;
|
|
589
|
+
this.lastLookupEntry = null;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (this.indexMemoryBytes < 0)
|
|
594
|
+
this.indexMemoryBytes = 0;
|
|
595
|
+
const durationNs = this.getNowNs() - startNs;
|
|
596
|
+
this.performanceMetrics.totalMaintenanceTimeNs += durationNs;
|
|
597
|
+
return { removed, durationMs: durationNs / 1e6 };
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Optimized index lookup with single-entry cache
|
|
601
|
+
*
|
|
602
|
+
* The hot path often involves looking up the same key multiple times
|
|
603
|
+
* in quick succession (read -> check promotion -> update index).
|
|
604
|
+
* This cache avoids redundant Map lookups.
|
|
605
|
+
*
|
|
606
|
+
* @param key - The key to look up
|
|
607
|
+
* @returns The index entry or undefined if not found
|
|
608
|
+
*/
|
|
609
|
+
getIndexEntryOptimized(key) {
|
|
610
|
+
const startNs = this.getNowNs();
|
|
611
|
+
this.performanceMetrics.lookupCount++;
|
|
612
|
+
// Check single-entry cache first (hot path optimization)
|
|
613
|
+
if (this.lastLookupKey === key && this.lastLookupEntry) {
|
|
614
|
+
this.performanceMetrics.cacheHitCount++;
|
|
615
|
+
this.performanceMetrics.totalLookupTimeNs += this.getNowNs() - startNs;
|
|
616
|
+
return this.lastLookupEntry;
|
|
617
|
+
}
|
|
618
|
+
this.performanceMetrics.cacheMissCount++;
|
|
619
|
+
const entry = this.storageIndex.get(key);
|
|
620
|
+
// Update cache
|
|
621
|
+
this.lastLookupKey = key;
|
|
622
|
+
this.lastLookupEntry = entry ?? null;
|
|
623
|
+
this.performanceMetrics.totalLookupTimeNs += this.getNowNs() - startNs;
|
|
624
|
+
return entry;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Check if a key should be promoted to a higher tier
|
|
628
|
+
*
|
|
629
|
+
* Uses optimized lookup to avoid redundant Map accesses.
|
|
630
|
+
*
|
|
631
|
+
* @param key - The storage key
|
|
632
|
+
* @param currentTier - The current tier of the data
|
|
633
|
+
* @returns Promotion decision with target tier if applicable
|
|
634
|
+
*/
|
|
635
|
+
shouldPromote(key, currentTier) {
|
|
636
|
+
if (!this.config.autoPromote)
|
|
637
|
+
return { promote: false };
|
|
638
|
+
const entry = this.getIndexEntryOptimized(key);
|
|
639
|
+
if (!entry)
|
|
640
|
+
return { promote: false };
|
|
641
|
+
if (currentTier === 'cold' && entry.accessCount >= this.config.coldToWarmThreshold)
|
|
642
|
+
return { promote: true, targetTier: 'warm' };
|
|
643
|
+
if (currentTier === 'warm' && entry.accessCount >= this.config.warmToHotThreshold) {
|
|
644
|
+
const age = Date.now() - entry.created;
|
|
645
|
+
if (age <= this.config.promotionWindowMs)
|
|
646
|
+
return { promote: true, targetTier: 'hot' };
|
|
647
|
+
}
|
|
648
|
+
return { promote: false };
|
|
649
|
+
}
|
|
650
|
+
isCircuitOpen(tier) {
|
|
651
|
+
if (!this.config.circuitBreaker?.enabled)
|
|
652
|
+
return false;
|
|
653
|
+
const cb = this.circuitBreakers[tier];
|
|
654
|
+
if (cb.state === 'open' && cb.openedAt) {
|
|
655
|
+
const resetTimeout = this.config.circuitBreaker.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_RESET_TIMEOUT_MS;
|
|
656
|
+
if (Date.now() - cb.openedAt >= resetTimeout) {
|
|
657
|
+
cb.state = 'half-open';
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return cb.state === 'open';
|
|
662
|
+
}
|
|
663
|
+
recordCircuitBreakerFailure(tier, error) {
|
|
664
|
+
if (!this.config.circuitBreaker?.enabled)
|
|
665
|
+
return;
|
|
666
|
+
const cb = this.circuitBreakers[tier];
|
|
667
|
+
cb.failures++;
|
|
668
|
+
cb.lastFailure = Date.now();
|
|
669
|
+
if (cb.failures >= (this.config.circuitBreaker.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_FAILURE_THRESHOLD)) {
|
|
670
|
+
cb.state = 'open';
|
|
671
|
+
cb.openedAt = Date.now();
|
|
672
|
+
this.notifyCircuitBreakerChange({ tier, state: 'open', failures: cb.failures, error });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
recordCircuitBreakerSuccess(tier) {
|
|
676
|
+
if (!this.config.circuitBreaker?.enabled)
|
|
677
|
+
return;
|
|
678
|
+
const cb = this.circuitBreakers[tier];
|
|
679
|
+
cb.lastSuccess = Date.now();
|
|
680
|
+
if (cb.state === 'half-open') {
|
|
681
|
+
cb.state = 'closed';
|
|
682
|
+
cb.failures = 0;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async read(key, options) {
|
|
686
|
+
const startTime = this.config.metrics?.trackLatency ? Date.now() : 0;
|
|
687
|
+
const skipPromotion = options?.skipPromotion ?? false;
|
|
688
|
+
const skipTracking = options?.skipTracking ?? false;
|
|
689
|
+
const ctx = options?.ctx;
|
|
690
|
+
const correlationId = `read-${++this.correlationCounter}`;
|
|
691
|
+
const retryCount = this.keyRetryCounters.get(key) ?? 0;
|
|
692
|
+
this.keyRetryCounters.set(key, retryCount + 1);
|
|
693
|
+
const coldCircuitOpen = this.isCircuitOpen('cold');
|
|
694
|
+
const recordLatency = () => { if (this.config.metrics?.trackLatency && startTime > 0) {
|
|
695
|
+
this.latencyMeasurements.push(Date.now() - startTime);
|
|
696
|
+
if (this.latencyMeasurements.length > 1000)
|
|
697
|
+
this.latencyMeasurements.shift();
|
|
698
|
+
} };
|
|
699
|
+
if (this.swrCacheLayer) {
|
|
700
|
+
try {
|
|
701
|
+
const result = await this.swrCacheLayer.get(key, ctx);
|
|
702
|
+
if (result.hit) {
|
|
703
|
+
if (!skipTracking)
|
|
704
|
+
this.updateIndex(key, 'hot', result.data?.length ?? 0);
|
|
705
|
+
this.recordCircuitBreakerSuccess('hot');
|
|
706
|
+
recordLatency();
|
|
707
|
+
return { data: result.data, tier: 'hot', stale: result.stale, revalidating: result.revalidating };
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch (e) {
|
|
711
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
712
|
+
this.tierHealth.hot = 'degraded';
|
|
713
|
+
this.recordCircuitBreakerFailure('hot', error);
|
|
714
|
+
this.notifyError({ context: 'tier_read_failure', error, tier: 'hot', key, timestamp: Date.now(), retryCount, correlationId });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else if (this.cacheLayer) {
|
|
718
|
+
try {
|
|
719
|
+
const cached = await this.cacheLayer.get(key);
|
|
720
|
+
if (cached) {
|
|
721
|
+
if (!skipTracking)
|
|
722
|
+
this.updateIndex(key, 'hot', cached.length);
|
|
723
|
+
this.recordCircuitBreakerSuccess('hot');
|
|
724
|
+
recordLatency();
|
|
725
|
+
return { data: cached, tier: 'hot' };
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
730
|
+
this.tierHealth.hot = 'degraded';
|
|
731
|
+
this.recordCircuitBreakerFailure('hot', error);
|
|
732
|
+
this.notifyError({ context: 'tier_read_failure', error, tier: 'hot', key, timestamp: Date.now(), retryCount, correlationId });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
const doData = await this.doStorage.get(this.doKey(key));
|
|
737
|
+
if (doData) {
|
|
738
|
+
this.warmStats.reads++;
|
|
739
|
+
this.warmStats.bytesRead += doData.length;
|
|
740
|
+
if (!skipTracking)
|
|
741
|
+
this.updateIndex(key, 'warm', doData.length);
|
|
742
|
+
this.recordCircuitBreakerSuccess('warm');
|
|
743
|
+
if (!skipPromotion) {
|
|
744
|
+
const { promote, targetTier } = this.shouldPromote(key, 'warm');
|
|
745
|
+
if (promote && targetTier === 'hot')
|
|
746
|
+
this.promoteToHot(key, doData).catch(() => { });
|
|
747
|
+
}
|
|
748
|
+
recordLatency();
|
|
749
|
+
return { data: doData, tier: 'warm', fallback: coldCircuitOpen ? true : undefined };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch (e) {
|
|
753
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
754
|
+
this.tierHealth.warm = 'degraded';
|
|
755
|
+
this.recordCircuitBreakerFailure('warm', error);
|
|
756
|
+
this.notifyError({ context: 'tier_read_failure', error, tier: 'warm', key, timestamp: Date.now(), retryCount, correlationId });
|
|
757
|
+
}
|
|
758
|
+
if (coldCircuitOpen) {
|
|
759
|
+
recordLatency();
|
|
760
|
+
return { data: null };
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
const r2Data = await this.r2Layer.get(key);
|
|
764
|
+
if (r2Data) {
|
|
765
|
+
if (!skipTracking)
|
|
766
|
+
this.updateIndex(key, 'cold', r2Data.length);
|
|
767
|
+
this.recordCircuitBreakerSuccess('cold');
|
|
768
|
+
if (!skipPromotion) {
|
|
769
|
+
const { promote, targetTier } = this.shouldPromote(key, 'cold');
|
|
770
|
+
if (promote && targetTier === 'warm')
|
|
771
|
+
this.promoteToWarm(key, r2Data).catch(() => { });
|
|
772
|
+
}
|
|
773
|
+
recordLatency();
|
|
774
|
+
return { data: r2Data, tier: 'cold' };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
catch (e) {
|
|
778
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
779
|
+
this.tierHealth.cold = 'degraded';
|
|
780
|
+
this.recordCircuitBreakerFailure('cold', error);
|
|
781
|
+
this.notifyError({ context: 'tier_read_failure', error, tier: 'cold', key, timestamp: Date.now(), retryCount, correlationId });
|
|
782
|
+
}
|
|
783
|
+
recordLatency();
|
|
784
|
+
return { data: null };
|
|
785
|
+
}
|
|
786
|
+
async write(key, data, options) {
|
|
787
|
+
const tier = options?.tier ?? 'warm';
|
|
788
|
+
const writeThrough = options?.writeThrough ?? this.config.writeThrough;
|
|
789
|
+
const skipIndex = options?.skipIndex ?? false;
|
|
790
|
+
switch (tier) {
|
|
791
|
+
case 'hot':
|
|
792
|
+
await this.doStorage.put(this.doKey(key), data);
|
|
793
|
+
this.warmStats.writes++;
|
|
794
|
+
this.warmStats.bytesWritten += data.length;
|
|
795
|
+
if (this.swrCacheLayer)
|
|
796
|
+
await this.swrCacheLayer.put(key, data);
|
|
797
|
+
else if (this.cacheLayer)
|
|
798
|
+
await this.cacheLayer.put(key, data, { lastAccessed: Date.now(), accessCount: 1 });
|
|
799
|
+
if (!skipIndex)
|
|
800
|
+
this.updateIndex(key, 'hot', data.length, writeThrough ? false : true);
|
|
801
|
+
break;
|
|
802
|
+
case 'warm':
|
|
803
|
+
await this.doStorage.put(this.doKey(key), data);
|
|
804
|
+
this.warmStats.writes++;
|
|
805
|
+
this.warmStats.bytesWritten += data.length;
|
|
806
|
+
if (!skipIndex)
|
|
807
|
+
this.updateIndex(key, 'warm', data.length, writeThrough ? false : true);
|
|
808
|
+
break;
|
|
809
|
+
case 'cold':
|
|
810
|
+
await this.r2Layer.put(key, data, { customMetadata: options?.metadata });
|
|
811
|
+
if (!skipIndex)
|
|
812
|
+
this.updateIndex(key, 'cold', data.length, false);
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
if (writeThrough && tier !== 'cold') {
|
|
816
|
+
try {
|
|
817
|
+
await this.r2Layer.put(key, data, { customMetadata: { ...options?.metadata, writeThroughAt: Date.now().toString(), sourceTier: tier } });
|
|
818
|
+
const entry = this.storageIndex.get(key);
|
|
819
|
+
if (entry)
|
|
820
|
+
entry.dirty = false;
|
|
821
|
+
}
|
|
822
|
+
catch (e) {
|
|
823
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
824
|
+
this.tierHealth.cold = 'degraded';
|
|
825
|
+
this.notifyError({ context: 'write_through_failure', error, tier: 'cold', key, timestamp: Date.now() });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async delete(key) {
|
|
830
|
+
const errors = [];
|
|
831
|
+
if (this.swrCacheLayer) {
|
|
832
|
+
try {
|
|
833
|
+
await this.swrCacheLayer.invalidate(key);
|
|
834
|
+
}
|
|
835
|
+
catch (e) {
|
|
836
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
else if (this.cacheLayer) {
|
|
840
|
+
try {
|
|
841
|
+
await this.cacheLayer.delete(key);
|
|
842
|
+
}
|
|
843
|
+
catch (e) {
|
|
844
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
await this.doStorage.delete(this.doKey(key));
|
|
849
|
+
this.warmStats.deletes++;
|
|
850
|
+
}
|
|
851
|
+
catch (e) {
|
|
852
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
await this.r2Layer.delete(key);
|
|
856
|
+
}
|
|
857
|
+
catch (e) {
|
|
858
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
859
|
+
}
|
|
860
|
+
// Update memory tracking before deleting from index
|
|
861
|
+
if (this.storageIndex.has(key)) {
|
|
862
|
+
this.indexMemoryBytes -= TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD + key.length * 2;
|
|
863
|
+
if (this.indexMemoryBytes < 0)
|
|
864
|
+
this.indexMemoryBytes = 0;
|
|
865
|
+
}
|
|
866
|
+
this.storageIndex.delete(key);
|
|
867
|
+
if (errors.length === 3)
|
|
868
|
+
throw new Error(`Failed to delete from all tiers: ${errors.map(e => e.message).join('; ')}`);
|
|
869
|
+
}
|
|
870
|
+
async promoteToWarm(key, data) {
|
|
871
|
+
const timestamp = Date.now();
|
|
872
|
+
const pageData = data ?? await this.r2Layer.get(key);
|
|
873
|
+
if (!pageData) {
|
|
874
|
+
const event = { key, fromTier: 'cold', toTier: 'warm', success: false, timestamp, error: 'Data not found in cold tier', reason: 'access_pattern' };
|
|
875
|
+
this.promotionStats.failed++;
|
|
876
|
+
this.notifyMigration(event);
|
|
877
|
+
return event;
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
await this.doStorage.put(this.doKey(key), pageData);
|
|
881
|
+
this.warmStats.writes++;
|
|
882
|
+
this.warmStats.bytesWritten += pageData.length;
|
|
883
|
+
this.promotionStats.coldToWarm++;
|
|
884
|
+
this.updateIndex(key, 'warm', pageData.length);
|
|
885
|
+
const event = { key, fromTier: 'cold', toTier: 'warm', success: true, timestamp, reason: 'access_pattern' };
|
|
886
|
+
this.notifyMigration(event);
|
|
887
|
+
return event;
|
|
888
|
+
}
|
|
889
|
+
catch (e) {
|
|
890
|
+
this.promotionStats.failed++;
|
|
891
|
+
const event = { key, fromTier: 'cold', toTier: 'warm', success: false, timestamp, error: e instanceof Error ? e.message : String(e), reason: 'access_pattern' };
|
|
892
|
+
this.notifyMigration(event);
|
|
893
|
+
return event;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async promoteToHot(key, data) {
|
|
897
|
+
const timestamp = Date.now();
|
|
898
|
+
const pageData = data ?? await this.doStorage.get(this.doKey(key));
|
|
899
|
+
if (!pageData) {
|
|
900
|
+
const event = { key, fromTier: 'warm', toTier: 'hot', success: false, timestamp, error: 'Data not found in warm tier', reason: 'access_pattern' };
|
|
901
|
+
this.promotionStats.failed++;
|
|
902
|
+
this.notifyMigration(event);
|
|
903
|
+
return event;
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
if (this.swrCacheLayer)
|
|
907
|
+
await this.swrCacheLayer.put(key, pageData);
|
|
908
|
+
else if (this.cacheLayer) {
|
|
909
|
+
const entry = this.storageIndex.get(key);
|
|
910
|
+
await this.cacheLayer.put(key, pageData, { lastAccessed: timestamp, accessCount: entry?.accessCount ?? 1 });
|
|
911
|
+
}
|
|
912
|
+
this.promotionStats.warmToHot++;
|
|
913
|
+
this.updateIndex(key, 'hot', pageData.length);
|
|
914
|
+
const event = { key, fromTier: 'warm', toTier: 'hot', success: true, timestamp, reason: 'access_pattern' };
|
|
915
|
+
this.notifyMigration(event);
|
|
916
|
+
return event;
|
|
917
|
+
}
|
|
918
|
+
catch (e) {
|
|
919
|
+
this.promotionStats.failed++;
|
|
920
|
+
const event = { key, fromTier: 'warm', toTier: 'hot', success: false, timestamp, error: e instanceof Error ? e.message : String(e), reason: 'access_pattern' };
|
|
921
|
+
this.notifyMigration(event);
|
|
922
|
+
return event;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async demoteFromHot(key, reason = 'ttl_expired') {
|
|
926
|
+
const timestamp = Date.now();
|
|
927
|
+
try {
|
|
928
|
+
if (this.swrCacheLayer)
|
|
929
|
+
await this.swrCacheLayer.invalidate(key);
|
|
930
|
+
else if (this.cacheLayer)
|
|
931
|
+
await this.cacheLayer.delete(key);
|
|
932
|
+
this.demotionStats.hotToWarm++;
|
|
933
|
+
this.updateIndex(key, 'warm', this.storageIndex.get(key)?.size ?? 0);
|
|
934
|
+
const event = { key, fromTier: 'hot', toTier: 'warm', success: true, timestamp, reason };
|
|
935
|
+
this.notifyMigration(event);
|
|
936
|
+
return event;
|
|
937
|
+
}
|
|
938
|
+
catch (e) {
|
|
939
|
+
this.demotionStats.failed++;
|
|
940
|
+
const event = { key, fromTier: 'hot', toTier: 'warm', success: false, timestamp, error: e instanceof Error ? e.message : String(e), reason };
|
|
941
|
+
this.notifyMigration(event);
|
|
942
|
+
return event;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
async demoteFromWarm(key, reason = 'ttl_expired') {
|
|
946
|
+
const timestamp = Date.now();
|
|
947
|
+
try {
|
|
948
|
+
const data = await this.doStorage.get(this.doKey(key));
|
|
949
|
+
if (!data) {
|
|
950
|
+
const event = { key, fromTier: 'warm', toTier: 'cold', success: false, timestamp, error: 'Data not found in warm tier', reason };
|
|
951
|
+
this.demotionStats.failed++;
|
|
952
|
+
this.notifyMigration(event);
|
|
953
|
+
return event;
|
|
954
|
+
}
|
|
955
|
+
await this.r2Layer.put(key, data, { customMetadata: { demotedAt: timestamp.toString(), reason } });
|
|
956
|
+
await this.doStorage.delete(this.doKey(key));
|
|
957
|
+
this.warmStats.deletes++;
|
|
958
|
+
this.demotionStats.warmToCold++;
|
|
959
|
+
this.updateIndex(key, 'cold', data.length);
|
|
960
|
+
const event = { key, fromTier: 'warm', toTier: 'cold', success: true, timestamp, reason };
|
|
961
|
+
this.notifyMigration(event);
|
|
962
|
+
return event;
|
|
963
|
+
}
|
|
964
|
+
catch (e) {
|
|
965
|
+
this.demotionStats.failed++;
|
|
966
|
+
const event = { key, fromTier: 'warm', toTier: 'cold', success: false, timestamp, error: e instanceof Error ? e.message : String(e), reason };
|
|
967
|
+
this.notifyMigration(event);
|
|
968
|
+
return event;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
async runDemotionCycle(maxDemotions = 10) {
|
|
972
|
+
if (!this.config.autoDemote)
|
|
973
|
+
return [];
|
|
974
|
+
const events = [];
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
const hotCandidates = [];
|
|
977
|
+
for (const entry of this.storageIndex.values()) {
|
|
978
|
+
// Skip pinned entries
|
|
979
|
+
if (this.pinnedEntries.has(entry.key))
|
|
980
|
+
continue;
|
|
981
|
+
if (entry.tier === 'hot' && now - entry.lastAccess >= this.config.hotTtlMs)
|
|
982
|
+
hotCandidates.push(entry);
|
|
983
|
+
}
|
|
984
|
+
hotCandidates.sort((a, b) => a.lastAccess - b.lastAccess);
|
|
985
|
+
for (const entry of hotCandidates.slice(0, maxDemotions))
|
|
986
|
+
events.push(await this.demoteFromHot(entry.key));
|
|
987
|
+
const warmCandidates = [];
|
|
988
|
+
for (const entry of this.storageIndex.values()) {
|
|
989
|
+
// Skip pinned entries
|
|
990
|
+
if (this.pinnedEntries.has(entry.key))
|
|
991
|
+
continue;
|
|
992
|
+
if (entry.tier === 'warm' && now - entry.lastAccess >= this.config.warmTtlMs)
|
|
993
|
+
warmCandidates.push(entry);
|
|
994
|
+
}
|
|
995
|
+
warmCandidates.sort((a, b) => a.lastAccess - b.lastAccess);
|
|
996
|
+
const remainingSlots = maxDemotions - events.length;
|
|
997
|
+
for (const entry of warmCandidates.slice(0, remainingSlots))
|
|
998
|
+
events.push(await this.demoteFromWarm(entry.key));
|
|
999
|
+
return events;
|
|
1000
|
+
}
|
|
1001
|
+
async evictLRUFromHot(count) {
|
|
1002
|
+
const hotPages = [];
|
|
1003
|
+
for (const entry of this.storageIndex.values()) {
|
|
1004
|
+
if (entry.tier === 'hot')
|
|
1005
|
+
hotPages.push(entry);
|
|
1006
|
+
}
|
|
1007
|
+
hotPages.sort((a, b) => a.lastAccess - b.lastAccess);
|
|
1008
|
+
const events = [];
|
|
1009
|
+
for (const entry of hotPages.slice(0, count))
|
|
1010
|
+
events.push(await this.demoteFromHot(entry.key, 'lru_eviction'));
|
|
1011
|
+
return events;
|
|
1012
|
+
}
|
|
1013
|
+
async syncDirtyToCold() {
|
|
1014
|
+
let synced = 0;
|
|
1015
|
+
for (const entry of this.storageIndex.values()) {
|
|
1016
|
+
if (entry.dirty) {
|
|
1017
|
+
try {
|
|
1018
|
+
const data = await this.doStorage.get(this.doKey(entry.key));
|
|
1019
|
+
if (data) {
|
|
1020
|
+
await this.r2Layer.put(entry.key, data, { customMetadata: { syncedAt: Date.now().toString(), sourceTier: entry.tier } });
|
|
1021
|
+
entry.dirty = false;
|
|
1022
|
+
synced++;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
catch (e) {
|
|
1026
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
1027
|
+
this.notifyError({ context: 'sync_dirty_to_cold', error, key: entry.key, timestamp: Date.now() });
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return synced;
|
|
1032
|
+
}
|
|
1033
|
+
getIndexEntry(key) { return this.storageIndex.get(key); }
|
|
1034
|
+
hasInIndex(key) { return this.storageIndex.has(key); }
|
|
1035
|
+
getTier(key) { return this.storageIndex.get(key)?.tier; }
|
|
1036
|
+
/**
|
|
1037
|
+
* Get detailed memory usage breakdown for the storage index
|
|
1038
|
+
*
|
|
1039
|
+
* ## Memory Components
|
|
1040
|
+
*
|
|
1041
|
+
* - **entryOverhead**: Fixed overhead per entry (~120 bytes)
|
|
1042
|
+
* - **keyStorage**: Memory for key strings (2 bytes per char in JS)
|
|
1043
|
+
* - **mapOverhead**: Estimated Map internal structures (~10% of entry memory)
|
|
1044
|
+
*
|
|
1045
|
+
* @returns Detailed memory breakdown in bytes
|
|
1046
|
+
*/
|
|
1047
|
+
getIndexMemoryBreakdown() {
|
|
1048
|
+
let keyStorage = 0;
|
|
1049
|
+
for (const key of this.storageIndex.keys()) {
|
|
1050
|
+
keyStorage += key.length * 2; // JS strings are UTF-16
|
|
1051
|
+
}
|
|
1052
|
+
const entryOverhead = this.storageIndex.size * TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD;
|
|
1053
|
+
// Estimate Map internal overhead as ~10% of entry memory
|
|
1054
|
+
const mapOverheadEstimate = Math.ceil(entryOverhead * MAP_OVERHEAD_ESTIMATE_PERCENT);
|
|
1055
|
+
const totalBytes = entryOverhead + keyStorage + mapOverheadEstimate;
|
|
1056
|
+
const bytesPerEntry = this.storageIndex.size > 0
|
|
1057
|
+
? totalBytes / this.storageIndex.size
|
|
1058
|
+
: 0;
|
|
1059
|
+
return {
|
|
1060
|
+
totalBytes,
|
|
1061
|
+
entryOverhead,
|
|
1062
|
+
keyStorage,
|
|
1063
|
+
mapOverheadEstimate,
|
|
1064
|
+
bytesPerEntry,
|
|
1065
|
+
entries: this.storageIndex.size,
|
|
1066
|
+
peakBytes: this.performanceMetrics.peakMemoryBytes,
|
|
1067
|
+
utilizationPercent: this.config.maxIndexMemoryBytes
|
|
1068
|
+
? (this.indexMemoryBytes / this.config.maxIndexMemoryBytes) * 100
|
|
1069
|
+
: null,
|
|
1070
|
+
memoryBudgetBytes: this.config.maxIndexMemoryBytes ?? null,
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Estimate memory for a given number of entries with average key length
|
|
1075
|
+
*
|
|
1076
|
+
* Useful for capacity planning:
|
|
1077
|
+
* ```typescript
|
|
1078
|
+
* // How much memory for 10,000 entries with ~30 char keys?
|
|
1079
|
+
* const estimate = orchestrator.estimateIndexMemory(10000, 30)
|
|
1080
|
+
* console.log(`Estimated: ${estimate / 1024 / 1024}MB`)
|
|
1081
|
+
* ```
|
|
1082
|
+
*
|
|
1083
|
+
* @param entryCount - Number of entries to estimate for
|
|
1084
|
+
* @param avgKeyLength - Average key length in characters (default: 20)
|
|
1085
|
+
* @returns Estimated memory usage in bytes
|
|
1086
|
+
*/
|
|
1087
|
+
estimateIndexMemory(entryCount, avgKeyLength = DEFAULT_AVG_KEY_LENGTH_CHARS) {
|
|
1088
|
+
const entryOverhead = entryCount * TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD;
|
|
1089
|
+
const keyStorage = entryCount * avgKeyLength * 2; // UTF-16
|
|
1090
|
+
const mapOverhead = Math.ceil(entryOverhead * MAP_OVERHEAD_ESTIMATE_PERCENT);
|
|
1091
|
+
return entryOverhead + keyStorage + mapOverhead;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Get the current memory usage as a percentage of the configured limit
|
|
1095
|
+
*
|
|
1096
|
+
* @returns Percentage (0-100) or null if no limit configured
|
|
1097
|
+
*/
|
|
1098
|
+
getIndexMemoryUtilization() {
|
|
1099
|
+
if (!this.config.maxIndexMemoryBytes)
|
|
1100
|
+
return null;
|
|
1101
|
+
return (this.indexMemoryBytes / this.config.maxIndexMemoryBytes) * 100;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Check if the index is approaching memory limits
|
|
1105
|
+
*
|
|
1106
|
+
* @param threshold - Warning threshold as decimal (default: 0.8 = 80%)
|
|
1107
|
+
* @returns Object with warning status and details
|
|
1108
|
+
*/
|
|
1109
|
+
checkIndexMemoryStatus(threshold = 0.8) {
|
|
1110
|
+
const utilization = this.getIndexMemoryUtilization();
|
|
1111
|
+
return {
|
|
1112
|
+
isNearLimit: utilization !== null && utilization >= threshold * 100,
|
|
1113
|
+
currentBytes: this.indexMemoryBytes,
|
|
1114
|
+
limitBytes: this.config.maxIndexMemoryBytes ?? null,
|
|
1115
|
+
utilizationPercent: utilization,
|
|
1116
|
+
warningThreshold: threshold * 100,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Get comprehensive statistics including performance metrics
|
|
1121
|
+
*
|
|
1122
|
+
* ## Performance Metrics Included
|
|
1123
|
+
*
|
|
1124
|
+
* - **lookupCount**: Total number of index lookups
|
|
1125
|
+
* - **updateCount**: Total number of index updates
|
|
1126
|
+
* - **evictionCount**: Total entries evicted
|
|
1127
|
+
* - **avgLookupTimeNs**: Average lookup time in nanoseconds
|
|
1128
|
+
* - **avgUpdateTimeNs**: Average update time in nanoseconds
|
|
1129
|
+
* - **peakEntries**: Maximum index size observed
|
|
1130
|
+
* - **peakMemoryBytes**: Maximum memory usage observed
|
|
1131
|
+
* - **cacheHitCount/cacheMissCount**: Hot path cache effectiveness
|
|
1132
|
+
*
|
|
1133
|
+
* @returns Complete orchestrator statistics
|
|
1134
|
+
*/
|
|
1135
|
+
getStats() {
|
|
1136
|
+
const indexStats = {
|
|
1137
|
+
totalEntries: this.storageIndex.size,
|
|
1138
|
+
byTier: { hot: 0, warm: 0, cold: 0 },
|
|
1139
|
+
totalSize: 0,
|
|
1140
|
+
dirtyEntries: 0,
|
|
1141
|
+
memoryBytes: this.indexMemoryBytes,
|
|
1142
|
+
performance: {
|
|
1143
|
+
lookupCount: this.performanceMetrics.lookupCount,
|
|
1144
|
+
updateCount: this.performanceMetrics.updateCount,
|
|
1145
|
+
evictionCount: this.performanceMetrics.evictionCount,
|
|
1146
|
+
totalLookupTimeNs: this.performanceMetrics.totalLookupTimeNs,
|
|
1147
|
+
totalUpdateTimeNs: this.performanceMetrics.totalUpdateTimeNs,
|
|
1148
|
+
totalEvictionTimeNs: this.performanceMetrics.totalEvictionTimeNs,
|
|
1149
|
+
totalMaintenanceTimeNs: this.performanceMetrics.totalMaintenanceTimeNs,
|
|
1150
|
+
avgLookupTimeNs: this.performanceMetrics.lookupCount > 0
|
|
1151
|
+
? this.performanceMetrics.totalLookupTimeNs / this.performanceMetrics.lookupCount
|
|
1152
|
+
: 0,
|
|
1153
|
+
avgUpdateTimeNs: this.performanceMetrics.updateCount > 0
|
|
1154
|
+
? this.performanceMetrics.totalUpdateTimeNs / this.performanceMetrics.updateCount
|
|
1155
|
+
: 0,
|
|
1156
|
+
peakEntries: this.performanceMetrics.peakEntries,
|
|
1157
|
+
peakMemoryBytes: this.performanceMetrics.peakMemoryBytes,
|
|
1158
|
+
evictionBatches: this.performanceMetrics.evictionBatches,
|
|
1159
|
+
entriesPerEvictionBatch: this.performanceMetrics.evictionBatches > 0
|
|
1160
|
+
? this.performanceMetrics.evictionCount / this.performanceMetrics.evictionBatches
|
|
1161
|
+
: 0,
|
|
1162
|
+
cacheHitCount: this.performanceMetrics.cacheHitCount,
|
|
1163
|
+
cacheMissCount: this.performanceMetrics.cacheMissCount,
|
|
1164
|
+
},
|
|
1165
|
+
};
|
|
1166
|
+
for (const entry of this.storageIndex.values()) {
|
|
1167
|
+
indexStats.byTier[entry.tier]++;
|
|
1168
|
+
indexStats.totalSize += entry.size;
|
|
1169
|
+
if (entry.dirty)
|
|
1170
|
+
indexStats.dirtyEntries++;
|
|
1171
|
+
}
|
|
1172
|
+
return {
|
|
1173
|
+
index: indexStats,
|
|
1174
|
+
hot: this.swrCacheLayer?.getStats() ?? this.cacheLayer?.getStats() ?? { hits: 0, misses: 0, writes: 0, deletes: 0, bytesRead: 0, bytesWritten: 0, errors: 0, hitRatio: 0 },
|
|
1175
|
+
warm: { ...this.warmStats },
|
|
1176
|
+
cold: this.r2Layer.getStats(),
|
|
1177
|
+
promotions: { ...this.promotionStats },
|
|
1178
|
+
demotions: { ...this.demotionStats },
|
|
1179
|
+
health: { ...this.tierHealth },
|
|
1180
|
+
errors: { ...this.errorCounts },
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Reset all statistics counters
|
|
1185
|
+
*
|
|
1186
|
+
* Resets:
|
|
1187
|
+
* - Warm tier stats (reads, writes, deletes, bytes)
|
|
1188
|
+
* - Promotion/demotion counters
|
|
1189
|
+
* - Performance metrics (lookup/update/eviction counts and timing)
|
|
1190
|
+
*
|
|
1191
|
+
* Does NOT reset:
|
|
1192
|
+
* - Peak values (peakEntries, peakMemoryBytes)
|
|
1193
|
+
* - Current index state
|
|
1194
|
+
*/
|
|
1195
|
+
resetStats() {
|
|
1196
|
+
this.warmStats = { reads: 0, writes: 0, deletes: 0, bytesRead: 0, bytesWritten: 0 };
|
|
1197
|
+
this.promotionStats = { coldToWarm: 0, warmToHot: 0, failed: 0 };
|
|
1198
|
+
this.demotionStats = { hotToWarm: 0, warmToCold: 0, failed: 0 };
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Reset only performance metrics while preserving operational stats
|
|
1202
|
+
*
|
|
1203
|
+
* Useful for:
|
|
1204
|
+
* - Starting a new measurement window
|
|
1205
|
+
* - Benchmarking specific operations
|
|
1206
|
+
* - A/B testing configuration changes
|
|
1207
|
+
*
|
|
1208
|
+
* @param preservePeaks - If true, keeps peakEntries and peakMemoryBytes (default: false)
|
|
1209
|
+
*/
|
|
1210
|
+
resetPerformanceMetrics(preservePeaks = false) {
|
|
1211
|
+
const peakEntries = preservePeaks ? this.performanceMetrics.peakEntries : 0;
|
|
1212
|
+
const peakMemoryBytes = preservePeaks ? this.performanceMetrics.peakMemoryBytes : 0;
|
|
1213
|
+
this.performanceMetrics = {
|
|
1214
|
+
lookupCount: 0,
|
|
1215
|
+
updateCount: 0,
|
|
1216
|
+
evictionCount: 0,
|
|
1217
|
+
totalLookupTimeNs: 0,
|
|
1218
|
+
totalUpdateTimeNs: 0,
|
|
1219
|
+
totalEvictionTimeNs: 0,
|
|
1220
|
+
totalMaintenanceTimeNs: 0,
|
|
1221
|
+
peakEntries,
|
|
1222
|
+
peakMemoryBytes,
|
|
1223
|
+
evictionBatches: 0,
|
|
1224
|
+
cacheHitCount: 0,
|
|
1225
|
+
cacheMissCount: 0,
|
|
1226
|
+
};
|
|
1227
|
+
// Clear lookup cache
|
|
1228
|
+
this.lastLookupKey = null;
|
|
1229
|
+
this.lastLookupEntry = null;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Get a human-readable performance summary
|
|
1233
|
+
*
|
|
1234
|
+
* @returns Formatted string with key performance metrics
|
|
1235
|
+
*/
|
|
1236
|
+
getPerformanceSummary() {
|
|
1237
|
+
const m = this.performanceMetrics;
|
|
1238
|
+
const cacheHitRate = (m.cacheHitCount + m.cacheMissCount) > 0
|
|
1239
|
+
? ((m.cacheHitCount / (m.cacheHitCount + m.cacheMissCount)) * 100).toFixed(1)
|
|
1240
|
+
: '0.0';
|
|
1241
|
+
const avgLookup = m.lookupCount > 0
|
|
1242
|
+
? (m.totalLookupTimeNs / m.lookupCount / 1000).toFixed(2)
|
|
1243
|
+
: '0.00';
|
|
1244
|
+
const avgUpdate = m.updateCount > 0
|
|
1245
|
+
? (m.totalUpdateTimeNs / m.updateCount / 1000).toFixed(2)
|
|
1246
|
+
: '0.00';
|
|
1247
|
+
const avgEvictionBatch = m.evictionBatches > 0
|
|
1248
|
+
? (m.totalEvictionTimeNs / m.evictionBatches / 1000).toFixed(2)
|
|
1249
|
+
: '0.00';
|
|
1250
|
+
return [
|
|
1251
|
+
`Index Performance Summary:`,
|
|
1252
|
+
` Entries: ${this.storageIndex.size} current, ${m.peakEntries} peak`,
|
|
1253
|
+
` Memory: ${(this.indexMemoryBytes / 1024).toFixed(1)}KB current, ${(m.peakMemoryBytes / 1024).toFixed(1)}KB peak`,
|
|
1254
|
+
` Lookups: ${m.lookupCount} total, ${avgLookup}μs avg`,
|
|
1255
|
+
` Updates: ${m.updateCount} total, ${avgUpdate}μs avg`,
|
|
1256
|
+
` Evictions: ${m.evictionCount} entries in ${m.evictionBatches} batches, ${avgEvictionBatch}μs/batch`,
|
|
1257
|
+
` Cache: ${cacheHitRate}% hit rate (${m.cacheHitCount} hits, ${m.cacheMissCount} misses)`,
|
|
1258
|
+
` Maintenance: ${(m.totalMaintenanceTimeNs / 1e6).toFixed(1)}ms total`,
|
|
1259
|
+
].join('\n');
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Clear the index and all associated tracking
|
|
1263
|
+
*/
|
|
1264
|
+
clearIndex() {
|
|
1265
|
+
this.storageIndex.clear();
|
|
1266
|
+
this.indexMemoryBytes = 0;
|
|
1267
|
+
this.memoryWarningEmitted = false;
|
|
1268
|
+
this.lastLookupKey = null;
|
|
1269
|
+
this.lastLookupEntry = null;
|
|
1270
|
+
}
|
|
1271
|
+
resetHealth(tier) {
|
|
1272
|
+
if (tier)
|
|
1273
|
+
this.tierHealth[tier] = 'healthy';
|
|
1274
|
+
else
|
|
1275
|
+
this.tierHealth = { hot: 'healthy', warm: 'healthy', cold: 'healthy' };
|
|
1276
|
+
}
|
|
1277
|
+
getConfig() { return { ...this.config }; }
|
|
1278
|
+
// DO Lifecycle Hooks
|
|
1279
|
+
async onHibernate() {
|
|
1280
|
+
const timestamp = Date.now();
|
|
1281
|
+
let persistedCount = 0;
|
|
1282
|
+
const hotKeys = [];
|
|
1283
|
+
for (const entry of this.storageIndex.values()) {
|
|
1284
|
+
if (entry.tier === 'hot')
|
|
1285
|
+
hotKeys.push(entry.key);
|
|
1286
|
+
}
|
|
1287
|
+
for (const key of hotKeys) {
|
|
1288
|
+
const entry = this.storageIndex.get(key);
|
|
1289
|
+
if (entry) {
|
|
1290
|
+
entry.tier = 'warm';
|
|
1291
|
+
persistedCount++;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const syncedToCold = await this.syncDirtyToCold();
|
|
1295
|
+
const indexSnapshot = new Map(this.storageIndex);
|
|
1296
|
+
return { success: true, persistedCount, syncedToCold, hotKeys, indexSnapshot, timestamp };
|
|
1297
|
+
}
|
|
1298
|
+
async onWake(hibernateState, options) {
|
|
1299
|
+
let restoredCount = 0, prefetchedCount = 0, indexRepairs = 0;
|
|
1300
|
+
const indexValidated = options?.validateIndex ?? false;
|
|
1301
|
+
if (hibernateState?.hotKeys) {
|
|
1302
|
+
for (const key of hibernateState.hotKeys) {
|
|
1303
|
+
try {
|
|
1304
|
+
const data = await this.doStorage.get(this.doKey(key));
|
|
1305
|
+
if (data) {
|
|
1306
|
+
if (this.swrCacheLayer)
|
|
1307
|
+
await this.swrCacheLayer.put(key, data);
|
|
1308
|
+
else if (this.cacheLayer)
|
|
1309
|
+
await this.cacheLayer.put(key, data, { lastAccessed: Date.now(), accessCount: 1 });
|
|
1310
|
+
this.updateIndex(key, 'hot', data.length);
|
|
1311
|
+
restoredCount++;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
catch (e) {
|
|
1315
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
1316
|
+
this.notifyError({ context: 'wake_restore', error, key, timestamp: Date.now() });
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (options?.prefetchThreshold && hibernateState?.indexSnapshot) {
|
|
1321
|
+
for (const [key, entry] of hibernateState.indexSnapshot) {
|
|
1322
|
+
if (entry.accessCount >= options.prefetchThreshold && entry.tier !== 'hot') {
|
|
1323
|
+
try {
|
|
1324
|
+
const event = await this.promoteToHot(key);
|
|
1325
|
+
if (event.success)
|
|
1326
|
+
prefetchedCount++;
|
|
1327
|
+
}
|
|
1328
|
+
catch (e) {
|
|
1329
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
1330
|
+
this.notifyError({ context: 'wake_prefetch', error, key, timestamp: Date.now() });
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (indexValidated) {
|
|
1336
|
+
for (const [key, entry] of this.storageIndex) {
|
|
1337
|
+
let found = false, actualTier;
|
|
1338
|
+
const warmData = await this.doStorage.get(this.doKey(key));
|
|
1339
|
+
if (warmData) {
|
|
1340
|
+
found = true;
|
|
1341
|
+
actualTier = 'warm';
|
|
1342
|
+
}
|
|
1343
|
+
if (!found) {
|
|
1344
|
+
const coldData = await this.r2Layer.get(key);
|
|
1345
|
+
if (coldData) {
|
|
1346
|
+
found = true;
|
|
1347
|
+
actualTier = 'cold';
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (!found) {
|
|
1351
|
+
this.storageIndex.delete(key);
|
|
1352
|
+
indexRepairs++;
|
|
1353
|
+
}
|
|
1354
|
+
else if (actualTier && actualTier !== entry.tier && entry.tier !== 'hot') {
|
|
1355
|
+
entry.tier = actualTier;
|
|
1356
|
+
indexRepairs++;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return { restoredCount, prefetchedCount, indexValidated, indexRepairs };
|
|
1361
|
+
}
|
|
1362
|
+
// Background Task Scheduling
|
|
1363
|
+
async scheduleAlarm(alarmConfig) {
|
|
1364
|
+
const storage = this.doStorage;
|
|
1365
|
+
if (alarmConfig.skipIfExists && storage.getAlarm) {
|
|
1366
|
+
const existingAlarm = await storage.getAlarm();
|
|
1367
|
+
if (existingAlarm !== null && existingAlarm !== undefined)
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const scheduledTime = Date.now() + alarmConfig.delayMs;
|
|
1371
|
+
if (storage.setAlarm)
|
|
1372
|
+
await storage.setAlarm(scheduledTime);
|
|
1373
|
+
}
|
|
1374
|
+
async handleAlarm(alarmConfig) {
|
|
1375
|
+
const result = {};
|
|
1376
|
+
switch (alarmConfig.type) {
|
|
1377
|
+
case 'demotion': {
|
|
1378
|
+
const events = await this.runDemotionCycle();
|
|
1379
|
+
result.demotionCount = events.length;
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
case 'sync': {
|
|
1383
|
+
result.syncedCount = await this.syncDirtyToCold();
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
default: assertNever(alarmConfig.type, 'Unknown alarm type');
|
|
1387
|
+
}
|
|
1388
|
+
if (alarmConfig.rescheduleMs) {
|
|
1389
|
+
await this.scheduleAlarm({ type: alarmConfig.type, delayMs: alarmConfig.rescheduleMs });
|
|
1390
|
+
result.nextAlarmMs = alarmConfig.rescheduleMs;
|
|
1391
|
+
}
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1394
|
+
// Circuit Breaker Pattern
|
|
1395
|
+
getCircuitBreakerState(tier) {
|
|
1396
|
+
const state = { ...this.circuitBreakers[tier] };
|
|
1397
|
+
if (state.state === 'open' && state.openedAt) {
|
|
1398
|
+
const resetTimeout = this.config.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_RESET_TIMEOUT_MS;
|
|
1399
|
+
if (Date.now() - state.openedAt >= resetTimeout) {
|
|
1400
|
+
this.circuitBreakers[tier].state = 'half-open';
|
|
1401
|
+
state.state = 'half-open';
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return state;
|
|
1405
|
+
}
|
|
1406
|
+
setCircuitBreakerState(tier, state) {
|
|
1407
|
+
this.circuitBreakers[tier].state = state;
|
|
1408
|
+
if (state === 'open')
|
|
1409
|
+
this.circuitBreakers[tier].openedAt = Date.now();
|
|
1410
|
+
}
|
|
1411
|
+
// Metrics Aggregation
|
|
1412
|
+
getAggregatedMetrics() {
|
|
1413
|
+
const hotStats = this.swrCacheLayer?.getStats() ?? this.cacheLayer?.getStats() ?? { hits: 0, misses: 0, writes: 0, bytesRead: 0, bytesWritten: 0, hitRatio: 0 };
|
|
1414
|
+
const coldStats = this.r2Layer.getStats();
|
|
1415
|
+
let warmBytesStored = 0, coldBytesStored = 0;
|
|
1416
|
+
for (const entry of this.storageIndex.values()) {
|
|
1417
|
+
if (entry.tier === 'warm')
|
|
1418
|
+
warmBytesStored += entry.size;
|
|
1419
|
+
if (entry.tier === 'cold')
|
|
1420
|
+
coldBytesStored += entry.size;
|
|
1421
|
+
}
|
|
1422
|
+
const metrics = {
|
|
1423
|
+
timestamp: Date.now(),
|
|
1424
|
+
period: 'current',
|
|
1425
|
+
tiers: {
|
|
1426
|
+
hot: { reads: 'hits' in hotStats ? hotStats.hits + hotStats.misses : 0, writes: hotStats.writes, hitRatio: 'hitRatio' in hotStats ? hotStats.hitRatio : 0, bytesRead: hotStats.bytesRead, bytesWritten: hotStats.bytesWritten },
|
|
1427
|
+
warm: { reads: this.warmStats.reads, writes: this.warmStats.writes, bytesStored: warmBytesStored, bytesRead: this.warmStats.bytesRead, bytesWritten: this.warmStats.bytesWritten },
|
|
1428
|
+
cold: { reads: coldStats.reads, writes: coldStats.writes, bytesStored: coldBytesStored, bytesRead: coldStats.bytesRead, bytesWritten: coldStats.bytesWritten },
|
|
1429
|
+
},
|
|
1430
|
+
migrations: { promotions: this.promotionStats.coldToWarm + this.promotionStats.warmToHot, demotions: this.demotionStats.hotToWarm + this.demotionStats.warmToCold, failures: this.promotionStats.failed + this.demotionStats.failed },
|
|
1431
|
+
circuitBreakers: { hot: this.getCircuitBreakerState('hot'), warm: this.getCircuitBreakerState('warm'), cold: this.getCircuitBreakerState('cold') },
|
|
1432
|
+
errors: { totalErrors: this.errorCounts.hot + this.errorCounts.warm + this.errorCounts.cold, byTier: { ...this.errorCounts } },
|
|
1433
|
+
};
|
|
1434
|
+
if (this.config.metrics?.trackLatency && this.latencyMeasurements.length > 0) {
|
|
1435
|
+
const sorted = [...this.latencyMeasurements].sort((a, b) => a - b);
|
|
1436
|
+
metrics.latency = { p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0, p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0, p99: sorted[Math.floor(sorted.length * 0.99)] ?? 0 };
|
|
1437
|
+
}
|
|
1438
|
+
if (this.config.metrics?.trackCost && this.config.metrics.costConfig) {
|
|
1439
|
+
const cc = this.config.metrics.costConfig;
|
|
1440
|
+
const r2Cost = (coldBytesStored / (1024 * 1024 * 1024)) * cc.r2StoragePerGBMonth + coldStats.writes * cc.r2ClassAOperations + coldStats.reads * cc.r2ClassBOperations;
|
|
1441
|
+
const doCost = (warmBytesStored / (1024 * 1024 * 1024)) * cc.doStoragePerGBMonth;
|
|
1442
|
+
metrics.estimatedCost = { r2: r2Cost, do: doCost, cache: 0, total: r2Cost + doCost };
|
|
1443
|
+
}
|
|
1444
|
+
return metrics;
|
|
1445
|
+
}
|
|
1446
|
+
exportMetrics(format) {
|
|
1447
|
+
const metrics = this.getAggregatedMetrics();
|
|
1448
|
+
if (format === 'json')
|
|
1449
|
+
return metrics;
|
|
1450
|
+
const lines = [
|
|
1451
|
+
'# HELP tiered_storage_writes_total Total number of writes', '# TYPE tiered_storage_writes_total counter',
|
|
1452
|
+
`tiered_storage_writes_total{tier="hot"} ${metrics.tiers.hot.writes}`, `tiered_storage_writes_total{tier="warm"} ${metrics.tiers.warm.writes}`, `tiered_storage_writes_total{tier="cold"} ${metrics.tiers.cold.writes}`, '',
|
|
1453
|
+
'# HELP tiered_storage_bytes_written Total bytes written', '# TYPE tiered_storage_bytes_written counter',
|
|
1454
|
+
`tiered_storage_bytes_written{tier="hot"} ${metrics.tiers.hot.bytesWritten}`, `tiered_storage_bytes_written{tier="warm"} ${metrics.tiers.warm.bytesWritten}`, `tiered_storage_bytes_written{tier="cold"} ${metrics.tiers.cold.bytesWritten}`, '',
|
|
1455
|
+
'# HELP tiered_storage_reads_total Total number of reads', '# TYPE tiered_storage_reads_total counter',
|
|
1456
|
+
`tiered_storage_reads_total{tier="hot"} ${metrics.tiers.hot.reads}`, `tiered_storage_reads_total{tier="warm"} ${metrics.tiers.warm.reads}`, `tiered_storage_reads_total{tier="cold"} ${metrics.tiers.cold.reads}`, '',
|
|
1457
|
+
'# HELP tiered_storage_errors_total Total number of errors', '# TYPE tiered_storage_errors_total counter',
|
|
1458
|
+
`tiered_storage_errors_total{tier="hot"} ${metrics.errors.byTier.hot}`, `tiered_storage_errors_total{tier="warm"} ${metrics.errors.byTier.warm}`, `tiered_storage_errors_total{tier="cold"} ${metrics.errors.byTier.cold}`,
|
|
1459
|
+
];
|
|
1460
|
+
return lines.join('\n');
|
|
1461
|
+
}
|
|
1462
|
+
// Component Integration
|
|
1463
|
+
createVFSInterface(_options) {
|
|
1464
|
+
return {
|
|
1465
|
+
readPage: async (pageId) => { const result = await this.read(pageId); return result.data; },
|
|
1466
|
+
writePage: async (pageId, data) => { await this.write(pageId, data); },
|
|
1467
|
+
deletePage: async (pageId) => { await this.delete(pageId); },
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
configureAutoPromoter(config) {
|
|
1471
|
+
this.autoPromoterConfig = { ...config };
|
|
1472
|
+
this.config.coldToWarmThreshold = config.coldToWarm.accessThreshold;
|
|
1473
|
+
this.config.warmToHotThreshold = config.warmToHot.accessThreshold;
|
|
1474
|
+
if (config.warmToHot.timeWindowMs)
|
|
1475
|
+
this.config.promotionWindowMs = config.warmToHot.timeWindowMs;
|
|
1476
|
+
}
|
|
1477
|
+
getAutoPromoterConfig() { return { ...this.autoPromoterConfig }; }
|
|
1478
|
+
configureAutoDemoter(config) {
|
|
1479
|
+
this.autoDemoterConfig = { ...config };
|
|
1480
|
+
this.config.hotTtlMs = config.hotToWarm.ttlMs;
|
|
1481
|
+
this.config.warmTtlMs = config.warmToCold.ttlMs;
|
|
1482
|
+
}
|
|
1483
|
+
getAutoDemoterConfig() { return { ...this.autoDemoterConfig }; }
|
|
1484
|
+
setDemotionStrategy(tier, strategy) { this.demotionStrategies[tier] = strategy; }
|
|
1485
|
+
async runDemotionWithStrategy(tier, count) {
|
|
1486
|
+
const strategy = this.demotionStrategies[tier];
|
|
1487
|
+
if (!strategy) {
|
|
1488
|
+
if (tier === 'hot')
|
|
1489
|
+
return this.evictLRUFromHot(count);
|
|
1490
|
+
return [];
|
|
1491
|
+
}
|
|
1492
|
+
const entries = [];
|
|
1493
|
+
for (const entry of this.storageIndex.values()) {
|
|
1494
|
+
if (entry.tier === tier)
|
|
1495
|
+
entries.push(entry);
|
|
1496
|
+
}
|
|
1497
|
+
const keysToDemote = strategy(entries, count);
|
|
1498
|
+
const events = [];
|
|
1499
|
+
for (const key of keysToDemote) {
|
|
1500
|
+
const event = tier === 'hot' ? await this.demoteFromHot(key, 'lru_eviction') : await this.demoteFromWarm(key, 'lru_eviction');
|
|
1501
|
+
events.push(event);
|
|
1502
|
+
}
|
|
1503
|
+
return events;
|
|
1504
|
+
}
|
|
1505
|
+
// Error Recovery
|
|
1506
|
+
async runConsistencyCheck() {
|
|
1507
|
+
const repairs = [];
|
|
1508
|
+
for (const [key, entry] of this.storageIndex) {
|
|
1509
|
+
let found = false, actualTier;
|
|
1510
|
+
const warmData = await this.doStorage.get(this.doKey(key));
|
|
1511
|
+
if (warmData) {
|
|
1512
|
+
found = true;
|
|
1513
|
+
actualTier = 'warm';
|
|
1514
|
+
}
|
|
1515
|
+
if (!found) {
|
|
1516
|
+
const coldData = await this.r2Layer.get(key);
|
|
1517
|
+
if (coldData) {
|
|
1518
|
+
found = true;
|
|
1519
|
+
actualTier = 'cold';
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (!found) {
|
|
1523
|
+
repairs.push({ key, issue: `Data not found in ${entry.tier} tier`, action: 'removed from index' });
|
|
1524
|
+
this.storageIndex.delete(key);
|
|
1525
|
+
}
|
|
1526
|
+
else if (actualTier && actualTier !== entry.tier && entry.tier !== 'hot') {
|
|
1527
|
+
repairs.push({ key, issue: `Index says ${entry.tier} but found in ${actualTier}`, action: `updated tier to ${actualTier}` });
|
|
1528
|
+
entry.tier = actualTier;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return { inconsistencies: repairs.length, repairs };
|
|
1532
|
+
}
|
|
1533
|
+
// Advanced Tier Management
|
|
1534
|
+
setIndexEntryTimestamp(key, field, timestamp) {
|
|
1535
|
+
const entry = this.storageIndex.get(key);
|
|
1536
|
+
if (entry)
|
|
1537
|
+
entry[field] = timestamp;
|
|
1538
|
+
}
|
|
1539
|
+
getAccessPatternMetrics(key) {
|
|
1540
|
+
const entry = this.storageIndex.get(key);
|
|
1541
|
+
if (!entry)
|
|
1542
|
+
return { isBurst: false, accessRate: 0, recentAccesses: 0 };
|
|
1543
|
+
const ageMs = Math.max(1, Date.now() - entry.created);
|
|
1544
|
+
const accessRate = (entry.accessCount / ageMs) * 1000;
|
|
1545
|
+
return { isBurst: accessRate > 5, accessRate, recentAccesses: entry.accessCount };
|
|
1546
|
+
}
|
|
1547
|
+
async evictByMemoryPressure(bytesToFree) {
|
|
1548
|
+
const events = [];
|
|
1549
|
+
let freedBytes = 0;
|
|
1550
|
+
const hotEntries = Array.from(this.storageIndex.values()).filter(e => e.tier === 'hot').sort((a, b) => b.size - a.size);
|
|
1551
|
+
for (const entry of hotEntries) {
|
|
1552
|
+
if (freedBytes >= bytesToFree)
|
|
1553
|
+
break;
|
|
1554
|
+
const event = await this.demoteFromHot(entry.key, 'lru_eviction');
|
|
1555
|
+
events.push(event);
|
|
1556
|
+
if (event.success)
|
|
1557
|
+
freedBytes += entry.size;
|
|
1558
|
+
}
|
|
1559
|
+
return events;
|
|
1560
|
+
}
|
|
1561
|
+
setEntryPriority(key, priority) { this.entryPriorities.set(key, priority); }
|
|
1562
|
+
getEntryPriority(key) { return this.entryPriorities.get(key) ?? 'normal'; }
|
|
1563
|
+
async batchPromoteToWarm(keys) { return Promise.all(keys.map(key => this.promoteToWarm(key))); }
|
|
1564
|
+
async batchDemoteFromWarm(keys) { return Promise.all(keys.map(key => this.demoteFromWarm(key))); }
|
|
1565
|
+
async healthCheck() {
|
|
1566
|
+
const checkTier = async (tier) => {
|
|
1567
|
+
const start = Date.now();
|
|
1568
|
+
try {
|
|
1569
|
+
if (tier === 'hot')
|
|
1570
|
+
return { status: this.cacheLayer || this.swrCacheLayer ? 'healthy' : 'unavailable', latency: Date.now() - start };
|
|
1571
|
+
if (tier === 'warm') {
|
|
1572
|
+
await this.doStorage.get('__health_check__');
|
|
1573
|
+
return { status: 'healthy', latency: Date.now() - start };
|
|
1574
|
+
}
|
|
1575
|
+
await this.r2Layer.get('__health_check__');
|
|
1576
|
+
return { status: 'healthy', latency: Date.now() - start };
|
|
1577
|
+
}
|
|
1578
|
+
catch (e) {
|
|
1579
|
+
return { status: 'unhealthy', latency: Date.now() - start, error: e instanceof Error ? e.message : String(e) };
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
const [hot, warm, cold] = await Promise.all([checkTier('hot'), checkTier('warm'), checkTier('cold')]);
|
|
1583
|
+
const statuses = [hot.status, warm.status, cold.status];
|
|
1584
|
+
let overall = 'healthy';
|
|
1585
|
+
if (statuses.some(s => s === 'unhealthy'))
|
|
1586
|
+
overall = 'degraded';
|
|
1587
|
+
if (statuses.every(s => s === 'unhealthy'))
|
|
1588
|
+
overall = 'unhealthy';
|
|
1589
|
+
return { overall, tiers: { hot, warm, cold }, latency: { hot: hot.latency, warm: warm.latency, cold: cold.latency } };
|
|
1590
|
+
}
|
|
1591
|
+
getMemoryUsage() {
|
|
1592
|
+
let hot = 0, warm = 0, cold = 0;
|
|
1593
|
+
for (const entry of this.storageIndex.values()) {
|
|
1594
|
+
if (entry.tier === 'hot')
|
|
1595
|
+
hot += entry.size;
|
|
1596
|
+
else if (entry.tier === 'warm')
|
|
1597
|
+
warm += entry.size;
|
|
1598
|
+
else
|
|
1599
|
+
cold += entry.size;
|
|
1600
|
+
}
|
|
1601
|
+
const total = hot + warm + cold;
|
|
1602
|
+
const limit = (this.memoryLimits.hot ?? Infinity) + (this.memoryLimits.warm ?? Infinity);
|
|
1603
|
+
return { hot, warm, cold, total, limit, utilizationPercent: limit === Infinity ? 0 : (total / limit) * 100 };
|
|
1604
|
+
}
|
|
1605
|
+
setMemoryLimit(limits) { this.memoryLimits = { ...this.memoryLimits, ...limits }; }
|
|
1606
|
+
getMemoryLimits() { return { ...this.memoryLimits }; }
|
|
1607
|
+
async prefetch(keys, options) {
|
|
1608
|
+
const targetTier = options?.targetTier ?? 'warm';
|
|
1609
|
+
const maxConcurrency = options?.maxConcurrency ?? 5;
|
|
1610
|
+
const failedKeys = [];
|
|
1611
|
+
let prefetchedCount = 0;
|
|
1612
|
+
for (let i = 0; i < keys.length; i += maxConcurrency) {
|
|
1613
|
+
const batch = keys.slice(i, i + maxConcurrency);
|
|
1614
|
+
const results = await Promise.allSettled(batch.map(async (key) => {
|
|
1615
|
+
const event = targetTier === 'hot' ? await this.promoteToHot(key) : await this.promoteToWarm(key);
|
|
1616
|
+
if (!event.success)
|
|
1617
|
+
throw new Error(event.error);
|
|
1618
|
+
return key;
|
|
1619
|
+
}));
|
|
1620
|
+
for (let j = 0; j < results.length; j++) {
|
|
1621
|
+
const result = results[j];
|
|
1622
|
+
if (result?.status === 'fulfilled')
|
|
1623
|
+
prefetchedCount++;
|
|
1624
|
+
else if (result?.status === 'rejected' && batch[j])
|
|
1625
|
+
failedKeys.push(batch[j]);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return { prefetchedCount, failedKeys };
|
|
1629
|
+
}
|
|
1630
|
+
subscribeToEvents(callback, options) {
|
|
1631
|
+
const subscriber = { callback, filter: options?.filter };
|
|
1632
|
+
this.eventSubscribers.push(subscriber);
|
|
1633
|
+
return () => { const idx = this.eventSubscribers.indexOf(subscriber); if (idx !== -1)
|
|
1634
|
+
this.eventSubscribers.splice(idx, 1); };
|
|
1635
|
+
}
|
|
1636
|
+
async createSnapshot() {
|
|
1637
|
+
return { version: 1, timestamp: Date.now(), index: Array.from(this.storageIndex.values()), stats: this.getStats() };
|
|
1638
|
+
}
|
|
1639
|
+
async restoreFromSnapshot(snapshot) {
|
|
1640
|
+
this.storageIndex.clear();
|
|
1641
|
+
this.indexMemoryBytes = 0;
|
|
1642
|
+
for (const entry of snapshot.index) {
|
|
1643
|
+
this.storageIndex.set(entry.key, { ...entry });
|
|
1644
|
+
this.indexMemoryBytes += TieredStorageOrchestrator.ENTRY_MEMORY_OVERHEAD + entry.key.length * 2;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async warmup(options) {
|
|
1648
|
+
let candidates = Array.from(this.storageIndex.values()).filter(e => e.tier === 'cold');
|
|
1649
|
+
if (options.strategy === 'most-accessed')
|
|
1650
|
+
candidates.sort((a, b) => b.accessCount - a.accessCount);
|
|
1651
|
+
else if (options.strategy === 'recent')
|
|
1652
|
+
candidates.sort((a, b) => b.lastAccess - a.lastAccess);
|
|
1653
|
+
if (options.limit)
|
|
1654
|
+
candidates = candidates.slice(0, options.limit);
|
|
1655
|
+
let bytesTransferred = 0;
|
|
1656
|
+
const warmedKeys = [];
|
|
1657
|
+
for (const entry of candidates) {
|
|
1658
|
+
if (options.maxBytes && bytesTransferred + entry.size > options.maxBytes)
|
|
1659
|
+
break;
|
|
1660
|
+
const event = options.targetTier === 'hot' ? await this.promoteToHot(entry.key) : await this.promoteToWarm(entry.key);
|
|
1661
|
+
if (event.success) {
|
|
1662
|
+
bytesTransferred += entry.size;
|
|
1663
|
+
warmedKeys.push(entry.key);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return { warmedUp: warmedKeys.length, bytesTransferred, keys: warmedKeys };
|
|
1667
|
+
}
|
|
1668
|
+
async garbageCollect(options) {
|
|
1669
|
+
let removedEntries = 0;
|
|
1670
|
+
const keysToRemove = [];
|
|
1671
|
+
for (const [key, entry] of this.storageIndex) {
|
|
1672
|
+
if (entry.tier === 'hot')
|
|
1673
|
+
continue;
|
|
1674
|
+
let exists = false;
|
|
1675
|
+
if (entry.tier === 'warm') {
|
|
1676
|
+
const data = await this.doStorage.get(this.doKey(key));
|
|
1677
|
+
exists = !!data;
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
const data = await this.r2Layer.get(key);
|
|
1681
|
+
exists = !!data;
|
|
1682
|
+
}
|
|
1683
|
+
if (!exists)
|
|
1684
|
+
keysToRemove.push(key);
|
|
1685
|
+
}
|
|
1686
|
+
for (const key of keysToRemove) {
|
|
1687
|
+
this.storageIndex.delete(key);
|
|
1688
|
+
removedEntries++;
|
|
1689
|
+
}
|
|
1690
|
+
return options?.compactCold ? { removedEntries, compactionStats: { bytesReclaimed: 0 } } : { removedEntries };
|
|
1691
|
+
}
|
|
1692
|
+
pinToTier(key, _tier) { this.pinnedEntries.add(key); }
|
|
1693
|
+
unpinFromTier(key) { this.pinnedEntries.delete(key); }
|
|
1694
|
+
isPinned(key) { return this.pinnedEntries.has(key); }
|
|
1695
|
+
getCostBreakdown() {
|
|
1696
|
+
const metrics = this.getAggregatedMetrics();
|
|
1697
|
+
const cc = this.config.metrics?.costConfig ?? { r2StoragePerGBMonth: 0.015, r2ClassAOperations: 0.0000045, r2ClassBOperations: 0.00000036, doStoragePerGBMonth: 0.20, cacheOperations: 0 };
|
|
1698
|
+
const r2Storage = (metrics.tiers.cold.bytesStored / (1024 ** 3)) * cc.r2StoragePerGBMonth;
|
|
1699
|
+
const doStorage = (metrics.tiers.warm.bytesStored / (1024 ** 3)) * cc.doStoragePerGBMonth;
|
|
1700
|
+
const r2Writes = metrics.tiers.cold.writes * cc.r2ClassAOperations;
|
|
1701
|
+
const r2Reads = metrics.tiers.cold.reads * cc.r2ClassBOperations;
|
|
1702
|
+
const total = r2Storage + doStorage + r2Writes + r2Reads;
|
|
1703
|
+
return { storage: { r2: r2Storage, do: doStorage, cache: 0 }, operations: { r2Writes, r2Reads, doWrites: 0, doReads: 0 }, total, projectedMonthly: total };
|
|
1704
|
+
}
|
|
1705
|
+
async optimizeTierPlacement() {
|
|
1706
|
+
const promotions = [];
|
|
1707
|
+
const demotions = [];
|
|
1708
|
+
for (const entry of this.storageIndex.values()) {
|
|
1709
|
+
if (entry.tier === 'cold' && entry.accessCount > 10)
|
|
1710
|
+
promotions.push({ key: entry.key, currentTier: 'cold', suggestedTier: 'warm', reason: 'High access count' });
|
|
1711
|
+
if (entry.tier === 'warm' && entry.accessCount < 5 && Date.now() - entry.lastAccess > 86400000)
|
|
1712
|
+
demotions.push({ key: entry.key, currentTier: 'warm', suggestedTier: 'cold', reason: 'Low access, stale' });
|
|
1713
|
+
}
|
|
1714
|
+
return { promotions, demotions, estimatedSavings: 0 };
|
|
1715
|
+
}
|
|
1716
|
+
async ensureDurability(key) {
|
|
1717
|
+
const coldData = await this.r2Layer.get(key);
|
|
1718
|
+
if (coldData)
|
|
1719
|
+
return { durable: true, tier: 'cold', copied: false };
|
|
1720
|
+
const warmData = await this.doStorage.get(this.doKey(key));
|
|
1721
|
+
if (warmData) {
|
|
1722
|
+
await this.r2Layer.put(key, warmData, { customMetadata: { durabilityBackup: 'true' } });
|
|
1723
|
+
return { durable: true, tier: 'warm', copied: true };
|
|
1724
|
+
}
|
|
1725
|
+
return { durable: false, tier: null, copied: false };
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
export function createTieredStorageOrchestrator(config) {
|
|
1729
|
+
return new TieredStorageOrchestrator(config);
|
|
1730
|
+
}
|
|
1731
|
+
//# sourceMappingURL=tiered-orchestrator.js.map
|