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 entitiesSource AVehicle 1, all attributesSource BVehicle 2, all attributesSplit entitiesSource AVehicle 1: speedSource BVehicle 1: serviceDue
- parts of entities, assembled into one answer
- AndromedaWhole: filters run at the sources. Split: filters run here
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 entities | Split entities | |
|---|---|---|
| Filters run at | The sources | The broker, after assembling |
| What sources send | Only the matches | Every candidate |
federation.query.max_entities counts | Matches | Candidates |
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.
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
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
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.