Skip to main content
PostHog uses a dual-database architecture: PostgreSQL for application metadata and ClickHouse for high-volume analytics data.

Database Architecture

Core Concepts

Team-Based Isolation

All data in PostHog is scoped to a team_id:
Security critical: Never query without filtering by team_id. This is enforced in code review and testing.

Team vs Organization

  • Organization: Billing entity, contains multiple teams
  • Team: Data isolation boundary (formerly called “Project”)
  • User: Can belong to multiple organizations

PostgreSQL Schema

Core Models

Key points:
  • Each team has a unique API token for event ingestion
  • Teams are the primary data isolation boundary
  • Team settings control product behavior
Key points:
  • Users belong to organizations via OrganizationMembership
  • Access control is organization-level
  • Billing is per organization

Product Models

Product-specific models live in products/<product>/backend/models.py:
Do not add domain-specific fields to the Team model. Use a Team Extension model instead. See posthog/models/team/README.md for the pattern.

ClickHouse Schema

Events Table

The core analytics table storing all events:
Key characteristics:
  • Partitioned by month for efficient pruning
  • Replicated for high availability
  • Replacing merge tree for deduplication
  • Indexed on team_id, event, and timestamp

Person & Distinct IDs

PostHog maintains identity resolution across devices:
Identity resolution flow:
  1. Event arrives with distinct_id (e.g., device ID)
  2. Look up person_id in person_distinct_id table
  3. Merge person properties if $identify event
  4. Associate all events with resolved person_id

Session Recordings

Recording data uses a hybrid approach:
  • Small snapshots: Stored inline in ClickHouse
  • Large recordings: Stored in S3, referenced by session_id
  • Metadata: Always in ClickHouse for fast queries

Data Flow

1

Event Capture

Client SDK sends event to /e/ endpoint:
2

Ingestion & Enrichment

  • Validate API key → resolve team_id
  • Enrich with GeoIP data
  • Add server-side properties
  • Queue for processing
3

Identity Resolution

  • Look up person by distinct_id
  • Create person if first-time user
  • Merge on $identify events
4

Storage

  • Write event to ClickHouse sharded_events
  • Update person properties if changed
  • Trigger webhooks/exports if configured

Querying Data

HogQL

PostHog’s SQL-like query language for ClickHouse:
Security features:
  • Automatic team_id injection
  • SQL injection prevention via AST parsing
  • Query timeout limits
  • Result size limits

Query Runners

Python code that executes queries:

Migrations

PostgreSQL Migrations

Standard Django migrations:

ClickHouse Migrations

Managed migrations for ClickHouse schema changes:
ClickHouse migrations require careful coordination in distributed environments. Always test on staging first.

Performance Considerations

PostgreSQL Optimization

  • Use select_related() and prefetch_related() to avoid N+1 queries
  • Add indexes on frequently queried fields
  • Use only() to fetch specific fields

ClickHouse Optimization

  • Filter on team_id and timestamp first (partition keys)
  • Use materialized columns for frequently accessed JSON properties
  • Limit result sizes with LIMIT
  • Use sampling for approximate results on large datasets

Next Steps

Development Setup

Set up your local environment

Testing

Learn about testing strategies