apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cameleer-clickhouse
  namespace: cameleer
spec:
  serviceName: cameleer-clickhouse
  replicas: 1
  selector:
    matchLabels:
      app: cameleer-clickhouse
  template:
    metadata:
      labels:
        app: cameleer-clickhouse
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 101
        fsGroup: 101
      containers:
        - name: cameleer-clickhouse
          image: clickhouse/clickhouse-server:24.12
          env:
            - name: CLICKHOUSE_USER
              valueFrom:
                secretKeyRef:
                  name: cameleer-clickhouse-credentials
                  key: CLICKHOUSE_USER
            - name: CLICKHOUSE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cameleer-clickhouse-credentials
                  key: CLICKHOUSE_PASSWORD
            - name: CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT
              value: "1"
          ports:
            - containerPort: 8123
              name: http
            - containerPort: 9000
              name: native
          volumeMounts:
            - name: data
              mountPath: /var/lib/clickhouse
            - name: initdb
              mountPath: /docker-entrypoint-initdb.d
            - name: config
              mountPath: /etc/clickhouse-server/config.d/low-memory.xml
              subPath: low-memory.xml
              readOnly: true
            - name: config
              mountPath: /etc/clickhouse-server/users.d/low-memory-users.xml
              subPath: low-memory-users.xml
              readOnly: true
          resources:
            requests:
              memory: "2Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
              cpu: "2000m"
          livenessProbe:
            httpGet:
              path: /ping
              port: 8123
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ping
              port: 8123
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
      volumes:
        - name: initdb
          configMap:
            name: cameleer-clickhouse-initdb
        - name: config
          configMap:
            name: cameleer-clickhouse-config
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 50Gi
---
apiVersion: v1
kind: Service
metadata:
  name: cameleer-clickhouse
  namespace: cameleer
spec:
  clusterIP: None
  selector:
    app: cameleer-clickhouse
  ports:
    - port: 8123
      targetPort: 8123
      name: http
    - port: 9000
      targetPort: 9000
      name: native
---
apiVersion: v1
kind: Service
metadata:
  name: cameleer-clickhouse-external
  namespace: cameleer
spec:
  type: NodePort
  selector:
    app: cameleer-clickhouse
  ports:
    - port: 8123
      targetPort: 8123
      nodePort: 30123
      name: http
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cameleer-clickhouse-config
  namespace: cameleer
data:
  # Server-level tuning for 4Gi container.
  # Based on https://kb.altinity.com/altinity-kb-setup-and-maintenance/configure_clickhouse_for_low_mem_envs/
  low-memory.xml: |
    <clickhouse>
        <!-- Reduce log verbosity (default is trace) -->
        <logger>
            <level>warning</level>
        </logger>

        <!-- Let OS page out binary segments instead of pinning them in RAM -->
        <mlock_executable>false</mlock_executable>

        <!-- Disable unused protocol ports -->
        <mysql_port remove="1" />
        <postgresql_port remove="1" />

        <!-- Disable noisy system log tables -->
        <query_thread_log remove="1" />
        <opentelemetry_span_log remove="1" />
        <processors_profile_log remove="1" />

        <!-- Reserve 25% of cgroup memory for OS/kernel (default 0.9 is too aggressive) -->
        <max_server_memory_usage_to_ram_ratio>0.75</max_server_memory_usage_to_ram_ratio>
        <max_server_memory_usage>0</max_server_memory_usage>

        <!-- Shrink caches — defaults are sized for 64+ GiB servers -->
        <mark_cache_size>268435456</mark_cache_size>           <!-- 256 MiB -->
        <index_mark_cache_size>67108864</index_mark_cache_size> <!-- 64 MiB -->
        <uncompressed_cache_size>16777216</uncompressed_cache_size> <!-- 16 MiB -->
        <compiled_expression_cache_size>67108864</compiled_expression_cache_size> <!-- 64 MiB -->

        <!-- Connection & concurrency limits -->
        <max_thread_pool_size>2000</max_thread_pool_size>
        <max_connections>64</max_connections>
        <max_concurrent_queries>8</max_concurrent_queries>

        <!-- Background merge pools — keep low to avoid memory spikes -->
        <background_pool_size>2</background_pool_size>
        <background_merges_mutations_concurrency_ratio>2</background_merges_mutations_concurrency_ratio>
        <background_buffer_flush_schedule_pool_size>1</background_buffer_flush_schedule_pool_size>
        <background_move_pool_size>1</background_move_pool_size>
        <background_fetches_pool_size>1</background_fetches_pool_size>
        <background_common_pool_size>2</background_common_pool_size>
        <background_schedule_pool_size>4</background_schedule_pool_size>
        <background_message_broker_schedule_pool_size>1</background_message_broker_schedule_pool_size>
        <background_distributed_schedule_pool_size>1</background_distributed_schedule_pool_size>
        <tables_loader_foreground_pool_size>0</tables_loader_foreground_pool_size>
        <tables_loader_background_pool_size>0</tables_loader_background_pool_size>

        <!-- MergeTree: limit merge memory and allow more concurrent smaller merges -->
        <merge_tree>
            <merge_max_block_size>1024</merge_max_block_size>
            <max_bytes_to_merge_at_max_space_in_pool>1073741824</max_bytes_to_merge_at_max_space_in_pool> <!-- 1 GiB -->
            <min_bytes_for_wide_part>134217728</min_bytes_for_wide_part>
            <vertical_merge_algorithm_min_bytes_to_activate>134217728</vertical_merge_algorithm_min_bytes_to_activate>
            <number_of_free_entries_in_pool_to_lower_max_size_of_merge>2</number_of_free_entries_in_pool_to_lower_max_size_of_merge>
            <number_of_free_entries_in_pool_to_execute_mutation>2</number_of_free_entries_in_pool_to_execute_mutation>
            <number_of_free_entries_in_pool_to_execute_optimize_entire_partition>2</number_of_free_entries_in_pool_to_execute_optimize_entire_partition>
        </merge_tree>
    </clickhouse>
  # User profile settings — per-query limits and parallelism.
  low-memory-users.xml: |
    <clickhouse>
        <profiles>
            <default>
                <!-- Limit per-query thread count (default = nproc) -->
                <max_threads>2</max_threads>
                <!-- Cap per-query memory at 1.5 GiB -->
                <max_memory_usage>1610612736</max_memory_usage>
                <max_block_size>8192</max_block_size>
                <queue_max_wait_ms>1000</queue_max_wait_ms>
                <max_execution_time>600</max_execution_time>
                <!-- Disable parallel parse/format to reduce per-query memory -->
                <input_format_parallel_parsing>0</input_format_parallel_parsing>
                <output_format_parallel_formatting>0</output_format_parallel_formatting>
                <!-- Spill to disk instead of OOM for large aggregations/sorts -->
                <max_bytes_before_external_group_by>1073741824</max_bytes_before_external_group_by>
                <max_bytes_before_external_sort>1073741824</max_bytes_before_external_sort>
            </default>
        </profiles>
    </clickhouse>
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cameleer-clickhouse-initdb
  namespace: cameleer
data:
  01-create-database.sql: |
    CREATE DATABASE IF NOT EXISTS cameleer;
