|
- -- 01-partition.sql
- -- First-run initialization for new-api's partitioned logs table.
- -- Executed by the postgres official entrypoint against the database named by
- -- POSTGRES_DB (new-api), as a superuser.
- --
- -- This script is the SINGLE source of truth for the logs partitioning setup.
- -- The new-api Go application does NOT create or maintain partitions; it only
- -- skips GORM AutoMigrate for the logs table on PostgreSQL (see
- -- model/main.go::ensureLogTable). pg_partman owns partition lifecycle, pg_cron
- -- drives periodic maintenance.
-
- -- 1. Extensions --------------------------------------------------------------
- -- Install pg_partman into a dedicated schema named "partman" so its functions
- -- are referenced as partman.xxx (pg_partman 5.x does not create the schema
- -- automatically; without this CREATE EXTENSION lands in public and
- -- partman.create_parent fails with "schema partman does not exist").
- CREATE SCHEMA IF NOT EXISTS partman;
- CREATE EXTENSION IF NOT EXISTS pg_partman WITH SCHEMA partman;
- -- pg_cron must be created in the database set by cron.database_name (new-api),
- -- which requires shared_preload_libraries='pg_cron' (configured in the image).
- -- Include partman in the default search_path so its functions resolve without
- -- schema-qualifying every call below.
- SET search_path = partman, public;
- CREATE EXTENSION IF NOT EXISTS pg_cron;
-
- -- 2. Partitioned parent table -----------------------------------------------
- -- Columns mirror model.Log exactly. PG requires the partition key (created_at)
- -- to be part of the primary key, so it is a composite (id, created_at). The app
- -- never queries logs by id alone, so this does not affect business logic.
- CREATE TABLE IF NOT EXISTS public.logs (
- id BIGSERIAL,
- user_id INTEGER,
- created_at BIGINT NOT NULL,
- type INTEGER,
- content TEXT,
- username VARCHAR(64) DEFAULT '',
- token_name VARCHAR(255) DEFAULT '',
- model_name VARCHAR(255) DEFAULT '',
- quota INTEGER DEFAULT 0,
- prompt_tokens INTEGER DEFAULT 0,
- completion_tokens INTEGER DEFAULT 0,
- use_time INTEGER DEFAULT 0,
- is_stream BOOLEAN,
- channel_id INTEGER,
- token_id INTEGER DEFAULT 0,
- "group" VARCHAR(255),
- ip VARCHAR(64) DEFAULT '',
- request_id VARCHAR(64) DEFAULT '',
- chat_id VARCHAR(128) DEFAULT '',
- upstream_id VARCHAR(128) DEFAULT '',
- other TEXT,
- PRIMARY KEY (id, created_at)
- ) PARTITION BY RANGE (created_at);
-
- -- 3. Hand off to pg_partman --------------------------------------------------
- -- Weekly native range partitioning on the bigint epoch (seconds) column.
- -- p_epoch='seconds' -> created_at is a unix-seconds bigint
- -- p_type='range' -> pg_partman 5.x uses PG-native partitioning with
- -- p_type values 'range'/'list' (the old 'native'
- -- alias from 4.x was removed)
- -- p_interval='1 week' -> one partition per week (pg_partman 5.x dropped
- -- the 'weekly' preset in favor of native PG
- -- interval values)
- -- p_date_trunc_interval='week' -> align partition boundaries to ISO weeks (Monday)
- -- p_premake=8 -> always keep 8 future weeks pre-created
- -- p_default_table=true -> create a DEFAULT partition catching out-of-range
- -- inserts so they never fail silently (RecordConsumeLog
- -- only logs errors without retrying)
- -- No retention is set: per current requirement we do NOT auto-drop old partitions.
- SELECT partman.create_parent(
- p_parent_table => 'public.logs',
- p_control => 'created_at',
- p_type => 'range',
- p_interval => '1 week',
- p_epoch => 'seconds',
- p_date_trunc_interval => 'week',
- p_premake => 8,
- p_default_table => true
- );
-
- -- 4. Core indexes on the parent table ---------------------------------------
- -- PG 11+ propagates indexes created on a partitioned parent to all child
- -- partitions automatically. These cover the hot query paths in model/log.go
- -- (GetAllLogs / GetUserLogs ordering by created_at desc, id desc; lookups by
- -- user/model/channel/request/token). Trimmed from the model's 16 index tags to
- -- the 6 actually used, cutting index write overhead.
- CREATE INDEX IF NOT EXISTS idx_logs_created_at_id ON public.logs (created_at DESC, id DESC);
- CREATE INDEX IF NOT EXISTS idx_logs_user_id_created ON public.logs (user_id, created_at DESC);
- CREATE INDEX IF NOT EXISTS idx_logs_model_name ON public.logs (model_name);
- CREATE INDEX IF NOT EXISTS idx_logs_channel_id ON public.logs (channel_id);
- CREATE INDEX IF NOT EXISTS idx_logs_request_id ON public.logs (request_id);
- CREATE INDEX IF NOT EXISTS idx_logs_token_id ON public.logs (token_id);
-
- -- 5. Schedule periodic maintenance ------------------------------------------
- -- run_maintenance_proc() inspects partman.part_config and premakes the next
- -- partitions when needed. Every 30 minutes is more than enough; weekly partitions
- -- only need creation roughly once a week. No retention => no drops.
- SELECT cron.schedule(
- 'log-partition-maint',
- '*/30 * * * *',
- $$CALL partman.run_maintenance_proc()$$
- );
|