Metricbeat SQL Module with cursor rereading rows

Hello!

I think I may have hit a bug where Metricbeat SQL module's cursor state can be read from the wrong Elastic Agent component data path, causing cursor reset and duplicate ingestion.

This is on Elastic Agent / Metricbeat 9.4.2.

This was first observed through a custom integration package, but the package only renders a normal Elastic Agent sql/metrics input using the upstream Metricbeat SQL cursor config.
The input has a stable cursor.state_id, stable query text, and stable cursor column/direction.

What happens:

  1. SQL cursor runs normally and advances.
  2. I change an unrelated metric integration in the same Agent policy.
  3. After the policy reload, the SQL cursor sometimes starts from its default again and rereads rows.

In the logs I can see SQL cursor registries being created under different component paths. One registry was created under the expected sql/metrics-default path:

Jun 24, 2026 @ 18:39:08.034 component.id=sql/metrics-default
Created shared SQL cursor registry at /opt/Elastic/Agent/data/elastic-agent-9.4.2-dd9ee6/run/sql/metrics-default/sql-cursor (ptr=0x3348fa9e55c0)

Later, another SQL cursor registry was created under the system/metrics-default component path and then used by sql/metrics-default:

Jun 24, 2026 @ 18:40:04.553 component.id=system/metrics-default
Created shared SQL cursor registry at /opt/Elastic/Agent/data/elastic-agent-9.4.2-dd9ee6/run/system/metrics-default/sql-cursor (ptr=0x3348fad47e60)

Jun 24, 2026 @ 18:40:04.553 component.id=sql/metrics-default
Using shared SQL cursor registry at 0x3348fad47e60

For one cursor, the same state_key was already advanced immediately before this happened:

Jun 24, 2026 @ 18:39:10.912 component.id=sql/metrics-default
Cursor fetch completed: state_key=sql-cursor::fba2dd494f9d31a0 cursor_before=0 cursor_after=79 rows=79

After the system/metrics-default registry was used, the same state_key started from 0 again and reread the same rows:

Jun 24, 2026 @ 18:40:04.568 component.id=system/metrics-default
Finished loading transaction log file for '/opt/Elastic/Agent/data/elastic-agent-9.4.2-dd9ee6/run/system/metrics-default/sql-cursor/cursor-state'. Active transaction id=0

Jun 24, 2026 @ 18:40:06.152 component.id=sql/metrics-default
Cursor fetch start: driver=mysql timeout=1m0s state_key=sql-cursor::fba2dd494f9d31a0 cursor=0

Jun 24, 2026 @ 18:40:06.171 component.id=sql/metrics-default
Cursor fetch completed: state_key=sql-cursor::fba2dd494f9d31a0 cursor_before=0 cursor_after=79 rows=79

The interesting part is that this seems limited to the Agent OTel runtime. With this extra Agent config, I have not been able to reproduce the issue so far:

agent.internal:
  runtime:
    metricbeat:
      sql/metrics: process

So my vague guess is that the SQL cursor registry may be using a shared/global path.data value that can point at another Metricbeat component under the OTel runtime.

Does this look like a bug in the Metricbeat SQL cursor implementation, or is there some expected Agent/OTel behavior I am missing?

Hey @lpeter thanks for reporting this!

This seems to be a bug in how the Otel runtime is handling the state store for Metricbeat (maybe for all beats :scream:).

I'll take a look/get someone to take a look at it.

In the mean time, could you share the custom integration you're using? Having the same integration code will be very helpful.

I managed to reproduce it, I'll create a GitHub issue.

Hi @TiagoQueiroz!

Thanks for looking into it so quicky. I see you managed to reproduce it without the custom integration, so probably there's not much use for it, but here it is anyway: GitHub - lpeter91/elastic-sql-cursor-input-integration · GitHub (It's basically a mostly vibe-coded fork of the official SQL input integration, so do whatever you will with it.)

Here's the corresponding GitHub issue for future generations: Metricbeat receivers share the same store and "corrupt" data · Issue #15154 · elastic/elastic-agent · GitHub