Vmalert fixes (#3788)

* vmalert: use group's ID in UI to avoid collisions

Identical group names are allowed. So we should used IDs
for various groupings and aggregations in UI.

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: prevent disabling state updates tracking

The minimum number of update states to track is now set to 1.

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: properly update `debug` and `update_entries_limit` params on hot-reload

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: display `debug` field for rule in UI

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: exclude `updates` field from json marhsaling

This field isn't correctly marshaled right now.
And implementing the correct marshaling for it doesn't
seem right, since json representation is mostly used
by systems like Grafana. And Grafana doesn't expect this
field to be present.

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* fix test for disabled state

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* fix test for disabled state

Signed-off-by: hagen1778 <roman@victoriametrics.com>

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2023-02-08 14:34:03 +01:00 committed by Aliaksandr Valialkin
parent 9d658ccce3
commit 4e922eb93b
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
7 changed files with 143 additions and 125 deletions

View file

@ -421,7 +421,9 @@ func (ar *AlertingRule) UpdateWith(r Rule) error {
ar.Labels = nr.Labels
ar.Annotations = nr.Annotations
ar.EvalInterval = nr.EvalInterval
ar.Debug = nr.Debug
ar.q = nr.q
ar.state = nr.state
return nil
}
@ -498,6 +500,7 @@ func (ar *AlertingRule) ToAPI() APIRule {
LastSamples: lastState.samples,
MaxUpdates: ar.state.size(),
Updates: ar.state.getAll(),
Debug: ar.Debug,
// encode as strings to avoid rounding in JSON
ID: fmt.Sprintf("%d", ar.ID()),

View file

@ -37,8 +37,6 @@ type ruleState struct {
sync.RWMutex
entries []ruleStateEntry
cur int
// disabled defines whether ruleState tracks ruleStateEntry
disabled bool
}
type ruleStateEntry struct {
@ -61,7 +59,7 @@ type ruleStateEntry struct {
func newRuleState(size int) *ruleState {
if size < 1 {
return &ruleState{disabled: true}
size = 1
}
return &ruleState{
entries: make([]ruleStateEntry, size),
@ -69,10 +67,6 @@ func newRuleState(size int) *ruleState {
}
func (s *ruleState) getLast() ruleStateEntry {
if s.disabled {
return ruleStateEntry{}
}
s.RLock()
defer s.RUnlock()
return s.entries[s.cur]
@ -85,10 +79,6 @@ func (s *ruleState) size() int {
}
func (s *ruleState) getAll() []ruleStateEntry {
if s.disabled {
return nil
}
entries := make([]ruleStateEntry, 0)
s.RLock()
@ -111,10 +101,6 @@ func (s *ruleState) getAll() []ruleStateEntry {
}
func (s *ruleState) add(e ruleStateEntry) {
if s.disabled {
return
}
s.Lock()
defer s.Unlock()

View file

@ -14,13 +14,13 @@ func TestRule_stateDisabled(t *testing.T) {
}
state.add(ruleStateEntry{at: time.Now()})
if !e.at.IsZero() {
t.Fatalf("expected entry to be zero")
}
state.add(ruleStateEntry{at: time.Now()})
state.add(ruleStateEntry{at: time.Now()})
if len(state.getAll()) != 0 {
if len(state.getAll()) != 1 {
// state should store at least one update at any circumstances
t.Fatalf("expected for state to have %d entries; got %d",
0, len(state.getAll()),
1, len(state.getAll()),
)
}
}

View file

@ -40,9 +40,9 @@
for _, g := range groups {
for _, r := range g.Rules {
if r.LastError != "" {
rNotOk[g.Name]++
rNotOk[g.ID]++
} else {
rOk[g.Name]++
rOk[g.ID]++
}
}
}
@ -50,11 +50,11 @@
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
{% for _, g := range groups %}
<div class="group-heading{% if rNotOk[g.Name] > 0 %} alert-danger{% endif %}" data-bs-target="rules-{%s g.ID %}">
<div class="group-heading{% if rNotOk[g.ID] > 0 %} alert-danger{% endif %}" data-bs-target="rules-{%s g.ID %}">
<span class="anchor" id="group-{%s g.ID %}"></span>
<a href="#group-{%s g.ID %}">{%s g.Name %}{% if g.Type != "prometheus" %} ({%s g.Type %}){% endif %} (every {%f.0 g.Interval %}s)</a>
{% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.Name] %}</span> {% endif %}
<span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.Name] %}</span>
{% if rNotOk[g.ID] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.ID] %}</span> {% endif %}
<span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.ID] %}</span>
<p class="fs-6 fw-lighter">{%s g.File %}</p>
{% if len(g.Params) > 0 %}
<div class="fs-6 fw-lighter">Extra params
@ -427,6 +427,16 @@
</div>
</div>
</div>
<div class="container border-bottom p-2">
<div class="row">
<div class="col-2">
Debug
</div>
<div class="col">
{%v rule.Debug %}
</div>
</div>
</div>
{% endif %}
<div class="container border-bottom p-2">
<div class="row">

View file

@ -171,9 +171,9 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
for _, g := range groups {
for _, r := range g.Rules {
if r.LastError != "" {
rNotOk[g.Name]++
rNotOk[g.ID]++
} else {
rOk[g.Name]++
rOk[g.ID]++
}
}
}
@ -189,7 +189,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
qw422016.N().S(`
<div class="group-heading`)
//line app/vmalert/web.qtpl:53
if rNotOk[g.Name] > 0 {
if rNotOk[g.ID] > 0 {
//line app/vmalert/web.qtpl:53
qw422016.N().S(` alert-danger`)
//line app/vmalert/web.qtpl:53
@ -230,11 +230,11 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
qw422016.N().S(`s)</a>
`)
//line app/vmalert/web.qtpl:56
if rNotOk[g.Name] > 0 {
if rNotOk[g.ID] > 0 {
//line app/vmalert/web.qtpl:56
qw422016.N().S(`<span class="badge bg-danger" title="Number of rules with status Error">`)
//line app/vmalert/web.qtpl:56
qw422016.N().D(rNotOk[g.Name])
qw422016.N().D(rNotOk[g.ID])
//line app/vmalert/web.qtpl:56
qw422016.N().S(`</span> `)
//line app/vmalert/web.qtpl:56
@ -243,7 +243,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
qw422016.N().S(`
<span class="badge bg-success" title="Number of rules withs status Ok">`)
//line app/vmalert/web.qtpl:57
qw422016.N().D(rOk[g.Name])
qw422016.N().D(rOk[g.ID])
//line app/vmalert/web.qtpl:57
qw422016.N().S(`</span>
<p class="fs-6 fw-lighter">`)
@ -1313,10 +1313,24 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
</div>
</div>
</div>
<div class="container border-bottom p-2">
<div class="row">
<div class="col-2">
Debug
</div>
<div class="col">
`)
//line app/vmalert/web.qtpl:436
qw422016.E().V(rule.Debug)
//line app/vmalert/web.qtpl:436
qw422016.N().S(`
</div>
</div>
</div>
`)
//line app/vmalert/web.qtpl:430
//line app/vmalert/web.qtpl:440
}
//line app/vmalert/web.qtpl:430
//line app/vmalert/web.qtpl:440
qw422016.N().S(`
<div class="container border-bottom p-2">
<div class="row">
@ -1325,17 +1339,17 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
</div>
<div class="col">
<a target="_blank" href="`)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.E().S(prefix)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.N().S(`groups#group-`)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.E().S(rule.GroupID)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.E().S(rule.GroupID)
//line app/vmalert/web.qtpl:437
//line app/vmalert/web.qtpl:447
qw422016.N().S(`</a>
</div>
</div>
@ -1343,13 +1357,13 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
<br>
<div class="display-6 pb-3">Last `)
//line app/vmalert/web.qtpl:443
//line app/vmalert/web.qtpl:453
qw422016.N().D(len(rule.Updates))
//line app/vmalert/web.qtpl:443
//line app/vmalert/web.qtpl:453
qw422016.N().S(`/`)
//line app/vmalert/web.qtpl:443
//line app/vmalert/web.qtpl:453
qw422016.N().D(rule.MaxUpdates)
//line app/vmalert/web.qtpl:443
//line app/vmalert/web.qtpl:453
qw422016.N().S(` updates</span>:</div>
<table class="table table-striped table-hover table-sm">
<thead>
@ -1364,201 +1378,201 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
<tbody>
`)
//line app/vmalert/web.qtpl:456
//line app/vmalert/web.qtpl:466
for _, u := range rule.Updates {
//line app/vmalert/web.qtpl:456
//line app/vmalert/web.qtpl:466
qw422016.N().S(`
<tr`)
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:467
if u.err != nil {
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:467
qw422016.N().S(` class="alert-danger"`)
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:467
}
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:467
qw422016.N().S(`>
<td>
<span class="badge bg-primary rounded-pill me-3" title="Updated at">`)
//line app/vmalert/web.qtpl:459
//line app/vmalert/web.qtpl:469
qw422016.E().S(u.time.Format(time.RFC3339))
//line app/vmalert/web.qtpl:459
//line app/vmalert/web.qtpl:469
qw422016.N().S(`</span>
</td>
<td class="text-center" wi>`)
//line app/vmalert/web.qtpl:461
//line app/vmalert/web.qtpl:471
qw422016.N().D(u.samples)
//line app/vmalert/web.qtpl:461
//line app/vmalert/web.qtpl:471
qw422016.N().S(`</td>
<td class="text-center">`)
//line app/vmalert/web.qtpl:462
//line app/vmalert/web.qtpl:472
qw422016.N().FPrec(u.duration.Seconds(), 3)
//line app/vmalert/web.qtpl:462
//line app/vmalert/web.qtpl:472
qw422016.N().S(`s</td>
<td class="text-center">`)
//line app/vmalert/web.qtpl:463
//line app/vmalert/web.qtpl:473
qw422016.E().S(u.at.Format(time.RFC3339))
//line app/vmalert/web.qtpl:463
//line app/vmalert/web.qtpl:473
qw422016.N().S(`</td>
<td>
<textarea class="curl-area" rows="1" onclick="this.focus();this.select()">`)
//line app/vmalert/web.qtpl:465
//line app/vmalert/web.qtpl:475
qw422016.E().S(u.curl)
//line app/vmalert/web.qtpl:465
//line app/vmalert/web.qtpl:475
qw422016.N().S(`</textarea>
</td>
</tr>
</li>
`)
//line app/vmalert/web.qtpl:469
//line app/vmalert/web.qtpl:479
if u.err != nil {
//line app/vmalert/web.qtpl:469
//line app/vmalert/web.qtpl:479
qw422016.N().S(`
<tr`)
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:480
if u.err != nil {
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:480
qw422016.N().S(` class="alert-danger"`)
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:480
}
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:480
qw422016.N().S(`>
<td colspan="5">
<span class="alert-danger">`)
//line app/vmalert/web.qtpl:472
//line app/vmalert/web.qtpl:482
qw422016.E().V(u.err)
//line app/vmalert/web.qtpl:472
//line app/vmalert/web.qtpl:482
qw422016.N().S(`</span>
</td>
</tr>
`)
//line app/vmalert/web.qtpl:475
//line app/vmalert/web.qtpl:485
}
//line app/vmalert/web.qtpl:475
//line app/vmalert/web.qtpl:485
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:476
//line app/vmalert/web.qtpl:486
}
//line app/vmalert/web.qtpl:476
//line app/vmalert/web.qtpl:486
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:488
tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:488
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
}
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
func WriteRuleDetails(qq422016 qtio422016.Writer, r *http.Request, rule APIRule) {
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
StreamRuleDetails(qw422016, r, rule)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
}
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
func RuleDetails(r *http.Request, rule APIRule) string {
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
WriteRuleDetails(qb422016, r, rule)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
return qs422016
//line app/vmalert/web.qtpl:479
//line app/vmalert/web.qtpl:489
}
//line app/vmalert/web.qtpl:483
//line app/vmalert/web.qtpl:493
func streambadgeState(qw422016 *qt422016.Writer, state string) {
//line app/vmalert/web.qtpl:483
//line app/vmalert/web.qtpl:493
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:485
//line app/vmalert/web.qtpl:495
badgeClass := "bg-warning text-dark"
if state == "firing" {
badgeClass = "bg-danger"
}
//line app/vmalert/web.qtpl:489
//line app/vmalert/web.qtpl:499
qw422016.N().S(`
<span class="badge `)
//line app/vmalert/web.qtpl:490
//line app/vmalert/web.qtpl:500
qw422016.E().S(badgeClass)
//line app/vmalert/web.qtpl:490
//line app/vmalert/web.qtpl:500
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:490
//line app/vmalert/web.qtpl:500
qw422016.E().S(state)
//line app/vmalert/web.qtpl:490
//line app/vmalert/web.qtpl:500
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
}
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
func writebadgeState(qq422016 qtio422016.Writer, state string) {
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
streambadgeState(qw422016, state)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
}
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
func badgeState(state string) string {
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
writebadgeState(qb422016, state)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
return qs422016
//line app/vmalert/web.qtpl:491
//line app/vmalert/web.qtpl:501
}
//line app/vmalert/web.qtpl:493
//line app/vmalert/web.qtpl:503
func streambadgeRestored(qw422016 *qt422016.Writer) {
//line app/vmalert/web.qtpl:493
//line app/vmalert/web.qtpl:503
qw422016.N().S(`
<span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span>
`)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
}
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
func writebadgeRestored(qq422016 qtio422016.Writer) {
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
}
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
func badgeRestored() string {
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
writebadgeRestored(qb422016)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
return qs422016
//line app/vmalert/web.qtpl:495
//line app/vmalert/web.qtpl:505
}

View file

@ -113,18 +113,20 @@ type APIRule struct {
// Additional fields
// Type of the rule: recording or alerting
// DatasourceType of the rule: prometheus or graphite
DatasourceType string `json:"datasourceType"`
LastSamples int `json:"lastSamples"`
// ID is a unique Alert's ID within a group
ID string `json:"id"`
// GroupID is an unique Group's ID
GroupID string `json:"group_id"`
// Debug shows whether debug mode is enabled
Debug bool `json:"debug"`
// MaxUpdates is the max number of recorded ruleStateEntry objects
MaxUpdates int `json:"max_updates_entries"`
// Updates contains the ordered list of recorded ruleStateEntry objects
Updates []ruleStateEntry `json:"updates"`
Updates []ruleStateEntry `json:"-"`
}
// WebLink returns a link to the alert which can be used in UI.

View file

@ -21,6 +21,9 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): optimize VMUI for use from smarthones and tablets. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3707).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): fix panic in nomad watcher because of improperly initialized object. Thanks to @mr-karan for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3784)
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): fix display of rules number per-group for groups with identical names in UI.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): prevent disabling state updates tracking per rule via setting values < 1. The minimum number of update states to track is now set to 1.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly update `debug` and `update_entries_limit` rule's params on config's hot-reload.
## [v1.87.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.87.0)
@ -102,7 +105,7 @@ Released at 2023-01-10
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): allow using unicode letters in identifiers. For example, `температура{город="Киев"}` is a valid MetricsQL expression now. Previously every non-ascii letters should be escaped with `\` char when used inside MetricsQL expression: `\т\е\м\п\е\р\а\т\у\р\а{\г\о\р\о\д="Киев"}`. Now both expressions are equivalent. Thanks to @hzwwww for the [pull request](https://github.com/VictoriaMetrics/metricsql/pull/7).
* FEATURE: [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling): add support for `keepequal` and `dropequal` relabeling actions, which are supported by Prometheus starting from [v2.41.0](https://github.com/prometheus/prometheus/releases/tag/v2.41.0). These relabeling actions are almost identical to `keep_if_equal` and `drop_if_equal` relabeling actions supported by VictoriaMetrics since `v1.38.0` - see [these docs](https://docs.victoriametrics.com/vmagent.html#relabeling-enhancements) - so it is recommended sticking to `keep_if_equal` and `drop_if_equal` actions instead of switching to `keepequal` and `dropequal`.
* FEATURE: [csvimport](https://docs.victoriametrics.com/#how-to-import-csv-data): support empty values for imported metrics. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3540).
* FEATURE: [vmalert](httpпоs://docs.victoriametrics.com/vmalert.html): allow configuring the default number of stored rule's update states in memory via global `-rule.updateEntriesLimit` command-line flag or per-rule via rule's `update_entries_limit` configuration param. See [these docs](https://docs.victoriametrics.com/vmalert.html#rules) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3556).
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow configuring the default number of stored rule's update states in memory via global `-rule.updateEntriesLimit` command-line flag or per-rule via rule's `update_entries_limit` configuration param. See [these docs](https://docs.victoriametrics.com/vmalert.html#rules) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3556).
* FEATURE: improve the logic benhind `-maxConcurrentInserts` command-line flag. Previously this flag was limiting the number of concurrent connections from clients, which write data to VictoriaMetrics or [vmagent](https://docs.victoriametrics.com/vmagent.html). Some of these connections could be idle for some time. These connections do not need significant amounts of CPU and memory, so there is no sense in limiting their count. The updated logic behind `-maxConcurrentInserts` limits the number of **active** insert requests, not counting idle connections.
* FEATURE: protect all the http endpoints with `-httpAuth.*` command-line flag. Previously endpoints protected by `-*AuthKey` command-line flags weren't protected by `-httpAuth.*`. This could complicate the proper security setup. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3060).
* FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add `-maxConcurrentInserts` and `-insert.maxQueueDuration` command-line flags to `vmstorage`, so they could be tuned if needed in the same way as at `vminsert` nodes.