Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ DETECT_PRICE_MOVEMENTS_INTERVAL_MINUTES=5
TWAP_COMPUTATION_ENABLED=true
TWAP_COMPUTATION_INTERVAL_MINUTES=5

# Daily on-chain key analytics snapshots (#1155)
KEY_ANALYTICS_SNAPSHOT_ENABLED=true
KEY_ANALYTICS_SNAPSHOT_INTERVAL_MINUTES=1440
KEY_ANALYTICS_SNAPSHOT_BATCH_SIZE=100
KEY_ANALYTICS_SNAPSHOT_RETRY_INTERVAL_MINUTES=5
KEY_ANALYTICS_SNAPSHOT_RETRY_WINDOW_MINUTES=60

# Request body size limits (see docs/body-size-limits.md)
BODY_SIZE_LIMIT_DEFAULT=10mb
# BODY_SIZE_LIMIT_AUTH=100kb
Expand Down
10 changes: 10 additions & 0 deletions docs/ENVIRONMENT_VARIABLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ no need to thread it through function arguments.
| `OWNERSHIP_SNAPSHOT_CLEANUP_DRY_RUN` | boolean | No | `true` | Run cleanup in dry-run mode (log only, no deletion) |
| `OWNERSHIP_SNAPSHOT_CLEANUP_INTERVAL_MINUTES` | number | No | `60` | Interval in minutes between cleanup job runs |

### Key Analytics Snapshots

| Variable | Type | Required | Default | Description |
| ---------------------------------------------- | ------- | -------- | ------- | --------------------------------------------------------------- |
| `KEY_ANALYTICS_SNAPSHOT_ENABLED` | boolean | No | `true` | Enable daily on-chain snapshots for active registered keys |
| `KEY_ANALYTICS_SNAPSHOT_INTERVAL_MINUTES` | number | No | `1440` | Interval between full daily snapshot collections |
| `KEY_ANALYTICS_SNAPSHOT_BATCH_SIZE` | number | No | `100` | Active keys/errors processed per database batch |
| `KEY_ANALYTICS_SNAPSHOT_RETRY_INTERVAL_MINUTES`| number | No | `5` | Minutes between retry passes for failed snapshots |
| `KEY_ANALYTICS_SNAPSHOT_RETRY_WINDOW_MINUTES` | number | No | `60` | Maximum time after failure during which the snapshot is retried |

### Job Coordination

| Variable | Type | Required | Default | Description |
Expand Down
34 changes: 34 additions & 0 deletions prisma/schema/key-snapshot.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
model KeySnapshot {
id String @id @default(cuid())
/// Registered on-chain key contract address.
keyId String
/// UTC calendar day this snapshot represents.
snapshotDate DateTime @db.Date
/// Metrics returned by take_daily_snapshot, kept as JSON to preserve
/// contract metrics without requiring a schema change for each new field.
metrics Json
createdAt DateTime @default(now())

@@unique([keyId, snapshotDate])
@@index([snapshotDate])
@@map("key_snapshots")
}

model SnapshotError {
id String @id @default(cuid())
/// Registered on-chain key contract address.
keyId String
/// UTC calendar day the failed snapshot belongs to.
snapshotDate DateTime @db.Date
error String
firstFailedAt DateTime @default(now())
retryAt DateTime
retryExpiresAt DateTime
retryCount Int @default(0)
resolvedAt DateTime?
updatedAt DateTime @updatedAt

@@unique([keyId, snapshotDate])
@@index([resolvedAt, retryAt, retryExpiresAt])
@@map("snapshot_errors")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
CREATE TABLE "key_snapshots" (
"id" TEXT NOT NULL,
"keyId" TEXT NOT NULL,
"snapshotDate" DATE NOT NULL,
"metrics" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "key_snapshots_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "key_snapshots_keyId_snapshotDate_key"
ON "key_snapshots"("keyId", "snapshotDate");
CREATE INDEX "key_snapshots_snapshotDate_idx"
ON "key_snapshots"("snapshotDate");

CREATE TABLE "snapshot_errors" (
"id" TEXT NOT NULL,
"keyId" TEXT NOT NULL,
"snapshotDate" DATE NOT NULL,
"error" TEXT NOT NULL,
"firstFailedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"retryAt" TIMESTAMP(3) NOT NULL,
"retryExpiresAt" TIMESTAMP(3) NOT NULL,
"retryCount" INTEGER NOT NULL DEFAULT 0,
"resolvedAt" TIMESTAMP(3),
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "snapshot_errors_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "snapshot_errors_keyId_snapshotDate_key"
ON "snapshot_errors"("keyId", "snapshotDate");
CREATE INDEX "snapshot_errors_resolvedAt_retryAt_retryExpiresAt_idx"
ON "snapshot_errors"("resolvedAt", "retryAt", "retryExpiresAt");
23 changes: 23 additions & 0 deletions src/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,29 @@ export const envSchema = z
.positive()
.default(5),

// Daily on-chain key analytics snapshots and bounded failure retries.
KEY_ANALYTICS_SNAPSHOT_ENABLED: booleanCoerce.default(true),
KEY_ANALYTICS_SNAPSHOT_INTERVAL_MINUTES: z.coerce
.number()
.int()
.positive()
.default(1440),
KEY_ANALYTICS_SNAPSHOT_BATCH_SIZE: z.coerce
.number()
.int()
.positive()
.default(100),
KEY_ANALYTICS_SNAPSHOT_RETRY_INTERVAL_MINUTES: z.coerce
.number()
.int()
.positive()
.default(5),
KEY_ANALYTICS_SNAPSHOT_RETRY_WINDOW_MINUTES: z.coerce
.number()
.int()
.positive()
.default(60),

// Minimum number of observations a TWAPUpdated snapshot must carry for
// GET /keys/:key_id/twap to treat it as valid (#1082). Below this the
// endpoint responds with TWAPInvalid instead of a price.
Expand Down
Loading
Loading