Open-Source Wikis

/

TiDB

/

Systems

/

DDL framework

pingcap/tidb

DDL framework

DDL (data-definition language) statements in TiDB are not executed inline. They are turned into asynchronous jobs that a single elected owner drives through a state machine. This is what makes online schema change safe across many TiDB instances.

Purpose

pkg/ddl/ implements:

  • The job submission API (CREATE TABLE, ALTER TABLE, DROP TABLE, …).
  • A scheduler that picks up jobs across the cluster.
  • A worker pool that runs each job through its state machine.
  • Reorg logic for index/column backfills.
  • Multi-version schema reload coordination so all TiDB nodes converge on the new infoschema.
  • Specialised flows for partitioning, BDR (bidirectional replication), placement policies, TiFlash replicas, and online ADD INDEX with distributed reorg.

The DDL framework's source-of-truth runbook lives at docs/agents/ddl/README.md.

Directory layout

pkg/ddl/
├── ddl.go                  # Public API: NewDDL, RegisterStatsHandle, etc.
├── job_submitter.go        # Submits a job to the cluster job table
├── job_scheduler.go        # Picks the next job to run on this owner
├── job_worker.go           # Drives a job through its state machine
├── ddl_running_jobs.go     # Per-instance running-job state
├── ddl_workerpool.go       # Worker pool for parallel small jobs
├── ddl_history.go          # Records completed/failed jobs in mysql.tidb_ddl_history
├── owner_mgr.go            # Owner election helper
├── dist_owner.go           # Distributed-owner selection for big jobs
├── ddl_tiflash_api.go      # TiFlash replica management
├── reorg.go, reorg_util.go # Backfill state machine
├── backfilling*.go         # Online ADD INDEX backfill, distributed reorg
├── delete_range.go         # Background deletion after DROP/TRUNCATE
├── job_worker_*.go, job_submitter_*.go  # Tests
├── add_column.go, column.go, modify_column.go  # Column DDL flows
├── index.go, index_*.go    # Index DDL flows
├── partition.go            # Partition DDL flows (≈190 KB)
├── create_table.go, schema.go  # Database/table creation
├── foreign_key.go          # FK validation and constraints
├── placement_policy.go     # Placement policies
├── resource_group.go       # Resource group DDL
├── bdr.go                  # Bidirectional replication safety checks
├── ttl.go                  # TTL job hooks
├── multi_schema_change.go  # Atomic multi-statement ALTER TABLE
├── rollingback.go          # Rollback paths for every job type
├── sanity_check.go         # Cross-cuts run before execution
├── schema_version.go       # Coordinates infoschema reload
├── schema.go, schemaver/   # Schema-version tracking
├── stat.go                 # ADMIN SHOW DDL STATS
├── notifier/, schematracker/, serverstate/, session/
├── ingest/                 # Lightning-style ingestion path used by ADD INDEX
├── label/, logutil/, mock/, options.go, table*.go, ttl_test.go,
└── tests/, testargsv1/, util/, copr/

Top-level doc.go (pkg/ddl/doc.go) is required reading per AGENTS.md's pre-flight checklist.

Key abstractions

Type File Purpose
ddl.DDL pkg/ddl/ddl.go Public façade. Each TiDB instance has one.
ddl.DDLJobSubmitter pkg/ddl/job_submitter.go Translates SQL into a model.Job and inserts it into the cluster job table.
ddl.JobScheduler pkg/ddl/job_scheduler.go The DDL owner runs this; it picks the next runnable job.
ddl.worker pkg/ddl/job_worker.go Drives a single job: state machine, transactions, infoschema bumps.
ddl.reorgInfo pkg/ddl/reorg.go Backfill state and progress checkpointing.
model.Job pkg/meta/model/job.go Persistent job record (state, args, schema/table IDs, error).
ddl.OwnerManager pkg/ddl/owner_mgr.go Tracks the current DDL owner via pkg/owner/ (etcd-backed).
ddl.JobType pkg/meta/model/ Enumeration of every supported DDL action.

Job lifecycle

stateDiagram-v2
  [*] --> Queueing : SQL submitted
  Queueing --> Running : owner picks up
  Running --> Reorganizing : if needs backfill
  Reorganizing --> Done : backfill complete
  Running --> Done : non-reorg jobs
  Running --> RollingBack : error / cancel
  RollingBack --> Cancelled
  Done --> [*]
  Cancelled --> [*]

Implementation map:

  1. Submission: ddl.SchemaSyncer validates against the current infoschema; job_submitter.go writes the job into mysql.tidb_ddl_job and returns to the user. The user's session blocks on a notification channel until the job finishes.
  2. Scheduling: only the elected DDL owner runs jobs. owner_mgr.go uses etcd via pkg/owner/ to elect one. job_scheduler.go picks the next runnable job (general or reorg queue).
  3. Execution: job_worker.go is the state-machine driver. It runs each step in a TiKV transaction, advances the job's state, and updates infoschema versions via schema_version.go.
  4. Reorg: For ADD INDEX, MODIFY COLUMN type changes, and similar, reorg.go coordinates a row-by-row backfill. For large indexes, the distributed reorg path in backfilling_dist_*.go partitions the keyspace across TiDB nodes via the DXF framework.
  5. Schema-version bump: when a job advances, schema_version.go writes a new schema version into etcd. Other TiDB instances reload their infoschema (pkg/domain/domain.go loadInfoSchema).
  6. Completion / rollback: rollingback.go defines the rollback path for each job type. ddl_history.go archives the result to mysql.tidb_ddl_history.

DDL job types

pkg/meta/model enumerates every action: ActionCreateSchema, ActionDropSchema, ActionCreateTable, ActionAddColumn, ActionDropColumn, ActionModifyColumn, ActionAddIndex, ActionDropIndex, ActionAddForeignKey, ActionAlterTablePartitioning, ActionExchangeTablePartition, ActionFlashbackCluster, ActionMultiSchemaChange, etc. Each has a dedicated implementation file (add_column.go, index.go, partition.go, …) that the worker dispatches to.

DXF and distributed reorg

For very large indexes, the DXF (Distributed eXecution Framework) in pkg/dxf/ lets multiple TiDB nodes share the backfill workload. The DDL side wires into DXF via:

  • backfilling_dist_executor.go — the executor that runs on each participating node.
  • backfilling_dist_scheduler.go — partitions the key space and dispatches subtasks.
  • backfilling_import_cloud.go — uses cloud storage to ship sorted SST files between nodes.

This path is enabled per-job via tidb_ddl_enable_fast_reorg and friends.

Online safety

DDL respects multiple invariants enforced in code:

  • Schema version waiting: schema_version.go waits for all live TiDB nodes to reload before advancing certain states. The maximum wait is bounded; otherwise the job pauses with WaitSchemaSynced errors.
  • BDR pre-flight: bdr.go rejects DDL types that aren't safe under bi-directional replication.
  • Sanity checks: sanity_check.go runs before execution (e.g., reject column drop if it's referenced by an FK).
  • Reorg checkpointing: reorg.go periodically writes progress so a crash can resume.
  • Cancel paths: cancel_test.go exercises every job type for proper cancellation.

Multi-schema change and BDR

multi_schema_change.go lets a single ALTER TABLE perform several sub-actions atomically. bdr.go ensures these aren't dispatched in BDR clusters when they would break replication.

Tests

pkg/ddl/tests/ is a large directory of subtests (create, index, partition, reorg, cancel, schemaver, …). The DDL-specific test guidance is in docs/agents/ddl/ and .agents/skills/tidb-test-guidelines.

Integration points

  • SQL frontend: pkg/executor/ddl.go and pkg/executor/operate_ddl_jobs.go translate SQL into DDL.DoDDLJob.
  • Meta: pkg/meta/ persists schema definitions.
  • Infoschema: pkg/infoschema/ is the in-memory schema cache; reloads happen via pkg/domain/domain.go.
  • Statistics: stats tied to dropped/altered columns are invalidated through pkg/statistics/handle/.
  • Lightning: distributed reorg uses pkg/lightning/backend/ to write SST files for ingestion.
  • Owner / etcd: pkg/owner/ provides leader election; pkg/util/etcd/ wraps the etcd client.

Entry points for modification

The DDL module's contributor checklist (from AGENTS.md):

  1. Read docs/agents/ddl/README.md first. It is the authoritative map of the execution framework. Treat anything else under docs/agents/ddl/* as hypothesis until verified.
  2. Pick the job type and locate its file (add_column.go, index.go, partition.go, …).
  3. Define new states only by extending the existing state-machine vocabulary; rollback paths must be filled in rollingback.go.
  4. If your change adds a new top-level DDL action, register it in pkg/meta/model/action.go and add a case in job_worker.go.
  5. Tests must include a cancel test in pkg/ddl/tests/ and a regression integration test in tests/integrationtest/t/ddl/....
  6. If implementation drifts from the docs in docs/agents/ddl/, update the docs in the same PR.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

DDL framework – TiDB wiki | Factory