# Sample configuration for cassandra-on-mysql proxy
#
# Usage:
#   go run ./cmd/proxy -config config.sample.yaml
#   # or
#   ./bin/cassandra-proxy -config config.sample.yaml
#
# Notes:
# - The proxy uses Cassandra PasswordAuthenticator. The username/password a client provides are used
#   to connect to MySQL per-tenant/user.
# - You can override `system_tables.advertised_address` at startup with:
#     - flag: `-advertised-address <ip-or-host>`
#     - env:  `SYSTEM_TABLES_ADVERTISED_ADDRESS`

mysql:
  # MySQL address in host:port form
  host: "127.0.0.1:4000"

  # Per-tenant/user pool sizing limits (see tenancy.limits too)
  max_open_conns: 20000
  max_idle_conns: 100

  # Timeouts
  connect_timeout: 30s
  read_timeout: 30s
  write_timeout: 30s

  # Connection lifetime controls
  conn_max_lifetime: 1h
  conn_max_idle_time: 10m

  # Transparent server-side retry of backend writes.
  retry:
    # Deadlock (MySQL 1213) retry for standalone single statements. A deadlock
    # fully rolls the transaction back, so the write is safe to re-run; TiDB
    # detects deadlocks fast, so retry is quick and usually succeeds on the next
    # attempt. Enabled by default. Batch requests are NOT retried.
    deadlock:
      enabled: true
      max_attempts: 3        # total tries including the first
      base_backoff: 10ms     # first retry backoff ceiling; doubles each retry
      max_backoff: 100ms     # cap on the per-retry backoff ceiling
  # NOTE: lock-wait timeout (MySQL 1205) is NOT retried yet. To surface 1205 as a
  # retryable error at all (instead of hitting read_timeout first), set a short
  # session lock wait via dsn_params, e.g.:
  #   dsn_params:
  #     innodb_lock_wait_timeout: "2"

  # MySQL driver TLS settings (go-sql-driver/mysql `tls` parameter)
  # mode: "true" | "skip-verify" | "preferred" | "<custom-name>"
  tls:
    mode: "preferred"
    # ca_file: "/path/to/ca.pem"
    # cert_file: "/path/to/client-cert.pem"
    # key_file: "/path/to/client-key.pem"
    # server_name: "mysql.example.com"
    insecure_skip_verify: false

  # Limits used when mapping secondary indexes to MySQL (safe defaults for InnoDB)
  max_index_bytes: 3072
  min_index_prefix_chars: 8
  min_index_prefix_bytes: 8

  # Default number of MySQL partitions when partitioning is enabled
  partition_default_size: 10

  # When true, CREATE TABLE emits PRIMARY KEY ... CLUSTERED.
  # When false, CREATE TABLE emits PRIMARY KEY ... NONCLUSTERED explicitly.
  enable_clustered_index: false

  # Compatibility toggles (disable_* keeps legacy behavior when set to true)
  # false => allow TEXT/BLOB partition keys (partition clause may be skipped)
  disable_text_blob_partition_key: false
  # false => allow CREATE INDEX on TEXT/BLOB by auto-applying key prefixes
  disable_text_blob_secondary_index: false

server:
  # Max concurrent CQL client connections allowed
  max_connections: 10000

  # Optional pprof/debug HTTP server address
  # debug_addr: "127.0.0.1:6060"
  debug_addr: ""
  
  drain:
    # On SIGTERM, readiness flips to 503 immediately, then the proxy waits this
    # long before closing listeners and draining existing connections.
    #
    # IMPORTANT (L4 load balancers such as AWS NLB): existing TCP connections are
    # pinned to this pod; readiness=503 only stops NEW connections. Draining
    # breaks existing connections so the driver reconnects THROUGH the LB. For
    # that reconnect to land on a healthy pod, readiness_delay must be >= the time
    # for the LB to deregister this pod (health-check + propagation), typically
    # 20-40s, and system_tables.advertised_address must be the LB VIP, not the pod
    # IP. Too short a value makes the driver reconnect back to this draining pod,
    # causing sustained errors and latency.
    readiness_delay: 40s
    # After listeners close, wait this long for in-flight requests to finish and
    # connections to close cleanly before force-closing the remainder. Ensure
    # readiness_delay + wait_timeout < terminationGracePeriodSeconds.
    wait_timeout: 25s

  # One or more listeners for Cassandra native protocol
  listeners:
    - name: "plain"
      address: "0.0.0.0:9042"
      tls:
        # disabled | manual | auto
        mode: "disabled"
        # cert_file: "/path/to/server-cert.pem"   # for manual
        # key_file: "/path/to/server-key.pem"     # for manual
        # client_ca_file: "/path/to/client-ca.pem" # for mTLS
        # client_auth: "require_and_verify"        # none|request|require|verify|require_and_verify
        # min_version: "1.2"
        # max_version: "1.3"

    # Example TLS listener (optional)
    - name: "tls"
      address: "127.0.0.1:9142"
      tls:
        mode: "auto"  # auto generates a self-signed cert for the host name
  admin:
    address: "0.0.0.0:6060"
    pprof:
      enabled: true
      path: "/debug/pprof"
    metrics:
      enabled: true
      path: "/metrics"               # default
      slow_request_threshold: 250ms  # optional: logs slow requests with stage timings

# Tenant routing rules:
# - Default: treat "tenant:" prefix in username as tenant name.
# - Fallback: when no regex match, use the whole username as tenant ("username").
tenancy:
  tenant_regex: "^(?P<tenant>[^:]+):"
  tenant_regex_fallback: "username"

  # Interval to prune idle tenant pools
  prune_interval: 5m

  # Hard limits per tenant/user
  limits:
    max_proxy_connections: 10000
    max_mysql_connections: 5000

# Values exposed via system tables for Cassandra client compatibility.
# Most applications can keep these defaults.
system_tables:
  cluster_name: "Cassandra"
  datacenter: "datacenter1"
  rack: "rack1"

  # If empty, the proxy tries to infer from the first listener or uses 127.0.0.1.
  advertised_address: ""

  # If 0, the native port is inferred from the first listener, or defaults to 9042.
  native_port: 0

  # Defaults to 7000 when 0.
  storage_port: 7000

  release_version: "4.0"
  cql_version: "3.4.7"

  # Optional; if empty, the implementation defaults are used.
  native_protocol_version: ""

  partitioner: "org.apache.cassandra.dht.Murmur3Partitioner"

  # Optional token list used for system tables; leave empty unless you need stable tokens.
  tokens: []

  # Schema version strategy for system tables.
  # strategy: "cql_meta" (derive from MySQL schema metadata) or "fixed".
  schema_version:
    strategy: "cql_meta"
    fixed: ""

  # Optional peers shown in system.peers* for multi-node illusions.
  peers: []

  # Capability profile toggles what clients see as supported.
  capability_profile: "compat_v4"

# Multi-instance schema metadata sync.
# Enabled by default; set enabled: false to disable.
schema_sync:
  enabled: true
  check_interval: 5s

executor:
  # When true, schema sync runs in background (not on query path).
  # Strongly recommended to avoid schema_sync on queries.
  schema_sync_background: true
  # strict_cassandra keeps compatibility semantics; extended_sql enables
  # non-compatible extensions (column default/on-update, insert increment upsert,
  # and safe batch UPDATE merge to multi-values upsert).
  protocol_mode: extended_sql
  # Fully-qualified columns (keyspace.table.column) that should use additive
  # conflict updates on INSERT duplicate keys: col = col + VALUES(col).
  insert_duplicate_increment_columns: []
  # When true, UNLOGGED BATCH executes in best-effort parallel mode (non-atomic).
  unlogged_batch_parallel: true
  # When true, UNLOGGED best-effort parallel first coalesces compatible INSERTs and
  # falls back to per-statement retry when a merged INSERT fails.
  unlogged_merge_before_parallel: true
  # Max goroutines for parallel UNLOGGED BATCH. <= 0 means CPU cores * 4.
  unlogged_batch_parallelism: 128
  # Caps MySQL connections used by UNLOGGED BATCH worker fan-out so non-batch reads
  # keep backend headroom. <= 0 defaults to 75% of the effective MySQL connection budget.
  unlogged_batch_max_mysql_connections: 3750
  # Max rows coalesced into one INSERT ... VALUES during batch optimization. <= 0 uses 256.
  batch_insert_merge_max_rows: 256
  # Max estimated SQL bytes for one merged INSERT statement. <= 0 uses 256KB.
  batch_insert_merge_max_bytes: 262144
  # When true, batch merge grouping uses TiDB HASH partition buckets as the split key
  # for tables that can be verified as PARTITION BY HASH(single_int_partition_key).
  batch_group_by_tidb_hash_partition: true
  # Bounds the per-tenant CQL prepared statement cache (keyed by keyspace+query).
  # Evicted statements are transparently re-prepared by clients via CQL UNPREPARED
  # recovery, so this only caps memory. <= 0 uses 10000.
  prepared_cache_max_statements: 10000
  # Drops cached CQL prepared statements not used within this idle TTL on next access.
  # The size cap above bounds memory regardless. <= 0 uses 1h.
  prepared_cache_idle_ttl: 1h

features:
  # If true, views/triggers are exposed only as metadata (not fully implemented behavior).
  metadata_only_views: false
  metadata_only_triggers: false

logging:
  # debug | info | warn | error
  level: "debug"

  # stdout | file
  output: "stdout"

  # Required when output: file
  file_path: ""

  # Pretty console logging when true; JSON when false
  development: true

  # Query redaction is enabled by default when unset.
  redact_queries: true

  # Optional additional redaction patterns
  redact_patterns: []
