Skip to content

opentelemetry-ecto: Creating spans after-the-fact causes misattributed child spans (e.g. opentelemetry-sqlcommenter IDs) #532

@ivucica

Description

@ivucica

Describe the bug

In opentelemetry-ecto:

Because Ecto emits telemetry events only after queries have finished, OpentelemetryEcto estimates the start time of the span by subtracting the reported total duration from the current timestamp.

opentelemetry-sqlcommenter attaches SQLCommenter-style annotations to generated queries, resembling this:

SELECT 
  f0."id", f0."user_id", f0."filter_id", f0."hide", f0."whole_word", f0."phrase", f0."context",
  f0."expires_at", f0."inserted_at", f0."updated_at" 
FROM 
  "filters" AS f0
WHERE 
  ((f0."expires_at" IS NULL) OR (f0."expires_at" > $1)) AND (f0."hide")
  AND
  (f0."user_id" = $2)
ORDER BY
  f0."id" DESC/*traceparent='00-adcd837ab305e206fc7bf5fc3395ca95-d503af1be7dc8fbc-01'*/

Per opentelemetry-sqlcommenter's instructions, it's hooked into a Ecto in lib/pleroma/repo.ex inside defmodule Pleroma.Repo do ... end block as follows:

defdelegate prepare_query(operation, query, opts), to: OpentelemetrySqlcommenter

prepare_query (or prepare_query_sampled) set comment: comment, but also prepare: :unnamed, the latter seemingly a trick to disable prepared statements, so that the preparation will happen every time, resulting in the delegate being called.

Neat trick, but it has a flaw: it happens while Ecto is making the query, meaning when the delegated prepare_query invokes OpenTelemetry.Tracer.current_span_ctx(), it's receiving the parent context -- or frequently none at all. (I'm not an Elixir or Erlang developer, so this is a lot of tapping in the dark. Please correct me if I am wrong.)

This seems to be the cause of queries sent by pg_tracing being detached from spans created by opentelemetry-ecto (sometimes with no parent span -- with pg_tracing just using part of the trace ID instead -- and sometimes with parent-of-ecto-span as parent).

Image

I previously thought it was a pg_tracing bug: DataDog/pg_tracing#84 but I think the issues I saw there are all coming from opentelemetry-ecto faking the start event.

opentelemetry-sqlcommenter seems to be doing the right thing: getting the span context, and using it to build the comment, so the issue with the wrong parent seems to be elsewhere.

(Possibly related: #425)

Expected behavior
Correct span ID should be known at the time the query is being executed, so the correct parent span ID can be sent into the database.

Some thoughts?

  1. Would Ecto / ecto-sql itself have to be patched to correctly start and end spans? Or can the fix live fully outside of that core?
  2. Am I setting things up incorrectly or missing something? Should I be using something else to create the spans?
  3. Would some integration between the opentelemetry-sqlcommenter's hack and opentelemetry-ecto make sense, e.g. starting the span during query prep (giving up on "end time, minus telemetry-observed total duration" trick to compute an accurate start_time) make sense (with fallback to that kind of start_time if need be)?
  4. If it does make sense, what would be the best way in Elixir to store the fact that a span has already been started? A thread-local variable, presumably? (A global variable with mutex guarding it is, I expect, not a very Erlang-like approach.)

Additional context
I do not actually know Elixir or Erlang, neither the languages nor the wider ecosystem. I am not familiar with Ecto, ecto-sql, or OpenTelemetry implementation in Elixir or Erlang. I am very unfamiliar with how threading or global vars (such as a tracing context) work in Erlang and Elixir.

Honestly, I was mainly trying to figure out the causes of performance issues I have with Pleroma (a fediverse server, implementing a federated social network over HTTP) and with queries it's sending into PostgreSQL.

In my setup, for each request I propagate a single trace:

  1. starting in nginx (creates trace ID and a span),
  2. getting into Pleroma in Phoenix (receives the trace ID and parent span ID, creates a span) which
  3. during the handling of a route causes many Ecto queries to PostgreSQL
  4. these get annotated with traceparent comment as an SQLCommenter-style comment
  5. this is used by the pg_tracing extension installed into PostgreSQL
  6. both Pleroma and PostgreSQL have been configured to talk to local otelcol-contrib, which currently forwards this to Google Cloud Trace

I haven't set up Oban telemetry or anything else I might be missing. Some processing happens without HTTP requests as well.

I'm also running this on top of whatever's in my Debian-based install -- apparently, "Elixir 1.14.0 (compiled with Erlang/OTP 24)". This is mostly Pleroma v2.9.1 with these OpenTelemetry-related changes installed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions