Mistral AI upsert timeout during high throughput data ingestion

Problem – Mistral AI Upsert Timeout During High‑Throughput Data Ingestion

In a production deployment of Mistral AI on AWS EC2 (c5.4xlarge, 8 vCPU, 32 GB RAM) the /v1/ingest/upsert endpoint began raising MistralUpsertTimeoutError: Upsert operation did not complete within 30 seconds. The failure manifested as:

  • Batch ingest jobs stalling after ~10 k records/second.
  • Training pipelines downstream receiving incomplete data sets.
  • Server logs containing ERROR: canceling statement due to statement timeout (SQLSTATE 57014).
  • Client‑side SDK logs showing repeated retries and back‑off spikes.

The symptom pattern matches several real incidents documented in the evidence package, notably the c5.4xlarge deployment that experienced connection‑pool exhaustion and the Auto Scaling group spike that triggered PostgreSQL statement timeouts.

Root Cause – Interaction Between Mistral’s Request Timeout, PostgreSQL Settings, and Connection Pooling

Mistral AI’s Data Ingestion API defaults to a 30 s request_timeout (see Mistral AI Documentation – “Data Ingestion API Reference”). The server forwards each upsert to PostgreSQL using a prepared statement with the database‑level statement_timeout (default 20 s in many RDS configurations). When ingestion rates exceed the capacity of the configured connection pool (default size 50), the following chain occurs:

  1. Connection pool exhaustion: New upsert requests wait for a free connection. The wait time adds to the overall request latency.
  2. Statement timeout firing: Once a connection is obtained, PostgreSQL may already be under load. Long‑running upserts exceed the statement_timeout, causing the backend to abort the statement and emit ERROR: canceling statement due to statement timeout (SQLSTATE 57014).
  3. Client‑side timeout: The aborted statement propagates back to the Mistral service, which then exceeds its own 30 s request_timeout and raises MistralUpsertTimeoutError to the SDK caller.

Additional contributors observed in the community:

  • Autovacuum pauses increasing lock contention (GitHub issue #1175).
  • Network jitter causing occasional TCP retransmissions that add latency (real incident logs).
  • Insufficient max_locks_per_transaction leading to lock wait timeouts (Stack Overflow answer).

Debug – Systematic Investigation Steps

1. Capture Server and Database Logs


# Mistral server log snippet (timestamp omitted for brevity)
2024-06-12 14:23:07,842 ERROR [upsert-worker-12] UpsertTimeoutError: Upsert operation did not complete within 30 seconds
2024-06-12 14:23:07,845 DEBUG [db-connector] Executing SQL: INSERT INTO ingestion (id, payload) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET payload = EXCLUDED.payload;

# PostgreSQL log (RDS parameter group)
2024-06-12 14:23:07.842 UTC [12345] LOG:  statement: INSERT INTO ingestion (id, payload) VALUES (...)
2024-06-12 14:23:07.842 UTC [12345] ERROR:  canceling statement due to statement timeout
2024-06-12 14:23:07.842 UTC [12345] CONTEXT:  PL/pgSQL function upsert_ingestion()

2. Verify Connection Pool Metrics


# Query pgbouncer (if used) for pool status
psql -h db-proxy.example.com -p 6432 -U admin -c "SHOW POOLS;"

Expected output when exhausted:


+----------------+------+--------+-----------+--------+----------+-----------+
| database       | user | cl_active | cl_waiting | sv_active | sv_idle | sv_used |
+----------------+------+--------+-----------+--------+----------+-----------+
| mistral_db     | app  | 50      | 120       | 50      | 0        | 50        |
+----------------+------+--------+-----------+--------+----------+-----------+

3. Inspect PostgreSQL Timeout Settings


# Show current statement_timeout
psql -U admin -d mistral_db -c "SHOW statement_timeout;"

Typical output:


 statement_timeout 
-------------------
 20000
(1 row)

4. Measure Network Latency


# Simple TCP ping to RDS endpoint
nc -vz -w 2 rds-instance.abcdefg.us-east-1.rds.amazonaws.com 5432

Sample output indicating occasional retransmissions:


Connection to rds-instance.abcdefg.us-east-1.rds.amazonaws.com 5432 port [tcp/postgresql] succeeded!

Collect tcpdump for a 30‑second window during a spike and look for retransmission flags (R).

5. Review Mistral SDK Timeout Configuration


from mistral_sdk import MistralClient

client = MistralClient(
    endpoint="https://mistral.example.com",
    request_timeout=30,   # seconds, default
    retry_policy={"max_retries": 3, "backoff_factor": 2}
)

Solution – Tuning Both Mistral and PostgreSQL for High‑Throughput Ingestion

1. Increase PostgreSQL Statement Timeout and Lock Resources

Parameter Before After
statement_timeout 20000 ms 60000 ms
max_locks_per_transaction 64 128
max_connections 100 200

# RDS parameter group modification (via AWS CLI)
aws rds modify-db-parameter-group \
    --db-parameter-group-name mistral-db-pg \
    --parameters "ParameterName=statement_timeout,ParameterValue=60000,ApplyMethod=immediate" \
                "ParameterName=max_locks_per_transaction,ParameterValue=128,ApplyMethod=immediate" \
                "ParameterName=max_connections,ParameterValue=200,ApplyMethod=immediate"

2. Deploy PgBouncer as a Transaction‑Pooling Proxy

PgBouncer reduces the number of persistent TCP connections to PostgreSQL and reuses them across short‑lived upserts.


# Example pgbouncer.ini excerpt
[databases]
mistral_db = host=postgres.internal port=5432 dbname=mistral_db pool_size=200 reserve_pool=20

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 500
default_pool_size = 100
reserve_pool_size = 20

3. Adjust Mistral’s Request Timeout and Enable Keep‑Alive


# Update Mistral server config (mistral.yaml)
ingestion:
  upsert:
    request_timeout: 60   # seconds, increased from 30
    keep_alive: true
    keep_alive_interval: 15
    retry_policy:
      max_retries: 5
      backoff_factor: 1.5

Restart the Mistral service after the change:


systemctl restart mistral-server

4. Switch to Bulk Insert with Asynchronous Workers

Instead of issuing a single upsert per record, batch 500‑1000 rows into a temporary table and perform a single INSERT … ON CONFLICT statement. This reduces transaction overhead and lock contention.


# Python worker example
from mistral_sdk import MistralClient
import asyncio

client = MistralClient(endpoint="https://mistral.example.com", request_timeout=60)

async def batch_upsert(records):
    await client.ingest.bulk_upsert(
        table="ingestion",
        rows=records,
        conflict_target="id",
        update_columns=["payload"]
    )

# Dispatcher
async def main():
    batch = []
    async for record in source_stream():
        batch.append(record)
        if len(batch) >= 800:
            await batch_upsert(batch)
            batch.clear()
    if batch:
        await batch_upsert(batch)

asyncio.run(main())

Verification – Confirming the Fix

1. Monitor Request Latency


# Grafana query (Prometheus metric)
histogram_quantile(0.95, sum(rate(mistral_upsert_duration_seconds_bucket[5m])) by (le))

Expect 95th‑percentile latency < 30 s after changes.

2. Validate PostgreSQL Logs No Longer Show Timeouts


# Tail recent logs
journalctl -u postgresql -f | grep "statement timeout"

Output should be empty for the ingestion window.

3. Check PgBouncer Pool Utilization


psql -h localhost -p 6432 -U admin -c "SHOW POOLS;"

Expected output:


+----------------+------+--------+-----------+--------+----------+-----------+
| database       | user | cl_active | cl_waiting | sv_active | sv_idle | sv_used |
+----------------+------+--------+-----------+--------+----------+-----------+
| mistral_db     | app  | 120      | 0          | 100      | 20      | 100       |
+----------------+------+--------+-----------+--------+----------+-----------+

4. Run End‑to‑End Ingestion Test


# Simulate 15 k records/second for 2 minutes
python load_generator.py --rate 15000 --duration 120

Observe no MistralUpsertTimeoutError in the client logs and confirm that downstream training jobs receive the full dataset.

Prevention – Operational Guardrails for Future Scale‑Ups

  • Capacity Planning: Align EC2 instance size, PgBouncer pool size, and PostgreSQL max_connections with the expected peak ingest rate (e.g., 1 connection per 200 records/second).
  • Monitoring & Alerting: Set alerts on:
    • PostgreSQL statement_timeout occurrences.
    • PgBouncer cl_waiting > 10% of cl_active.
    • Mistral upsert latency > 25 s.
  • Scheduled Maintenance: Run autovacuum outside peak ingestion windows; tune autovacuum_vacuum_cost_delay to avoid pauses.
  • Network Stability: Deploy EC2 instances and RDS in the same AZ and enable Enhanced Networking to reduce jitter.
  • Retry Policy Review: Use exponential back‑off with jitter to prevent thundering‑herd retries during spikes.

Related Topic Hub: LLM Systems Troubleshooting Hub

FAQ

  1. Why does increasing request_timeout alone not fix the issue? The client timeout only masks the underlying database statement timeout and connection‑pool exhaustion. Without addressing PostgreSQL limits, upserts will still be cancelled, leading to data loss or duplicate retries.
  2. Can I rely on the default PostgreSQL max_connections of 100? Not for high‑throughput ingestion. Each Mistral worker thread may open a connection; with 10 k records/second you quickly exceed 100 connections, causing pool exhaustion. Increase max_connections and use PgBouncer to multiplex.
  3. What is the impact of autovacuum on upserts? Autovacuum can acquire heavyweight locks on the target table, causing upserts to wait and eventually hit the statement timeout. Schedule autovacuum during off‑peak windows or lower autovacuum_vacuum_cost_delay to smooth its impact.
  4. Is bulk insert always safe for upserts? Bulk insert reduces per‑row overhead but you must ensure the ON CONFLICT clause correctly resolves duplicates. Test the bulk path with a representative data sample before production rollout.
  5. How do I verify that network jitter is not contributing? Capture tcpdump on both the EC2 and RDS side during a spike; look for retransmission flags (R) or high RTT values. If jitter is significant, consider placing the instances in a placement group or using enhanced networking.