"Ontology" is back in fashion. Palantir Foundry is not pitched as yet another data warehouse — it builds an ontology for the enterprise: tables, streams, and sensors become "Customer," "Order," and "Device," with relationships and actions defined so data becomes a world model programs can reason over. LLMs made the pitch louder — models still need to know what exists in the world.
For our field: what is the APM ontology? And if you build it, does incident triage just work? Two separate questions.
Q1 · How to build an APM ontology
An APM ontology is two steps: extract entities and relationships from traces, then materialize them into metric tables.
Setup: service A has instances A-1 and A-2; two endpoints; both call database DB and run the same SQL1. Four traces:
| # | service | instance | endpoint | call | sql |
|---|---|---|---|---|---|
| T1 | A | A-1 | ep1 | DB | SQL1 |
| T2 | A | A-1 | ep2 | DB | SQL1 |
| T3 | A | A-2 | ep1 | DB | SQL1 |
| T4 | A | A-2 | ep2 | DB | SQL1 |
Step 1 — extract. From T1: entities are service A, instance A-1, endpoint ep1, database DB, SQL1. Relationships: A has instance A-1; A exposes ep1; A-1 serves requests on ep1; ep1 calls DB; ep1 executes SQL1; DB has statement SQL1. P99, latency, and call count are not entities — they are observations attached to entities.
All four traces together:
- Service hasInstance Instance: A → A-1, A-2
- Service exposes Endpoint: A → ep1, ep2
- Instance serves request on Endpoint: A-1 / A-2 both serve ep1 and ep2
- Endpoint calls DB: ep1 → DB; ep2 → DB
- Endpoint executes SQL: ep1 → SQL1; ep2 → SQL1
- DB hasStatement SQL: DB → SQL1
Step 2 — materialize. Extracted entities become queryable metric tables. In DataBuff's Doris layer (schema is open source), dimension columns are entities; call_count / resp_time are observations:
| Table | Dimensions | Observations | Entities |
|---|---|---|---|
| metric_service | service | call_count / resp_time / error_count | Service |
| metric_service_instance | service + instance | same | Service, Instance |
| metric_service_http | service + instance + endpoint | same | Service, Instance, Endpoint |
| metric_service_db | service + instance + db + sql | db calls / db latency | Service, Instance, DB, SQL |
Aggregating the four traces:
- metric_service: service A → 1 row, 4 entry calls
- metric_service_instance: by instance → 2 rows
- metric_service_http: by instance + endpoint → 4 rows
- metric_service_db: by instance + DB + SQL → 2 rows
Ontology done: traces tell you what exists and what connects; metric tables materialize those entities.
Q2 · Is ontology enough?
Entities and relationships complete — is troubleshooting solved? Consider how a connection pool actually behaves:
Even if you add ConnectionPool as Service → ConnectionPool → Database, you only label the big boxes. The branches and states in between cannot be covered by adding one more entity — that layer is logic:
a) Acquire: idle → return; can grow → create; pool full → lock wait; timeout → error
b) Use / return: run SQL → return → wake waiters
c) Background maintenance: heartbeat, evict stale connections
Entity-only reasoning fails when an endpoint slows down:
| Ontology only | With logic | |
|---|---|---|
| Reasoning | endpoint → DB, slow ⇒ DB slow | stuck in lock wait, SQL never ran |
| Symptom | endpoint latency up | DB execution may be fine |
| Conclusion | root cause is DB | root cause is waiting for a connection |
First principles: program = data + logic.
| Ontology | Logic | |
|---|---|---|
| Manages | what exists, who connects | how requests flow, where they stall |
| Locks onto | target entities | last-mile root cause |
| Without it | no drill-down target | stuck at "something is wrong" |
Connection pool is one slice — it shows entity-only is not enough. See how DataBuff breaks down latency for one endpoint:
Entry average ~240ms; breakdown shows HTTP service-b ~100ms, RPC service-b ~80ms, remainder in MySQL, ES, Redis, Kafka. That is logic modeling for one entry — track entry latency and state, then each operation type after the entry.
Q3 · How to model logic
Scale to a real app: track latency and state for every entry, and for each operation type after every entry. Entries include RPC, HTTP, MQ consumers, scheduled jobs, and more.
After a request enters, it typically lands in 5 operation types + 1 behavior:
| Type | Key dimensions |
|---|---|
| DB remote operation | dal group / table / operation / sql |
| Redis remote operation | command |
| MQ remote operation | exchange / routingKey / vhost |
| RPC remote operation | downstream service / remote method |
| Local operation | no extra attributes yet |
| Thrown exception | exception name |
For remote operations, split latency into three parts:
a) Client: connect, send, receive
b) Network: transport
c) Server: peer execution
Pool-full lock wait accrues on the client — waiting for a connection ≠ slow SQL.
Incidents then follow one chain: which entries → local ops → remote ops (down to SQL/method) → network vs server → exceptions.
Ontology locks entities; logic locks root cause. Logic models entry → operation → remote three-part latency → exception, with latency and state at each layer. For AI to separate "waiting for a connection" from "slow SQL," it must land on entities via ontology, then walk this logic chain to the actual reason.


Top comments (0)