Segments
Segments are saved player filters. A segment is defined as a tree of trait + event conditions; materialised as a set of player_ids in players.segment_ids[]. Campaign fan-out joins on the GIN-indexed array — fast even on 1M-player tenants.
Definition shape
{
"operator": "AND" | "OR",
"conditions": [
{ "type": "trait", "field": "total_deposited", "op": ">", "value": 100000 },
{ "type": "trait", "field": "kyc_status", "op": "=", "value": "verified" },
{
"type": "event",
"event": "bet_settled",
"window": { "kind": "rolling", "days": 7 },
"aggregation": "count",
"op": ">=",
"value": 5
},
{
"operator": "OR",
"conditions": [
{ "type": "trait", "field": "current_tier_code", "op": "=", "value": "gold" },
{ "type": "trait", "field": "current_tier_code", "op": "=", "value": "platinum" }
]
}
]
}
Trait conditions evaluate against the players row (Postgres). Event conditions evaluate against raw_events in ClickHouse with a rolling time window. Groups can nest — operators build complex segments by combining AND/OR.
Allowed trait fields
external_id, email, phone, telegram_id,
status, kyc_status, kyc_level, current_tier_code,
default_currency_code, timezone,
total_deposited, total_withdrawn, total_wagered, total_won, ggr,
bet_count, session_count,
churn_score, ltv_predicted,
first_seen_at, last_seen_at, last_bet_at, last_deposit_at,
created_at,
rg_self_excluded
Plus vertical_activity.casino.bet_count and similar for sportsbook (read from JSONB).
Allowed operators
- Equality:
=,!= - Numeric:
>,>=,<,<= - Set:
in,not_in(with array value) - String:
contains,starts_with - Existence:
exists(no value)
Refresh strategies
| Strategy | When membership updates |
|---|---|
on_demand | Manual POST /v1/segments/:id/refresh only |
scheduled | Cron (default daily) |
realtime | Stream-processor calls /v1/internal/recompute-player-segments after every player attribute mutation; sweeper closes the gap with a 30s catch-up cycle |
realtime is the default for new segments. Set via the Admin UI or PATCH /v1/segments/:id { refresh_strategy: "realtime" }.
Sample preview
The segment editor shows a live count + 10-row sample as you build the condition. Counts are estimates (use the /v1/segments/:id/refresh endpoint to get exact numbers + materialise membership).
Segment templates
Built-in templates for common patterns: "high rollers", "churn risk", "first-deposit declined", "sportsbook bettors", "VIP candidates". Pick + tweak; saves the typed shape with a name.
Performance notes
- Trait-only segments compile to a single Postgres
WHEREfragment + are evaluated in O(rows scanned) via the indexes onplayers.{tenant_id, last_seen_at, kyc_status, current_tier_code, …}. - Event-aggregation segments hit ClickHouse with a tenant + time-window filter — the columnar engine + ordering on
(tenant_id, project_id, player_id, timestamp)keeps this fast even for 100M-event tenants. - Membership materialisation is
UPDATE players SET segment_ids = ?per affected row — touched rows are typically <1% of the player table.