Skip to main content

Split entities

Every distributed setup answers one question that decides how much work a query costs. Does each source hold whole entities, or can one entity be spread over several sources? The specification calls the second kind split entities.

  • Whole entities

    urn:ngsi-ld:Vehicle:42 lives in exactly one place, with all its attributes. The registrations divide the entities among the sources.

  • Split entities

    The broker holds the name and location of urn:ngsi-ld:Vehicle:42, a fleet system holds its speed, and a maintenance system holds its serviceDue. The registrations divide the attributes of one entity among the sources.

  1. Whole entities
    Source AVehicle 1, all attributes
    Source BVehicle 2, all attributes
    Split entities
    Source AVehicle 1: speed
    Source BVehicle 1: serviceDue
  2. parts of entities, assembled into one answer
  3. AndromedaWhole: filters run at the sources. Split: filters run here
AndromedaA system Andromeda doesn't run

What it changes​

A query filter is a statement about an entity, whether it is q=speed>80, a geoquery on location, a scopeQ or a selection with attrs. When sources hold whole entities, each one can apply the filter to the entities it has and return only the matches. Nothing is lost, and the broker merges short answers.

When an entity is split, no single source can decide. The source that holds speed doesn't hold location, and the entity matches q=speed>80;location... only once the two are put together. So with split entities Andromeda drops those filters from the forwarded request, asks the sources for the broad set, assembles each entity from its parts and applies the filters itself. The answer is just as correct, but more data crosses the network and is read into memory.

Whole entitiesSplit entities
Filters run atThe sourcesThe broker, after assembling
What sources sendOnly the matchesEvery candidate
federation.query.max_entities countsMatchesCandidates

Choosing​

  • Use whole entities whenever you can. Divide the entities among the sources by type, area or scope, rather than dividing the attributes of one entity.
  • Use split entities only when attributes of one entity really do live in different systems and can't be moved.
An entity split behind a whole-entity setting

If the setting says whole entities and an entity is split anyway, a filter that needs attributes from two sources matches nothing. The source with speed and the source with location each see half of the entity and reject it. Nothing marks the answer as wrong, so an unexpectedly empty answer is the only symptom.

Where it is set​

  • federation.query.split_entities

    The default for requests and subscriptions that don't say. "complete", the default, means whole entities, and "split" means entities may be split.

  • The splitEntities request parameter

    A Boolean on a query, a temporal query or a subscription. true means entities may be split for this request, and false means they are whole. It doesn't apply with local=true, which asks only for the broker's own data.

Set the default to what is usual in the deployment, and use the parameter for the exceptions. Clients that never think about it get the cheap behaviour when the data is whole, and a client that knows it has to look across systems says so.

Temporal queries follow the same rule. With splitEntities, the filters are applied after merging, and Andromeda asks each source what it held before the window to find the instance in effect when the window opens. Distributed operations describes the merge.

Example deployment​

One broker, its database, and two sources that each hold part of the same vehicles. The default is "split" here, since the whole point of this deployment is that Vehicle entities are spread over the two sources. fleet-system and maintenance-system stand for your own services, and their image names are placeholders.

Start it​

echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" > .env
docker-compose.yaml
name: andromeda

services:
postgresql:
image: ghcr.io/mskvarc/postgresql:18.6-timescaledb2.31.0-postgis3.6.4-tsl
restart: unless-stopped
environment:
POSTGRES_USER: andromeda
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: andromeda
mem_limit: 4g
cpus: 2
volumes:
- postgresql-data:/var/lib/postgresql

andromeda:
image: ghcr.io/mskvarc/andromeda:1.0.0
restart: unless-stopped
depends_on:
postgresql:
condition: service_healthy
environment:
ANDROMEDA__DATABASE__CONNECTION: postgres://andromeda:${POSTGRES_PASSWORD}@postgresql:5432/andromeda
ANDROMEDA__SERVER__PUBLIC_URL: http://andromeda:1056/
ANDROMEDA__FEDERATION__IDENTITY__SOURCE_ALIAS: andromeda
ANDROMEDA__FEDERATION__QUERY__SPLIT_ENTITIES: split
ANDROMEDA__FEDERATION__COOLDOWN__MODE: exponential
ports:
- "1056:1056"

# Your own services, standing for the two sources. Replace the images.
fleet-system:
image: registry.example.org/fleet-system:1
restart: unless-stopped
expose:
- "8080"

maintenance-system:
image: registry.example.org/maintenance-system:1
restart: unless-stopped
expose:
- "8080"

volumes:
postgresql-data:

Start it with docker compose up -d.

Register the sources​

Register each source for its own attributes of Vehicle.

curl -i http://localhost:1056/ngsi-ld/v1/csourceRegistrations \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:ContextSourceRegistration:fleet-system",
"type": "ContextSourceRegistration",
"information": [
{
"entities": [
{
"type": "Vehicle"
}
],
"propertyNames": [
"speed"
]
}
],
"endpoint": "http://fleet-system:8080/"
}'

curl -i http://localhost:1056/ngsi-ld/v1/csourceRegistrations \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:ContextSourceRegistration:maintenance-system",
"type": "ContextSourceRegistration",
"information": [
{
"entities": [
{
"type": "Vehicle"
}
],
"propertyNames": [
"serviceDue"
]
}
],
"endpoint": "http://maintenance-system:8080/"
}'

Query across both​

A query that needs attributes from both systems is now answered by assembling each vehicle first.

curl -s -G http://localhost:1056/ngsi-ld/v1/entities \
--data-urlencode 'type=Vehicle' \
--data-urlencode 'q=speed>80;serviceDue<2027-01-01T00:00:00Z' \
-H 'Accept: application/ld+json'

A client that knows a request touches only whole entities can send splitEntities=false and let the sources filter.

If it goes wrong​

  • A filter over attributes from two systems returns nothing, and each system has matching data

    The request was treated as whole entities. Set federation.query.split_entities to "split", or send splitEntities=true on that request.

  • Queries are slow, and TooManyResults appears sooner than the matches would explain

    Split handling gathers candidates, and the candidates count against federation.query.max_entities. If the entities are in fact whole, set the default back to "complete" and let the sources filter.

  • orderBy is refused with 400 BadRequestData

    A registration takes part in the answer, and ordering across sources isn't supported. Send local=true to order the broker's own data. Distributed operations explains why.