Repository: unkeyed/unkey
Pull request #3240: feat: add minute granularity - This should be merged before key details
Author: login: ogzhanolguncu
Created: 2025-05-12T11:01:35Z | Merged: 2025-05-14T13:05:59Z

## What does this PR do?

This PR adds minute granularity for the upcoming Keys Details page and better chart results. We have to merge this first so we can have some data before we actually enable the minute granularity for charts.

## Type of change

<!-- Please mark the relevant points by using [x] -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] Enhancement (small improvements)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->

- Check /apis page **charts**
- Check /keys **outcome chart** and **active keys chart**



## Checklist

<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did  -->

### Required

- [x] Filled out the "How to test" section in this PR
- [x] Read [Contributing Guide](./CONTRIBUTING.md)
- [x] Self-reviewed my own code
- [x] Commented on my code in hard-to-understand areas
- [x] Ran `pnpm build`
- [x] Ran `pnpm fmt`
- [x] Checked for warnings, there are none
- [x] Removed all `console.logs`
- [x] Merged the latest changes from main onto my branch with `git pull origin main`
- [x] My changes don't cause any responsiveness issues

### Appreciated

- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
- [ ] Updated the Unkey Docs if changes were necessary


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for minute-level, 5-minute, and 30-minute timeseries granularity for verification and active key data, enabling finer detail in analytics and dashboards.
  - Introduced new database tables and materialized views to efficiently store and aggregate verification events at minute-level resolution.

- **Improvements**
  - Enhanced flexibility in timeseries granularity selection for verification data, allowing more detailed analysis for short time ranges.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/dashboard/lib/trpc/routers/utils/granularity.ts b/apps/dashboard/lib/trpc/routers/utils/granularity.ts
index 3b91d096b9..4b70396989 100644
--- a/apps/dashboard/lib/trpc/routers/utils/granularity.ts
+++ b/apps/dashboard/lib/trpc/routers/utils/granularity.ts
@@ -52,6 +52,8 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
   context: TContext,
   startTime?: number | null,
   endTime?: number | null,
+  // INFO: We'll delete this once key details is merged. Required for accumulating data for minutely charts before merging the key details.
+  allowMinutelyForVerifications = false,
 ): TimeseriesConfig<TContext> => {
   const now = Date.now();
   const WEEK_IN_MS = DAY_IN_MS * 7;
@@ -62,7 +64,6 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
   if (!startTime && !endTime) {
     const defaultGranularity = DEFAULT_GRANULARITY[context];
     const defaultDuration = context === "forVerifications" ? DAY_IN_MS : HOUR_IN_MS;
-
     return {
       granularity: defaultGranularity as TimeseriesGranularityMap[TContext],
       startTime: now - defaultDuration,
@@ -76,9 +77,7 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
   // Set default start time if missing (defaults vary by context)
   const defaultDuration = context === "forVerifications" ? DAY_IN_MS : HOUR_IN_MS;
   const effectiveStartTime = startTime ?? effectiveEndTime - defaultDuration;
-
   const timeRange = effectiveEndTime - effectiveStartTime;
-
   let granularity: CompoundTimeseriesGranularity;
 
   if (context === "forVerifications") {
@@ -93,7 +92,25 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
     } else if (timeRange >= WEEK_IN_MS) {
       granularity = "perHour";
     } else {
-      granularity = "perHour";
+      // Use the minutely granularity only if allowMinutelyForVerifications is true
+      if (allowMinutelyForVerifications) {
+        if (timeRange >= DAY_IN_MS * 3) {
+          granularity = "perHour";
+        } else if (timeRange >= DAY_IN_MS) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 16) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 8) {
+          granularity = "per30Minutes";
+        } else if (timeRange >= HOUR_IN_MS * 4) {
+          granularity = "per5Minutes";
+        } else {
+          granularity = "perMinute";
+        }
+      } else {
+        // Fall back to hourly granularity if minutely is disabled
+        granularity = "perHour";
+      }
     }
   } else {
     if (timeRange >= DAY_IN_MS * 7) {
diff --git a/go/pkg/clickhouse/schema/databases/001_verifications/021_key_verifications_per_minute_v1.sql b/go/pkg/clickhouse/schema/databases/001_verifications/021_key_verifications_per_minute_v1.sql
new file mode 100644
index 0000000000..15b1466a32
--- /dev/null
+++ b/go/pkg/clickhouse/schema/databases/001_verifications/021_key_verifications_per_minute_v1.sql
@@ -0,0 +1,16 @@
+CREATE TABLE verifications.key_verifications_per_minute_v1
+(
+  time          DateTime,
+  workspace_id  String,
+  key_space_id  String,
+  identity_id   String,
+  key_id        String,
+  outcome       LowCardinality(String),
+  tags          Array(String),
+  count         Int64
+)
+ENGINE = SummingMergeTree()
+ORDER BY (workspace_id, key_space_id, identity_id, key_id, time, tags, outcome)
+;
+
+
diff --git a/go/pkg/clickhouse/schema/databases/001_verifications/022_key_verifications_per_minute_mv_v1.sql b/go/pkg/clickhouse/schema/databases/001_verifications/022_key_verifications_per_minute_mv_v1.sql
new file mode 100644
index 0000000000..760d6ae6fa
--- /dev/null
+++ b/go/pkg/clickhouse/schema/databases/001_verifications/022_key_verifications_per_minute_mv_v1.sql
@@ -0,0 +1,22 @@
+CREATE MATERIALIZED VIEW IF NOT EXISTS verifications.key_verifications_per_minute_mv_v1
+TO verifications.key_verifications_per_minute_v1
+AS
+SELECT
+  workspace_id,
+  key_space_id,
+  identity_id,
+  key_id,
+  outcome,
+  count(*) as count,
+  toStartOfMinute(fromUnixTimestamp64Milli(time)) AS time,
+  tags
+FROM verifications.raw_key_verifications_v1
+GROUP BY
+  workspace_id,
+  key_space_id,
+  identity_id,
+  key_id,
+  outcome,
+  time,
+  tags
+;
diff --git a/internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql b/internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql
new file mode 100644
index 0000000000..2b68530cdc
--- /dev/null
+++ b/internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql
@@ -0,0 +1,19 @@
+-- +goose up
+CREATE TABLE verifications.key_verifications_per_minute_v1
+(
+  time          DateTime,
+  workspace_id  String,
+  key_space_id  String,
+  identity_id   String,
+  key_id        String,
+  outcome       LowCardinality(String),
+  tags          Array(String),
+  count         Int64
+)
+ENGINE = SummingMergeTree()
+ORDER BY (workspace_id, key_space_id, identity_id, key_id, time, tags, outcome)
+;
+
+
+-- +goose down
+DROP TABLE verifications.key_verifications_per_minute_v1;
diff --git a/internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql b/internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql
new file mode 100644
index 0000000000..2a358d855c
--- /dev/null
+++ b/internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql
@@ -0,0 +1,26 @@
+-- +goose up
+CREATE MATERIALIZED VIEW IF NOT EXISTS verifications.key_verifications_per_minute_mv_v1
+TO verifications.key_verifications_per_minute_v1
+AS
+SELECT
+  workspace_id,
+  key_space_id,
+  identity_id,
+  key_id,
+  outcome,
+  count(*) as count,
+  toStartOfMinute(fromUnixTimestamp64Milli(time)) AS time,
+  tags
+FROM verifications.raw_key_verifications_v1
+GROUP BY
+  workspace_id,
+  key_space_id,
+  identity_id,
+  key_id,
+  outcome,
+  time,
+  tags
+;
+
+-- +goose down
+DROP VIEW verifications.key_verifications_per_minute_mv_v1;
diff --git a/internal/clickhouse/src/index.ts b/internal/clickhouse/src/index.ts
index 1ca77dae1b..6bc529568c 100644
--- a/internal/clickhouse/src/index.ts
+++ b/internal/clickhouse/src/index.ts
@@ -2,10 +2,13 @@ import { getBillableRatelimits, getBillableVerifications } from "./billing";
 import { Client, type Inserter, Noop, type Querier } from "./client";
 import {
   getDailyActiveKeysTimeseries,
+  getFiveMinutelyActiveKeysTimeseries,
   getFourHourlyActiveKeysTimeseries,
   getHourlyActiveKeysTimeseries,
+  getMinutelyActiveKeysTimeseries,
   getMonthlyActiveKeysTimeseries,
   getSixHourlyActiveKeysTimeseries,
+  getThirtyMinutelyActiveKeysTimeseries,
   getThreeDayActiveKeysTimeseries,
   getTwelveHourlyActiveKeysTimeseries,
   getTwoHourlyActiveKeysTimeseries,
@@ -55,10 +58,13 @@ import { getActiveWorkspacesPerMonth } from "./success";
 import { insertSDKTelemetry } from "./telemetry";
 import {
   getDailyVerificationTimeseries,
+  getFiveMinutelyVerificationTimeseries,
   getFourHourlyVerificationTimeseries,
   getHourlyVerificationTimeseries,
+  getMinutelyVerificationTimeseries,
   getMonthlyVerificationTimeseries,
   getSixHourlyVerificationTimeseries,
+  getThirtyMinutelyVerificationTimeseries,
   getThreeDayVerificationTimeseries,
   getTwelveHourlyVerificationTimeseries,
   getTwoHourlyVerificationTimeseries,
@@ -112,25 +118,39 @@ export class ClickHouse {
       perMonth: getVerificationsPerMonth(this.querier),
       latest: getLatestVerifications(this.querier),
       timeseries: {
+        // Minute-based granularity
+        perMinute: getMinutelyVerificationTimeseries(this.querier),
+        per5Minutes: getFiveMinutelyVerificationTimeseries(this.querier),
+        per30Minutes: getThirtyMinutelyVerificationTimeseries(this.querier),
+        // Hour-based granularity
         perHour: getHourlyVerificationTimeseries(this.querier),
         per2Hours: getTwoHourlyVerificationTimeseries(this.querier),
         per4Hours: getFourHourlyVerificationTimeseries(this.querier),
         per6Hours: getSixHourlyVerificationTimeseries(this.querier),
         per12Hours: getTwelveHourlyVerificationTimeseries(this.querier),
+        // Day-based granularity
         perDay: getDailyVerificationTimeseries(this.querier),
         per3Days: getThreeDayVerificationTimeseries(this.querier),
         perWeek: getWeeklyVerificationTimeseries(this.querier),
+        // Month-based granularity
         perMonth: getMonthlyVerificationTimeseries(this.querier),
       },
       activeKeysTimeseries: {
+        // Minute-based granularity
+        perMinute: getMinutelyActiveKeysTimeseries(this.querier),
+        per5Minutes: getFiveMinutelyActiveKeysTimeseries(this.querier),
+        per30Minutes: getThirtyMinutelyActiveKeysTimeseries(this.querier),
+        // Hour-based granularity
         perHour: getHourlyActiveKeysTimeseries(this.querier),
         per2Hours: getTwoHourlyActiveKeysTimeseries(this.querier),
         per4Hours: getFourHourlyActiveKeysTimeseries(this.querier),
         per6Hours: getSixHourlyActiveKeysTimeseries(this.querier),
         per12Hours: getTwelveHourlyActiveKeysTimeseries(this.querier),
+        // Day-based granularity
         perDay: getDailyActiveKeysTimeseries(this.querier),
         per3Days: getThreeDayActiveKeysTimeseries(this.querier),
         perWeek: getWeeklyActiveKeysTimeseries(this.querier),
+        // Month-based granularity
         perMonth: getMonthlyActiveKeysTimeseries(this.querier),
       },
     };
diff --git a/internal/clickhouse/src/keys/active_keys.ts b/internal/clickhouse/src/keys/active_keys.ts
index 7ce5919358..35a0aaca4d 100644
--- a/internal/clickhouse/src/keys/active_keys.ts
+++ b/internal/clickhouse/src/keys/active_keys.ts
@@ -60,6 +60,23 @@ type TimeInterval = {
 };
 
 const ACTIVE_KEYS_INTERVALS: Record<string, TimeInterval> = {
+  // Minute-based intervals
+  minute: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 1,
+  },
+  fiveMinutes: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 5,
+  },
+  thirtyMinutes: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 30,
+  },
+  // Hour-based intervals
   hour: {
     table: "verifications.key_verifications_per_hour_v3",
     step: "HOUR",
@@ -67,24 +84,25 @@ const ACTIVE_KEYS_INTERVALS: Record<string, TimeInterval> = {
   },
   twoHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 2,
   },
   fourHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 4,
   },
   sixHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 6,
   },
   twelveHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 12,
   },
+  // Day-based intervals
   day: {
     table: "verifications.key_verifications_per_day_v3",
     step: "DAY",
@@ -92,19 +110,20 @@ const ACTIVE_KEYS_INTERVALS: Record<string, TimeInterval> = {
   },
   threeDays: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 3,
   },
   week: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 7,
   },
   twoWeeks: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 14,
   },
+  // Monthly-based intervals
   month: {
     table: "verifications.key_verifications_per_month_v3",
     step: "MONTH",
@@ -112,29 +131,26 @@ const ACTIVE_KEYS_INTERVALS: Record<string, TimeInterval> = {
   },
   quarter: {
     table: "verifications.key_verifications_per_month_v3",
-    step: "MONTHS",
+    step: "MONTH",
     stepSize: 3,
   },
 } as const;
 
 function createActiveKeysTimeseriesQuery(interval: TimeInterval, whereClause: string) {
   const intervalUnit = {
+    MINUTE: "minute",
     HOUR: "hour",
-    HOURS: "hour",
     DAY: "day",
     DAYS: "day",
     MONTH: "month",
-    MONTHS: "month",
   }[interval.step];
 
   // For millisecond step calculation
   const msPerUnit = {
+    MINUTE: 60_000,
     HOUR: 3600_000,
-    HOURS: 3600_000,
     DAY: 86400_000,
-    DAYS: 86400_000,
     MONTH: 2592000_000,
-    MONTHS: 2592000_000,
   }[interval.step];
 
   const stepMs = msPerUnit! * interval.stepSize;
@@ -264,7 +280,18 @@ function createActiveKeysTimeseriesQuerier(interval: TimeInterval) {
     })(parameters);
   };
 }
+// Minute-based timeseries
+export const getMinutelyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
+  ACTIVE_KEYS_INTERVALS.minute,
+);
+export const getFiveMinutelyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
+  ACTIVE_KEYS_INTERVALS.fiveMinutes,
+);
+export const getThirtyMinutelyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
+  ACTIVE_KEYS_INTERVALS.thirtyMinutes,
+);
 
+// Hour-based timeseries
 export const getHourlyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
   ACTIVE_KEYS_INTERVALS.hour,
 );
@@ -280,6 +307,8 @@ export const getSixHourlyActiveKeysTimeseries = createActiveKeysTimeseriesQuerie
 export const getTwelveHourlyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
   ACTIVE_KEYS_INTERVALS.twelveHours,
 );
+
+// Day-based timeseries
 export const getDailyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
   ACTIVE_KEYS_INTERVALS.day,
 );
@@ -292,6 +321,8 @@ export const getWeeklyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
 export const getTwoWeeklyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
   ACTIVE_KEYS_INTERVALS.twoWeeks,
 );
+
+// Month-based timeseries
 export const getMonthlyActiveKeysTimeseries = createActiveKeysTimeseriesQuerier(
   ACTIVE_KEYS_INTERVALS.month,
 );
diff --git a/internal/clickhouse/src/verifications.ts b/internal/clickhouse/src/verifications.ts
index 78423adcf3..c5ca953de7 100644
--- a/internal/clickhouse/src/verifications.ts
+++ b/internal/clickhouse/src/verifications.ts
@@ -194,6 +194,23 @@ type TimeInterval = {
 };
 
 const INTERVALS: Record<string, TimeInterval> = {
+  // Minute-based intervals
+  minute: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 1,
+  },
+  fiveMinutes: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 5,
+  },
+  thirtyMinutes: {
+    table: "verifications.key_verifications_per_minute_v1",
+    step: "MINUTE",
+    stepSize: 30,
+  },
+  // Hour-based intervals
   hour: {
     table: "verifications.key_verifications_per_hour_v3",
     step: "HOUR",
@@ -201,24 +218,25 @@ const INTERVALS: Record<string, TimeInterval> = {
   },
   twoHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 2,
   },
   fourHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 4,
   },
   sixHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 6,
   },
   twelveHours: {
     table: "verifications.key_verifications_per_hour_v3",
-    step: "HOURS",
+    step: "HOUR",
     stepSize: 12,
   },
+  // Day-based intervals
   day: {
     table: "verifications.key_verifications_per_day_v3",
     step: "DAY",
@@ -226,17 +244,17 @@ const INTERVALS: Record<string, TimeInterval> = {
   },
   threeDays: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 3,
   },
   week: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 7,
   },
   twoWeeks: {
     table: "verifications.key_verifications_per_day_v3",
-    step: "DAYS",
+    step: "DAY",
     stepSize: 14,
   },
   // Monthly-based intervals
@@ -247,29 +265,25 @@ const INTERVALS: Record<string, TimeInterval> = {
   },
   quarter: {
     table: "verifications.key_verifications_per_month_v3",
-    step: "MONTHS",
+    step: "MONTH",
     stepSize: 3,
   },
 } as const;
 
 function createVerificationTimeseriesQuery(interval: TimeInterval, whereClause: string) {
   const intervalUnit = {
+    MINUTE: "minute",
     HOUR: "hour",
-    HOURS: "hour",
     DAY: "day",
-    DAYS: "day",
     MONTH: "month",
-    MONTHS: "month",
   }[interval.step];
 
   // For millisecond step calculation
   const msPerUnit = {
+    MINUTE: 60_000,
     HOUR: 3600_000,
-    HOURS: 3600_000,
     DAY: 86400_000,
-    DAYS: 86400_000,
     MONTH: 2592000_000,
-    MONTHS: 2592000_000,
   }[interval.step];
 
   const stepMs = msPerUnit! * interval.stepSize;
@@ -484,38 +498,46 @@ function mergeVerificationTimeseriesResults(
   return Array.from(mergedMap.values()).sort((a, b) => a.x - b.x);
 }
 
+// Minute-based timeseries
+export const getMinutelyVerificationTimeseries =
+  (ch: Querier) => (args: VerificationTimeseriesParams) =>
+    batchVerificationTimeseries(ch, INTERVALS.minute, args);
+export const getFiveMinutelyVerificationTimeseries =
+  (ch: Querier) => (args: VerificationTimeseriesParams) =>
+    batchVerificationTimeseries(ch, INTERVALS.fiveMinutes, args);
+export const getThirtyMinutelyVerificationTimeseries =
+  (ch: Querier) => (args: VerificationTimeseriesParams) =>
+    batchVerificationTimeseries(ch, INTERVALS.thirtyMinutes, args);
+
+// Hour-based timeseries
 export const getHourlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.hour, args);
-
 export const getTwoHourlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.twoHours, args);
-
 export const getFourHourlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.fourHours, args);
-
 export const getSixHourlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.sixHours, args);
-
 export const getTwelveHourlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.twelveHours, args);
 
+// Day-based timeseries
 export const getDailyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.day, args);
-
 export const getThreeDayVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.threeDays, args);
-
 export const getWeeklyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.week, args);
 
+// Month-based timeseries
 export const getMonthlyVerificationTimeseries =
   (ch: Querier) => (args: VerificationTimeseriesParams) =>
     batchVerificationTimeseries(ch, INTERVALS.month, args);


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>apps/dashboard/lib/trpc/routers/utils/granularity.ts (2)</summary><blockquote>

`55-56`: **Document parameter usage and future plans.**

The comment indicates this parameter is temporary and will be removed after the key details feature is merged. Consider adding more context about why this approach was chosen and what will replace it in the future.

---

`97-103`: **Simplify redundant conditions in the time range logic.**

There are redundant conditions that always result in the same "perHour" granularity:
- Lines 97-100 all set granularity to "perHour" for time ranges between 1-3 days
- Lines 101-102 set granularity to "perHour" for time ranges between 8-16 hours

These could be simplified to reduce code complexity.

```diff
-        if (timeRange >= DAY_IN_MS * 3) {
-          granularity = "perHour";
-        } else if (timeRange >= DAY_IN_MS) {
-          granularity = "perHour";
-        } else if (timeRange >= HOUR_IN_MS * 16) {
-          granularity = "perHour";
+        if (timeRange >= HOUR_IN_MS * 16) {
+          granularity = "perHour";
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c824e5f115dd15dfa840fd053c5bae87ee6aa5f5 and 21b320ec0db0eb8a2c1552ebec2ac560c351c72f.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `apps/dashboard/lib/trpc/routers/utils/granularity.ts` (2 hunks)
* `internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql` (1 hunks)
* `internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql` (1 hunks)
* `internal/clickhouse/src/index.ts` (3 hunks)
* `internal/clickhouse/src/keys/active_keys.ts` (8 hunks)
* `internal/clickhouse/src/verifications.ts` (5 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>apps/dashboard/lib/trpc/routers/utils/granularity.ts (1)</summary><blockquote>

<details>
<summary>apps/dashboard/lib/trpc/routers/utils/constants.ts (2)</summary>

* `DAY_IN_MS` (2-2)
* `HOUR_IN_MS` (1-1)

</details>

</blockquote></details>
<details>
<summary>internal/clickhouse/src/index.ts (2)</summary><blockquote>

<details>
<summary>internal/clickhouse/src/verifications.ts (12)</summary>

* `getMinutelyVerificationTimeseries` (510-512)
* `getFiveMinutelyVerificationTimeseries` (513-515)
* `getThirtyMinutelyVerificationTimeseries` (516-518)
* `getHourlyVerificationTimeseries` (521-523)
* `getTwoHourlyVerificationTimeseries` (524-526)
* `getFourHourlyVerificationTimeseries` (527-529)
* `getSixHourlyVerificationTimeseries` (530-532)
* `getTwelveHourlyVerificationTimeseries` (533-535)
* `getDailyVerificationTimeseries` (538-540)
* `getThreeDayVerificationTimeseries` (541-543)
* `getWeeklyVerificationTimeseries` (544-546)
* `getMonthlyVerificationTimeseries` (549-551)

</details>
<details>
<summary>internal/clickhouse/src/keys/active_keys.ts (11)</summary>

* `getMinutelyActiveKeysTimeseries` (291-293)
* `getFiveMinutelyActiveKeysTimeseries` (294-296)
* `getThirtyMinutelyActiveKeysTimeseries` (297-299)
* `getHourlyActiveKeysTimeseries` (302-304)
* `getTwoHourlyActiveKeysTimeseries` (305-307)
* `getFourHourlyActiveKeysTimeseries` (308-310)
* `getSixHourlyActiveKeysTimeseries` (311-313)
* `getTwelveHourlyActiveKeysTimeseries` (314-316)
* `getDailyActiveKeysTimeseries` (319-321)
* `getThreeDayActiveKeysTimeseries` (322-324)
* `getWeeklyActiveKeysTimeseries` (325-327)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (19)</summary>

* GitHub Check: Test Go API Local / Test (Shard 4/8)
* GitHub Check: Test Go API Local / Test (Shard 8/8)
* GitHub Check: Test Go API Local / Test (Shard 7/8)
* GitHub Check: Test Go API Local / Test (Shard 5/8)
* GitHub Check: Test Go API Local / Test (Shard 3/8)
* GitHub Check: Test Go API Local / Test (Shard 6/8)
* GitHub Check: Test Go API Local / Test (Shard 2/8)
* GitHub Check: Test Go API Local / Test (Shard 1/8)
* GitHub Check: Build / Build
* GitHub Check: Test API / API Test Local
* GitHub Check: Test Packages / Test ./packages/api
* GitHub Check: Test Packages / Test ./packages/rbac
* GitHub Check: Test Packages / Test ./packages/cache
* GitHub Check: Test Packages / Test ./packages/hono
* GitHub Check: Test Packages / Test ./packages/nextjs
* GitHub Check: Test Packages / Test ./apps/dashboard
* GitHub Check: Test Packages / Test ./internal/clickhouse
* GitHub Check: autofix
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (18)</summary><blockquote>

<details>
<summary>internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql (1)</summary>

`1-23`: **LGTM! Well-structured materialized view for minute-level aggregation.**

The materialized view correctly:
- Aggregates raw verification data at minute-level granularity
- Groups by all relevant dimensions
- Uses toStartOfMinute for proper timestamp bucketing
- Targets the appropriate table

</details>
<details>
<summary>internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql (1)</summary>

`1-15`: **LGTM! Schema is well-designed for minute-level aggregation.**

The table structure is appropriately designed with:
- SummingMergeTree engine for efficient aggregation workloads
- Proper columns for all required dimensions
- Effective ordering for query performance

</details>
<details>
<summary>internal/clickhouse/src/keys/active_keys.ts (3)</summary>

`290-299`: **LGTM! New minute-based timeseries functions are properly implemented.**

The new exported functions for minute-level granularity are correctly implemented using the timeseries querier factory pattern, consistent with the existing hour, day, and month-based functions.

---

`301-338`: **Good organizational improvement with granularity comments.**

Adding comments to group timeseries functions by granularity type (minute, hour, day, month) improves code readability and organization.

---

`63-78`: <details>
<summary>✅ Verification successful</summary>

**Verify the table reference for minute-based intervals.**

All minute-based intervals reference "verifications.key_verifications_per_minute_v1" table. Confirm that active keys data should be queried from the verifications table rather than having a dedicated active_keys_per_minute table.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's an active_keys_per_minute table or if active keys data is stored in the verifications table
rg -A 3 "CREATE TABLE.*active_keys.*minute" --glob "*.sql"
rg -A 3 "FROM verifications.key_verifications_per_minute" --glob "*.ts" | grep -v "active_keys.ts"
```

Length of output: 159

---

🏁 Script executed:

```shell
#!/bin/bash
# Broad search for any SQL table or view related to active keys
rg -n 'active_keys' --glob '*.sql'
rg -n 'active_keys_per_minute' --glob '*.sql'
rg -n 'key_active_keys_per_minute_v1' --glob '*.sql'

# Search references in TS code
rg -n 'active_keys' --glob '*.ts'
rg -n 'active_keys_per_minute' --glob '*.ts'
```

Length of output: 277

---



</details>

**Minute-based intervals use the correct table**  
I don’t see any `active_keys_per_minute` table in our SQL definitions, so querying from `verifications.key_verifications_per_minute_v1` for all minute-based intervals is appropriate.

</details>
<details>
<summary>internal/clickhouse/src/index.ts (6)</summary>

`5-11`: **Import organization looks good.**

The addition of import statements for the minute-level granularity functions is well-organized and follows the existing code pattern.

---

`61-67`: **Import structure is consistent.**

The new verification timeseries import statements are properly organized alongside existing imports, maintaining the codebase's style.

---

`121-124`: **Well-structured minute granularity addition for verification timeseries.**

The new minute-based granularity methods are properly implemented in the `verifications.timeseries` object with a clear comment indicating the granularity level. This follows the PR objective of adding minute-level granularity support.

---

`131-136`: **Good organization with descriptive comments.**

Adding section comments to distinguish between granularity levels improves code readability and maintainability.

---

`139-143`: **Consistent implementation for active keys timeseries.**

The minute-based granularity functions for active keys are implemented consistently with the verification timeseries pattern.

---

`149-154`: **Consistent organization across the codebase.**

The section comments for day-based and month-based granularities maintain the same structure as the added minute-based sections, ensuring consistency throughout the file.

</details>
<details>
<summary>internal/clickhouse/src/verifications.ts (7)</summary>

`197-212`: **Well-defined intervals for minute-based granularity.**

The addition of minute, five-minute, and thirty-minute intervals is well-structured and consistent with the existing interval definitions. All new intervals correctly reference the new `verifications.key_verifications_per_minute_v1` table.

---

`275-277`: **Appropriate interval unit mapping for minutes.**

The interval unit mappings for "MINUTE" and "MINUTES" are correctly set to "minute", consistent with how other units are mapped.

---

`287-289`: **Correct millisecond conversion for minute intervals.**

The millisecond per unit mappings for "MINUTE" and "MINUTES" are properly set to 60,000 (60 seconds * 1000 milliseconds), ensuring accurate time calculations.

---

`509-519`: **Well-implemented minute-based timeseries functions.**

The new minute-based verification timeseries functions are implemented correctly, following the established pattern and using the appropriate interval constants.

---

`520-536`: **Good organization with descriptive comments.**

Adding the "Hour-based timeseries" comment improves code organization and readability, making it clear which functions operate at which time granularity.

---

`537-547`: **Consistent commenting for day-based timeseries.**

The "Day-based timeseries" comment maintains the organizational structure introduced with the minute and hour sections.

---

`548-551`: **Complete organization with month-based comment.**

The "Month-based timeseries" comment finishes the organizational structure, ensuring all granularity levels are clearly labeled.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T11:06:07Z
user: login: ogzhanolguncu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T11:07:11Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T11:07:39Z
user: login: chronark
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T20:11:37Z
user: login: chronark
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-14T13:01:19Z

Review comments:
diff_hunk: @@ -93,7 +92,25 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
     } else if (timeRange >= WEEK_IN_MS) {
       granularity = "perHour";
     } else {
-      granularity = "perHour";
+      // Use the minutely granularity only if allowMinutelyForVerifications is true
+      if (allowMinutelyForVerifications) {
+        if (timeRange >= DAY_IN_MS * 3) {
+          granularity = "perHour";
+        } else if (timeRange >= DAY_IN_MS) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 16) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 8) {
+          granularity = "per30Minutes";
+        } else if (timeRange >= HOUR_IN_MS * 4) {
+          granularity = "per5Minutes";
+        } else {
+          granularity = "perMinute";
+        }
+      } else {
+        // Fall back to hourly granularity if minutely is disabled
+        granularity = "perHour";
+      }
     }
path: apps/dashboard/lib/trpc/routers/utils/granularity.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Add test coverage for the new minute granularity logic.**

The new conditional logic for minute granularity selection will affect data visualization. Ensure it's thoroughly tested with various time ranges to validate correct granularity selection.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for existing test coverage of the granularity function
rg -A 5 "getTimeseriesGranularity" --glob "*.test.ts" --glob "*.spec.ts"
```

Length of output: 11636

---

🏁 Script executed:

```shell
#!/bin/bash
sed -n '1,200p' apps/dashboard/lib/trpc/routers/utils/granularity.test.ts
```

Length of output: 6968

---

🏁 Script executed:

```shell
#!/bin/bash
grep -R -n "allowMinutelyForVerifications" -n apps/dashboard/lib/trpc/routers/utils/granularity.ts
```

Length of output: 275

---

🏁 Script executed:

```shell
#!/bin/bash
sed -n '1,200p' apps/dashboard/lib/trpc/routers/utils/granularity.ts
```

Length of output: 6252

---



</details>

**Add missing tests for `allowMinutelyForVerifications` branch**

We currently only exercise the default (`allowMinutelyForVerifications = false`) path. Please extend  
`apps/dashboard/lib/trpc/routers/utils/granularity.test.ts` with a new `describe` block for the “forVerifications” context when the fourth argument is `true`, covering these ranges:

- timeRange < 4 hours → `"perMinute"`
- 4 hours ≤ timeRange < 8 hours → `"per5Minutes"`
- 8 hours ≤ timeRange < 16 hours → `"per30Minutes"`
- 16 hours ≤ timeRange < 1 day → `"perHour"`
- 1 day ≤ timeRange < 3 days → `"perHour"`
- timeRange ≥ 3 days → `"perHour"`

This will ensure the new minute‐level logic is properly validated.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T11:06:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -93,7 +92,25 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
     } else if (timeRange >= WEEK_IN_MS) {
       granularity = "perHour";
     } else {
-      granularity = "perHour";
+      // Use the minutely granularity only if allowMinutelyForVerifications is true
+      if (allowMinutelyForVerifications) {
+        if (timeRange >= DAY_IN_MS * 3) {
+          granularity = "perHour";
+        } else if (timeRange >= DAY_IN_MS) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 16) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 8) {
+          granularity = "per30Minutes";
+        } else if (timeRange >= HOUR_IN_MS * 4) {
+          granularity = "per5Minutes";
+        } else {
+          granularity = "perMinute";
+        }
+      } else {
+        // Fall back to hourly granularity if minutely is disabled
+        granularity = "perHour";
+      }
     }
path: apps/dashboard/lib/trpc/routers/utils/granularity.ts
user: login: ogzhanolguncu
body: Required tests will be added in the following PR
created_at: 2025-05-12T11:07:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -93,7 +92,25 @@ export const getTimeseriesGranularity = <TContext extends TimeseriesContext>(
     } else if (timeRange >= WEEK_IN_MS) {
       granularity = "perHour";
     } else {
-      granularity = "perHour";
+      // Use the minutely granularity only if allowMinutelyForVerifications is true
+      if (allowMinutelyForVerifications) {
+        if (timeRange >= DAY_IN_MS * 3) {
+          granularity = "perHour";
+        } else if (timeRange >= DAY_IN_MS) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 16) {
+          granularity = "perHour";
+        } else if (timeRange >= HOUR_IN_MS * 8) {
+          granularity = "per30Minutes";
+        } else if (timeRange >= HOUR_IN_MS * 4) {
+          granularity = "per5Minutes";
+        } else {
+          granularity = "perMinute";
+        }
+      } else {
+        // Fall back to hourly granularity if minutely is disabled
+        granularity = "perHour";
+      }
     }
path: apps/dashboard/lib/trpc/routers/utils/granularity.ts
user: login: coderabbitai[bot]
body: @ogzhanolguncu Sounds good! Looking forward to seeing those tests in the follow-up PR. This approach makes sense to get the minute granularity feature in first.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@ogzhanolguncu Sounds good! Looking forward to seeing those tests in the follow-up PR. This approach makes sense to get the minute granularity feature in first.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T11:07:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-12T11:01:39Z
body: ###  ⚠️  No Changeset found

Latest commit: 09b37863bc3bd92140aea37e49433a88901ce9c7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/unkeyed/unkey/new/add-minute-granularity-for-verifications?filename=.changeset/mean-apes-explain.md&value=---%0A%22%40unkey%2Fclickhouse%22%3A%20patch%0A%22%40unkey%2Fdashboard%22%3A%20patch%0A---%0A%0Afeat%3A%20add%20minute%20granularity%20-%20This%20should%20be%20merged%20before%20key%20details%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-12T11:01:40Z
body: [vc]: #XZf72Qp13T5FqP8n9yNqu4LE7MbAkDTHjOJLMwLDFtk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJkYXNoYm9hcmQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9kYXNoYm9hcmQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZGFzaGJvYXJkLWdpdC1hZGQtbWludXRlLWdyYW51bGFyaXR5LWZvci12ZXJpZmljYXRpb25zLXVua2V5LnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3Vua2V5L2Rhc2hib2FyZC82Q3FCZkt4dGhweXVMeHBZS1dMSHE3NldGQlI3IiwicHJldmlld1VybCI6ImRhc2hib2FyZC1naXQtYWRkLW1pbnV0ZS1ncmFudWxhcml0eS1mb3ItdmVyaWZpY2F0aW9ucy11bmtleS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn0seyJuYW1lIjoiZW5naW5lZXJpbmciLCJyb290RGlyZWN0b3J5IjoiYXBwcy9lbmdpbmVlcmluZyIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3Vua2V5L2VuZ2luZWVyaW5nL0hYdjl6bTl2Z0wyOWY3eGZlNVRUeWVXd0preFQiLCJwcmV2aWV3VXJsIjoiZW5naW5lZXJpbmctZ2l0LWFkZC1taW51dGUtZ3JhbnVsYXJpdHktZm9yLXZlcmlmaWNhdGlvbnMtdW5rZXkudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **dashboard** | ✅ Ready ([Inspect](https://vercel.com/unkey/dashboard/6CqBfKxthpyuLxpYKWLHq76WFBR7)) | [Visit Preview](https://dashboard-git-add-minute-granularity-for-verifications-unkey.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/dashboard-git-add-minute-granularity-for-verifications-unkey.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 10:23am |

<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **engineering** | ⬜️ Ignored ([Inspect](https://vercel.com/unkey/engineering/HXv9zm9vgL29f7xfe5TTyeWwJkxT)) | [Visit Preview](https://engineering-git-add-minute-granularity-for-verifications-unkey.vercel.app) |  | May 13, 2025 10:23am |
</details>



author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-12T11:01:42Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

The changes introduce minute-level granularity support for verification and active key timeseries queries, including new ClickHouse tables and materialized views for per-minute aggregation. The backend utilities and query functions are extended to allow selection of minute, five-minute, and thirty-minute intervals, with optional control over their usage in the granularity utility.

## Changes

| File(s)                                                                                 | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
|----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| apps/dashboard/lib/trpc/routers/utils/granularity.ts                                   | Added an optional boolean parameter `allowMinutelyForVerifications` to `getTimeseriesGranularity`, enabling minutely-level granularities for "forVerifications" context when set.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql<br>go/pkg/clickhouse/schema/databases/001_verifications/021_key_verifications_per_minute_v1.sql | Created a new ClickHouse table `verifications.key_verifications_per_minute_v1` to store per-minute aggregated verification event data using the `SummingMergeTree` engine.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql<br>go/pkg/clickhouse/schema/databases/001_verifications/022_key_verifications_per_minute_mv_v1.sql | Added a materialized view `verifications.key_verifications_per_minute_mv_v1` to aggregate raw verification events into per-minute summaries, grouping and counting by relevant columns.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| internal/clickhouse/src/index.ts                                                       | Added minute-based timeseries data retrieval functions (`perMinute`, `per5Minutes`, `per30Minutes`) for both verifications and active keys to the `ClickHouse` class, organizing them by granularity.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| internal/clickhouse/src/keys/active_keys.ts                                            | Introduced new minute-based intervals and corresponding exported querier functions for active keys: `getMinutelyActiveKeysTimeseries`, `getFiveMinutelyActiveKeysTimeseries`, and `getThirtyMinutelyActiveKeysTimeseries`. Updated interval mappings and grouped code by granularity.                                                                                                                                                                                                                                                                                                                                                                                                            |
| internal/clickhouse/src/verifications.ts                                               | Added minute-based intervals and exported functions for verification timeseries: `getMinutelyVerificationTimeseries`, `getFiveMinutelyVerificationTimeseries`, and `getThirtyMinutelyVerificationTimeseries`. Updated mappings and grouped code by granularity.                                                                                                                                                                                                                                                                                                                                                                                                                                   |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant DashboardAPI
    participant GranularityUtil
    participant ClickHouse
    participant ClickHouseDB

    Client->>DashboardAPI: Request verification timeseries (with allowMinutelyForVerifications)
    DashboardAPI->>GranularityUtil: getTimeseriesGranularity(params, allowMinutelyForVerifications)
    GranularityUtil-->>DashboardAPI: Return selected granularity (minute, 5min, 30min, hour, etc.)
    DashboardAPI->>ClickHouse: Query timeseries via appropriate function (e.g., perMinute)
    ClickHouse->>ClickHouseDB: Query per-minute (or selected) table/materialized view
    ClickHouseDB-->>ClickHouse: Return aggregated timeseries data
    ClickHouse-->>DashboardAPI: Return timeseries data
    DashboardAPI-->>Client: Respond with timeseries data
```

## Suggested reviewers

- perkinsjr
- mcstepp
- chronark

</details>

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d8f783807af2ad8c5c14f9f8d4fdc5d277b94c33 and 09b37863bc3bd92140aea37e49433a88901ce9c7.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `internal/clickhouse/src/keys/active_keys.ts` (4 hunks)
* `internal/clickhouse/src/verifications.ts` (3 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* internal/clickhouse/src/keys/active_keys.ts
* internal/clickhouse/src/verifications.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (19)</summary>

* GitHub Check: Test Go API Local / Test (Shard 3/8)
* GitHub Check: Test Go API Local / Test (Shard 8/8)
* GitHub Check: Test Go API Local / Test (Shard 1/8)
* GitHub Check: Test Go API Local / Test (Shard 7/8)
* GitHub Check: Test Go API Local / Test (Shard 4/8)
* GitHub Check: Test Go API Local / Test (Shard 5/8)
* GitHub Check: Test Go API Local / Test (Shard 6/8)
* GitHub Check: Test Go API Local / Test (Shard 2/8)
* GitHub Check: Test Agent Local / test_agent_local
* GitHub Check: Test API / API Test Local
* GitHub Check: Build / Build
* GitHub Check: Test Packages / Test ./apps/dashboard
* GitHub Check: Test Packages / Test ./packages/hono
* GitHub Check: Test Packages / Test ./internal/clickhouse
* GitHub Check: Test Packages / Test ./packages/cache
* GitHub Check: Test Packages / Test ./internal/billing
* GitHub Check: Test Packages / Test ./packages/api
* GitHub Check: autofix
* GitHub Check: Analyze (javascript-typescript)

</details>

</details>
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNxU3bABsvkCiQBHbGlcABpIcVwvOkgAIgAzEmouNFp6ZngMPBJIIiosr2d1eSh3ZERYfG96ARy2ClIaknj8f0gAaxJ5JVxtL0RQ2MgAdzRy7AEM3Boa+WxESn4iAC9YTHwvImwMBmx0ZFtILOZavgBmACYAFgAGDRhYOu0sTwpufAX+eIjH6ztUAgoDC4CiKbAMOqZbJgaJSXx5TDeIq4eQtPi4X7YbhMDIYIiQADSXWQABESL14P0eGhSOgMPRAfBmLx8FJGGsKLg/NJvLhEPc4DkGGs8TlUJkaPSYoDapB6o1ILU0TkyCovJl8bjsrl8kiKMVIGj2c4+eE0D58MMNZBaNQ0AofKJxPgsJl0LQJJgIRo3L9DqgGIVEIh4PF4DExugjiRhoakrhsG0MdR3bRkPFtmJ4C7zQarRiqlyBP40O1rSQAB5IcR4xUkNYSbMUAUha2ZRAg8HOjDITKB7BKSBSfXxWTW4Um5Aun45AD0aG4HhpOUwDN+hZxK/p6CzbM6smQE85U6wGLn++Q3GXPoMgoUwP1AjwrQUzOiMyjwtE7XVHcgZAcfVazPP4gX7JRkHtMNHXoWIAAlLQifAIhCIYFizF0zVoR5/B2HJARApgH3gJ8a3xLZ4CUcIFi8eIwH8RsY3CJhBxxNhgV7LAcW4aIK3QEsBnscEIWDDNfCfSl6FXQ1WmYagyL8bZBJUBY8K+EZnAwDVIO3IjEA2HIvHwIhBMQWQdlgUEtOWahsywfNYBnOVnkVfJhTNbcMGQ/xEHeHtJDIaRe2DYJ+UgAA5ZCAFUAEljVFKc+FofBdnY3pu0gLFbRoZBhkoMV6XgBhqAjS8TXUjFUFsG8LEgABhFg0vGJwXF9HIXS8eQ2PYfhTwQS9vF8fwghCFB01BZgnPmRZhSs5x2nCYZKm5EaO2lX5UlodQ7PU+0mG4eR8G+EDegEaJ7C/OTkH8QoPwI34JwS6juFEUMivNTr9jLWsjRAgADOqbAAUQAQWgIGYBBgAhAAZCGYoAMQigB5aBICBgANGKAGVoGxv77HSkgmpGdRKjwMCvAHa1kuGDBYRIXwMgRbswrvYbQo/RBLsjF7ORc+6ci2/wxFaeQ/qIfBZ24doiFnQMivacmFlnbnHjk2dspUMZpD+m870ONA8EqChwmM1Z1k2TNsHCHy/KUKSGHaLzhmiWgiGtECOdG6TMi24q7seeRhiqLx6CM/B2iBRlNAi5CM05HDXxJl8tu5+YQxdXL8u5JgKAdm99GMcAoDIegjpwAhiECqgP264EuF4fhhCdAKZC6xRlFUdQtB0IuTFKfqxrpSvCFIcha5ievOD8NBYwcFr5DkBQlCobvNG0XQwEMYvTAMBduEQTWxlgAR8GcWhZ3VARZxBbFZ1BbIKCPvBKSPhECmRWQND5DgDFiABBhaogxitXCeJV6ALzki4dSj1SCICMCDaMsZ8DcG7OaRU+ADKYGpFQNgNA+B/Q+paAAslCGgnUEatAAGqUDegHOyiACYAAolDxCNl4BSgJ2H9BIAASg0pBNI0pkL/VILgaATJpB0OkAAcV1IUfUKICYZh2N2AUQ94iFHxEREEGxcqPDPHwLUFDZAwhIHCHUiJFHbWkPxFcFo8r0F+r8BINC6FhgYVnIYujKxciWmQJy4g2Cz1FMPaIwYfg4JdDkPKJB2j3CsAxbM8xOrhCNG4igtCRzvVZkMByEQpGhPgZlKUfAYkjBIPE8IIEP56jzJGc0owDz2HJEhOIfMEKJliPcSKwxwihkgEQxxZCsimKoVkjxuTGEEwBBQYINTfiqIwlgGiTp0yZEWHUmx4g7ECB1uXPq+Ein5FIFwWIfNRnZCGEaYc8gKitC5Kc6Q4QLmUAAKxXJykMF8byKBnBuF86QNyXwSkoGwLaJVikvLpLBTpVQKAgr4EZUUfBnm5TJm6ZM/iqkJMgDFY6SztHDx4QsRZORtlf0NB9GQaAnbtL+V0xF+w6zKl6chfARjIBGQ9gwfgfBfEVj5JUtocC6A3nMJYEGnDlCs3aSBJQgZnC2SzupSs7xOQxBfJ4M6RV/zAlsQggwUAQbCKksg/gaC7IYKvHg8kixhlGWGECyh7icleJ7FwM+2CMAsLYRwrkf1SUkD+gIwW/4Kwao/MsjKEtySSLYAsfUciFFf1mVgIh3BD7HwqGfC+V8SK3zeAwB+hZKAv3EP0WclKlHfz5Hrf+gDjV7zBRQDA5p5bqidsrEgqseazhuO8m4AB9BgJYaDDuHPQlVPYND7knVMj1iBh182HSYkgk6ACMGhEABC8H/ABsQgFStAePWVMQoHOEOt8MVRqyjxVpBKUEtBwR2PtOQWMdUu3tC6Z8U65120QqGVOzxM7+TzpA9MrOK7KBrvIRuiQm6CYQXgEQcgDJkIdlaDkSDHr/xSGBDaO06AiB5BIEQCBPBFjro0fhNUOQ1bE3tH2KmEEHSOB7DJdEUiOxoGZJAZhf1gkhr4QMpQBqwzlq4yMVo7RfJ0pIOEfc9grwQjE+wYoHl6D7jNNnHwLKQK4ZnfwPAm5dP6QYPASjvQTJac/FUQjFcUq7AoLhEStGhkbhYCG9jzAsDTUgkMmGlo6oX0yLmFEzDsYgg1HwgmKIXrtNQcE+AywGMECoLSI0/gXqUY7EBfEnoqbSA8/+nIAWnJ/Wxo4XERASGUFINAfwPmyAe3IItBAwohJZsecgZonjwyEZpGRijGVl5QM9r8P6TBti4AJkwKmfmbSJmtPKErDwhbEdQK0VeMRl57RYO8EMNAOhdHvCGDs1oK4hwoHJ1TOQYokiU2d+TEJ8VPZQOJyI8hHvPZ+x94TNSaTKW3F5kJgJktMlSzkEaMC+ZojknhDzzNa47XNPpSmA4302ktKssd8A0HtNoKCbgQT6MoG+OQOg4qjCSsgNKghYH5W/EVYopnFd1WPK1XwHVXb9WRHDEaqA4UYwRHJ2O+MdAuB/SM6zOdXQF3urAzBigcGxkIaQ6TDEvnON/WYcJyAJISoJsUzJ27r2N2UUgNFgrf3h0W+HVbm3Gp1MGpRI7+gzu8R26dzF73JncCbkgMF4YoX87hfVJFr3RBRNi5MvT1zaBZBRb9zH5iDmuQxWBAANiuHFzKIZaxVZqxqerDQSBNaqQTVrmzYX8t27MIZzCbt3YUx7u3DvKKu+++307she+A7j4JMH/CG2HqMBAMARhW3tq8J2xWPa+3qzQAO95m7R3joQ4usD8v++y8YSrtX2Q10SC3TuvdB7AHANPTXPLNWr2wJFPA30AYn8FT0S+kSUYP3OQIVZ9UaW9AjEsYgGMQf0EG2+rMh+66J+W66alW++WcBMjGckHmwBJGI2JUyAWshoE0lWVAwww6EBSuUBiG8W5Oy8eQVQi4tY04f0LenetAf04Q4BCujBzBQylEGm7ulEHBrB/evBLBI+HBIE665iliwmvG/Gq8AUzieBIErQqGEe+GPUg+0kQmwOesG2GB/go2O0M2HETkxwpw6kIsO2l4Wyj83A4Q/WrccI9y9++oyw1oymiBWAFi7AFhxi8GpWPGvQ/GqAXkFAckABIiTkvGnIaqdKjk66Be1oQm+A0WJoyM8QQKzC8QE0UUWkFYJuUh3AueZCPg8A+uUifCcWyOqGqO04LGWOMgnKjkhE46O0gmWIoadexOqCAmf0tMvqAivGNAJMLiTwf+uYgBQ44YwwEqx69OMqVRnG4arOyqcqHOkaXO5cPOEwfO3BguE+9UTR04FcckIxoRQBExwGkBjCu+iu06UBq6MBzAp+ZBWuDRSyGwTq1oIYaG1AiYJAf8kAug9UGwHGiA0uDB92HufB867BLBXBbuAhTBLB86ghnmpm3mfBBhc2AmGJphog5hseQmUiLCYhDMvgkh/h3A+JNmTCBg/xUA2MCKb2ZW0ubh/IBBRBCuLJcBNJAJ8i1B1o+2+mC2wJEaEIhO02Gef0xqhuJOhMJUaU0mohJU+ooxMQ6BMuFxWcVxnJdx8GsBTxWKLOuOcolRM6Po4+zaU+BgM+HaCs3aVQKsiAFAJafslYP8IJjaR61+YC56kCjh16D60gbUAZnEH+r6qY20OYpJPGMi2BxG/gMWFiGCMajC0mZ82uLJdedK4ge4xIzxBpOQAM36v6PmSqwY9w2ML0lmoGPgsg4QP+Yh+yCw9ANaBokhMZco5IlQaY9iqYUuQy4iLqsg2SNxdkuRMZfB4iCMAUg5w5oG3YY5yaTCdmcaEiCAnIsgM5GpGAC5guKiL4LJdmIYGQiin0oBzimYcqRoWZAUfebMjwnwzg7+5Gk8UkKKJkXBEa1Y1o5MFAaSRGlItZlS8Sf50kzALoGIn0LZKIHZBYaYHmP+bZi5MFXZkEbQVBWIEYukjU7Ae28gUF8gzCDZBy4QP5YAjZdA4QtoZi5FtA4QYFwIsAZFByYayETILIbISqtaderQFG1kppccn5l2tYPKeqL4lAoIfAIotA6otYqA/gYFUg6x6AsxdAAA3E5JtBGZxn4mXBpZ6JSOTohYLkRr0NyPpFTHLrTtMQzrKimQsaIGzssd8JzpqkpbznqtsYGVKaag7BakZXYiyHzLssgNsKvOcSQZcf5UwkCIwEGMgIWYrMWX9H8QCX9JcvBlwAOfBp1LOVBtudGYufrv1BoLDuGBQHFtyVAGlR8kCiCbkOSFOVIJuRFS6DudIEVUgCVcEMmuVZKbSUMnzACrVZlfGmuSiM1SOa1QVYLh1fyKVZQBVd5Wan5dNQFSTpQMFaUmFeqS1bOteVIESAeG1dFW6KWfFV+olfaSGilVVeleriNbgIOSDLuCQIdYgMdbNV1TIr1ZVQNTVfBnVZOdOdlbIM9dma9cSB9ZVHNd1WVRVf1dVf8oCgDQ9e4OuU9S9W9VDcVfNT9eaZPtPsCJQLPvPnadNKrE6bOBePOC9eyQeG6Zfp6Set6S+UJIvI/pgM/reA9J3KmMgPWfBkxU2dHJQEVsgIJuunwWGE1QDXwZVOjbLSxZViDHVNADFNQkDMOgSEDAAJrYzDoxThTgw2DUIgwwz4zORZou7/jRHcqZCdAYbhF8Y5AXVOzFli5nQ5CkTPE2ihiJC4RchrSk4hhpY6RrjpYkCk7bDqBxAkKG1RTgw9L4qB29D0hhah3hE0BR1aQp0Fbpgvg/mUVJ52b0Xa6tpi2KjyA5aFCWa1g8SJhJmyTi3wTIxRQ2DYyxCvIkggx62d2x3IxG1wQd0CIFKF5bCKJcbMDN1wSt02B92xDd063z0kID3QBwSxB8L3ANSubSD2yrYHzaQxXTab5g0BRY2rWIAACK3Vsge5fA5dGC0dwqoFlIv4uJWFGAw4mcnGj5mU3A2UYRNRg4IEsQcd4UCdQMQwT9de8lrIOQ9dVAvgnoypHEvSouLlH4uNhoF5KZv9T6YIEIziL4ohgtNFItFAYt0uWV6unUp9B1kNF9E5DVwNNDoNmNDDia45y54iaN41INdDENR1jD9wPlWlH0gF08qFK4y1gI6FpOD9VI0kGDMQuN7cxp6uJFCKRdgFoF4FjkAc5GihdiRonFxQ7KwZPZckwDyElYX5wlxkRUNhrmBdq4MlRAZsAq4FoIvgWiiEtQsgLo4dnwOl/kqq0kvGad4eNklltOwCsxTO9lnF7Ozlqxrl/KPAmxHlbuOxS1vlyj9AWDyZWcN1/Z5IGN4N59nDi58Bf01pc+tpSsV1FNJa1N+1G6F4bpfVqVQNMtrDAjlT0i1TR9dTpNjT5NjpLTxINN4NdN/I9av1K5vDG5/D7DQjVTu5wzRNbaNp36i+EzVNUzbTsznTHpE+LaWzJNDTezlNLJDNpzXpZ6rNl6MCFct6QZLEKoQqZcyAIEtjQlhWW5hS6zxjODkZBo+2aQq2pDBy5DlDQyktLB0tJAtVHB6h8tfD6uS5ylvgsOY4wEU2tzxBk1PY0BupTxZWPtQdpSMdf0oD8didBM0kdsdhEdwd0OyAm64Q7ydmAKHmf0htxtpt5tBMckVtslIV/91myE0do4hMkd1Ludh9gIY9eok94ttLM9bdsQfBC9Pd2rLBoDq969bRy81ddK1oCDjdwR6rLdbdHdOri99rBrK9g99rI9ZM9gGoqrMS+dnj/ka0Ow38gJO9vkgTGof5/0EuJUuVHqx119lAt92DaidkAA5PFQo1keoIy9uH9FPVYJQJm5iaKzQfHr/SE75YyDsKxgWXS+Awy5bS9I7bS+utq3XtnjcKEDcF28aUUehIE9dLvSy3+bA42HXVTIg7eTeCLrGPk0myslI9HM+q+o7Q4D1pEbi5NtIhakRcLQo3VSuRNXOaOYwywT08iyDTGzOsdaizmzw2Ncs6w5e/OcIxtp8EU5xtNE5H8wpH9PsoHrAE+8e8C9FY8F4HzD7SBD/nuyI5C+gjWenqwJ4T2ZtGEXIxGlGjEO+2o2ePANxiEtzOaL8eo9CDRZo4mELRRURtRcRXXqXYxTRVO8hLepY6kPhKIjzW0Li9yg4wwE4xJZAFJW4x4/eHoj406h5sx68IzPIJtN1lGtJrEfhbC+jnXjK/clnQq0cHxuOC6ARjE3E4znKok45SmSsRh25Zk3yp5ULvTstbO++1Q2Uxe1uR9cKFwPGz1QIgALx6ACbOAmRcCAdTXAdWDKpT3ee+dJIsBIAkDABBf5XAdG69BWD4ASgADaAAunoJKSanZ6k9GqCxgI57gI1ee4+y5xfcwm55AB53DZAD535w0HVfF8daF3gogBF9YBNDF3FxV4l3aCl+l1lzl7Z3k/l5h4V8V0s4e3la57AO57DQtfV758wv581314M4Lm13xh18t119FwsL17tcdUl2gIN8CJl9l6cxaUYJLNLLLKM3s/2lrORUfF2+viyQOhcOvkS0e9Bjqermfruvuvc8zY83fuza82/kakgj/q7T+ldR7QBs7fQDtcS+BhyVucugD8fvqYeJvrQBogCOTqgChmhmEVhm0G4SoYRjgcNroZRuBzRsnZjmxkKX5r60CyEHxqToJsJqGt3m9FJkaGCQpn9ipmL59twTo9uDpiynlPppGIZoCyPpmcGClFZndMDnyxifNkCUtpT3YpB44CYU5gwC5m5utneBSxVv9NVqwKXg1hXs1tXniJsh1kVI5MQsMH1vEANj1PT+RsZhXJGxKYaOGGHMtgVh2eXneZtqZdtvnPlI3gdsyB8OoDkMpnpHY/iBXPQbJtCUMlCeCcibU19sUBCYiQrqX/zywVSTe6j8IUllahkGlpAFx/DrJF6FuFJKRgz+olZQZ7ZaqsZ0saZyk+Z+k+5VZ9k15cLqLhS1GzMMyVj1qVj6S4D08QUuz56gs/zwJklxXqUQs6LxCBCQJtHnwgs8X23rwRf6nlfwjbCT3nfyngVo/6lUiUwff+/ws038wiHjDxbRZ8xQN/rFg/5VV6+AmEGInmTyX8IBQyXXgJizy4Bc8V/UoOThrzkBpc9vWrGXkawu8pS0AcnDtkWByBpczeAviX1op957c1AwXjwRoFf8akUiIHLZgDybgKq+NPeHdxlhywrmTTFAivhe46w3uNwD7ljy+4XA6a1xP7iSxx4ToHiQPC/KD3pw35wEXMP0hzQSiIJt2Spf/NDlOKi4zyRfTHrtWx6wZ7ijxTXAGAJ4xV/oLJZDHaHIoXRl8RPZAOgUD6jY7EOBDIiwHwJzwZBnJclhQTwpWFrQZhfOF4WI4nYyG04E6H4R57h9GYqPfnh5i8HGY5GajfPubmoGQk2CeQmEuX0YH5D4SfBYQuSgiDzIdglGWImSR57Ll6+9wCZDbS6xyMBkXIDEj81+DGFFgFcSIXBW0L1CAih4XThtUw54F7QmbPiMMPkZYBjyb9IiN2UBD2hFOqaLivMHiIEAkinIFImkT8HMBphuRckgUVfrFFhMZRLQneCOJ0IVSRg+eBliN4bQ++QfD8D5F5Ahl2OBZX7nlQsGq4YCIQz2lMSH5zEfmXwm0A5TH6qozOaxKfpZ35yGpdidUfYlgEOL6C7h4xUXGjzkEY8986/BQRuiUEhDy8XCcnNiN+Fr9zBG/XHprgKRyMIwLwvQtOBEoMAzSTaIuAYAHj6py43wI2FXHB51xsKDcWePPC0HLwPma8NQBvD7jbxORJcZOOoA9zLpkkMYOgPbl6CREORXI77gIEuA3BRANwWgAIH1ECAAAHGgAuAMBN07yd5BcBIC1AGAFwOlO8nbYMAzga+BgAAHYLg3wfuPKNoCmj4gno00WcFNE3BPRaAeIE6IDEMB3kVoq4PEAACc8QU0bQATG0BYxtAC4J6M9ECBExVwN0WcC3iGAuRNwRMbqODHZ4zgAgN0QIFoCJjvutwNAEkDOCeiSAVwfMWcDOBoBTRpoxMeIIhCJivRxY3eFADYiKjKIyoixBMTVG6V9AQAA= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=unkeyed/unkey&utm_content=3240):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-12T11:01:45Z
body: Thank you for following the naming conventions for pull request titles! 🙏
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: chronark
created_at: 2025-05-12T11:17:41Z
body: could you add a copy of the table schemas here as well please. no downlevel migrations, just the `CREATE TABLE IF NOT EXISTS`
go/pkg/clickhouse/schema/databases
author_association: COLLABORATOR
user: login: ogzhanolguncu
created_at: 2025-05-12T11:18:22Z
body: lemme see
author_association: CONTRIBUTOR