Skip to main content

Asynchronous history

A batch write sent with Prefer: respond-async gets its answer as soon as the current entities are written. The history of those writes is recorded in the background. Reads don't notice the difference. Queued history shows up straight away, and in the right order.

The specification doesn't define this preference for batches. It is Andromeda's own. A client that never sends it gets every write exactly as the specification describes.

What respond-async changes​

POST /ngsi-ld/v1/entityOperations/update
Content-Type: application/json
Prefer: respond-async

[
{
"id": "urn:ngsi-ld:Room:1",
"type": "Room",
"temperature": {
"type": "Property",
"value": 21
}
}
]
HTTP/1.1 204 No Content
Preference-Applied: respond-async

With the preference, the batch is answered once the current entities are written. The history the batch produces is queued and recorded in the background. The response says so with Preference-Applied: respond-async.

Without the preference, the history is recorded in the same transaction as the write, before you get the response.

A write to a Snapshot always records its history straight away, whatever the preference says.

What you can rely on​

Reads see queued history at once. A temporal read returns queued history together with recorded history, including the entity's types, attributes, deletions and Scope. No read waits for the queue. A temporal query gives the same answer before and after the queue is applied.

History keeps commit order. An entity's queued history is applied in the order its writes committed, however many brokers share the database. A deletion always ends the instance an earlier write created. The history's modifiedAt is its latest change. An instanceId you chose holds the value of the last write that stated it.

A later write waits its turn. If you write synchronously to an entity that still has history in the queue, its history goes into the queue behind what is already waiting. That keeps the order. You don't see this happen, and entities with nothing queued are recorded immediately.

A failure stops only the entity it belongs to. If applying one entity's history fails, that entity's later history waits behind it until a retry succeeds. Other entities carry on. Delete Temporal Evolution still works on such an entity. It deletes the whole history, the queued part included, and with it whatever was waiting.

Notifications aren't dropped either. A worker raises the batch's notifications after the answer. If that fails, it retries, waiting up to entity.side_effects.queue.max_retry_interval between attempts, for as long as it takes. From entity.side_effects.stuck_after_attempts failed attempts on, each failure is logged as a warning and counted as stuck (see Metrics).

Temporal API changes wait for queued history​

Three Temporal API operations change part of the recorded history and keep the rest:

  • Delete Attribute from Temporal Evolution
  • Modify Attribute Instance
  • Delete Attribute Instance

Before one of them runs, Andromeda applies the entity's history that was queued when the request arrived. Anything queued after that counts as a concurrent write, and it is ordered after the change.

The wait covers applying that history as well, and it lasts the shorter of temporal.history.queue.lease_timeout and half of request_limits.timeout. With the defaults, that is 15 seconds.

If the history still isn't all applied when the wait runs out, you get 503 with the ServiceUnavailable problem type and a Retry-After header. Another broker may still hold part of it, or there may be more queued than one wait can apply. Whatever was applied stays applied, so a retry after that delay carries on from where the last attempt stopped.

If the queued history itself fails to apply, you get that failure. The answer is 503 when the datastore can't be reached, and 500 otherwise.

Delete Temporal Evolution is different, because it keeps nothing of the history, so it doesn't wait. It deletes the entity's queued history in the same transaction as the recorded history, without applying it first. Everything the entity's writes queued before the deletion goes with it. A write that commits after the deletion has its history recorded after it. An entity whose history is still entirely in the queue has a Temporal Evolution all the same, as reads already show, and deleting it answers 204.

The work doesn't disappear with the preference. It moves. A batch answers faster, and the Temporal API operation that reads the history later pays for applying it.

Configuration​

Where Andromeda interprets the specification​

The specification doesn't define Prefer: respond-async for batch operations. It uses Prefer for other things, such as the specification version and body=json, but not for this. Answering early is Andromeda's own behaviour. It is built on the generic preference mechanism, and a client that never sends the preference gets exactly the synchronous behaviour the specification describes. Commit order keeps the guarantees the specification gives instance identifiers, deletions and modifiedAt. The 503 with ServiceUnavailable follows the specification's error table, which admits the HTTP binding's own status codes.