Repository: VictoriaMetrics/VictoriaMetrics
Pull request #4481 (2023-06-21 07:40:30)

docs/VictoriaLogs: add reference to benchmark setup in readme


Patch:
diff --git a/app/vlinsert/elasticsearch/elasticsearch.go b/app/vlinsert/elasticsearch/elasticsearch.go
index 1a1526420..ed72cd0cf 100644
--- a/app/vlinsert/elasticsearch/elasticsearch.go
+++ b/app/vlinsert/elasticsearch/elasticsearch.go
@@ -9,7 +9,6 @@ import (
 	"net/http"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vlstorage"
@@ -19,11 +18,11 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logjson"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/writeconcurrencylimiter"
 	"github.com/VictoriaMetrics/metrics"
-	"github.com/valyala/fastjson"
 )
 
 var (
@@ -240,28 +239,21 @@ func readBulkLine(sc *bufio.Scanner, timeField, msgField string,
 		return false, fmt.Errorf(`missing log message after the "create" or "index" command`)
 	}
 	line = sc.Bytes()
-	pctx := getParserCtx()
-	if err := pctx.parseLogMessage(line); err != nil {
+	p := logjson.GetParser()
+	if err := p.ParseLogMessage(line); err != nil {
 		return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
 	}
 
-	timestamp, err := extractTimestampFromFields(timeField, pctx.fields)
+	timestamp, err := extractTimestampFromFields(timeField, p.Fields)
 	if err != nil {
 		return false, fmt.Errorf("cannot parse timestamp: %w", err)
 	}
-	updateMessageFieldName(msgField, pctx.fields)
-	processLogMessage(timestamp, pctx.fields)
-	putParserCtx(pctx)
+	updateMessageFieldName(msgField, p.Fields)
+	processLogMessage(timestamp, p.Fields)
+	logjson.PutParser(p)
 	return true, nil
 }
 
-var parserPool fastjson.ParserPool
-
-var (
-	invalidTimestampLogger = logger.WithThrottler("invalidTimestampLogger", 5*time.Second)
-	invalidJSONLineLogger  = logger.WithThrottler("invalidJSONLineLogger", 5*time.Second)
-)
-
 func extractTimestampFromFields(timeField string, fields []logstorage.Field) (int64, error) {
 	for i := range fields {
 		f := &fields[i]
@@ -291,102 +283,6 @@ func updateMessageFieldName(msgField string, fields []logstorage.Field) {
 	}
 }
 
-type parserCtx struct {
-	p         fastjson.Parser
-	buf       []byte
-	prefixBuf []byte
-	fields    []logstorage.Field
-}
-
-func (pctx *parserCtx) reset() {
-	pctx.buf = pctx.buf[:0]
-	pctx.prefixBuf = pctx.prefixBuf[:0]
-
-	fields := pctx.fields
-	for i := range fields {
-		lf := &fields[i]
-		lf.Name = ""
-		lf.Value = ""
-	}
-	pctx.fields = fields[:0]
-}
-
-func getParserCtx() *parserCtx {
-	v := parserCtxPool.Get()
-	if v == nil {
-		return &parserCtx{}
-	}
-	return v.(*parserCtx)
-}
-
-func putParserCtx(pctx *parserCtx) {
-	pctx.reset()
-	parserCtxPool.Put(pctx)
-}
-
-var parserCtxPool sync.Pool
-
-func (pctx *parserCtx) parseLogMessage(msg []byte) error {
-	s := bytesutil.ToUnsafeString(msg)
-	v, err := pctx.p.Parse(s)
-	if err != nil {
-		return fmt.Errorf("cannot parse json: %w", err)
-	}
-	if t := v.Type(); t != fastjson.TypeObject {
-		return fmt.Errorf("expecting json dictionary; got %s", t)
-	}
-	pctx.reset()
-	pctx.fields, pctx.buf, pctx.prefixBuf = appendLogFields(pctx.fields, pctx.buf, pctx.prefixBuf, v)
-	return nil
-}
-
-func appendLogFields(dst []logstorage.Field, dstBuf, prefixBuf []byte, v *fastjson.Value) ([]logstorage.Field, []byte, []byte) {
-	o := v.GetObject()
-	o.Visit(func(k []byte, v *fastjson.Value) {
-		t := v.Type()
-		switch t {
-		case fastjson.TypeNull:
-			// Skip nulls
-		case fastjson.TypeObject:
-			// Flatten nested JSON objects.
-			// For example, {"foo":{"bar":"baz"}} is converted to {"foo.bar":"baz"}
-			prefixLen := len(prefixBuf)
-			prefixBuf = append(prefixBuf, k...)
-			prefixBuf = append(prefixBuf, '.')
-			dst, dstBuf, prefixBuf = appendLogFields(dst, dstBuf, prefixBuf, v)
-			prefixBuf = prefixBuf[:prefixLen]
-		case fastjson.TypeArray, fastjson.TypeNumber, fastjson.TypeTrue, fastjson.TypeFalse:
-			// Convert JSON arrays, numbers, true and false values to their string representation
-			dstBufLen := len(dstBuf)
-			dstBuf = v.MarshalTo(dstBuf)
-			value := dstBuf[dstBufLen:]
-			dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
-		case fastjson.TypeString:
-			// Decode JSON strings
-			dstBufLen := len(dstBuf)
-			dstBuf = append(dstBuf, v.GetStringBytes()...)
-			value := dstBuf[dstBufLen:]
-			dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
-		default:
-			logger.Panicf("BUG: unexpected JSON type: %s", t)
-		}
-	})
-	return dst, dstBuf, prefixBuf
-}
-
-func appendLogField(dst []logstorage.Field, dstBuf, prefixBuf, k, value []byte) ([]logstorage.Field, []byte) {
-	dstBufLen := len(dstBuf)
-	dstBuf = append(dstBuf, prefixBuf...)
-	dstBuf = append(dstBuf, k...)
-	name := dstBuf[dstBufLen:]
-
-	dst = append(dst, logstorage.Field{
-		Name:  bytesutil.ToUnsafeString(name),
-		Value: bytesutil.ToUnsafeString(value),
-	})
-	return dst, dstBuf
-}
-
 func parseElasticsearchTimestamp(s string) (int64, error) {
 	if len(s) < len("YYYY-MM-DD") || s[len("YYYY")] != '-' {
 		// Try parsing timestamp in milliseconds
diff --git a/app/vlselect/logsql/logsql.go b/app/vlselect/logsql/logsql.go
index 05d6f8830..72dacb3c7 100644
--- a/app/vlselect/logsql/logsql.go
+++ b/app/vlselect/logsql/logsql.go
@@ -4,12 +4,18 @@ import (
 	"net/http"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vlstorage"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bufferedwriter"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
 )
 
+var (
+	maxSortBufferSize = flagutil.NewBytes("select.maxSortBufferSize", 1024*1024, "Query results from /select/logsql/query are automatically sorted by _time "+
+		"if their summary size doesn't exceed this value; otherwise query results are streamed in the response without sorting; "+
+		"too big value for this flag may result in high memory usage, since the sorting is performed in memory")
+)
+
 // ProcessQueryRequest handles /select/logsql/query request
 func ProcessQueryRequest(w http.ResponseWriter, r *http.Request, stopCh <-chan struct{}) {
 	// Extract tenantID
@@ -27,9 +33,8 @@ func ProcessQueryRequest(w http.ResponseWriter, r *http.Request, stopCh <-chan s
 	}
 	w.Header().Set("Content-Type", "application/stream+json; charset=utf-8")
 
-	bw := bufferedwriter.Get(w)
-	defer bufferedwriter.Put(bw)
-
+	sw := getSortWriter()
+	sw.Init(w, maxSortBufferSize.IntN())
 	tenantIDs := []logstorage.TenantID{tenantID}
 	vlstorage.RunQuery(tenantIDs, q, stopCh, func(columns []logstorage.BlockColumn) {
 		if len(columns) == 0 {
@@ -41,13 +46,11 @@ func ProcessQueryRequest(w http.ResponseWriter, r *http.Request, stopCh <-chan s
 		for rowIdx := 0; rowIdx < rowsCount; rowIdx++ {
 			WriteJSONRow(bb, columns, rowIdx)
 		}
-		// Do not check for error here, since the only valid error is when the client
-		// closes the connection during Write() call. There is no need in logging this error,
-		// since it may be too verbose and it doesn't give any actionable info.
-		_, _ = bw.Write(bb.B)
+		sw.MustWrite(bb.B)
 		blockResultPool.Put(bb)
 	})
-	_ = bw.Flush()
+	sw.FinalFlush()
+	putSortWriter(sw)
 }
 
 var blockResultPool bytesutil.ByteBufferPool
diff --git a/app/vlselect/logsql/query_response.qtpl b/app/vlselect/logsql/query_response.qtpl
index c98b0c9bd..06205615e 100644
--- a/app/vlselect/logsql/query_response.qtpl
+++ b/app/vlselect/logsql/query_response.qtpl
@@ -17,4 +17,25 @@
 }{% newline %}
 {% endfunc %}
 
+// JSONRows prints formatted rows
+{% func JSONRows(rows [][]logstorage.Field) %}
+	{% if len(rows) == 0 %}
+		{% return %}
+	{% endif %}
+	{% for _, fields := range rows %}
+	{
+		{% if len(fields) > 0 %}
+			{% code
+				f := fields[0]
+				fields = fields[1:]
+			%}
+			{%q= f.Name %}:{%q= f.Value %}
+			{% for _, f := range fields %}
+				,{%q= f.Name %}:{%q= f.Value %}
+			{% endfor %}
+		{% endif %}
+	}{% newline %}
+	{% endfor %}
+{% endfunc %}
+
 {% endstripspace %}
diff --git a/app/vlselect/logsql/query_response.qtpl.go b/app/vlselect/logsql/query_response.qtpl.go
index d3d6cf1c1..dd3458c21 100644
--- a/app/vlselect/logsql/query_response.qtpl.go
+++ b/app/vlselect/logsql/query_response.qtpl.go
@@ -88,3 +88,79 @@ func JSONRow(columns []logstorage.BlockColumn, rowIdx int) string {
 	return qs422016
 //line app/vlselect/logsql/query_response.qtpl:18
 }
+
+// JSONRows prints formatted rows
+
+//line app/vlselect/logsql/query_response.qtpl:21
+func StreamJSONRows(qw422016 *qt422016.Writer, rows [][]logstorage.Field) {
+//line app/vlselect/logsql/query_response.qtpl:22
+	if len(rows) == 0 {
+//line app/vlselect/logsql/query_response.qtpl:23
+		return
+//line app/vlselect/logsql/query_response.qtpl:24
+	}
+//line app/vlselect/logsql/query_response.qtpl:25
+	for _, fields := range rows {
+//line app/vlselect/logsql/query_response.qtpl:25
+		qw422016.N().S(`{`)
+//line app/vlselect/logsql/query_response.qtpl:27
+		if len(fields) > 0 {
+//line app/vlselect/logsql/query_response.qtpl:29
+			f := fields[0]
+			fields = fields[1:]
+
+//line app/vlselect/logsql/query_response.qtpl:32
+			qw422016.N().Q(f.Name)
+//line app/vlselect/logsql/query_response.qtpl:32
+			qw422016.N().S(`:`)
+//line app/vlselect/logsql/query_response.qtpl:32
+			qw422016.N().Q(f.Value)
+//line app/vlselect/logsql/query_response.qtpl:33
+			for _, f := range fields {
+//line app/vlselect/logsql/query_response.qtpl:33
+				qw422016.N().S(`,`)
+//line app/vlselect/logsql/query_response.qtpl:34
+				qw422016.N().Q(f.Name)
+//line app/vlselect/logsql/query_response.qtpl:34
+				qw422016.N().S(`:`)
+//line app/vlselect/logsql/query_response.qtpl:34
+				qw422016.N().Q(f.Value)
+//line app/vlselect/logsql/query_response.qtpl:35
+			}
+//line app/vlselect/logsql/query_response.qtpl:36
+		}
+//line app/vlselect/logsql/query_response.qtpl:36
+		qw422016.N().S(`}`)
+//line app/vlselect/logsql/query_response.qtpl:37
+		qw422016.N().S(`
+`)
+//line app/vlselect/logsql/query_response.qtpl:38
+	}
+//line app/vlselect/logsql/query_response.qtpl:39
+}
+
+//line app/vlselect/logsql/query_response.qtpl:39
+func WriteJSONRows(qq422016 qtio422016.Writer, rows [][]logstorage.Field) {
+//line app/vlselect/logsql/query_response.qtpl:39
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line app/vlselect/logsql/query_response.qtpl:39
+	StreamJSONRows(qw422016, rows)
+//line app/vlselect/logsql/query_response.qtpl:39
+	qt422016.ReleaseWriter(qw422016)
+//line app/vlselect/logsql/query_response.qtpl:39
+}
+
+//line app/vlselect/logsql/query_response.qtpl:39
+func JSONRows(rows [][]logstorage.Field) string {
+//line app/vlselect/logsql/query_response.qtpl:39
+	qb422016 := qt422016.AcquireByteBuffer()
+//line app/vlselect/logsql/query_response.qtpl:39
+	WriteJSONRows(qb422016, rows)
+//line app/vlselect/logsql/query_response.qtpl:39
+	qs422016 := string(qb422016.B)
+//line app/vlselect/logsql/query_response.qtpl:39
+	qt422016.ReleaseByteBuffer(qb422016)
+//line app/vlselect/logsql/query_response.qtpl:39
+	return qs422016
+//line app/vlselect/logsql/query_response.qtpl:39
+}
diff --git a/app/vlselect/logsql/sort_writer.go b/app/vlselect/logsql/sort_writer.go
new file mode 100644
index 000000000..dcb3b8893
--- /dev/null
+++ b/app/vlselect/logsql/sort_writer.go
@@ -0,0 +1,222 @@
+package logsql
+
+import (
+	"bytes"
+	"io"
+	"sort"
+	"sync"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logjson"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
+)
+
+func getSortWriter() *sortWriter {
+	v := sortWriterPool.Get()
+	if v == nil {
+		return &sortWriter{}
+	}
+	return v.(*sortWriter)
+}
+
+func putSortWriter(sw *sortWriter) {
+	sw.reset()
+	sortWriterPool.Put(sw)
+}
+
+var sortWriterPool sync.Pool
+
+// sortWriter expects JSON line stream to be written to it.
+//
+// It buffers the incoming data until its size reaches maxBufLen.
+// Then it streams the buffered data and all the incoming data to w.
+//
+// The FinalFlush() must be called when all the data is written.
+// If the buf isn't empty at FinalFlush() call, then the buffered data
+// is sorted by _time field.
+type sortWriter struct {
+	mu         sync.Mutex
+	w          io.Writer
+	maxBufLen  int
+	buf        []byte
+	bufFlushed bool
+
+	hasErr bool
+}
+
+func (sw *sortWriter) reset() {
+	sw.w = nil
+	sw.maxBufLen = 0
+	sw.buf = sw.buf[:0]
+	sw.bufFlushed = false
+	sw.hasErr = false
+}
+
+func (sw *sortWriter) Init(w io.Writer, maxBufLen int) {
+	sw.reset()
+
+	sw.w = w
+	sw.maxBufLen = maxBufLen
+}
+
+func (sw *sortWriter) MustWrite(p []byte) {
+	sw.mu.Lock()
+	defer sw.mu.Unlock()
+
+	if sw.hasErr {
+		return
+	}
+
+	if sw.bufFlushed {
+		if _, err := sw.w.Write(p); err != nil {
+			sw.hasErr = true
+		}
+		return
+	}
+	if len(sw.buf)+len(p) < sw.maxBufLen {
+		sw.buf = append(sw.buf, p...)
+		return
+	}
+	sw.bufFlushed = true
+	if len(sw.buf) > 0 {
+		if _, err := sw.w.Write(sw.buf); err != nil {
+			sw.hasErr = true
+			return
+		}
+		sw.buf = sw.buf[:0]
+	}
+	if _, err := sw.w.Write(p); err != nil {
+		sw.hasErr = true
+	}
+}
+
+func (sw *sortWriter) FinalFlush() {
+	if sw.hasErr || sw.bufFlushed {
+		return
+	}
+	rs := getRowsSorter()
+	rs.parseRows(sw.buf)
+	rs.sort()
+	WriteJSONRows(sw.w, rs.rows)
+	putRowsSorter(rs)
+}
+
+func getRowsSorter() *rowsSorter {
+	v := rowsSorterPool.Get()
+	if v == nil {
+		return &rowsSorter{}
+	}
+	return v.(*rowsSorter)
+}
+
+func putRowsSorter(rs *rowsSorter) {
+	rs.reset()
+	rowsSorterPool.Put(rs)
+}
+
+var rowsSorterPool sync.Pool
+
+type rowsSorter struct {
+	buf       []byte
+	fieldsBuf []logstorage.Field
+	rows      [][]logstorage.Field
+	times     []string
+}
+
+func (rs *rowsSorter) reset() {
+	rs.buf = rs.buf[:0]
+
+	fieldsBuf := rs.fieldsBuf
+	for i := range fieldsBuf {
+		fieldsBuf[i].Reset()
+	}
+	rs.fieldsBuf = fieldsBuf[:0]
+
+	rows := rs.rows
+	for i := range rows {
+		rows[i] = nil
+	}
+	rs.rows = rows[:0]
+
+	times := rs.times
+	for i := range times {
+		times[i] = ""
+	}
+	rs.times = times[:0]
+}
+
+func (rs *rowsSorter) parseRows(src []byte) {
+	rs.reset()
+
+	buf := rs.buf
+	fieldsBuf := rs.fieldsBuf
+	rows := rs.rows
+	times := rs.times
+
+	p := logjson.GetParser()
+	for len(src) > 0 {
+		var line []byte
+		n := bytes.IndexByte(src, '\n')
+		if n < 0 {
+			line = src
+			src = nil
+		} else {
+			line = src[:n]
+			src = src[n+1:]
+		}
+		if len(line) == 0 {
+			continue
+		}
+
+		p.ParseLogMessage(line)
+
+		timeValue := ""
+		fieldsBufLen := len(fieldsBuf)
+		for _, f := range p.Fields {
+			bufLen := len(buf)
+			buf = append(buf, f.Name...)
+			name := bytesutil.ToUnsafeString(buf[bufLen:])
+
+			bufLen = len(buf)
+			buf = append(buf, f.Value...)
+			value := bytesutil.ToUnsafeString(buf[bufLen:])
+
+			fieldsBuf = append(fieldsBuf, logstorage.Field{
+				Name:  name,
+				Value: value,
+			})
+
+			if name == "_time" {
+				timeValue = value
+			}
+		}
+		rows = append(rows, fieldsBuf[fieldsBufLen:])
+		times = append(times, timeValue)
+	}
+	logjson.PutParser(p)
+
+	rs.buf = buf
+	rs.fieldsBuf = fieldsBuf
+	rs.rows = rows
+	rs.times = times
+}
+
+func (rs *rowsSorter) Len() int {
+	return len(rs.rows)
+}
+
+func (rs *rowsSorter) Less(i, j int) bool {
+	times := rs.times
+	return times[i] < times[j]
+}
+
+func (rs *rowsSorter) Swap(i, j int) {
+	times := rs.times
+	rows := rs.rows
+	times[i], times[j] = times[j], times[i]
+	rows[i], rows[j] = rows[j], rows[i]
+}
+
+func (rs *rowsSorter) sort() {
+	sort.Sort(rs)
+}
diff --git a/app/vmalert/README.md b/app/vmalert/README.md
index 7a56af4fd..29ebd14c6 100644
--- a/app/vmalert/README.md
+++ b/app/vmalert/README.md
@@ -208,11 +208,13 @@ expr: <string>
 # Please note, that if rule's query params contain sensitive
 # information - it will be printed to logs.
 # Is applicable to alerting rules only.
+# Available starting from https://docs.victoriametrics.com/CHANGELOG.html#v1820
 [ debug: <bool> | default = false ]
 
 # Defines the number of rule's updates entries stored in memory
 # and available for view on rule's Details page.
 # Overrides `rule.updateEntriesLimit` value for this specific rule.
+# Available starting from https://docs.victoriametrics.com/CHANGELOG.html#v1860
 [ update_entries_limit: <integer> | default 0 ]
 
 # Labels to add or overwrite for each alert.
@@ -792,7 +794,8 @@ If `-remoteWrite.url` command-line flag is configured, vmalert will persist aler
 changed in time.
 
 vmalert stores last `-rule.updateEntriesLimit` (or `update_entries_limit` [per-rule config](https://docs.victoriametrics.com/vmalert.html#alerting-rules)) 
-state updates for each rule. To check updates, click on `Details` link next to rule's name on `/vmalert/groups` page 
+state updates for each rule starting from [v1.86](https://docs.victoriametrics.com/CHANGELOG.html#v1860).
+To check updates, click on `Details` link next to rule's name on `/vmalert/groups` page 
 and check the `Last updates` section:
 
 <img alt="vmalert state" src="vmalert_state.png">
@@ -804,8 +807,8 @@ moment when rule was evaluated.
 
 ### Debug mode
 
-vmalert allows configuring more detailed logging for specific alerting rule. Just set `debug: true` in rule's configuration
-and vmalert will start printing additional log messages:
+vmalert allows configuring more detailed logging for specific alerting rule starting from [v1.82](https://docs.victoriametrics.com/CHANGELOG.html#v1820).
+Just set `debug: true` in rule's configuration and vmalert will start printing additional log messages:
 ```terminal
 2022-09-15T13:35:41.155Z  DEBUG rule "TestGroup":"Conns" (2601299393013563564) at 2022-09-15T15:35:41+02:00: query returned 0 samples (elapsed: 5.896041ms)
 2022-09-15T13:35:56.149Z  DEBUG datasource request: executing POST request with params "denyPartialResponse=true&query=sum%28vm_tcplistener_conns%7Binstance%3D%22localhost%3A8429%22%7D%29+by%28instance%29+%3E+0&step=15s&time=1663248945"
@@ -818,7 +821,8 @@ and vmalert will start printing additional log messages:
 
 ### Never-firing alerts
 
-vmalert can detect if alert's expression doesn't match any time series in runtime. This problem usually happens
+vmalert can detect if alert's expression doesn't match any time series in runtime 
+starting from [v1.91](https://docs.victoriametrics.com/CHANGELOG.html#v1910). This problem usually happens
 when alerting expression selects time series which aren't present in the datasource (i.e. wrong `job` label)
 or there is a typo in the series selector (i.e. `env=rpod`). Such alerting rules will be marked with special icon in 
 vmalerts UI and exposed via `vmalert_alerting_rules_last_evaluation_series_fetched` metric. The metric value will
diff --git a/dashboards/victorialogs.json b/dashboards/victorialogs.json
new file mode 100644
index 000000000..487216fea
--- /dev/null
+++ b/dashboards/victorialogs.json
@@ -0,0 +1,779 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": {
+          "type": "grafana",
+          "uid": "-- Grafana --"
+        },
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "links": [],
+  "liveNow": false,
+  "panels": [
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 16,
+      "panels": [],
+      "title": "Stats",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "How many log entries are in storage",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 2,
+        "w": 5,
+        "x": 0,
+        "y": 1
+      },
+      "id": 10,
+      "links": [],
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "auto"
+      },
+      "pluginVersion": "9.2.7",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "exemplar": false,
+          "expr": "sum(vl_rows)",
+          "format": "time_series",
+          "instant": true,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Total log entries",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "Shows the datapoints ingestion rate.",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 2,
+        "w": 5,
+        "x": 5,
+        "y": 1
+      },
+      "id": 22,
+      "links": [],
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "auto"
+      },
+      "pluginVersion": "9.2.7",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "exemplar": false,
+          "expr": "sum(rate(vl_rows_ingested_total))",
+          "format": "time_series",
+          "instant": true,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Ingestion rate",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "Total amount of used disk space",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 2,
+        "w": 5,
+        "x": 10,
+        "y": 1
+      },
+      "id": 24,
+      "links": [],
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "auto"
+      },
+      "pluginVersion": "9.2.7",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "exemplar": false,
+          "expr": "sum(vl_compressed_data_size_bytes)",
+          "format": "time_series",
+          "instant": true,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Disk space usage",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "The ratio of original data size and compressed data stored on disk",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              }
+            ]
+          },
+          "unit": "none"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 2,
+        "w": 5,
+        "x": 15,
+        "y": 1
+      },
+      "id": 25,
+      "links": [],
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "auto"
+      },
+      "pluginVersion": "9.2.7",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "exemplar": false,
+          "expr": " sum(vl_uncompressed_data_size_bytes) / sum(vl_compressed_data_size_bytes)",
+          "format": "time_series",
+          "instant": true,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Comprasion ratio",
+      "type": "stat"
+    },
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 3
+      },
+      "id": 18,
+      "panels": [],
+      "title": "Overview",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "How many datapoints are inserted into storage per second",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "links": [],
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 4
+      },
+      "id": 2,
+      "links": [],
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "lastNotNull"
+          ],
+          "displayMode": "table",
+          "placement": "bottom",
+          "showLegend": true,
+          "sortBy": "Last *",
+          "sortDesc": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "desc"
+        }
+      },
+      "pluginVersion": "9.1.0",
+      "targets": [
+        {
+          "datasource": {
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "expr": "sum(rate(vl_rows_ingested_total)) by (type)",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "{{type}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Datapoints ingestion rate ",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "* `*` - unsupported query path\n* `/insert` - insert into VM\n* `/metrics` - query VL system metrics\n* `/query` - read the data",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "links": [],
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 4
+      },
+      "id": 14,
+      "links": [],
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "table",
+          "placement": "bottom",
+          "showLegend": true,
+          "sortBy": "Last *",
+          "sortDesc": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "desc"
+        }
+      },
+      "pluginVersion": "9.1.0",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "expr": "sum(rate(vl_http_requests_total)) by (path) > 0",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "{{path}}",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Requests rate ",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "Shows the amount of on-disk space occupied by data before and after compressiom",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "links": [],
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 12
+      },
+      "id": 6,
+      "links": [],
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "table",
+          "placement": "bottom",
+          "showLegend": true,
+          "sortBy": "Last *",
+          "sortDesc": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "desc"
+        }
+      },
+      "pluginVersion": "9.1.0",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "expr": "sum(vl_compressed_data_size_bytes)",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "disk usage",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Disk space usage ",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "P4169E866C3094E38"
+      },
+      "description": "The number of the new log streams created over the last 24h",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "links": [],
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 12
+      },
+      "id": 26,
+      "links": [],
+      "options": {
+        "legend": {
+          "calcs": [
+            "mean",
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "table",
+          "placement": "bottom",
+          "showLegend": true,
+          "sortBy": "Last *",
+          "sortDesc": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "desc"
+        }
+      },
+      "pluginVersion": "9.1.0",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "editorMode": "code",
+          "expr": "increase(vl_streams_created_total[1d])",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "new log streams over 24h",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "Log stream churn rate",
+      "type": "timeseries"
+    }
+  ],
+  "schemaVersion": 37,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-30m",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "VictoriaLogs",
+  "uid": "OqPIZTX4z",
+  "version": 4,
+  "weekStart": ""
+}
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/filebeat-docker/README.md b/deployment/docker/victorialogs/filebeat-docker/README.md
new file mode 100644
index 000000000..9d00353ea
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-docker/README.md
@@ -0,0 +1,50 @@
+# Docker compose Filebeat integration with VictoriaLogs for docker
+
+The folder contains the example of integration of [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html) with Victorialogs
+
+To spin-up environment  run the following command:
+```
+docker compose up -d 
+```
+
+To shut down the docker-compose environment run the following command:
+```
+docker compose down
+docker compose rm -f
+```
+
+The docker compose file contains the following components:
+
+* filebeat - fileabeat is configured to collect logs from the `docker`, you can find configuration in the `filebeat.yml`. It writes data in VictoriaLogs
+* filebeat-exporter - it export metrics about the filebeat
+* VictoriaLogs - the log database, it accepts the data from `filebeat` by elastic protocol
+* VictoriaMetrics - collects metrics from `filebeat` via `filebeat-exporter`, `VictoriaLogs` and `VictoriaMetrics`(itself)
+* grafana - it comes with two predefined dashboards for `VictoriaLogs` and `VictoriaMetrics`
+
+
+the example of filebeat configuration(`filebeat.yml`)
+
+```yaml
+filebeat.autodiscover:
+  providers:
+    - type: docker
+      hints.enabled: true
+
+processors:
+  - add_docker_metadata: ~
+
+output.elasticsearch:
+  hosts: [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  worker: 5
+  parameters:
+    _msg_field: "message"
+    _time_field: "@timestamp"
+    _stream_fields: "container.name"
+
+http:
+  enabled: true
+  host: 0.0.0.0
+  port: 5066
+```
+
+Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#stream-fields) to achieve better performance.
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml b/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml
new file mode 100644
index 000000000..454041b89
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml
@@ -0,0 +1,61 @@
+version: '3'
+
+services:
+  filebeat-victorialogs:
+    image: docker.elastic.co/beats/filebeat:8.8.1
+    restart: on-failure
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+      - type: bind
+        source: /var/lib/docker/containers/
+        target: /var/lib/docker/containers/
+      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
+    user: root
+    command:
+      - "--strict.perms=false"
+    depends_on: [ victorialogs ]
+  beat-exporter-victorialogs:
+    image: trustpilot/beat-exporter:0.4.0
+    command:
+      - -beat.uri=http://filebeat-victorialogs:5066
+
+  # Run `make package-victoria-logs` to build victoria-logs image
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:heads-master-0-g88993f312-dirty-e01fb71f
+    volumes:
+      - victorialogs-filebeat-docker-vl:/vlogs
+    ports:
+      - '9428:9428'
+    command:
+      - -storageDataPath=/vlogs
+      - -loggerFormat=json
+  grafana:
+    container_name: grafana
+    image: grafana/grafana:9.2.7
+    depends_on:
+      - "victoriametrics"
+    ports:
+      - 3000:3000
+    volumes:
+      - victorialogs-filebeat-docker-grafana:/var/lib/grafana
+      - ./../../provisioning/:/etc/grafana/provisioning/
+      - ./../../../../dashboards/victoriametrics.json:/var/lib/grafana/dashboards/vm.json
+      - ./../../../../dashboards/victorialogs.json:/var/lib/grafana/dashboards/vl.json
+  victoriametrics:
+    image: victoriametrics/victoria-metrics:v1.91.2
+    ports:
+      - '8428:8428'
+    command:
+      - -storageDataPath=/vmsingle
+      - -promscrape.config=/promscrape.yml
+      - -loggerFormat=json
+    volumes:
+      - victorialogs-filebeat-docker-vm:/vmsingle
+      - ./scrape.yml:/promscrape.yml
+
+volumes:
+  victorialogs-filebeat-docker-vl:
+  victorialogs-filebeat-docker-vm:
+  victorialogs-filebeat-docker-grafana:
diff --git a/deployment/docker/victorialogs/filebeat-docker/filebeat.yml b/deployment/docker/victorialogs/filebeat-docker/filebeat.yml
new file mode 100644
index 000000000..67c7baf64
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-docker/filebeat.yml
@@ -0,0 +1,20 @@
+filebeat.autodiscover:
+  providers:
+    - type: docker
+      hints.enabled: true
+
+processors:
+  - add_docker_metadata: ~
+
+output.elasticsearch:
+  hosts: [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  worker: 5
+  parameters:
+    _msg_field: "message"
+    _time_field: "@timestamp"
+    _stream_fields: "container.name"
+
+http:
+  enabled: true
+  host: 0.0.0.0
+  port: 5066
diff --git a/deployment/docker/victorialogs/filebeat-docker/scrape.yml b/deployment/docker/victorialogs/filebeat-docker/scrape.yml
new file mode 100644
index 000000000..5bf5f9ddf
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-docker/scrape.yml
@@ -0,0 +1,16 @@
+scrape_configs:
+  - job_name: "filebeat"
+    scrape_interval: 30s
+    static_configs:
+      - targets:
+          - beat-exporter-victorialogs:9479
+  - job_name: "victoriametrics"
+    scrape_interval: 30s
+    static_configs:
+      - targets:
+          - victoriametrics:8428
+  - job_name: "victorialogs"
+    scrape_interval: 30s
+    static_configs:
+      - targets:
+          - victorialogs:9428
diff --git a/deployment/docker/victorialogs/filebeat-syslog/README.md b/deployment/docker/victorialogs/filebeat-syslog/README.md
new file mode 100644
index 000000000..3761c53fa
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-syslog/README.md
@@ -0,0 +1,40 @@
+# Docker compose Filebeat integration with VictoriaLogs for syslog
+
+The folder contains the example of integration of [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html) with Victorialogs
+
+To spin-up environment  run the following command:
+```
+docker compose up -d 
+```
+
+To shut down the docker-compose environment run the following command:
+```
+docker compose down
+docker compose rm -f
+```
+
+The docker compose file contains the following components:
+
+* filebeat - fileabeat is configured to accept `syslog` logs in `rfc3164` format on `5140` port, you can find configuration in the `filebeat.yml`. It writes data in VictoriaLogs
+* VictoriaLogs - the log database, it accepts the data from `filebeat` by elastic protocol
+
+the example of filebeat configuration(`filebeat.yml`)
+
+```yaml
+filebeat.inputs:
+  - type: syslog
+    format: rfc3164
+    protocol.tcp:
+      host: "0.0.0.0:5140"
+
+output.elasticsearch:
+  hosts: [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  worker: 5
+  bulk_max_size: 1000
+  parameters:
+    _msg_field: "message"
+    _time_field: "@timestamp"
+    _stream_fields: "host.name,process.program,process.pid,container.name"
+```
+
+Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#stream-fields) to achieve better performance.
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml b/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml
new file mode 100644
index 000000000..db79dfeee
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml
@@ -0,0 +1,26 @@
+version: "3"
+
+services:
+  filebeat-vlogs:
+    image: docker.elastic.co/beats/filebeat:8.8.1
+    user: root
+    command:
+      - "--strict.perms=false"
+    volumes:
+      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
+    depends_on: [victorialogs]
+    ports:
+      - "5140:5140"
+
+  # Run `make package-victoria-logs` to build victoria-logs image
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:heads-master-0-g88993f312-dirty-e01fb71f
+    volumes:
+      - victorialogs-filebeat-syslog-vl:/vlogs
+    ports:
+      - "9428:9428"
+    command:
+      - -storageDataPath=/vlogs
+
+volumes:
+  victorialogs-filebeat-syslog-vl:
diff --git a/deployment/docker/victorialogs/filebeat-syslog/filebeat.yml b/deployment/docker/victorialogs/filebeat-syslog/filebeat.yml
new file mode 100644
index 000000000..fcf716387
--- /dev/null
+++ b/deployment/docker/victorialogs/filebeat-syslog/filebeat.yml
@@ -0,0 +1,14 @@
+filebeat.inputs:
+  - type: syslog
+    format: rfc3164
+    protocol.tcp:
+      host: "0.0.0.0:5140"
+
+output.elasticsearch:
+  hosts: [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  worker: 5
+  bulk_max_size: 1000
+  parameters:
+    _msg_field: "message"
+    _time_field: "@timestamp"
+    _stream_fields: "host.name,process.program,process.pid,container.name"
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/fluentbit-docker/README.md b/deployment/docker/victorialogs/fluentbit-docker/README.md
new file mode 100644
index 000000000..05755295c
--- /dev/null
+++ b/deployment/docker/victorialogs/fluentbit-docker/README.md
@@ -0,0 +1,55 @@
+# Docker compose Fluentbit integration with VictoriaLogs for docker
+
+The folder contains the example of integration of [fluentbit](https://docs.fluentbit.io/manual) with Victorialogs
+
+To spin-up environment  run the following command:
+```
+docker compose up -d 
+```
+
+To shut down the docker-compose environment run the following command:
+```
+docker compose down
+docker compose rm -f
+```
+
+The docker compose file contains the following components:
+
+* fluentbit - fluentbit is configured to collect logs from the `docker`, you can find configuration in the `fluent-bit.conf`. It writes data in VictoriaLogs
+* VictoriaLogs - the log database, it accepts the data from `fluentbit` by json line protocol
+
+the example of fluentbit configuration(`filebeat.yml`)
+
+```shell
+[INPUT]
+    name              tail
+    path              /var/lib/docker/containers/**/*.log
+    multiline.parser  docker, cri
+    Parser docker
+    Docker_Mode  On
+
+[INPUT]
+    Name     syslog
+    Listen   0.0.0.0
+    Port     5140
+    Parser   syslog-rfc3164
+    Mode     tcp
+
+[SERVICE]
+    Flush        1
+    Parsers_File parsers.conf
+
+[Output]
+    Name http
+    Match *
+    host victorialogs
+    port 9428
+    compress gzip
+    uri /insert/jsonline/?_stream_fields=stream&_msg_field=log&_time_field=date
+    format json_lines
+    json_date_format iso8601
+    header AccountID 0
+    header ProjectID 0
+```
+
+Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#stream-fields) to achieve better performance.
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml b/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml
new file mode 100644
index 000000000..55022f848
--- /dev/null
+++ b/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml
@@ -0,0 +1,24 @@
+version: "3"
+
+services:
+  fluentbit:
+    image: cr.fluentbit.io/fluent/fluent-bit:2.1.4
+    volumes:
+      - /var/lib/docker/containers:/var/lib/docker/containers:ro
+      - ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
+    depends_on: [victorialogs]
+    ports:
+      - "5140:5140"
+
+  # Run `make package-victoria-logs` to build victoria-logs image
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:heads-master-0-g88993f312-dirty-e01fb71f
+    volumes:
+      - victorialogs-fluentbit-vl:/vlogs
+    ports:
+      - "9428:9428"
+    command:
+      - -storageDataPath=/vlogs
+
+volumes:
+  victorialogs-fluentbit-vl:
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf b/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf
new file mode 100644
index 000000000..a4291423c
--- /dev/null
+++ b/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf
@@ -0,0 +1,29 @@
+[INPUT]
+    name              tail
+    path              /var/lib/docker/containers/**/*.log
+    multiline.parser  docker, cri
+    Parser docker
+    Docker_Mode  On
+
+[INPUT]
+    Name     syslog
+    Listen   0.0.0.0
+    Port     5140
+    Parser   syslog-rfc3164
+    Mode     tcp
+
+[SERVICE]
+    Flush        1
+    Parsers_File parsers.conf
+
+[Output]
+    Name http
+    Match *
+    host victorialogs
+    port 9428
+    compress gzip
+    uri /insert/jsonline/?_stream_fields=stream&_msg_field=log&_time_field=date
+    format json_lines
+    json_date_format iso8601
+    header AccountID 0
+    header ProjectID 0
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/logstash/Dockerfile b/deployment/docker/victorialogs/logstash/Dockerfile
new file mode 100644
index 000000000..0b2592005
--- /dev/null
+++ b/deployment/docker/victorialogs/logstash/Dockerfile
@@ -0,0 +1,3 @@
+FROM docker.elastic.co/logstash/logstash:8.8.1
+
+RUN bin/logstash-plugin install logstash-output-opensearch
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/logstash/README.md b/deployment/docker/victorialogs/logstash/README.md
new file mode 100644
index 000000000..d4e19bdaa
--- /dev/null
+++ b/deployment/docker/victorialogs/logstash/README.md
@@ -0,0 +1,51 @@
+# Docker compose Logstash integration with VictoriaLogs for syslog
+
+It is required to use [OpenSearch plugin](https://github.com/opensearch-project/logstash-output-opensearch) for output configuration.
+Plugin can be installed by using the following command:
+```
+bin/logstash-plugin install logstash-output-opensearch
+```
+OpenSearch plugin is required because elasticsearch output plugin performs various checks for Elasticsearch version and license which are not applicable for VictoriaLogs.
+
+To spin-up environment  run the following command:
+```
+docker compose up -d 
+```
+
+To shut down the docker-compose environment run the following command:
+```
+docker compose down
+docker compose rm -f
+```
+
+The docker compose file contains the following components:
+
+* logstash - logstash is configured to accept `syslog` on `5140` port, you can find configuration in the `pipeline.conf`. It writes data in VictoriaLogs
+* VictoriaLogs - the log database, it accepts the data from `logstash` by elastic protocol
+
+
+Here is an example of logstash configuration(`pipeline.conf`):
+
+```
+input {
+  syslog {
+    port => 5140
+  }
+}
+output {
+  opensearch {
+    hosts => ["http://victorialogs:9428/insert/elasticsearch"]
+    custom_headers => {
+        "AccountID" => "0"
+        "ProjectID" => "0"
+    }
+    parameters => {
+        "_stream_fields" => "host.ip,process.name"
+        "_msg_field" => "message"
+        "_time_field" => "@timestamp"
+    }
+  }
+}
+```
+
+Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#stream-fields) to achieve better performance.
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/logstash/docker-compose.yml b/deployment/docker/victorialogs/logstash/docker-compose.yml
new file mode 100644
index 000000000..1ff406901
--- /dev/null
+++ b/deployment/docker/victorialogs/logstash/docker-compose.yml
@@ -0,0 +1,27 @@
+version: '3'
+
+services:
+  logstash:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    restart: on-failure
+    volumes:
+      - ./pipeline.conf:/usr/share/logstash/pipeline/logstash.conf:ro
+      - ./logstash.yml:/usr/share/logstash/config/logstash.yml:ro
+    depends_on: [victorialogs]
+    ports:
+      - "5140:5140"
+
+  # Run `make package-victoria-logs` to build victoria-logs image
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:heads-master-0-g88993f312-dirty-e01fb71f
+    volumes:
+      - victorialogs-logstash-vl:/vlogs
+    ports:
+      - "9428:9428"
+    command:
+      - -storageDataPath=/vlogs
+
+volumes:
+  victorialogs-logstash-vl:
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/logstash/logstash.yml b/deployment/docker/victorialogs/logstash/logstash.yml
new file mode 100644
index 000000000..c223de376
--- /dev/null
+++ b/deployment/docker/victorialogs/logstash/logstash.yml
@@ -0,0 +1,2 @@
+http.host: 0.0.0.0
+xpack.monitoring.enabled: false
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/logstash/pipeline.conf b/deployment/docker/victorialogs/logstash/pipeline.conf
new file mode 100644
index 000000000..8b62c6819
--- /dev/null
+++ b/deployment/docker/victorialogs/logstash/pipeline.conf
@@ -0,0 +1,20 @@
+input {
+  syslog {
+    port => 5140
+  }
+}
+
+output {
+  opensearch {
+    hosts => ["http://victorialogs:9428/insert/elasticsearch"]
+    custom_headers => {
+        "AccountID" => "0"
+        "ProjectID" => "0"
+    }
+    parameters => {
+        "_stream_fields" => "host.ip,process.name"
+        "_msg_field" => "message"
+        "_time_field" => "@timestamp"
+    }
+  }
+}
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/vector-docker/README.md b/deployment/docker/victorialogs/vector-docker/README.md
new file mode 100644
index 000000000..092786982
--- /dev/null
+++ b/deployment/docker/victorialogs/vector-docker/README.md
@@ -0,0 +1,55 @@
+# Docker compose Vector integration with VictoriaLogs for docker
+
+The folder contains the example of integration of [vector](https://vector.dev/docs/) with Victorialogs
+
+To spin-up environment  run the following command:
+```
+docker compose up -d 
+```
+
+To shut down the docker-compose environment run the following command:
+```
+docker compose down
+docker compose rm -f
+```
+
+The docker compose file contains the following components:
+
+* vector - vector is configured to collect logs from the `docker`, you can find configuration in the `vector.toml`. It writes data in VictoriaLogs. It pushes metrics to VictoriaMetrics.
+* VictoriaLogs - the log database, it accepts the data from `vector` by elastic protocol
+* VictoriaMetrics - collects metrics from `VictoriaLogs` and `VictoriaMetrics`(itself)
+
+the example of vector configuration(`vector.toml`)
+
+```
+[sources.docker]
+  type = "docker_logs"
+
+[transforms.msg_parser]
+  type = "remap"
+  inputs = ["docker"]
+  source = '''
+  .log = parse_json!(.message)
+  del(.message)
+  '''
+
+[sinks.vlogs]
+  type = "elasticsearch"
+  inputs = [ "msg_parser" ]
+  endpoints = [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  mode = "bulk"
+  api_version = "v8"
+  compression = "gzip"
+  healthcheck.enabled = false
+
+  [sinks.vlogs.query]
+    _msg_field = "log.msg"
+    _time_field = "timestamp"
+    _stream_fields = "source_type,host,container_name"
+
+  [sinks.vlogs.request.headers]
+    AccountID = "0"
+    ProjectID = "0"
+```
+
+Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#stream-fields) to achieve better performance.
diff --git a/deployment/docker/victorialogs/vector-docker/docker-compose.yml b/deployment/docker/victorialogs/vector-docker/docker-compose.yml
new file mode 100644
index 000000000..922022772
--- /dev/null
+++ b/deployment/docker/victorialogs/vector-docker/docker-compose.yml
@@ -0,0 +1,64 @@
+version: '3'
+
+services:
+  vector:
+    image: docker.io/timberio/vector:0.30.0-distroless-static
+    restart: on-failure
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+      - type: bind
+        source: /var/lib/docker
+        target: /var/lib/docker
+      - ./vector.toml:/etc/vector/vector.toml:ro
+    user: root
+    ports:
+      - '8686:8686'
+    depends_on:
+      victorialogs:
+        condition: service_healthy
+      victoriametrics:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8686/health"]
+      interval: 1s
+      timeout: 1s
+      retries: 10
+
+  # Run `make package-victoria-logs` to build victoria-logs image
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:heads-examples-vl-docker-compose-0-gdefae2a3f-dirty-de6e4df1
+    volumes:
+      - victorialogs-vector-docker-vl:/vlogs
+    ports:
+      - '9428:9428'
+    command:
+      - -storageDataPath=/vlogs
+      - -loggerFormat=json
+    healthcheck:
+      test: ["CMD", "wget", "-qO-", "http://localhost:9428/health"]
+      interval: 1s
+      timeout: 1s
+      retries: 10
+
+  victoriametrics:
+    image: victoriametrics/victoria-metrics:v1.91.2
+    ports:
+      - '8428:8428'
+    command:
+      - -storageDataPath=/vmsingle
+      - -promscrape.config=/promscrape.yml
+      - -loggerFormat=json
+    volumes:
+      - victorialogs-vector-docker-vm:/vmsingle
+      - ./scrape.yml:/promscrape.yml
+    healthcheck:
+      test: ["CMD", "wget", "-qO-", "http://localhost:8428/health"]
+      interval: 1s
+      timeout: 1s
+      retries: 10
+
+volumes:
+  victorialogs-vector-docker-vl:
+  victorialogs-vector-docker-vm:
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/vector-docker/scrape.yml b/deployment/docker/victorialogs/vector-docker/scrape.yml
new file mode 100644
index 000000000..8257db8ef
--- /dev/null
+++ b/deployment/docker/victorialogs/vector-docker/scrape.yml
@@ -0,0 +1,11 @@
+scrape_configs:
+  - job_name: "victoriametrics"
+    scrape_interval: 30s
+    static_configs:
+      - targets:
+          - victoriametrics:8428
+  - job_name: "victorialogs"
+    scrape_interval: 30s
+    static_configs:
+      - targets:
+          - victorialogs:9428
\ No newline at end of file
diff --git a/deployment/docker/victorialogs/vector-docker/vector.toml b/deployment/docker/victorialogs/vector-docker/vector.toml
new file mode 100644
index 000000000..b693611d6
--- /dev/null
+++ b/deployment/docker/victorialogs/vector-docker/vector.toml
@@ -0,0 +1,80 @@
+[api]
+  enabled = true
+  address = "0.0.0.0:8686"
+
+
+# ---------------------------------------------
+# Docker logs -> VictoriaLogs
+# ---------------------------------------------
+
+[sources.docker]
+  type = "docker_logs"
+
+[transforms.msg_parser]
+  type = "remap"
+  inputs = ["docker"]
+  source = '''
+  .log = parse_json!(.message)
+  del(.message)
+  '''
+
+[sinks.vlogs]
+  type = "elasticsearch"
+  inputs = [ "msg_parser" ]
+  endpoints = [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  mode = "bulk"
+  api_version = "v8"
+  compression = "gzip"
+  healthcheck.enabled = false
+
+  [sinks.vlogs.query]
+    _msg_field = "log.msg"
+    _time_field = "timestamp"
+    _stream_fields = "source_type,host,container_name"
+
+  [sinks.vlogs.request.headers]
+    AccountID = "0"
+    ProjectID = "0"
+
+
+# ---------------------------------------------
+# Generted demo logs -> VictoriaLogs
+# ---------------------------------------------
+
+[sources.demo]
+  type = "demo_logs"
+  format = "apache_common"
+  interval = 10
+
+[sinks.vlogs_demo]
+  type = "elasticsearch"
+  inputs = [ "demo" ]
+  endpoints = [ "http://victorialogs:9428/insert/elasticsearch/" ]
+  mode = "bulk"
+  api_version = "v8"
+  compression = "gzip"
+  healthcheck.enabled = false
+
+  [sinks.vlogs_demo.query]
+    _msg_field = "message"
+    _time_field = "timestamp"
+    _stream_fields = "source_type"
+
+  [sinks.vlogs_demo.request.headers]
+    AccountID = "0"
+    ProjectID = "0"
+
+
+
+# ---------------------------------------------
+# Vector Metrics -> VictoriaMetrics
+# ---------------------------------------------
+
+[sources.vector_metrics]
+  type = "internal_metrics"
+
+[sinks.victoriametrics]
+  type = "prometheus_remote_write"
+  endpoint = "http://victoriametrics:8428/api/v1/write"
+  inputs = ["vector_metrics"]
+  healthcheck.enabled = false
diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md
index 9d334023f..c146019c8 100644
--- a/docs/MetricsQL.md
+++ b/docs/MetricsQL.md
@@ -1065,7 +1065,7 @@ per each `job` over the last 5 minutes.
 
 `histogram_quantile(phi, buckets)` is a [transform function](#transform-functions), which calculates `phi`-[percentile](https://en.wikipedia.org/wiki/Percentile)
 over the given [histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350).
-`phi` must be in the range `[0...1]`. For example, `histogram_quantile(0.5, sum(rate(http_request_duration_seconds_bucket[5m]) by (le))`
+`phi` must be in the range `[0...1]`. For example, `histogram_quantile(0.5, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))`
 would return median request duration for all the requests during the last 5 minutes.
 
 The function accepts optional third arg - `boundsLabel`. In this case it returns `lower` and `upper` bounds for the estimated percentile with the given `boundsLabel` label.
diff --git a/docs/VictoriaLogs/LogsQL.md b/docs/VictoriaLogs/LogsQL.md
index b40476d01..fc0c67f92 100644
--- a/docs/VictoriaLogs/LogsQL.md
+++ b/docs/VictoriaLogs/LogsQL.md
@@ -1058,8 +1058,9 @@ See the [Roadmap](https://docs.victoriametrics.com/VictoriaLogs/Roadmap.html) fo
 
 ## Sorting
 
-By default VictoriaLogs doesn't sort the returned results because of performance and efficiency concerns
-described [here](https://docs.victoriametrics.com/VictoriaLogs/querying/#command-line).
+By default VictoriaLogs sorts the returned results by [`_time` field](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#time-field)
+if their total size doesn't exceed `-select.maxSortBufferSize` command-line value (by default it is set to one megabytes).
+Otherwise sorting is skipped because of performance and efficiency concerns described [here](https://docs.victoriametrics.com/VictoriaLogs/querying/).
 
 It is possible to sort the [selected log entries](#filters) at client side with `sort` Unix command
 according to [these docs](https://docs.victoriametrics.com/VictoriaLogs/querying/#command-line).
diff --git a/docs/VictoriaLogs/README.md b/docs/VictoriaLogs/README.md
index bb55e0c46..b53a5f297 100644
--- a/docs/VictoriaLogs/README.md
+++ b/docs/VictoriaLogs/README.md
@@ -94,3 +94,9 @@ If `AccountID` and/or `ProjectID` request headers aren't set, then the default `
 VictoriaLogs has very low overhead for per-tenant management, so it is OK to have thousands of tenants in a single VictoriaLogs instance.
 
 VictoriaLogs doesn't perform per-tenant authorization. Use [vmauth](https://docs.victoriametrics.com/vmauth.html) or similar tools for per-tenant authorization.
+
+## Benchmarks
+
+We encourage you to run benchmarks on your own. Please share the results or feedback with us by just dropping the line on any of our [Community channels](https://docs.victoriametrics.com/#community-and-contributions).
+
+However, we prepared a benchmark suite that covers ElasticSearch and VictoriaLogs. You can find it [here](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/logs-benchmark).
diff --git a/docs/VictoriaLogs/querying/README.md b/docs/VictoriaLogs/querying/README.md
index 23586354c..ec5ada43b 100644
--- a/docs/VictoriaLogs/querying/README.md
+++ b/docs/VictoriaLogs/querying/README.md
@@ -31,7 +31,9 @@ The response can be interrupted at any time by closing the connection to Victori
 This allows post-processing the returned lines at the client side with the usual Unix commands such as `grep`, `jq`, `less`, `head`, etc.
 See [these docs](#command-line) for more details.
 
-The returned lines aren't sorted by default, since sorting disables the ability to send matching log entries to response stream as soon as they are found.
+The returned lines are sorted by [`_time` field](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#time-field)
+if their total size doesn't exceed `-select.maxSortBufferSize` command-line flag value (by default it is set to one megabyte).
+Otherwise the returned lines aren't sorted, since sorting disables the ability to send matching log entries to response stream as soon as they are found.
 Query results can be sorted either at VictoriaLogs side according [to these docs](https://docs.victoriametrics.com/VictoriaLogs/LogsQL.html#sorting)
 or at client side with the usual `sort` command according to [these docs](#command-line).
 
diff --git a/docs/vmalert.md b/docs/vmalert.md
index 0727b9425..4bb91c3b1 100644
--- a/docs/vmalert.md
+++ b/docs/vmalert.md
@@ -219,11 +219,13 @@ expr: <string>
 # Please note, that if rule's query params contain sensitive
 # information - it will be printed to logs.
 # Is applicable to alerting rules only.
+# Available starting from https://docs.victoriametrics.com/CHANGELOG.html#v1820
 [ debug: <bool> | default = false ]
 
 # Defines the number of rule's updates entries stored in memory
 # and available for view on rule's Details page.
 # Overrides `rule.updateEntriesLimit` value for this specific rule.
+# Available starting from https://docs.victoriametrics.com/CHANGELOG.html#v1860
 [ update_entries_limit: <integer> | default 0 ]
 
 # Labels to add or overwrite for each alert.
@@ -803,7 +805,8 @@ If `-remoteWrite.url` command-line flag is configured, vmalert will persist aler
 changed in time.
 
 vmalert stores last `-rule.updateEntriesLimit` (or `update_entries_limit` [per-rule config](https://docs.victoriametrics.com/vmalert.html#alerting-rules)) 
-state updates for each rule. To check updates, click on `Details` link next to rule's name on `/vmalert/groups` page 
+state updates for each rule starting from [v1.86](https://docs.victoriametrics.com/CHANGELOG.html#v1860).
+To check updates, click on `Details` link next to rule's name on `/vmalert/groups` page 
 and check the `Last updates` section:
 
 <img alt="vmalert state" src="vmalert_state.png">
@@ -815,8 +818,8 @@ moment when rule was evaluated.
 
 ### Debug mode
 
-vmalert allows configuring more detailed logging for specific alerting rule. Just set `debug: true` in rule's configuration
-and vmalert will start printing additional log messages:
+vmalert allows configuring more detailed logging for specific alerting rule starting from [v1.82](https://docs.victoriametrics.com/CHANGELOG.html#v1820).
+Just set `debug: true` in rule's configuration and vmalert will start printing additional log messages:
 ```terminal
 2022-09-15T13:35:41.155Z  DEBUG rule "TestGroup":"Conns" (2601299393013563564) at 2022-09-15T15:35:41+02:00: query returned 0 samples (elapsed: 5.896041ms)
 2022-09-15T13:35:56.149Z  DEBUG datasource request: executing POST request with params "denyPartialResponse=true&query=sum%28vm_tcplistener_conns%7Binstance%3D%22localhost%3A8429%22%7D%29+by%28instance%29+%3E+0&step=15s&time=1663248945"
@@ -829,7 +832,8 @@ and vmalert will start printing additional log messages:
 
 ### Never-firing alerts
 
-vmalert can detect if alert's expression doesn't match any time series in runtime. This problem usually happens
+vmalert can detect if alert's expression doesn't match any time series in runtime 
+starting from [v1.91](https://docs.victoriametrics.com/CHANGELOG.html#v1910). This problem usually happens
 when alerting expression selects time series which aren't present in the datasource (i.e. wrong `job` label)
 or there is a typo in the series selector (i.e. `env=rpod`). Such alerting rules will be marked with special icon in 
 vmalerts UI and exposed via `vmalert_alerting_rules_last_evaluation_series_fetched` metric. The metric value will
diff --git a/lib/logjson/parser.go b/lib/logjson/parser.go
new file mode 100644
index 000000000..6bc8919d5
--- /dev/null
+++ b/lib/logjson/parser.go
@@ -0,0 +1,132 @@
+package logjson
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
+	"github.com/valyala/fastjson"
+)
+
+// Parser parses a single JSON log message into Fields.
+//
+// See https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#data-model
+//
+// Use GetParser() for obtaining the parser.
+type Parser struct {
+	// Fields contains the parsed JSON line after Parse() call
+	//
+	// The Fields are valid until the next call to ParseLogMessage()
+	// or until the parser is returned to the pool with PutParser() call.
+	Fields []logstorage.Field
+
+	// p is used for fast JSON parsing
+	p fastjson.Parser
+
+	// buf is used for holding the backing data for Fields
+	buf []byte
+
+	// prefixBuf is used for holding the current key prefix
+	// when it is composed from multiple keys.
+	prefixBuf []byte
+}
+
+func (p *Parser) reset() {
+	fields := p.Fields
+	for i := range fields {
+		lf := &fields[i]
+		lf.Name = ""
+		lf.Value = ""
+	}
+	p.Fields = fields[:0]
+
+	p.buf = p.buf[:0]
+	p.prefixBuf = p.prefixBuf[:0]
+}
+
+// GetParser returns Parser ready to parse JSON lines.
+//
+// Return the parser to the pool when it is no longer needed by calling PutParser().
+func GetParser() *Parser {
+	v := parserPool.Get()
+	if v == nil {
+		return &Parser{}
+	}
+	return v.(*Parser)
+}
+
+// PutParser returns the parser to the pool.
+//
+// The parser cannot be used after returning to the pool.
+func PutParser(p *Parser) {
+	p.reset()
+	parserPool.Put(p)
+}
+
+var parserPool sync.Pool
+
+// ParseLogMessage parses the given JSON log message msg into p.Fields.
+//
+// The p.Fields remains valid until the next call to ParseLogMessage() or PutParser().
+func (p *Parser) ParseLogMessage(msg []byte) error {
+	s := bytesutil.ToUnsafeString(msg)
+	v, err := p.p.Parse(s)
+	if err != nil {
+		return fmt.Errorf("cannot parse json: %w", err)
+	}
+	if t := v.Type(); t != fastjson.TypeObject {
+		return fmt.Errorf("expecting json dictionary; got %s", t)
+	}
+	p.reset()
+	p.Fields, p.buf, p.prefixBuf = appendLogFields(p.Fields, p.buf, p.prefixBuf, v)
+	return nil
+}
+
+func appendLogFields(dst []logstorage.Field, dstBuf, prefixBuf []byte, v *fastjson.Value) ([]logstorage.Field, []byte, []byte) {
+	o := v.GetObject()
+	o.Visit(func(k []byte, v *fastjson.Value) {
+		t := v.Type()
+		switch t {
+		case fastjson.TypeNull:
+			// Skip nulls
+		case fastjson.TypeObject:
+			// Flatten nested JSON objects.
+			// For example, {"foo":{"bar":"baz"}} is converted to {"foo.bar":"baz"}
+			prefixLen := len(prefixBuf)
+			prefixBuf = append(prefixBuf, k...)
+			prefixBuf = append(prefixBuf, '.')
+			dst, dstBuf, prefixBuf = appendLogFields(dst, dstBuf, prefixBuf, v)
+			prefixBuf = prefixBuf[:prefixLen]
+		case fastjson.TypeArray, fastjson.TypeNumber, fastjson.TypeTrue, fastjson.TypeFalse:
+			// Convert JSON arrays, numbers, true and false values to their string representation
+			dstBufLen := len(dstBuf)
+			dstBuf = v.MarshalTo(dstBuf)
+			value := dstBuf[dstBufLen:]
+			dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
+		case fastjson.TypeString:
+			// Decode JSON strings
+			dstBufLen := len(dstBuf)
+			dstBuf = append(dstBuf, v.GetStringBytes()...)
+			value := dstBuf[dstBufLen:]
+			dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
+		default:
+			logger.Panicf("BUG: unexpected JSON type: %s", t)
+		}
+	})
+	return dst, dstBuf, prefixBuf
+}
+
+func appendLogField(dst []logstorage.Field, dstBuf, prefixBuf, k, value []byte) ([]logstorage.Field, []byte) {
+	dstBufLen := len(dstBuf)
+	dstBuf = append(dstBuf, prefixBuf...)
+	dstBuf = append(dstBuf, k...)
+	name := dstBuf[dstBufLen:]
+
+	dst = append(dst, logstorage.Field{
+		Name:  bytesutil.ToUnsafeString(name),
+		Value: bytesutil.ToUnsafeString(value),
+	})
+	return dst, dstBuf
+}


Test patch:
diff --git a/app/vlselect/logsql/sort_writer_test.go b/app/vlselect/logsql/sort_writer_test.go
new file mode 100644
index 000000000..9313bed85
--- /dev/null
+++ b/app/vlselect/logsql/sort_writer_test.go
@@ -0,0 +1,39 @@
+package logsql
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestSortWriter(t *testing.T) {
+	f := func(maxBufLen int, data string, expectedResult string) {
+		t.Helper()
+
+		var bb bytes.Buffer
+		sw := getSortWriter()
+		sw.Init(&bb, maxBufLen)
+
+		for _, s := range strings.Split(data, "\n") {
+			sw.MustWrite([]byte(s + "\n"))
+		}
+		sw.FinalFlush()
+		putSortWriter(sw)
+
+		result := bb.String()
+		if result != expectedResult {
+			t.Fatalf("unexpected result;\ngot\n%s\nwant\n%s", result, expectedResult)
+		}
+	}
+
+	f(100, "", "")
+	f(100, "{}", "{}\n")
+
+	data := `{"_time":"def","_msg":"xxx"}
+{"_time":"abc","_msg":"foo"}`
+	resultExpected := `{"_time":"abc","_msg":"foo"}
+{"_time":"def","_msg":"xxx"}
+`
+	f(100, data, resultExpected)
+	f(10, data, data+"\n")
+}
diff --git a/lib/logjson/parser_test.go b/lib/logjson/parser_test.go
new file mode 100644
index 000000000..ee4f40faf
--- /dev/null
+++ b/lib/logjson/parser_test.go
@@ -0,0 +1,71 @@
+package logjson
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
+)
+
+func TestParserFailure(t *testing.T) {
+	f := func(data string) {
+		t.Helper()
+
+		p := GetParser()
+		err := p.ParseLogMessage([]byte(data))
+		if err == nil {
+			t.Fatalf("expecting non-nil error")
+		}
+		PutParser(p)
+	}
+	f("")
+	f("{foo")
+	f("[1,2,3]")
+	f(`{"foo",}`)
+}
+
+func TestParserSuccess(t *testing.T) {
+	f := func(data string, fieldsExpected []logstorage.Field) {
+		t.Helper()
+
+		p := GetParser()
+		err := p.ParseLogMessage([]byte(data))
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if !reflect.DeepEqual(p.Fields, fieldsExpected) {
+			t.Fatalf("unexpected fields;\ngot\n%s\nwant\n%s", p.Fields, fieldsExpected)
+		}
+		PutParser(p)
+	}
+
+	f("{}", nil)
+	f(`{"foo":"bar"}`, []logstorage.Field{
+		{
+			Name:  "foo",
+			Value: "bar",
+		},
+	})
+	f(`{"foo":{"bar":"baz"},"a":1,"b":true,"c":[1,2],"d":false}`, []logstorage.Field{
+		{
+			Name:  "foo.bar",
+			Value: "baz",
+		},
+		{
+			Name:  "a",
+			Value: "1",
+		},
+		{
+			Name:  "b",
+			Value: "true",
+		},
+		{
+			Name:  "c",
+			Value: "[1,2]",
+		},
+		{
+			Name:  "d",
+			Value: "false",
+		},
+	})
+}