diff --git a/docker/docker-compose.end2end.yml b/docker/docker-compose.end2end.yml index 1227a185b34..45338f5fe0a 100644 --- a/docker/docker-compose.end2end.yml +++ b/docker/docker-compose.end2end.yml @@ -47,6 +47,13 @@ services: ports: - '6379:6379' + usage-ingestor: + environment: + # Wait for the async insert to flush before acking, so a reported operation is queryable in + # Insights immediately instead of racing ClickHouse's non-deterministic async-insert timer + # (the source of usage e2e flakiness). Prod keeps the default fire-and-forget behavior. + CLICKHOUSE_WAIT_FOR_ASYNC_INSERT: '1' + server: environment: SUPERTOKENS_RATE_LIMIT: '0' diff --git a/e2e/helpers/usage.ts b/e2e/helpers/usage.ts index c08d30ab711..66aaedc99e8 100644 --- a/e2e/helpers/usage.ts +++ b/e2e/helpers/usage.ts @@ -90,7 +90,7 @@ export function createUsageHelper( return page.getByRole('link').filter({ hasText: operationName }).count(); }, - { timeout: 60_000, intervals: [2_000] }, + { timeout: 30_000, intervals: [2_000] }, ) .toBeGreaterThan(0); }, @@ -102,7 +102,7 @@ export function createUsageHelper( return page.getByText(version, { exact: true }).count(); }, - { timeout: 60_000, intervals: [2_000] }, + { timeout: 30_000, intervals: [2_000] }, ) .toBeGreaterThan(0); }, diff --git a/e2e/specs/usage.spec.ts b/e2e/specs/usage.spec.ts index 2a19dc2b51a..a1ca1eafef6 100644 --- a/e2e/specs/usage.spec.ts +++ b/e2e/specs/usage.spec.ts @@ -7,7 +7,7 @@ import type { UsageHelper } from '../helpers/usage'; // Each test waits on ClickHouse ingestion through several stacked polls (see helpers/usage.ts). // Keep the timeout above the sum of those poll budgets so a slow-but-succeeding poll isn't cut // off mid-wait by the per-test deadline. -test.describe.configure({ mode: 'serial', timeout: 180_000 }); +test.describe.configure({ mode: 'serial', timeout: 120_000 }); type UsageReport = { size: number; diff --git a/integration-tests/testkit/oidc-integration.ts b/integration-tests/testkit/oidc-integration.ts index d77e3533694..45d753b5516 100644 --- a/integration-tests/testkit/oidc-integration.ts +++ b/integration-tests/testkit/oidc-integration.ts @@ -299,7 +299,7 @@ export async function createOIDCIntegration(args: { const rawBody = await result.json(); - const body = z + const parsed = z .object({ user: z.object({ id: z.string(), @@ -311,7 +311,16 @@ export async function createOIDCIntegration(args: { ), }), }) - .parse(rawBody); + .safeParse(rawBody); + + if (!parsed.success) { + // SuperTokens answers 200 with a non-OK status (e.g. SIGN_IN_UP_NOT_ALLOWED) and no + // `user`. Surface the actual body so a flaky sign-in is diagnosable instead of a bare + // ZodError that hides why it failed. + throw new Error('Unexpected signinup response: ' + JSON.stringify(rawBody)); + } + + const body = parsed.data; const cookies = setCookie.parse(result.headers.getSetCookie()); return { accessToken: cookies.find(c => c.name === 'sAccessToken')?.value ?? '', diff --git a/packages/services/usage-ingestor/src/environment.ts b/packages/services/usage-ingestor/src/environment.ts index 99ea345b30a..17709962815 100644 --- a/packages/services/usage-ingestor/src/environment.ts +++ b/packages/services/usage-ingestor/src/environment.ts @@ -81,6 +81,11 @@ const ClickHouseModel = zod.object({ CLICKHOUSE_PASSWORD: zod.string(), CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS: emptyString(NumberFromString.optional()), CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE: emptyString(NumberFromString.optional()), + // Whether ClickHouse waits for the async insert to flush before acking. Defaults to 0 + // (fire-and-forget) for throughput; e2e sets it to 1 so reports are queryable immediately. + CLICKHOUSE_WAIT_FOR_ASYNC_INSERT: emptyString( + zod.union([zod.literal('0'), zod.literal('1')]).optional(), + ), }); const PrometheusModel = zod.object({ @@ -238,6 +243,7 @@ export const env = { password: clickhouse.CLICKHOUSE_PASSWORD, async_insert_busy_timeout_ms: clickhouse.CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS ?? 30_000, async_insert_max_data_size: clickhouse.CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE ?? 200_000_000, + wait_for_async_insert: clickhouse.CLICKHOUSE_WAIT_FOR_ASYNC_INSERT === '1' ? 1 : 0, }, heartbeat: base.HEARTBEAT_ENDPOINT ? { endpoint: base.HEARTBEAT_ENDPOINT } : null, sentry: sentry.SENTRY === '1' ? { dsn: sentry.SENTRY_DSN } : null, diff --git a/packages/services/usage-ingestor/src/writer.ts b/packages/services/usage-ingestor/src/writer.ts index ebaabd9fbc7..cc584c6de41 100644 --- a/packages/services/usage-ingestor/src/writer.ts +++ b/packages/services/usage-ingestor/src/writer.ts @@ -20,6 +20,7 @@ export interface ClickHouseConfig { password: string; async_insert_busy_timeout_ms: number; async_insert_max_data_size: number; + wait_for_async_insert: number; } const operationsFields = operationsOrder.join(', '); @@ -153,7 +154,7 @@ async function writeCsv( searchParams: { query, async_insert: 1, - wait_for_async_insert: 0, + wait_for_async_insert: config.wait_for_async_insert, async_insert_busy_timeout_ms: config.async_insert_busy_timeout_ms, async_insert_max_data_size: config.async_insert_max_data_size, },