-
Notifications
You must be signed in to change notification settings - Fork 514
clickhouse setup #1032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
clickhouse setup #1032
Changes from 6 commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
0c08896
clickhouse setup
BilalG1 41287db
fix clickhouse env vars
BilalG1 1320221
fix docker server test
BilalG1 dfb0245
fix default access management for docker test
BilalG1 0077d16
Merge branch 'dev' into clickhouse-setup
BilalG1 62c6646
merge dev
BilalG1 90cc6a9
remove unused table
BilalG1 c999d16
Merge branch 'dev' into clickhouse-setup
BilalG1 20b597f
Merge branch 'dev' into clickhouse-setup
BilalG1 73407b3
merge dev
BilalG1 688c9ce
small fixes
BilalG1 f6fb8cf
Merge remote-tracking branch 'origin/dev' into clickhouse-setup
BilalG1 44a3496
fix lint
BilalG1 63da706
Clickhouse events (#1038)
BilalG1 7a4bab9
Merge branch 'dev' into clickhouse-setup
BilalG1 39f94c9
fix frontend build
BilalG1 beae857
fix lint
BilalG1 8c030db
add query analytics page
BilalG1 e622514
stricter user in clickhouse migration
BilalG1 fae5166
modify settings test, clickhouse fixes
BilalG1 49c8d0f
various changes
N2D4 1134883
todos
N2D4 30a8212
clickhouse error code parsing
BilalG1 8e72a7b
merge dev
BilalG1 ba8110f
fix tests
BilalG1 136b25f
small fixes, use view for ch events
BilalG1 3760836
fix
BilalG1 3ff2f4f
merge
BilalG1 42bfed5
fix build
BilalG1 b9d72b0
small fix
BilalG1 9f501c8
Merge remote-tracking branch 'origin/dev' into clickhouse-setup
BilalG1 39099af
merge dev
BilalG1 176775d
merge dev
BilalG1 aa2a5dc
Merge branch 'dev' into clickhouse-setup
BilalG1 ab30ea3
Merge branch 'dev' into clickhouse-setup
BilalG1 14c70ef
remove analyzer
BilalG1 4f1ded7
Merge branch 'clickhouse-setup' of https://github.com/stack-auth/stac…
BilalG1 55fa597
pnpm lock file
BilalG1 bf0675f
change wal info port
BilalG1 54bbcf9
fix wall info port
BilalG1 e7b4bfc
Merge branch 'dev' into clickhouse-setup
BilalG1 1d8e9ec
Merge branch 'dev' into clickhouse-setup
BilalG1 360176f
Merge branch 'dev' into clickhouse-setup
BilalG1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { createClickhouseClient } from "@/lib/clickhouse"; | ||
| import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; | ||
|
|
||
| export async function runClickhouseMigrations() { | ||
| console.log("Running Clickhouse migrations..."); | ||
| const client = createClickhouseClient("admin"); | ||
| const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD"); | ||
| // todo: create migration files | ||
| await client.exec({ | ||
| query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH plaintext_password BY {clickhouseExternalPassword:String}", | ||
|
BilalG1 marked this conversation as resolved.
Outdated
|
||
| query_params: { clickhouseExternalPassword }, | ||
| }); | ||
|
BilalG1 marked this conversation as resolved.
|
||
| const queries = [ | ||
| "GRANT SELECT ON analytics.allowed_table1 TO limited_user;", | ||
|
BilalG1 marked this conversation as resolved.
Outdated
BilalG1 marked this conversation as resolved.
Outdated
|
||
| "REVOKE ALL ON system.* FROM limited_user;", | ||
| "REVOKE CREATE, ALTER, DROP, INSERT ON *.* FROM limited_user;" | ||
| ]; | ||
|
BilalG1 marked this conversation as resolved.
|
||
| for (const query of queries) { | ||
| console.log(query); | ||
| await client.exec({ query }); | ||
| } | ||
| console.log("Clickhouse migrations complete"); | ||
| await client.close(); | ||
|
BilalG1 marked this conversation as resolved.
BilalG1 marked this conversation as resolved.
BilalG1 marked this conversation as resolved.
|
||
| } | ||
|
BilalG1 marked this conversation as resolved.
|
||
|
|
||
|
|
||
|
|
||
| const EVENTS_TABLE_BASE_SQL = ` | ||
| CREATE TABLE IF NOT EXISTS events ( | ||
|
vercel[bot] marked this conversation as resolved.
Outdated
|
||
| event_id UUID DEFAULT generateUUIDv4(), | ||
| event_type LowCardinality(String), | ||
| event_at DateTime64(3, 'UTC'), | ||
| data JSON, | ||
| created_at DateTime64(3, 'UTC') DEFAULT now64(3) | ||
| ) | ||
| ENGINE MergeTree | ||
| PARTITION BY toYYYYMM(event_at) | ||
| ORDER BY (event_at, event_type); | ||
| `; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { createClickhouseClient, getQueryTimingStats } from "@/lib/clickhouse"; | ||
| import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; | ||
| import { KnownErrors } from "@stackframe/stack-shared"; | ||
| import { adaptSchema, jsonSchema, serverOrHigherAuthTypeSchema, yupBoolean, yupMixed, yupNumber, yupObject, yupRecord, yupString } from "@stackframe/stack-shared/dist/schema-fields"; | ||
| import { Result } from "@stackframe/stack-shared/dist/utils/results"; | ||
| import { randomUUID } from "crypto"; | ||
|
|
||
| export const POST = createSmartRouteHandler({ | ||
| metadata: { | ||
| summary: "Execute analytics query", | ||
| description: "Execute a ClickHouse query against the analytics database", | ||
| tags: ["Analytics"], | ||
| }, | ||
| request: yupObject({ | ||
| auth: yupObject({ | ||
| type: serverOrHigherAuthTypeSchema, | ||
| tenancy: adaptSchema, | ||
|
BilalG1 marked this conversation as resolved.
Outdated
|
||
| }).defined(), | ||
| body: yupObject({ | ||
| include_all_branches: yupBoolean().default(false), | ||
| query: yupString().defined().nonEmpty(), | ||
| params: yupRecord(yupString().defined(), yupMixed().defined()).default({}), | ||
| timeout_ms: yupNumber().integer().min(1).max(60000).default(1000), | ||
| }).defined(), | ||
| }), | ||
| response: yupObject({ | ||
| statusCode: yupNumber().oneOf([200]).defined(), | ||
| bodyType: yupString().oneOf(["json"]).defined(), | ||
| body: yupObject({ | ||
| result: jsonSchema.defined(), | ||
| stats: yupObject({ | ||
| cpu_time: yupNumber().defined(), | ||
| wall_clock_time: yupNumber().defined(), | ||
| }).defined(), | ||
| }).defined(), | ||
| }), | ||
| async handler({ body, auth }) { | ||
| const client = createClickhouseClient("external", body.timeout_ms); | ||
|
BilalG1 marked this conversation as resolved.
Outdated
|
||
| const queryId = randomUUID(); | ||
| const resultSet = await Result.fromPromise(client.query({ | ||
| query: body.query, | ||
| query_id: queryId, | ||
| query_params: body.params, | ||
| clickhouse_settings: { | ||
| SQL_tenancy_id: auth.tenancy.id, | ||
| }, | ||
| format: "JSONEachRow", | ||
| })); | ||
|
|
||
| if (resultSet.status === "error") { | ||
| const message = resultSet.error instanceof Error ? resultSet.error.message : null; | ||
| if (message === "Timeout error.") { | ||
| throw new KnownErrors.AnalyticsQueryTimeout(body.timeout_ms); | ||
| } | ||
| throw new KnownErrors.AnalyticsQueryError(message ?? "Unknown error"); | ||
| } | ||
|
|
||
| const rows = await resultSet.data.json<Record<string, unknown>[]>(); | ||
| const stats = await getQueryTimingStats(client, queryId); | ||
|
|
||
| return { | ||
| statusCode: 200, | ||
| bodyType: "json", | ||
| body: { | ||
| result: rows, | ||
| stats: { | ||
| cpu_time: stats.cpu_time_ms, | ||
| wall_clock_time: stats.wall_clock_time_ms, | ||
| }, | ||
| }, | ||
| }; | ||
| }, | ||
|
BilalG1 marked this conversation as resolved.
Outdated
|
||
| }); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { createClient, type ClickHouseClient } from "@clickhouse/client"; | ||
| import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; | ||
|
|
||
| const clickhouseUrl = getEnvVariable("STACK_CLICKHOUSE_URL"); | ||
| const clickhouseAdminUser = getEnvVariable("STACK_CLICKHOUSE_ADMIN_USER", "stackframe"); | ||
| const clickhouseExternalUser = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_USER", "limited_user"); | ||
| const clickhouseAdminPassword = getEnvVariable("STACK_CLICKHOUSE_ADMIN_PASSWORD"); | ||
| const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD"); | ||
| const clickhouseDatabase = getEnvVariable("STACK_CLICKHOUSE_DATABASE", "analytics"); | ||
|
|
||
| export function createClickhouseClient(authType: "admin" | "external", timeoutMs?: number) { | ||
| return createClient({ | ||
| url: clickhouseUrl, | ||
| username: authType === "admin" ? clickhouseAdminUser : clickhouseExternalUser, | ||
| password: authType === "admin" ? clickhouseAdminPassword : clickhouseExternalPassword, | ||
| database: clickhouseDatabase, | ||
| request_timeout: timeoutMs, | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| export const getQueryTimingStats = async (client: ClickHouseClient, queryId: string) => { | ||
| // Flush logs to ensure system.query_log has latest query result. | ||
| // Todo: for performance we should instead poll for this row to become available asynchronously after returning result. Flushed every 7.5 seconds by default | ||
| await client.exec({ | ||
| query: "SYSTEM FLUSH LOGS", | ||
| auth: { | ||
| username: clickhouseAdminUser, | ||
| password: clickhouseAdminPassword, | ||
| }, | ||
| }); | ||
| const profile = await client.query({ | ||
| query: ` | ||
| SELECT | ||
| ProfileEvents['CPUTimeMicroseconds'] / 1000 AS cpu_time_ms, | ||
| ProfileEvents['RealTimeMicroseconds'] / 1000 AS wall_clock_time_ms | ||
| FROM system.query_log | ||
| WHERE query_id = {query_id:String} AND type = 'QueryFinish' | ||
| ORDER BY event_time DESC | ||
| LIMIT 1 | ||
| `, | ||
| query_params: { query_id: queryId }, | ||
| auth: { | ||
| username: clickhouseAdminUser, | ||
| password: clickhouseAdminPassword, | ||
| }, | ||
| format: "JSON", | ||
| }); | ||
|
|
||
| const stats = await profile.json<{ | ||
| cpu_time_ms: number, | ||
| wall_clock_time_ms: number, | ||
| }>(); | ||
| return stats.data[0]; | ||
|
N2D4 marked this conversation as resolved.
BilalG1 marked this conversation as resolved.
|
||
| }; | ||
|
BilalG1 marked this conversation as resolved.
BilalG1 marked this conversation as resolved.
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.