Skip to content

Commit

Permalink
migrate events from mysql to clickhouse
Browse files Browse the repository at this point in the history
  • Loading branch information
tudddorrr committed Aug 18, 2024
1 parent 6e2f5fc commit ddf9e2e
Show file tree
Hide file tree
Showing 32 changed files with 655 additions and 298 deletions.
10 changes: 10 additions & 0 deletions clickhouse/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM clickhouse/clickhouse-server:24-alpine

RUN apk add --no-cache gettext

COPY /clickhouse/migrations /docker-entrypoint-initdb.d/
COPY /clickhouse/entrypoint.sh /usr/local/bin/entrypoint.sh

RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
21 changes: 21 additions & 0 deletions clickhouse/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
set -e

INIT_DIR="/docker-entrypoint-initdb.d"
FLAG_FILE="$INIT_DIR/.migrated"

if [ ! -f "$FLAG_FILE" ]; then
for file in ${INIT_DIR}/*.sql; do
if [ -f "$file" ]; then
envsubst < "$file" > "$file.processed" && mv "$file.processed" "$file"
echo "Processed migration $file"
fi
done

echo "Creating migrations lock"
touch "$FLAG_FILE"
else
echo "Migrations lock set; Skipping envsubst processing"
fi

exec /entrypoint.sh "$@"
16 changes: 16 additions & 0 deletions clickhouse/migrations/001CreateEventsTable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS ${CLICKHOUSE_DB}.events
(
id String,
name String,
game_id UInt32,
player_alias_id UInt32,
dev_build Boolean,
created_at DateTime,
updated_at DateTime,
PRIMARY KEY (id),
INDEX name_idx (name) TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX game_id_idx (game_id) TYPE minmax GRANULARITY 64,
INDEX player_alias_id_idx (player_alias_id) TYPE minmax GRANULARITY 64
)
ENGINE = MergeTree()
ORDER BY (id, created_at, game_id, player_alias_id);
6 changes: 6 additions & 0 deletions clickhouse/migrations/002CreateEventPropsTable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS ${CLICKHOUSE_DB}.event_props (
event_id String,
prop_key String,
prop_value String
) ENGINE = MergeTree()
ORDER BY (event_id, prop_key);
38 changes: 1 addition & 37 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,9 @@
services:
test-db:
image: mysql:8.4
command: --mysql-native-password=ON
environment:
- MYSQL_DATABASE=${DB_NAME}
- MYSQL_ROOT_PASSWORD=${DB_PASS}
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 2s
timeout: 2s
retries: 10
ports:
- ${DB_PORT}:3306
volumes:
- test-data:/var/lib/mysql
networks:
- test-network

test-redis:
image: bitnami/redis:7.2
command:
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- ${REDIS_PORT}:6379
depends_on:
test-db:
condition: service_healthy
networks:
- test-network

stripe-api:
image: stripe/stripe-mock:latest
ports:
- 12111:12111
- 12112:12112
networks:
- test-network

volumes:
test-data:

networks:
test-network:
5 changes: 3 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ services:
context: .
target: dev
image: backend
depends_on:
- db
ports:
- 3000:80
volumes:
- ./src:/usr/backend/src
- ./tests:/usr/backend/tests
depends_on:
- db

14 changes: 14 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ services:
networks:
- test-network

test-clickhouse:
build:
context: .
dockerfile: ./clickhouse/Dockerfile
environment:
CLICKHOUSE_USER: ${CLICKHOUSE_USER}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
CLICKHOUSE_DB: ${CLICKHOUSE_DB}
restart: unless-stopped
ports:
- ${CLICKHOUSE_PORT}:8123
networks:
- test-network

stripe-api:
image: stripe/stripe-mock:latest-arm64
ports:
Expand Down
18 changes: 17 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ services:
depends_on:
- db
- redis
- clickhouse

db:
image: mysql:8.4
command: --mysql-native-password=ON
environment:
- MYSQL_DATABASE=${DB_NAME}
- MYSQL_ROOT_PASSWORD=${DB_PASS}
restart: always
restart: unless-stopped
ports:
- 3306:3306
volumes:
Expand All @@ -24,5 +25,20 @@ services:
ports:
- 6379:6379

clickhouse:
build:
context: .
dockerfile: ./clickhouse/Dockerfile
environment:
CLICKHOUSE_USER: ${CLICKHOUSE_USER}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
CLICKHOUSE_DB: ${CLICKHOUSE_DB}
restart: unless-stopped
ports:
- ${CLICKHOUSE_PORT}:8123
volumes:
- clickhouse-data:/var/lib/clickhouse

volumes:
data:
clickhouse-data:
Empty file added envs/.dockerignore
Empty file.
6 changes: 6 additions & 0 deletions envs/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ DB_PASS=password

REDIS_PASSWORD=password

CLICKHOUSE_HOST=clickhouse
CLICKHOUSE_PORT=8123
CLICKHOUSE_USER=gs_ch
CLICKHOUSE_PASSWORD=password
CLICKHOUSE_DB=gs_ch_dev

DEMO_ORGANISATION_NAME=Talo Demo

SENDGRID_KEY=
Expand Down
6 changes: 6 additions & 0 deletions envs/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ DB_PORT=3307
DB_NAME=gs_test
DB_PASS=password

CLICKHOUSE_HOST=127.0.0.1
CLICKHOUSE_PORT=8124
CLICKHOUSE_USER=
CLICKHOUSE_PASSWORD=
CLICKHOUSE_DB=gs_ch_test

SENDGRID_KEY=
SENTRY_DSN=

Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"vitest": "^1.5.2"
},
"dependencies": {
"@clickhouse/client": "^1.4.1",
"@dinero.js/currencies": "^2.0.0-alpha.14",
"@koa/cors": "^5.0.0",
"@mikro-orm/core": "^6.3.2",
Expand Down
86 changes: 69 additions & 17 deletions src/entities/event.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
import { Entity, Embedded, ManyToOne, PrimaryKey, Property, Cascade } from '@mikro-orm/mysql'
import { v4 } from 'uuid'
import sanitiseProps from '../lib/props/sanitiseProps'
import Game from './game'
import PlayerAlias from './player-alias'
import Prop from './prop'
import { formatDateForClickHouse } from '../lib/clickhouse/formatDateTime'
import { EntityManager } from '@mikro-orm/mysql'
import createClickhouseClient from '../lib/clickhouse/createClient'

const eventMetaProps = ['META_OS', 'META_GAME_VERSION', 'META_WINDOW_MODE', 'META_SCREEN_WIDTH', 'META_SCREEN_HEIGHT']

@Entity()
export default class Event {
@PrimaryKey()
id: number

@Property()
export type ClickhouseEvent = {
id: string
name: string
game_id: number
player_alias_id: number
dev_build: boolean
created_at: string
updated_at: string
}

@Embedded(() => Prop, { array: true })
props: Prop[] = []
export type ClickhouseEventProp = {
event_id: string
prop_key: string
prop_value: string
}

@ManyToOne(() => Game)
export default class Event {
id: string
name: string
props: Prop[] = []
game: Game

@ManyToOne(() => PlayerAlias, { cascade: [Cascade.REMOVE] })
playerAlias: PlayerAlias

@Property()
createdAt: Date = new Date()

@Property({ onUpdate: () => new Date() })
createdAt: Date
updatedAt: Date = new Date()

constructor(name: string, game: Game) {
this.id = v4()
this.name = name
this.game = game
}
Expand All @@ -51,6 +57,26 @@ export default class Event {
})
}

getInsertableData(): ClickhouseEvent {
return {
id: this.id,
name: this.name,
game_id: this.game.id,
player_alias_id: this.playerAlias.id,
dev_build: this.playerAlias.player.isDevBuild(),
created_at: formatDateForClickHouse(this.createdAt),
updated_at: formatDateForClickHouse(this.updatedAt)
}
}

getInsertableProps(): ClickhouseEventProp[] {
return this.props.map((prop) => ({
event_id: this.id,
prop_key: prop.key,
prop_value: prop.value
}))
}

toJSON() {
return {
id: this.id,
Expand All @@ -62,3 +88,29 @@ export default class Event {
}
}
}

export async function createEventFromClickhouse(em: EntityManager, data: ClickhouseEvent, loadProps = false): Promise<Event> {
const game = await em.getRepository(Game).findOne(data.game_id)
const playerAlias = await em.getRepository(PlayerAlias).findOne(data.player_alias_id, { populate: ['player'] })

const event = new Event(data.name, game)
event.id = data.id
event.playerAlias = playerAlias
event.createdAt = new Date(data.created_at)
event.updatedAt = new Date(data.updated_at)

if (loadProps) {
const clickhouse = createClickhouseClient()

const props = await clickhouse.query({
query: `SELECT * FROM event_props WHERE event_id = '${data.id}'`,
format: 'JSONEachRow'
}).then((res) => res.json<ClickhouseEventProp>())

event.props = props.map((prop) => new Prop(prop.prop_key, prop.prop_value))

clickhouse.close()
}

return event
}
2 changes: 0 additions & 2 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Leaderboard from './leaderboard'
import LeaderboardEntry from './leaderboard-entry'
import APIKey from './api-key'
import DataExport from './data-export'
import Event from './event'
import FailedJob from './failed-job'
import Game from './game'
import Organisation from './organisation'
Expand Down Expand Up @@ -58,7 +57,6 @@ export default [
LeaderboardEntry,
DataExport,
APIKey,
Event,
Game,
FailedJob,
Organisation,
Expand Down
2 changes: 1 addition & 1 deletion src/entities/prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default class Prop {
key: string

@Property()
value: string|null
value: string | null

constructor(key: string, value?: string) {
this.key = key
Expand Down
8 changes: 8 additions & 0 deletions src/lib/clickhouse/createClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createClient } from '@clickhouse/client'
import { NodeClickHouseClient } from '@clickhouse/client/dist/client'

export default function createClickhouseClient(): NodeClickHouseClient {
return createClient({
url: `http://${process.env.CLICKHOUSE_USER}:${process.env.CLICKHOUSE_PASSWORD}@${process.env.CLICKHOUSE_HOST}:${process.env.CLICKHOUSE_PORT}/${process.env.CLICKHOUSE_DB}`
})
}
Loading

0 comments on commit ddf9e2e

Please sign in to comment.