Troubleshooting¶
Common issues¶
You may be experiencing one of the issues below.
Rows accepted over QWP but not visible to queries¶
flush(wait=True) (and QuestDB.dataframe) confirm the server accepted the rows; query
visibility follows once the WAL is applied, which is asynchronous. Poll for
the condition you need with a bounded timeout instead of asserting
immediately after the flush.
Where did my QWP server rejection go?¶
Over QWP/WebSocket a server rejection (schema mismatch, parse error, …)
can arrive after flush() already returned. Every rejection is pushed to
the error_handler passed to questdb.connect(); without one
it is logged through the questdb Python logger — ERROR for terminal
rejections, WARNING for retriable ones (the queue replays those). Make
sure Python logging is configured to show the questdb logger, or
register a handler. Terminal rejections additionally raise
QuestDBServerRejectionError
from the next call on the affected sender.
Production-optimized QuestDB configuration¶
If you can’t initially see your data through a select SQL query straight
away, this is normal: by default the database will only commit data it receives
though the line protocol periodically to maximize throughput.
For dev/testing you may want to tune the following database configuration parameters as so:
# server.conf
cairo.max.uncommitted.rows=1
line.tcp.maintenance.job.interval=100
The default QuestDB configuration is more applicable for a production environment.
For these and more configuration parameters refer to database configuration documentation.
Infrequent Flushing¶
You may not see data appear in a timely manner because you’re not calling
flush often enough.
You might be having issues with the Sender’s
auto-flush feature.
Errors during flushing¶
Decimal Column Errors¶
If you’re trying to ingest decimal data and encountering errors, check the following:
Pre-create table: Unlike other column types, DECIMAL columns cannot
be auto-created. You must create the table with DECIMAL(precision, scale)
columns before sending data:
CREATE TABLE my_table (
symbol SYMBOL,
price DECIMAL(18, 6),
timestamp TIMESTAMP_NS
) TIMESTAMP(timestamp) PARTITION BY DAY;
Protocol version mismatch: Decimal support requires protocol version 3, which is only available on QuestDB server 9.2.0 or later.
For HTTP/HTTPS: Protocol version 3 is auto-negotiated. Ensure your server is version 9.2.0 or later.
For TCP/TCPS: You must explicitly configure
protocol_version=3in your configuration string:tcp::addr=localhost:9009;protocol_version=3;
Precision/scale mismatch: Ensure the precision and scale of your Python
decimal.Decimal or PyArrow decimal values match the table definition.
For example, if the table has DECIMAL(12, 6), values with more than 6
decimal places or more than 12 total digits will cause errors.
For more details on decimal types, see the QuestDB DECIMAL documentation.
UUID, IPV4, BINARY, CHAR, DATE, LONG256 and GEOHASH Column Errors¶
These seven column types need a QWP sender and QuestDB server 10.0.0 or later, the first production QWP implementation. If a value of one of them is rejected, check the following:
Sender protocol: they are QWP-only. Configure udp::, ws:: or
wss::; the legacy ILP transports (http, https, tcp,
tcps) have no such column types and reject the values rather than
silently widening them.
GEOHASH precision: the scalar and bulk paths deliberately have different value contracts.
Geohashis strict: it requires1 <= precision <= 60and0 <= bits < 2**precision.Sender.rowalso pins the first GEOHASH precision used for a column within one buffer’s worth of rows. A later cell at a different precision is rejected and that row is rewound. A flush clears the client-side pin; it does not prove that the precision agrees with an existing server column.Bulk ingestion, from NumPy, Arrow and object columns alike, validates the declared precision and carrier width, but treats every value as a raw bit pattern. It does not check that the pattern has no bits set above the declared precision. Thus
32underGEOHASH(5b)is accepted even though it needs six bits. If the declared precision is wrong for the data, the write can succeed and store a different GEOHASH location orNULL. Only the lowceil(precision / 8)bytes are encoded, and the exact result of an inconsistent value is unspecified.
This unchecked bulk-value rule is a semantic data-integrity risk, not a memory-safety risk for otherwise valid NumPy/Arrow buffers. Validate bulk values before sending them. Malformed custom Arrow C Data structures remain invalid input and are outside this guarantee.
Fixed widths: a 'uuid' claim needs every non-null cell to be
exactly 16 bytes and a 'long256' claim exactly 32. An object column of
Python ints under 'long256' must satisfy 0 <= value < 2**256, and
one under 'ipv4' must fit uint32.
Integer columns need naming: IPV4, CHAR and GEOHASH ride on
integers, which say nothing about which type they are. Name them with
schema_overrides, or let df.attrs['questdb'] name them on a frame
that came out of QuestDB.query. Without
either, the column lands as a plain integer.
See UUID, IPV4, BINARY, CHAR, DATE, LONG256 and GEOHASH Columns for how each type is written.
ILP/TCP Server disconnects¶
If you’re using TCP instead of HTTP, you may see a server disconnect after flushing.
If the server receives invalid data over ILP/TCP it will drop the connection.
The ILP/TCP protocol does not send errors back to the client. Instead, by design, it will disconnect a client if it encounters any insertion errors. This is to avoid errors going unnoticed.
As an example, if a client were to insert a STRING value into a BOOLEAN
column, the QuestDB server would disconnect the client.
To determine the root cause of a disconnect, inspect the server logs.
Note
For a better developer experience consider using HTTP instead of TCP.
Logging outgoing messages¶
To understand what data was sent to the server, you may log outgoing messages from Python.
Over the text-based ILP transports, bytes(sender) exposes the pending
payload (QWP transports encode at flush time, so it is empty there).
Here’s an example if you append rows to the Sender object:
import logging
import textwrap
with Sender.from_conf(...) as sender:
# sender.row(...)
# sender.row(...)
# ...
pending = bytes(sender).decode('utf-8', errors='replace')
logging.info('About to flush:\n%s', textwrap.indent(pending, ' '))
sender.flush()
Alternatively, if you’re constructing buffers explicitly through the
legacy questdb.ingress shim:
import logging
import textwrap
buffer = sender.new_buffer()
# buffer.row(...)
# buffer.row(...)
# ...
pending = bytes(buffer).decode('utf-8', errors='replace')
logging.info('About to flush:\n%s', textwrap.indent(pending, ' '))
sender.flush(buffer)
Note that to handle out-of-order messages efficiently, the QuestDB server will delay applying changes it receives over ILP after a configurable commit lag.
Due to this commit lag, the line that caused the error may not be the last line.
Asking for help¶
The best way to get help is through our Community Forum.