diff --git a/docs/victorialogs/CHANGELOG.md b/docs/victorialogs/CHANGELOG.md index fbce3f0e7c..a4a95143a0 100644 --- a/docs/victorialogs/CHANGELOG.md +++ b/docs/victorialogs/CHANGELOG.md @@ -26,6 +26,7 @@ according to the following docs: * FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaLogs/blob/master/deployment/docker/rules): add new alerting rules `PersistentQueueRunsOutOfSpaceIn12Hours` and `PersistentQueueRunsOutOfSpaceIn4Hours` for `vlagent` persistent queue capacity. These alerts help users to take proactive actions before `vlagent` starts dropping logs due to insufficient persistent queue space. See [#10193](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10193) * FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): remove the `Date format` setting and always display timestamps with nanosecond precision. See [#1161](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1161). +* BUGFIX: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): fix [`sort by (_time) limit N`](https://docs.victoriametrics.com/victorialogs/logsql/#sort-pipe) returning logs out of order when the query pipeline included pipes like [`unpack_json`](https://docs.victoriametrics.com/victorialogs/logsql/#unpack_json-pipe) that overwrite `_time`. See [#1360](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1360). * BUGFIX: [vlagent](https://docs.victoriametrics.com/victorialogs/vlagent/): hide sensitive values passed via `-remoteWrite.proxyURL` in `/metrics`, `/flags`, and startup logs. Previously these values could be exposed in plain text. See [#1320](https://github.com/VictoriaMetrics/VictoriaLogs/pull/1320). * BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): sanitize markdown URLs in logs rendered with `markdown parsing` enabled, allowing only `http`, `https`, `mailto`, and `tel` schemes for active links and images. See [#1313](https://github.com/VictoriaMetrics/VictoriaLogs/pull/1313). * BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): improve context view highlight visibility in dark theme. The selected log entry is now highlighted with a more visible blue tint instead of barely visible gray background. See [#1196](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1196). diff --git a/lib/logstorage/pipe_sort_topk.go b/lib/logstorage/pipe_sort_topk.go index c36abfc9e8..23d6ce379d 100644 --- a/lib/logstorage/pipe_sort_topk.go +++ b/lib/logstorage/pipe_sort_topk.go @@ -679,7 +679,7 @@ func topkLess(ps *pipeSort, a, b *pipeTopkRow) bool { bb.B = marshalTimestampRFC3339NanoString(bb.B[:0], a.timestamp) vA = bytesutil.ToUnsafeString(bb.B) } else if isTimeB[i] { - bb.B = marshalTimestampRFC3339NanoString(bb.B[:0], a.timestamp) + bb.B = marshalTimestampRFC3339NanoString(bb.B[:0], b.timestamp) vB = bytesutil.ToUnsafeString(bb.B) } diff --git a/lib/logstorage/pipe_sort_topk_test.go b/lib/logstorage/pipe_sort_topk_test.go index e7a1cacd53..4bfc6ce428 100644 --- a/lib/logstorage/pipe_sort_topk_test.go +++ b/lib/logstorage/pipe_sort_topk_test.go @@ -4,6 +4,72 @@ import ( "testing" ) +func TestTopkLess(t *testing.T) { + parseTimestamp := func(s string) int64 { + t.Helper() + + timestamp, ok := TryParseTimestampRFC3339Nano(s) + if !ok { + t.Fatalf("cannot parse timestamp %q", s) + } + return timestamp + } + + ps := &pipeSort{ + byFields: []*bySortField{ + {name: "_time"}, + }, + } + psDesc := &pipeSort{ + byFields: []*bySortField{ + {name: "_time"}, + }, + isDesc: true, + } + + stringRow := func(s string) *pipeTopkRow { + return &pipeTopkRow{ + byColumns: []string{s}, + byColumnsIsTime: []bool{false}, + } + } + timeRow := func(s string) *pipeTopkRow { + return &pipeTopkRow{ + byColumns: []string{""}, + byColumnsIsTime: []bool{true}, + timestamp: parseTimestamp(s), + } + } + f := func(ps *pipeSort, a, b *pipeTopkRow, resultExpected bool) { + t.Helper() + + result := topkLess(ps, a, b) + if result != resultExpected { + t.Fatalf("unexpected result for topkLess(%#v, %#v); got %v; want %v", a, b, result, resultExpected) + } + } + + // string time vs string time + f(ps, stringRow("2026-04-25T10:00:54Z"), stringRow("2026-04-25T10:01:54Z"), true) + f(ps, stringRow("2026-04-25T10:01:54Z"), stringRow("2026-04-25T10:00:54Z"), false) + + // real time vs real time + f(ps, timeRow("2026-04-25T10:00:54Z"), timeRow("2026-04-25T10:01:54Z"), true) + f(ps, timeRow("2026-04-25T10:01:54Z"), timeRow("2026-04-25T10:00:54Z"), false) + + // string time is smaller than real time + f(ps, stringRow("2026-04-25T10:00:54Z"), timeRow("2026-04-25T10:01:54Z"), true) + f(ps, timeRow("2026-04-25T10:01:54Z"), stringRow("2026-04-25T10:00:54Z"), false) + + // real time is smaller than string time + f(ps, timeRow("2026-04-25T10:00:54Z"), stringRow("2026-04-25T10:01:54Z"), true) + f(ps, stringRow("2026-04-25T10:01:54Z"), timeRow("2026-04-25T10:00:54Z"), false) + + // string time vs real time with descending sort + f(psDesc, stringRow("2026-04-25T10:00:54Z"), timeRow("2026-04-25T10:01:54Z"), false) + f(psDesc, timeRow("2026-04-25T10:01:54Z"), stringRow("2026-04-25T10:00:54Z"), true) +} + func TestLessString(t *testing.T) { f := func(a, b string, resultExpected bool) { t.Helper()