-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[refactor] drop ch-go library for write path #7093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7093 +/- ##
==========================================
- Coverage 96.20% 96.15% -0.06%
==========================================
Files 358 358
Lines 21596 21300 -296
==========================================
- Hits 20777 20480 -297
Misses 613 613
- Partials 206 207 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
internal/storage/v2/clickhouse/tracestore/dbmodel/to_dbmodel.go
Outdated
Show resolved
Hide resolved
internal/storage/v2/clickhouse/tracestore/dbmodel/to_dbmodel.go
Outdated
Show resolved
Hide resolved
internal/storage/v2/clickhouse/tracestore/dbmodel/to_dbmodel.go
Outdated
Show resolved
Hide resolved
TraceState: sp.TraceState().AsRaw(), | ||
Name: sp.Name(), | ||
Kind: sp.Kind().String(), | ||
Duration: sp.EndTimestamp().AsTime(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if OTEL captures timestamp, why do we capture duration
in CH?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean we should keep startTime
and endTime
?
internal/storage/v2/clickhouse/tracestore/dbmodel/to_dbmodel.go
Outdated
Show resolved
Hide resolved
92d4fa7
to
32f6317
Compare
The `Value` type here actually refers to the `pdata` data types from the `otel-collector` pipeline. In our architecture, the `value_warpper` is responsible for wrapping the Protobuf-generated Go structures (which are the concrete implementation of `pdata`) into the `Value` type. Although `pdata` itself is based on the OTLP specification, encapsulating it into `Value` via the `value_warpper` creates a higher-level abstraction, which presents some challenges for directly storing `Value` in ClickHouse. Specifically, when deserializing `Slice` and `Map` data contained within the `Value`, the fact that JSON cannot natively distinguish whether a `Number` is an integer (`int`) or a floating-point number (`double`) leads to a loss of type information. Furthermore, directly handling the potentially dynamically nested `pdata` structures within the `Value` can also be quite complex. Therefore, to ensure the accuracy and completeness of data types in ClickHouse, and to effectively handle these nested telemetry data, we need to convert the `pdata` data inside `Value` into the standard `OTLP/JSON` format for storage. | ||
#### Mapping model to DB storage | ||
The table structure is defined as follows: | ||
``` sql |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it in the README? Don't we need this in the code to be executed against CH? The readme can just refer to that code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, moved it to schema.tmpl
and provided a link.
SpanAttributesBoolKey Array(String), | ||
SpanAttributesBoolValue Array(Bool), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: inaccurate naming, since each field is an array they should have plural names, e.g. SpanAttributesBoolKeys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, these names will be constantly going back and forth between server and CH, so maybe keep them shorter: SpanAttrBoolKeys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved.
LinksAttributesBytesValues Array(Array(Array(byte))), | ||
``` | ||
`TraceID` is actually a fixed-length `[16]byte`, and `SpanID` is a fixed-length `[8]byte`. | ||
While it's possible to store them using `Array(byte)`, using the `FixedString(16)` and `FixedString(8)` types can express the data length more precisely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment doesn't reflect the declaration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add more context now.
// Copyright (c) 2025 The Jaeger Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package dbmodel | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// EventsRow is a collection of Trace.Events fields. It maps one-to-one with the fields in the table structure. | ||
// When writing Trace.Events data, it needs to be first converted to the corresponding EventsRow. When reading, it's first mapped to EventsRow | ||
// and then converted back to a collection of Trace.Events. | ||
type EventsRow struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usage case:
writing traces.
batch, _ := conn.PrepareBatch(context.Background(), "INSERT INTO otel_traces")
traces := dbmodel.ToDBModel(SimpleTraces(2))
for _, trace := range traces {
resourceRow := trace.Resource
scopeRow := trace.Scope
spanRow := trace.Span
eventsRow := dbmodel.ToEventsRow(trace.Events)
eventsAllAttrRow := dbmodel.ToAllNestedAttrRow(eventsRow.NestedAttrRow)
linksRow := dbmodel.ToLinksRow(trace.Links)
linksAllAttrRow := dbmodel.ToAllNestedAttrRow(linksRow.NestedAttrRow)
err := batch.Append(
spanRow.Timestamp,
spanRow.TraceId,
spanRow.SpanId,
spanRow.ParentSpanId,
spanRow.TraceState,
spanRow.Name,
spanRow.Kind,
spanRow.Duration,
spanRow.StatusCode,
spanRow.StatusMessage,
spanRow.Attributes.BoolKeys,
spanRow.Attributes.BoolValues,
spanRow.Attributes.DoubleKeys,
spanRow.Attributes.DoubleValues,
spanRow.Attributes.IntKeys,
spanRow.Attributes.IntValues,
spanRow.Attributes.StrKeys,
spanRow.Attributes.StrValues,
spanRow.Attributes.BytesKeys,
spanRow.Attributes.BytesValues,
scopeRow.Name,
scopeRow.Version,
scopeRow.Attributes.BoolKeys,
scopeRow.Attributes.BoolValues,
scopeRow.Attributes.DoubleKeys,
scopeRow.Attributes.DoubleValues,
scopeRow.Attributes.IntKeys,
scopeRow.Attributes.IntValues,
scopeRow.Attributes.StrKeys,
scopeRow.Attributes.StrValues,
scopeRow.Attributes.BytesKeys,
scopeRow.Attributes.BytesValues,
resourceRow.Attributes.BoolKeys,
resourceRow.Attributes.BoolValues,
resourceRow.Attributes.DoubleKeys,
resourceRow.Attributes.DoubleValues,
resourceRow.Attributes.IntKeys,
resourceRow.Attributes.IntValues,
resourceRow.Attributes.StrKeys,
resourceRow.Attributes.StrValues,
resourceRow.Attributes.BytesKeys,
resourceRow.Attributes.BytesValues,
eventsRow.Names,
eventsRow.Timestamps,
eventsAllAttrRow.BoolAttrs,
eventsAllAttrRow.DoubleAttrs,
eventsAllAttrRow.IntAttrs,
eventsAllAttrRow.StrAttrs,
eventsAllAttrRow.BytesAttrs,
linksRow.TraceIds,
linksRow.SpanIds,
linksRow.TraceStates,
linksAllAttrRow.BoolAttrs,
linksAllAttrRow.DoubleAttrs,
linksAllAttrRow.IntAttrs,
linksAllAttrRow.StrAttrs,
linksAllAttrRow.BytesAttrs,
)
if err != nil {
panic(err)
}
}
batch.Send()
reading traces.
rows, err := conn.Query(context.Background(), "SELECT * FROM otel_traces")
if err != nil {
panic(err)
}
var result []dbmodel.Trace
for rows.Next() {
var trace dbmodel.Trace
var eventsName []string
var eventsTimestamp []time.Time
var eventsAllNestedAttrs dbmodel.AllNestedAttrRow
var linksTraceId [][]byte
var linksSpanId [][]byte
var linksTraceStatus []string
var linksAllNestedAttrs dbmodel.AllNestedAttrRow
err = rows.Scan(
&trace.Span.Timestamp,
&trace.Span.TraceId,
&trace.Span.SpanId,
&trace.Span.ParentSpanId,
&trace.Span.TraceState,
&trace.Span.Name,
&trace.Span.Kind,
&trace.Span.Duration,
&trace.Span.StatusCode,
&trace.Span.StatusMessage,
&trace.Span.Attributes.BoolKeys,
&trace.Span.Attributes.BoolValues,
&trace.Span.Attributes.DoubleKeys,
&trace.Span.Attributes.DoubleValues,
&trace.Span.Attributes.IntKeys,
&trace.Span.Attributes.IntValues,
&trace.Span.Attributes.StrKeys,
&trace.Span.Attributes.StrValues,
&trace.Span.Attributes.BytesKeys,
&trace.Span.Attributes.BytesValues,
&trace.Scope.Name,
&trace.Scope.Version,
&trace.Scope.Attributes.BoolKeys,
&trace.Scope.Attributes.BoolValues,
&trace.Scope.Attributes.DoubleKeys,
&trace.Scope.Attributes.DoubleValues,
&trace.Scope.Attributes.IntKeys,
&trace.Scope.Attributes.IntValues,
&trace.Scope.Attributes.StrKeys,
&trace.Scope.Attributes.StrValues,
&trace.Scope.Attributes.BytesKeys,
&trace.Scope.Attributes.BytesValues,
&trace.Resource.Attributes.BoolKeys,
&trace.Resource.Attributes.BoolValues,
&trace.Resource.Attributes.DoubleKeys,
&trace.Resource.Attributes.DoubleValues,
&trace.Resource.Attributes.IntKeys,
&trace.Resource.Attributes.IntValues,
&trace.Resource.Attributes.StrKeys,
&trace.Resource.Attributes.StrValues,
&trace.Resource.Attributes.BytesKeys,
&trace.Resource.Attributes.BytesValues,
&eventsName,
&eventsTimestamp,
&eventsAllNestedAttrs.BoolAttrs,
&eventsAllNestedAttrs.DoubleAttrs,
&eventsAllNestedAttrs.IntAttrs,
&eventsAllNestedAttrs.StrAttrs,
&eventsAllNestedAttrs.BytesAttrs,
&linksTraceId,
&linksSpanId,
&linksTraceStatus,
&linksAllNestedAttrs.BoolAttrs,
&linksAllNestedAttrs.DoubleAttrs,
&linksAllNestedAttrs.IntAttrs,
&linksAllNestedAttrs.StrAttrs,
&linksAllNestedAttrs.BytesAttrs,
)
if err != nil {
panic(err)
}
events := make([]dbmodel.Event, len(eventsName))
eventsAttrRow := dbmodel.FromAllNestedAttrRow(eventsAllNestedAttrs)
for i := 0; i < len(eventsName); i++ {
events[i].Name = eventsName[i]
events[i].Timestamp = eventsTimestamp[i]
events[i].Attributes = dbmodel.FromNestedAttrRow(eventsAttrRow[i])
}
trace.Events = events
links := make([]dbmodel.Link, len(linksTraceId))
linksAttrRow := dbmodel.FromAllNestedAttrRow(linksAllNestedAttrs)
for i := 0; i < len(linksTraceId); i++ {
links[i].TraceId = linksTraceId[i]
links[i].SpanId = linksSpanId[i]
links[i].TraceState = linksTraceStatus[i]
links[i].Attributes = dbmodel.FromNestedAttrRow(linksAttrRow[i])
}
trace.Links = links
result = append(result, trace)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, but what did you try to convey by this comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to demonstrate how the new file row.go
can be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can discard this file; it seems unnecessary this time. This PR is so large.
Events Nested( | ||
Name String, | ||
Timestamp DateTime64(9), | ||
BoolAttrs Nested ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not use the same pattern for top-level attribute fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, Do you mean use Nested struct for resource, scope and span Attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean using Nested for each pair of key/value arrays
|
||
##### Resource,Scope,Span | ||
`TraceID` is actually a fixed-length `[16]byte`, and `SpanID` is a fixed-length `[8]byte`. | ||
While it's possible to store them using `Array(UInt8)`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does this "while" clause refer to? Seems out of place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This README contains too many mistakes; I plan to rewrite it.
a85f961
to
463e884
Compare
Signed-off-by: zhengkezhou1 <[email protected]>
Signed-off-by: zhengkezhou1 <[email protected]>
993f31a
to
9e2b7d4
Compare
Signed-off-by: zhengkezhou1 <[email protected]>
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. You may re-open it if you need more time. |
type Link struct { | ||
TraceId string | ||
SpanId string | ||
ptrace.SpanEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Link
struct incorrectly embeds ptrace.SpanEvent
when it should be using ptrace.SpanLink
or no embedding at all. This is conceptually incorrect as links and events represent different concepts in the OpenTelemetry model - links reference other spans while events are timestamped annotations within a span. This embedding could lead to unexpected behavior or confusion when working with the model.
ptrace.SpanEvent | |
ptrace.SpanLink |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. You may re-open it if you need more time. |
Which problem is this PR solving?
ch-go
library does not support multi-instance writing. This could lead to performance bottlenecks. We have chosen to useclickhouse-go
instead.Description of the changes
DBmodel
instead of converting toproto.input
as required bych-go
.ch-go
.How was this change tested?
Checklist
jaeger
:make lint test
jaeger-ui
:npm run lint
andnpm run test