django-clickhouse-backend

repository·main·Indexed 41 days ago

https://github.com/jayvynl/django-clickhouse-backend

A Django database backend that enables the use of the Django ORM to interact directly with ClickHouse. It provides high-performance analytical capabilities, support for ClickHouse engines like MergeTree and Distributed, and a specialized ClickhouseModel base class. Features include a custom inspectdb command for table and view introspection, QuerySet.sample() for approximate queries, and integration with clickhouse-driver for NumPy and columnar support.

Tokens
10.8K
Snippets
27
Records
40
Agent score
51%

What's inside django-clickhouse-backend

  1. Overview of django-clickhouse-backend

    main
    The django-clickhouse-backend package provides a database backend for Django to interact with ClickHouse. It allows you to use ClickHouse as a database engine within your Django projects, enabling high-performance analytical queries and storage using Django's ORM and database abstractions.
  2. Understand ClickHouse migration limitations

    main

    Migrations in django-clickhouse-backend differ from standard RDBMS (like PostgreSQL) due to ClickHouse's architecture. Be aware of the following constraints:

    • Constraints: Unique constraints and database-level Foreign Key (FK) constraints are not supported. While you can use ForeignKey in Django models, no enforcement happens at the database level.
    • Indexes: Anonymous indexes or constraints (e.g., db_index, unique, index_together, unique_together) are not supported. You must provide explicit names for all indexes and constraints.
    • Data Migrations: Data migrations are not automatically rolled back if an exception occurs.
    • Field Type Alterations:
      • Changing a field type used in a PRIMARY KEY or ORDER BY clause is only allowed if the change is 'compatible' (i.e., value length is preserved and ordering is maintained). Incompatible changes will raise a DB::Exception.
      • Changing a field type used in an index is restricted. While changing from NOT NULL to Nullable is supported, the reverse (from Nullable to NOT NULL) is not supported and will raise a DB::Exception.
  3. Use Django built-in fields with ClickHouse

    main

    The ClickHouse backend supports standard Django built-in fields for compatibility with existing third-party apps. However, it is highly recommended to use ClickHouse-specific fields for new projects to leverage native data types and performance.

    Important Constraints:

    • ForeignKey, ManyToManyField, and OneToOneField are supported, but no database-level constraints (like referential integrity) are enforced by ClickHouse. This may lead to consistency issues.
    • Some Django fields map to specific ClickHouse types (e.g., CharField maps to FixedString, TextField to String).
  4. Understand AutoField behavior and ID generation

    main

    Since ClickHouse does not have auto-incrementing columns or unique constraints, Django's AutoField, SmallAutoField, and BigAutoField are mapped to the ClickHouse Int64 type.

    When a primary key is not specified during insertion, the backend uses clickhouse_driver.idworker.id_worker to generate a unique ID using the Twitter Snowflake algorithm.

    Configuring Snowflake ID Workers

    To ensure uniqueness across multiple datacenters or processes, configure the following environment variables:

    • CLICKHOUSE_WORKER_ID: An integer between 0 and 31 (default: 0).
    • CLICKHOUSE_DATACENTER_ID: An integer between 0 and 31 (defaults to a random value if not provided).

    If you need a custom, thread-safe ID worker, you can inherit from clickhouse.idworker.base.BaseIDWorker and set the CLICKHOUSE_ID_WORKER setting in your Django settings.py to the dotted import path of your instance.

  5. Use parallel testing with ClickHouse

    main

    The backend supports manage.py test --parallel. Each worker receives a cloned test database named <test database name>_<worker id>. The schema is reproduced by re-running migrations against the clone.

    Important for ReplicatedMergeTree: If your models use a hardcoded ZooKeeper path (a ReplicatedMergeTree path that does not include {uuid} or {database}), parallel testing will fail because replica paths will collide. To support parallel testing, ensure you use the default {uuid} based path or include {database} in the path.

  6. Route queries to ClickHouse using a Database Router

    main

    Since ClickHouse is often used alongside an RDBMS, use a Django database router to automatically direct queries. A common pattern is to route all models that inherit from clickhouse_backend.models.ClickhouseModel to the ClickHouse database.

    Example router implementation:

    from clickhouse_backend.models import ClickhouseModel
    
    class ClickHouseRouter:
        def __init__(self):
            self.route_model_names = set()
            # Logic to find all non-abstract subclasses of ClickhouseModel
            # ... (implementation details omitted for brevity)
    
        def db_for_read(self, model, **hints):
            if (model._meta.label_lower in self.route_model_names
                    or hints.get("clickhouse")):
                return "clickhouse"
            return None
    
        def db_for_write(self, model, **hints):
            if (model._meta.label_lower in self.route_model_names
                    or hints.get("clickhouse")):
                return "clickhouse"
            return None
    
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            if (f"{app_label}.{model_name}" in self.route_model_names
                    or hints.get("clickhouse")):
                return db == "clickhouse"
            elif db == "clickhouse":
                return False
            return None
  7. Configure the DATABASES setting for ClickHouse

    main

    To use the ClickHouse backend, update your Django DATABASES setting. The ENGINE must be set to clickhouse_backend.backend. Other parameters like NAME, HOST, PORT, USER, and PASSWORD have default values if not provided.

    DATABASES = {
        'default': {
            'ENGINE': 'clickhouse_backend.backend',
            'NAME': 'default',
            'HOST': 'localhost',
            'USER': 'DB_USER',
            'PASSWORD': 'DB_PASSWORD',
            'OPTIONS': {
                'settings': {'mutations_sync': 1}
            },
            'TEST': {
                'fake_transaction': True
            }
        }
    }
  8. Use clickhouse_backend.models.indexes.Index for model indexes

    main

    When defining indexes on your Django models, do not use the standard django.db.models.Index. Instead, use clickhouse_backend.models.indexes.Index to ensure compatibility with ClickHouse.

    from clickhouse_backend.models.indexes import Index
    
    class MyModel(models.Model):
        # Use the backend-specific Index
        class Meta:
            indexes = [Index(fields=['my_field'])]
  9. Use ClickHouse-specific fields for new projects

    main

    For optimal performance and type safety, use fields imported from clickhouse_backend.models. These fields map directly to native ClickHouse types.

    Key Features:

    • Nullable: Passing null=True to a field makes it a Nullable type in ClickHouse.
    • LowCardinality: If a type supports it, you can pass low_cardinality=True to enable LowCardinality optimization.
    • Naming Convention: Field classes are named by concatenating the ClickHouse type name with Field (e.g., DateTime64Field).
  10. Install django-clickhouse-backend

    main

    You can install the backend using pip or by installing from source.

    Via pip:

    pip install django-clickhouse-backend

    From source:

    git clone https://github.com/jayvynl/django-clickhouse-backend
    cd django-clickhouse-backend
    python setup.py install
    pip install django-clickhouse-backend
  11. Configure the ClickHouse database backend

    main

    To use ClickHouse in Django, add clickhouse_backend to your INSTALLED_APPS and define the database in DATABASES. The ENGINE must be set to clickhouse_backend.backend.

    Available configuration keys:

    • ENGINE (required): clickhouse_backend.backend
    • NAME: database name (default: default)
    • HOST: database host (default: localhost)
    • PORT: database port (default: 9000)
    • USER: database user (default: default)
    • PASSWORD: database password (default: empty)

    Example configuration for a multi-db setup with PostgreSQL and ClickHouse:

    INSTALLED_APPS = [
        # ...
        "clickhouse_backend",
        # ...
    ]
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.postgresql",
            "HOST": "localhost",
            "USER": "postgres",
            "PASSWORD": "123456",
            "NAME": "postgres",
        },
        "clickhouse": {
            "ENGINE": "clickhouse_backend.backend",
            "NAME": "default",
            "HOST": "localhost",
            "USER": "DB_USER",
            "PASSWORD": "DB_PASSWORD",
        }
    }
    DATABASE_ROUTERS = ["dbrouters.ClickHouseRouter"]
  12. Configure ClickHouse Clusters and Distributed Tables

    main

    For distributed environments, use the following configuration options in DATABASES[...]['OPTIONS']:

    • migration_cluster: Specifies the cluster name where the migration table should be created. If not specified, migrations are local.
    • distributed_migrations: (Boolean) If True, a distributed migration table is created on all nodes, allowing migrations to be performed via a load balancer.
    • TEST: Use this key to configure test database behavior for specific cluster nodes.

    Distributed Table Example: To create a distributed table, define the Distributed engine in your model's Meta class and specify the cluster attribute.

    class DistributedStudent(models.ClickhouseModel):
        name = models.StringField()
        score = models.Int8Field()
    
        class Meta:
            engine = models.Distributed(
                "cluster", 
                models.currentDatabase(), 
                Student._meta.db_table, 
                models.Rand()
            )
            cluster = "cluster"
    DATABASES = {
        "default": {
            "ENGINE": "clickhouse_backend.backend",
            "HOST": "clickhouse-load-balancer",
            "OPTIONS": {
                "migration_cluster": "cluster",
                "distributed_migrations": True,
                "settings": {
                    "mutations_sync": 2,
                    "insert_distributed_sync": 1,
                },
            },
        }
    }