Skip to content

Commit 872ebd9

Browse files
Merge branch 'main' into feature/tracegen-file-export
2 parents bbb5126 + 63b27e1 commit 872ebd9

File tree

13 files changed

+252
-14
lines changed

13 files changed

+252
-14
lines changed

.github/actions/block-pr-from-main-branch/action.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ runs:
1212
echo "Branch: ${{ github.event.pull_request.head.ref }}"
1313
1414
if [ "${{ github.event.pull_request.head.repo.fork }}" == "true" ] && [ "${{ github.event.pull_request.head.ref }}" == 'main' ]; then
15-
echo "PRs from the main branch of forked repositories are not allowed."
15+
echo "Error 🛑: PRs from the main branch of forked repositories are not allowed."
16+
echo " Please create a named branch and resubmit the PR."
17+
echo " See https://github.com/jaegertracing/jaeger/blob/main/CONTRIBUTING_GUIDELINES.md#branches"
1618
exit 1
1719
fi

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,58 @@ copy from UI changelog
2121

2222
</details>
2323

24+
v1.76.0 / v2.13.0 (2025-12-03)
25+
-------------------------------
26+
27+
### Backend Changes
28+
29+
#### 🐞 Bug fixes, Minor Improvements
30+
31+
* Fix: register basicauth extension in component factory ([@xenonnn4w](https://github.com/xenonnn4w) in [#7668](https://github.com/jaegertracing/jaeger/pull/7668))
32+
33+
#### 👷 CI Improvements
34+
35+
* Make error message better ([@yurishkuro](https://github.com/yurishkuro) in [#7675](https://github.com/jaegertracing/jaeger/pull/7675))
36+
* Clean go cache after installing gotip as suggested. ([@Kavish-12345](https://github.com/Kavish-12345) in [#7666](https://github.com/jaegertracing/jaeger/pull/7666))
37+
* Fix: build test tools with stable go, not gotip ([@Kavish-12345](https://github.com/Kavish-12345) in [#7665](https://github.com/jaegertracing/jaeger/pull/7665))
38+
39+
### 📊 UI Changes
40+
41+
#### 🐞 Bug fixes, Minor Improvements
42+
43+
* Add support for custom ui configuration in development mode ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3194](https://github.com/jaegertracing/jaeger-ui/pull/3194))
44+
* Remove duplicate antd dependencies ([@yurishkuro](https://github.com/yurishkuro) in [#3193](https://github.com/jaegertracing/jaeger-ui/pull/3193))
45+
* Fix css class typo in sidepanel details div ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3190](https://github.com/jaegertracing/jaeger-ui/pull/3190))
46+
* Reduce search form field margins for better viewport fit ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3189](https://github.com/jaegertracing/jaeger-ui/pull/3189))
47+
* Migrate deepdependencies/header and qualitymetrics/header from nameselector to searchableselect ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3185](https://github.com/jaegertracing/jaeger-ui/pull/3185))
48+
* Reorder checkbox before color by dropdown in tracestatisticsheader ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3184](https://github.com/jaegertracing/jaeger-ui/pull/3184))
49+
* Feat: add fuzzy search to searchableselect ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3182](https://github.com/jaegertracing/jaeger-ui/pull/3182))
50+
* Fix highlighting of the current tab in the main nav bar ([@SimonADW](https://github.com/SimonADW) in [#3183](https://github.com/jaegertracing/jaeger-ui/pull/3183))
51+
52+
#### 🚧 Experimental Features
53+
54+
* Sync themes with antd ([@yurishkuro](https://github.com/yurishkuro) in [#3196](https://github.com/jaegertracing/jaeger-ui/pull/3196))
55+
* Add dark theme selector ([@yurishkuro](https://github.com/yurishkuro) in [#3192](https://github.com/jaegertracing/jaeger-ui/pull/3192))
56+
57+
#### 👷 CI Improvements
58+
59+
* Add copyright year linter to npm lint command ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3197](https://github.com/jaegertracing/jaeger-ui/pull/3197))
60+
* Rename theme variables to match industry practice ([@yurishkuro](https://github.com/yurishkuro) in [#3174](https://github.com/jaegertracing/jaeger-ui/pull/3174))
61+
* Tweak codecov config ([@yurishkuro](https://github.com/yurishkuro) in [#3169](https://github.com/jaegertracing/jaeger-ui/pull/3169))
62+
63+
#### ⚙️ Refactoring
64+
65+
* Apply theme vars to common/emphasizednode ([@yurishkuro](https://github.com/yurishkuro) in [#3191](https://github.com/jaegertracing/jaeger-ui/pull/3191))
66+
* Fix ddg minimap border ([@yurishkuro](https://github.com/yurishkuro) in [#3188](https://github.com/jaegertracing/jaeger-ui/pull/3188))
67+
* Use token vars in common/utils.css ([@yurishkuro](https://github.com/yurishkuro) in [#3187](https://github.com/jaegertracing/jaeger-ui/pull/3187))
68+
* Apply theme vars to some shared components ([@yurishkuro](https://github.com/yurishkuro) in [#3181](https://github.com/jaegertracing/jaeger-ui/pull/3181))
69+
* Apply theme vars to search page ([@yurishkuro](https://github.com/yurishkuro) in [#3180](https://github.com/jaegertracing/jaeger-ui/pull/3180))
70+
* Use theme vars in errormessage & loadingindicator ([@yurishkuro](https://github.com/yurishkuro) in [#3177](https://github.com/jaegertracing/jaeger-ui/pull/3177))
71+
* Use theme vars in main page and topnav ([@yurishkuro](https://github.com/yurishkuro) in [#3176](https://github.com/jaegertracing/jaeger-ui/pull/3176))
72+
* Convert last remaining js files to typescript (excluding tests) ([@yurishkuro](https://github.com/yurishkuro) in [#3173](https://github.com/jaegertracing/jaeger-ui/pull/3173))
73+
* Convert some easy files to typescript ([@yurishkuro](https://github.com/yurishkuro) in [#3167](https://github.com/jaegertracing/jaeger-ui/pull/3167))
74+
75+
2476
v1.75.0 / v2.12.0 (2025-11-18)
2577
-------------------------------
2678

CONTRIBUTING_GUIDELINES.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ and open a pull request (PR).
3737
We do not assign issues to contributors. It is almost never the case that multiple
3838
people jump on the same issue, and practice showed that occasionally people who ask
3939
for an issue to be assigned to them later have a change in priorities and are unable
40-
to find time to finish it, which leaves the issue in limbo.
40+
to find time to finish it, which leaves the issue in limbo.
4141
So if you have a desire to work on an issue, feel free to mention it in the comment and just submit a PR.
4242

4343
### Creating a pull request
@@ -49,7 +49,7 @@ If you are new to GitHub's contribution workflow, we recommend the following set
4949
* After you clone your forked repo, running below command
5050
```bash
5151
git remote -v
52-
```
52+
```
5353
will show `origin`, e.g. `origin [email protected]:{username}/jaeger.git`
5454
* Add `upstream` remote:
5555
```bash
@@ -59,7 +59,7 @@ If you are new to GitHub's contribution workflow, we recommend the following set
5959
```bash
6060
git fetch upstream main
6161
```
62-
* Repoint your main branch:
62+
* Repoint your main branch:
6363
```bash
6464
git branch --set-upstream-to=upstream/main main
6565
```
@@ -70,7 +70,7 @@ Once you're ready to make changes:
7070
* Commit your changes, making sure **each commit is signed** ([see below](#certificate-of-origin---sign-your-work)):
7171
```bash
7272
git commit -s -m "Your commit message"
73-
```
73+
```
7474
* You do not need to squash the commits, it will happen once the PR is merged into the official repo (but each individual commit must be signed).
7575
* When satisfied, push the changes. Git will likely ask for upstream destination, so you push commits like this:
7676
```bash
@@ -182,5 +182,4 @@ git push --force
182182
183183
## Branches
184184
185-
Upstream repository should contain only maintenance branches (e.g. `release-1.0`). For feature
186-
branches use forked repository.
185+
Before submitting a PR make sure to create a named branch in your forked repository. Our CI will fail if you submit a PR from the `main` branch. If that happens, just create a new branch and re-submit the PR from that branch.

RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ Here are the release managers for future versions with the tentative release dat
8888
8989
| Version | Release Manager | Tentative release date |
9090
|---------|-----------------|------------------------|
91-
| 2.13.0 | @joe-elliott | 3 December 2025 |
9291
| 2.14.0 | @mahadzaryab1 | 7 January 2026 |
9392
| 2.15.0 | @jkowall | 4 February 2026 |
9493
| 2.16.0 | @yurishkuro | 5 March 2026 |
9594
| 2.17.0 | @albertteoh | 1 April 2026 |
9695
| 2.18.0 | @pavolloffay | 6 May 2026 |
96+
| 2.19.0 | @joe-elliott | 3 June 2026 |

cmd/jaeger/internal/components.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector"
88
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter"
99
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter"
10+
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension"
1011
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension"
1112
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension"
1213
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension"
@@ -76,6 +77,7 @@ func (b builders) build() (otelcol.Factories, error) {
7677
zpagesextension.NewFactory(),
7778

7879
// add-ons
80+
basicauthextension.NewFactory(),
7981
sigv4authextension.NewFactory(),
8082
jaegerquery.NewFactory(),
8183
jaegerstorage.NewFactory(),

cmd/jaeger/internal/integration/e2e_integration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/stretchr/testify/require"
1818
"go.uber.org/zap"
1919
"go.uber.org/zap/zaptest"
20-
"gopkg.in/yaml.v3"
20+
"go.yaml.in/yaml/v3"
2121

2222
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/integration/storagecleaner"
2323
"github.com/jaegertracing/jaeger/internal/storage/integration"
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Cassandra FindTraceIDs Duration Query Behavior
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Context
8+
9+
The Cassandra spanstore implementation in Jaeger handles trace queries with duration filters (DurationMin/DurationMax) through a separate code path that cannot efficiently intersect with other query parameters like tags or general operation name filters. This behavior differs from other storage backends like Badger and may seem counterintuitive to users.
10+
11+
### Data Model and Cassandra Constraints
12+
13+
Cassandra's data model imposes specific constraints on query patterns. The `duration_index` table is defined with the following schema structure (as referenced in the CQL insertion query in [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go)):
14+
15+
```cql
16+
INSERT INTO duration_index(service_name, operation_name, bucket, duration, start_time, trace_id)
17+
VALUES (?, ?, ?, ?, ?, ?)
18+
```
19+
20+
This schema uses a composite partition key consisting of `service_name`, `operation_name`, and `bucket` (an hourly time bucket), with `duration` as a clustering column. In Cassandra, **partition keys require equality constraints** in WHERE clauses - you cannot perform range queries or arbitrary intersections across different partition keys efficiently.
21+
22+
### Duration Index Structure
23+
24+
The duration index is bucketed by hour to limit partition size and improve query performance. From [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go) (line 57):
25+
26+
```go
27+
durationBucketSize = time.Hour
28+
```
29+
30+
When a span is indexed, its start time is rounded to the nearest hour bucket (line 231 in writer.go):
31+
32+
```go
33+
timeBucket := startTime.Round(durationBucketSize)
34+
```
35+
36+
The indexing function in `indexByDuration` (lines 229-243) creates two index entries per span:
37+
1. One indexed by service name alone (with empty operation name)
38+
2. One indexed by both service name and operation name
39+
40+
```go
41+
indexByOperationName("") // index by service name alone
42+
indexByOperationName(span.OperationName) // index by service name and operation name
43+
```
44+
45+
### Query Path Implementation
46+
47+
In [`internal/storage/v1/cassandra/spanstore/reader.go`](../../internal/storage/v1/cassandra/spanstore/reader.go), the `findTraceIDs` method (lines 275-301) performs an early return when duration parameters are present:
48+
49+
```go
50+
func (s *SpanReader) findTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {
51+
if traceQuery.DurationMin != 0 || traceQuery.DurationMax != 0 {
52+
return s.queryByDuration(ctx, traceQuery)
53+
}
54+
// ... other query paths
55+
}
56+
```
57+
58+
This early return means that when a duration query is detected, **all other query parameters except ServiceName and OperationName are effectively ignored** (tags, for instance, are not processed).
59+
60+
The `queryByDuration` method (lines 333-375) iterates over hourly buckets within the query time range and issues a Cassandra query for each bucket:
61+
62+
```go
63+
startTimeByHour := traceQuery.StartTimeMin.Round(durationBucketSize)
64+
endTimeByHour := traceQuery.StartTimeMax.Round(durationBucketSize)
65+
66+
for timeBucket := endTimeByHour; timeBucket.After(startTimeByHour) || timeBucket.Equal(startTimeByHour); timeBucket = timeBucket.Add(-1 * durationBucketSize) {
67+
query := s.session.Query(
68+
queryByDuration,
69+
timeBucket,
70+
traceQuery.ServiceName,
71+
traceQuery.OperationName,
72+
minDurationMicros,
73+
maxDurationMicros,
74+
traceQuery.NumTraces*limitMultiple)
75+
// execute query...
76+
}
77+
```
78+
79+
Each query specifies exact values for `bucket`, `service_name`, and `operation_name` (the partition key components), along with a range filter on `duration` (the clustering column). The query definition (lines 51-55) is:
80+
81+
```cql
82+
SELECT trace_id
83+
FROM duration_index
84+
WHERE bucket = ? AND service_name = ? AND operation_name = ? AND duration > ? AND duration < ?
85+
LIMIT ?
86+
```
87+
88+
### Why Not Intersect with Other Indices?
89+
90+
Unlike storage backends such as Badger (which can perform hash-joins and arbitrary index intersections), Cassandra's partition-based architecture makes cross-index intersections expensive and impractical:
91+
92+
1. **Partition key constraints**: The duration index requires equality on `(service_name, operation_name, bucket)`. You cannot efficiently query across multiple operations or join with the tag index without scanning many partitions.
93+
94+
2. **No server-side joins**: Cassandra does not support server-side joins. To intersect duration results with tag results, the client would need to:
95+
- Query the duration index for all matching trace IDs
96+
- Query the tag index for all matching trace IDs
97+
- Perform a client-side intersection
98+
99+
This would be inefficient for large result sets and would require fetching potentially many trace IDs over the network.
100+
101+
3. **Hourly bucket iteration**: The duration query already iterates over hourly buckets. Adding tag intersections would multiply the number of queries and result sets to merge.
102+
103+
### Comparison with Badger
104+
105+
The Badger storage backend handles duration queries differently. In [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) (around line 486), the `FindTraceIDs` method performs duration queries and then uses the results as a filter (`hashOuter`) that can be intersected with other index results:
106+
107+
```go
108+
if query.DurationMax != 0 || query.DurationMin != 0 {
109+
plan.hashOuter = r.durationQueries(plan, query)
110+
}
111+
```
112+
113+
Badger uses an embedded key-value store where range scans and in-memory filtering are efficient, allowing it to merge results from multiple indices. This is a fundamental difference from Cassandra's distributed, partition-oriented design.
114+
115+
## Decision
116+
117+
**The Cassandra spanstore will continue to treat duration queries as a separate query path that does not intersect with tag indices or other non-service/operation filters.**
118+
119+
When a `TraceQueryParameters` contains `DurationMin` or `DurationMax`:
120+
- The query will use the `duration_index` table exclusively
121+
- Only `ServiceName` and `OperationName` parameters will be respected (used as partition key components)
122+
- Tag filters and other parameters will be ignored
123+
- The code will iterate over hourly time buckets within the query time range
124+
125+
This approach is documented in code comments and in this ADR to set proper expectations.
126+
127+
## Consequences
128+
129+
### Positive
130+
131+
1. **Performance**: Duration queries execute efficiently by scanning only relevant Cassandra partitions (scoped to service, operation, and hourly bucket).
132+
2. **Scalability**: The bucketed partition strategy prevents hot partitions and distributes load across the cluster.
133+
3. **Simplicity**: The implementation is straightforward and leverages Cassandra's strengths (partition-scoped queries with range filtering on clustering columns).
134+
135+
### Negative
136+
137+
1. **Limited query expressiveness**: Users cannot combine duration filters with tag filters in a single query. They must choose one or the other.
138+
2. **Expectation mismatch**: Users familiar with other backends (like Badger) may expect duration and tags to be combinable.
139+
3. **Workarounds required**: Applications that need both duration and tag filtering must:
140+
- Issue separate queries (one with duration, one with tags)
141+
- Perform client-side intersection of results
142+
- Or use a different storage backend that supports combined queries
143+
144+
### Guidance for Users
145+
146+
- **When using Cassandra spanstore**: Be aware that specifying `DurationMin` or `DurationMax` will cause tag filters to be ignored. Validate that `ErrDurationAndTagQueryNotSupported` is returned if both are specified (enforced in `validateQuery` at line 227-229 in reader.go).
147+
148+
- **For combined filtering needs**: Consider using the Badger backend, or implement client-side filtering by:
149+
1. Querying with duration filters to get a candidate set of trace IDs
150+
2. Fetching those traces
151+
3. Filtering the results by tag values in your application code
152+
153+
- **Query design**: Structure queries to leverage the indices available. Use `ServiceName` and `OperationName` in conjunction with duration queries for best results.
154+
155+
## References
156+
157+
- Implementation files:
158+
- [`internal/storage/v1/cassandra/spanstore/reader.go`](../../internal/storage/v1/cassandra/spanstore/reader.go) - Query logic and duration query path
159+
- [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go) - Duration index schema and insertion logic
160+
- [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) - Badger implementation for comparison
161+
162+
- Cassandra documentation:
163+
- [Cassandra Data Modeling](https://cassandra.apache.org/doc/latest/data_modeling/index.html)
164+
- [CQL Partition Keys and Clustering Columns](https://cassandra.apache.org/doc/latest/cql/ddl.html#partition-key)
165+
166+
- Related code:
167+
- `durationIndex` constant (writer.go line 47-50): CQL insert statement
168+
- `queryByDuration` constant (reader.go line 51-55): CQL select statement
169+
- `durationBucketSize` constant (writer.go line 57): Hourly bucketing
170+
- Error `ErrDurationAndTagQueryNotSupported` (reader.go line 77): Validation that prevents combining duration and tag queries

docs/adr/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Architecture Decision Records (ADRs)
2+
3+
This directory contains Architecture Decision Records (ADRs) for the Jaeger project. ADRs document important architectural decisions made during the development of Jaeger, including the context, decision, and consequences of each choice.
4+
5+
## What is an ADR?
6+
7+
An Architecture Decision Record (ADR) is a document that captures an important architectural decision made along with its context and consequences. ADRs help teams understand why certain decisions were made and provide historical context for future contributors.
8+
9+
## ADRs in This Repository
10+
11+
- [Cassandra FindTraceIDs Duration Query Behavior](cassandra-find-traces-duration.md) - Explains why duration queries in the Cassandra spanstore use a separate code path and cannot be efficiently combined with other query parameters.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module github.com/jaegertracing/jaeger
22

33
go 1.24.6
44

5-
toolchain go1.25.3
5+
toolchain go1.25.4
66

77
require (
88
github.com/ClickHouse/ch-go v0.69.0

internal/storage/v1/cassandra/spanstore/reader.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ func (s *SpanReader) FindTraceIDs(ctx context.Context, traceQuery *spanstore.Tra
273273
}
274274

275275
func (s *SpanReader) findTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {
276+
// See docs/adr/cassandra-find-traces-duration.md for rationale: duration queries use the duration_index
277+
// and are handled as a separate path. Other query parameters (like tags) are ignored when duration is specified.
276278
if traceQuery.DurationMin != 0 || traceQuery.DurationMax != 0 {
277279
return s.queryByDuration(ctx, traceQuery)
278280
}

0 commit comments

Comments
 (0)