UPDATE
UPDATE shapes. The planner picks a strategy based on whether the predicate matches the table’s merge_keys. Keyed predicates route through the write log; non-keyed predicates bypass it (WAL-bypass risk — two safe patterns documented below). For the RPC contract and full enum reference, see the Execute hub.
Keyed updates
PK-matched predicates → write-log route → compactor MERGE on next drain. Safe under concurrent writes; visible immediately under freshness=STRONG.
KEYED_UPDATE — point UPDATE on PK
UPDATE events SET event_type = 'click_pricing'
WHERE id = 1 AND user_id = 'u-101';Response: strategy: WRITE_STRATEGY_KEYED_UPDATE, rowsWritten: "1", pendingDrain: true.
KEYED_RANGE — UPDATE … WHERE pk IN (range)
UPDATE events SET event_type = 'archived'
WHERE id BETWEEN 1000 AND 2000 AND user_id = 'u-101';K3 enumerates the affected keys via SELECT pk FROM events WHERE ... then writes one log entry per key.
Response: strategy: WRITE_STRATEGY_KEYED_RANGE. If the range resolved to zero keys, noop: true.
KEYED_FROM_SUBQUERY — UPDATE … WHERE pk IN (SELECT …)
-- Mark all events for inactive users
UPDATE events SET event_type = 'archived'
WHERE (id, user_id) IN (
SELECT e.id, e.user_id
FROM events e JOIN users u ON e.user_id = u.id
WHERE u.status = 'inactive'
);Response: strategy: WRITE_STRATEGY_KEYED_FROM_SUBQUERY.
Non-keyed updates — ⚠️ WAL-bypass
NON_KEYED_UPDATE
Predicate doesn’t match merge_keys → planner routes directly to Delta, bypasses write log.
-- WHERE column is NOT in merge_keys=[id, user_id]
UPDATE events SET event_type = 'archived'
WHERE occurred_at < TIMESTAMP '2025-01-01 00:00:00';Response includes a warning:
{
"write": {
"rowsWritten": "12000",
"strategy": "WRITE_STRATEGY_NON_KEYED_UPDATE",
"pendingDrain": false,
"targetTable": "events",
"warnings": [
"Routed to warehouse only; bypasses write log. Risk: un-drained log entries for affected rows may overwrite this change on next drain."
]
}
}Two safe patterns before running non-keyed writes on tables that take concurrent keyed writes:
-
Run
Compactfirst to fully drain the log:# Drain until truncated=false, then update directly dodil k3 table compact events --bucket kb-prod # Then issue the non-keyed UPDATE -
Or materialize the affected PKs into a subquery so the planner picks
KEYED_FROM_SUBQUERYinstead:UPDATE events SET event_type = 'archived' WHERE (id, user_id) IN ( SELECT id, user_id FROM events WHERE occurred_at < TIMESTAMP '2025-01-01 00:00:00' );
See also
- Execute — Overview — RPC contract, enums, refusals,
noopsemantics - SELECT · INSERT · DELETE · MERGE — sibling SQL shapes
- Data → Update — typed shortcut (same keyed-vs-non-keyed routing)
- Maintenance → Compact — drain the write log before non-keyed updates
- Tables → DescribeTable — post-drain row classification (
last_drain_*) - Core Concepts → WriteStrategy — every routing variant