From 97c4a8df6d9fb900029a9e442468271fc5fe4f62 Mon Sep 17 00:00:00 2001 From: Cuong Le Date: Mon, 27 Apr 2026 21:45:21 +0700 Subject: [PATCH 1/3] lib/logstorage: fix topk mixed time comparison --- lib/logstorage/pipe_sort_topk.go | 2 +- lib/logstorage/pipe_sort_topk_test.go | 58 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) 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..3a6edfc17a 100644 --- a/lib/logstorage/pipe_sort_topk_test.go +++ b/lib/logstorage/pipe_sort_topk_test.go @@ -4,6 +4,64 @@ 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 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() From 66a9c512e6e4dbae939f02052aca45f766e0fe53 Mon Sep 17 00:00:00 2001 From: Cuong Le Date: Mon, 27 Apr 2026 22:32:10 +0700 Subject: [PATCH 2/3] docs: update changelog for sort limit fix --- docs/victorialogs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/victorialogs/CHANGELOG.md b/docs/victorialogs/CHANGELOG.md index b6ada3a195..83dc4783cc 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: [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). From 1e3a4a018a9dc12ae207d01a08af0ebf46f6b635 Mon Sep 17 00:00:00 2001 From: Cuong Le Date: Thu, 14 May 2026 22:16:32 +0700 Subject: [PATCH 3/3] lib/logstorage: cover topk time comparisons --- lib/logstorage/pipe_sort_topk_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/logstorage/pipe_sort_topk_test.go b/lib/logstorage/pipe_sort_topk_test.go index 3a6edfc17a..4bfc6ce428 100644 --- a/lib/logstorage/pipe_sort_topk_test.go +++ b/lib/logstorage/pipe_sort_topk_test.go @@ -49,6 +49,14 @@ func TestTopkLess(t *testing.T) { } } + // 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)