> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aiaxoniq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Platform architecture

> The components between your exporter and the dashboard, why the platform uses four different stores, and which of them answers each question you ask.

You do not need this page to use aiAxonIQ. You need it when the product does
something that only makes sense once you know the shape underneath — a record
that is accepted but not yet searchable, a metric that survives past the day
its logs were deleted, a dashboard that is a second behind a live tail.

Each of those is a consequence of the design below, not a fault.

<Info>
  **For the short version**, read [How aiAxonIQ works](/get-started/how-it-works).
  This page is the same path with the reasoning left in.
</Info>

## The path your data takes

<Steps>
  <Step title="Your exporter sends OTLP">
    An OpenTelemetry SDK, a Collector, a Prometheus server or the zero-code
    agent. All four speak standard protocols; none of them is aiAxonIQ-specific
    software. See [Ingest endpoints](/send-data/endpoints).
  </Step>

  <Step title="The receiver authenticates and stamps the record">
    It validates your license key, applies rate limits, and attaches the
    organization the key belongs to. Then it acknowledges the request.
  </Step>

  <Step title="A streaming buffer holds the batch">
    Acknowledgement happens here, before storage. This is what keeps ingest
    available when a storage node is slow, and it is why `202 Accepted` means
    *accepted*, not *stored*.
  </Step>

  <Step title="Workers batch and write">
    A worker per signal reads the buffer and writes to the analytical store,
    flushing at 50,000 rows or five seconds, whichever comes first.
  </Step>

  <Step title="You query">
    The dashboard and the API read from whichever store answers the question,
    with a short response cache in front.
  </Step>
</Steps>

## Why acknowledgement comes before storage

The alternative — hold your request open until the row is committed — sounds
safer and is worse. It couples your application's latency to the platform's
write path, so a slow compaction becomes your outage. It also makes every
exporter retry a duplicate.

<Note>
  **The practical consequence: a five-second floor between sending and seeing.**
  On a busy account the row-count flush wins and it is much faster. On a quiet
  new account nothing else is arriving, so you wait out the five-second timer.
  That is why the [Quickstart](/get-started/quickstart) tells you to wait ten
  seconds before looking, and why "I sent it and it is not there" is almost
  never a real problem in the first ten seconds.
</Note>

## Four stores, four jobs

The platform does not use one database, because the four questions you ask have
genuinely different shapes. Which store answers a question determines how fast
it is, how far back it goes, and what syntax it accepts.

| Store                         | Answers                                                              | Why not one of the others                                                                                                                              |
| :---------------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Columnar analytical store** | "What was the p99 across 40 million spans last week?"                | Aggregation over columns at scale. A search index would be an order of magnitude slower and far larger.                                                |
| **Full-text search index**    | "Which log lines mention this stack frame?"                          | Relevance ranking, highlighting and fuzzy matching. The columnar store can substring-match, but it cannot rank.                                        |
| **Relational control plane**  | "Who is in this organization, and what are their alert rules?"       | Transactions and foreign keys. This is configuration, not telemetry — it is small, it is edited by humans, and it must never be eventually consistent. |
| **In-memory cache**           | "The same dashboard panel, again, for the fourth viewer this minute" | Repeated identical reads. Cached 10–300 seconds depending on the panel.                                                                                |

<Warning>
  **Logs are written to two stores, and the two are not identical.**

  Every log record goes to the columnar store *and* the full-text index. Field
  filters and time-range queries are served by the first; free-text relevance
  and fuzzy matching by the second. They are populated by the same worker but
  they are separate writes, so during a burst one can be marginally ahead of
  the other.

  If a field filter finds a record that a free-text search does not, wait a few
  seconds rather than concluding the record is malformed. See
  [Searching logs](/guides/logs/search).
</Warning>

## Rollups: why old metrics survive and old logs do not

As metrics are written, the analytical store continuously maintains
pre-aggregated one-minute and one-hour tables. A dashboard asking for a month
of data reads the hourly table — thousands of rows instead of hundreds of
millions.

This is invisible until it isn't:

* **A wide time range is served at coarser resolution.** A 30-day chart cannot
  show a 15-second spike, because the row it reads is an hour wide.
* **A record just written is in the raw table before it is in the rollup.** A
  fresh record can therefore be genuinely absent from a "last 24 hours" view
  while being present on "last 15 minutes". This is the single most common
  reason a first-install record appears to be missing.
* **Metrics outlive logs.** Raw telemetry is deleted after 30 days; the hourly
  metric rollup is kept for a year. See [Data retention](/concepts/retention).

## Tenancy is applied on the way in

Your organization's identifier is attached by the receiver, derived from the
license key. It is never read from the payload, so an exporter cannot claim to
be someone else by setting an attribute — and equally, you cannot route data to
a different organization by editing your resource attributes. The key decides.

Every query then filters on it. See
[Organizations and multi-tenancy](/concepts/organizations).

## The AI service

Anomaly detection, forecasting, natural-language query, log clustering and
retrieval-augmented answers run in a separate service. It is never reachable
from the internet and never from your network: the only caller is the aiAxonIQ
API, on your behalf, for your organization. See [AI features](/guides/ai/overview).

## What this means for you

<CardGroup cols={2}>
  <Card title="Accepted is not queryable" icon="clock" href="/get-started/verify-data">
    The five-second flush, and the three places to look before concluding data
    is lost.
  </Card>

  <Card title="Retention differs by signal" icon="calendar" href="/concepts/retention">
    30 days raw, 90 days at minute resolution, a year at hour resolution.
  </Card>

  <Card title="Your data is isolated by key" icon="lock" href="/concepts/organizations">
    What an organization is, and what crosses between them.
  </Card>

  <Card title="Nothing here is proprietary" icon="telescope" href="/send-data/otel/collector">
    Standard OTLP in, standard OTLP everywhere. Point the same exporter
    elsewhere and it works.
  </Card>
</CardGroup>
