Enforce a schema
By default, InfluxDB 3 Enterprise databases use implicit schema mode: a write that references a new table or column creates it. Explicit schema mode inverts that: tables and columns must be declared through the configuration API before you can write to them, and a write that references anything undeclared is rejected.
Explicit schema mode is available in InfluxDB 3 Enterprise only.
InfluxDB 3 Core rejects a request to create a database with explicit
schema mode with HTTP status 400
(explicit schema mode is only available in InfluxDB 3 Enterprise).
- Create a database with explicit schema mode
- Declare tables and columns
- Write to an explicit database
- Partial writes
- Evolve a declared schema
- Propagation across a cluster
- Limits
- Upgrade considerations
Create a database with explicit schema mode
Schema mode is set when you create a database and can’t be changed afterward.
influxdb3 create database \
--schema-mode explicit \
--token AUTH_TOKEN \
DATABASE_NAMEor
curl --request POST "http://localhost:8181/api/v3/configure/database" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer AUTH_TOKEN" \
--data '{
"db": "DATABASE_NAME",
"schema_mode": "explicit"
}'Replace the following:
DATABASE_NAME: the name of the database to createAUTH_TOKEN: your admin token
retention_period still works alongside schema_mode.
If you omit schema_mode or set it to implicit, the database uses the
default, unenforced behavior.
As a result, existing scripts and clients keep working unchanged.
Declare tables and columns
Use the same influxdb3 create table command
you’d use in an implicit database to declare a table’s tag and field
columns:
influxdb3 create table \
--database DATABASE_NAME \
--token AUTH_TOKEN \
--tags host,region \
--fields usage:float64,online:bool \
TABLE_NAMEor
curl --request POST "http://localhost:8181/api/v3/configure/table" \
--header "Authorization: Bearer AUTH_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"db": "DATABASE_NAME",
"table": "TABLE_NAME",
"tags": ["host", "region"],
"fields": [
{"name": "usage", "type": "float64"},
{"name": "online", "type": "bool"}
]
}'This is the same POST /api/v3/configure/table endpoint that declares
tables in an implicit database.
Explicit mode only changes what happens when a write later references a
table or column that isn’t declared.
A declared table always gets its time column automatically.
For field type options, see Field data types.
Write to an explicit database
Line protocol that matches the declared schema is accepted as usual:
influxdb3 write --database DATABASE_NAME 'TABLE_NAME,host=a,region=west usage=1.0,online=true'A write that names an undeclared table or column is rejected. An undeclared column error names the database, the table, the column, and the column type. An undeclared table error names the database and the table:
column 'rack' (iox::column_type::tag) is not defined in table 'TABLE_NAME' of database 'DATABASE_NAME',
which uses explicit schemas; add the column with the /api/v3/configure/table API before writing to ittable 'other_table' is not defined in database 'DATABASE_NAME', which uses explicit schemas;
create the table with the /api/v3/configure/table API before writing to itBoth errors return HTTP status 400.
A few things to know about rejections:
A declared column written at the wrong type is rejected the same way it is in an implicit database, with the existing
InvalidColumnTypeerror. Explicit mode adds nothing here. The error has the following form:invalid column type for column '<column>', expected <expected>, got <got>A declared tag written as a field, or a field written as a tag, is a wrong type on a declared column. It gets the same
invalid column typeerror, not the undeclared-column error.Every write endpoint rejects the line with status
400. The v1 (/write), v2 (/api/v2/write), and v3 (/api/v3/write_lp) endpoints all enforce the declared schema. The endpoint changes only the error format and whether other lines in the batch are stored. See Partial writes.
Partial writes
A rejection is a line-protocol error like any other.
As a result, the accept_partial parameter governs the rest of the batch
on the /api/v3/write_lp endpoint:
- With
accept_partial=false, the whole request fails and nothing is written. - With
accept_partial=true, the offending lines are reported and the rest of the batch is written.
The /api/v3/write_lp endpoint defaults accept_partial to true.
A client that sends a batch with one undeclared column and no
accept_partial parameter gets a 400 response and has its other lines
stored.
The legacy /write and /api/v2/write endpoints fail the whole request
when any line references an undeclared column, and they store nothing.
Setting accept_partial=true doesn’t change this.
Other ways data arrives
The schema check also applies to data that doesn’t come through a line protocol write endpoint.
| How the data arrives | Undeclared table or column | Other lines in the request |
|---|---|---|
/api/v3/write_lp, default (accept_partial=true) | 400 partial write of line protocol occurred, and the body lists each rejected line | Written |
/api/v3/write_lp, accept_partial=false | 400 line protocol parsing error | Not written |
/api/v2/write, /write | 400, and the server rejects the whole request | Not written |
Plugin, influxdb3_local.write_sync() | Raises an exception | Not applicable |
Plugin, influxdb3_local.write() | Fails after the plugin run, and the server logs an ERROR in system.processing_engine_logs | Not applicable |
Bulk import (import upload or import from-object-store) | 500 Could not modify catalog, and the server creates no import job | Not imported (the server rejects the whole file) |
Every rejection message names the table or column and says to add it with the
/api/v3/configure/table API.
For a schedule trigger, a failed write() doesn’t stop the trigger.
For details, see
Writes to explicit schema databases.
For bulk import, declare every column in the file before you import.
See Import data.
Declared columns written with the wrong type
A line that writes a declared column with the wrong type is a type error, not
an undeclared-column error, so declaring more columns doesn’t fix it.
The following results come from requests with accept_partial=false and one
line per request.
Each request returns HTTP status 400.
A declared tag written as a field (t,region=west host="x") returns the
following message:
invalid column type for column 'host', expected iox::column_type::tag, got iox::column_type::field::stringA declared field written as a tag (t,host=a,usage=5 …) returns the
following message:
invalid column type for column 'usage', expected iox::column_type::field::float, got iox::column_type::tagA declared field written with the wrong type (usage="str" for a float64
field) returns the following message:
invalid column type for column 'usage', expected iox::column_type::field::float, got iox::column_type::field::stringEvolve a declared schema
Add tag and field columns to a declared table with the
influxdb3 update table command
or a PATCH request to /api/v3/configure/table:
influxdb3 update table \
--database DATABASE_NAME \
--token AUTH_TOKEN \
--tags rack \
--fields temp:float64 \
TABLE_NAMEor
curl --request PATCH "http://localhost:8181/api/v3/configure/table" \
--header "Authorization: Bearer AUTH_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"db": "DATABASE_NAME",
"table": "TABLE_NAME",
"tags": ["rack"],
"fields": [{"name": "temp", "type": "float64"}]
}'The write that was rejected above is accepted once you declare the column. Schema evolution is add-only: you can declare new columns, but you can’t remove, rename, or retype an existing one. Declaring a column that already exists at its declared type is a no-op. Declaring it at a different type returns an error.
Use PATCH, not PUT, to add columns
PUT /api/v3/configure/table updates only a table’s retention period.
The endpoint ignores a tags or fields array in the request body and still returns HTTP status 200.
In an explicit database, the next write that references the undeclared column fails with a 400 error.
For more information, see Add columns to a table.
Propagation across a cluster
In a multi-node cluster, nodes poll the object store for catalog updates at
--catalog-sync-interval
(default 1s).
Enforcement reads each node’s local view of the catalog.
That view can lag the node that accepted a declaration by about one
interval, and sometimes longer.
A client that declares a column on one node and immediately writes it to a different node can have that write rejected. The reason is that the second node hasn’t yet seen the declaration. The rejection is a per-line error the client can retry. It self-corrects once the writing node’s catalog catches up.
A node’s catalog advances synchronously with a declaration made on that node. That’s why a column declared on the same node you write to is never rejected this way.
Add-only evolution is what keeps this safe. A lagging node’s view of a declared column is never ahead of the catalog, only behind. As a result, the lag can reject a write that should have been accepted. It can never accept a write that should have been rejected.
Limits
- Explicit schema mode is fixed at creation. No API changes it afterward. An implicit database can’t be made explicit. An explicit database can’t be relaxed to implicit. If you need a different mode, create a new database and migrate your data.
- Deleting and recreating a database resets the mode. The mode belongs
to the database that was created.
As a result, recreating a database without
schema_modecreates an implicit one. - The
_internaldatabase is always implicit and can’t be created, deleted, or patched through the API. - Schema evolution is add-only. Column removal, rename, and type change are out of scope.
- Value-level validation, per-column permissions, and retention rules are separate concerns and aren’t part of schema mode.
Upgrade considerations
Explicit schema mode requires your cluster to have already committed to 3.12
Creating an explicit database requires the cluster’s catalog to already be at the catalog feature level that ships with 3.12. The catalog commits that level automatically once every node in the cluster is running 3.12. No explicit database has to exist for this to happen. Once every node has started on 3.12 and the cluster has committed to that level, no node in the cluster can roll back to a 3.11.x binary, whether or not you ever create an explicit database.
By the time you can create an explicit database, your cluster has therefore already lost 3.11.x rollback compatibility. For more information about catalog version constraints during an upgrade, see Upgrade InfluxDB 3 Enterprise.
Was this page helpful?
Thank you for your feedback!
Support and feedback
Thank you for being part of our community! We welcome and encourage your feedback and bug reports for InfluxDB 3 Enterprise and this documentation. To find support, use the following resources:
Customers with an annual or support contract can contact InfluxData Support. Customers using a trial license can email trial@influxdata.com for assistance.