Skip to content

Commit a206324

Browse files
fix: pattern persistence feature flag (#18285)
1 parent a76e9d5 commit a206324

6 files changed

Lines changed: 389 additions & 48 deletions

File tree

‎pkg/pattern/drain/drain.go‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,18 @@ func DefaultConfig() *Config {
144144
}
145145
}
146146

147-
func New(tenantID string, config *Config, limits Limits, format string, writer aggregation.EntryWriter, metrics *Metrics) *Drain {
147+
func New(tenantID string, config *Config, limits Limits, format string, patternWriter aggregation.EntryWriter, metrics *Metrics) *Drain {
148148
if config.LogClusterDepth < 3 {
149149
panic("depth argument must be at least 3")
150150
}
151151
config.maxNodeDepth = config.LogClusterDepth - 2
152152

153153
d := &Drain{
154-
config: config,
155-
rootNode: createNode(),
156-
metrics: metrics,
157-
format: format,
158-
writer: writer,
154+
config: config,
155+
rootNode: createNode(),
156+
metrics: metrics,
157+
format: format,
158+
patternWriter: patternWriter,
159159
}
160160

161161
limiter := newLimiter(config.MaxEvictionRatio)
@@ -203,7 +203,7 @@ type Drain struct {
203203
state interface{}
204204
limiter *limiter
205205
pruning bool
206-
writer aggregation.EntryWriter
206+
patternWriter aggregation.EntryWriter
207207
}
208208

209209
func (d *Drain) Clusters() []*LogCluster {
@@ -313,8 +313,8 @@ func (d *Drain) writePattern(
313313
{Name: constants.LevelLabel, Value: lvl},
314314
}
315315

316-
if d.writer != nil {
317-
d.writer.WriteEntry(
316+
if d.patternWriter != nil {
317+
d.patternWriter.WriteEntry(
318318
ts.Time(),
319319
aggregation.PatternEntry(ts.Time(), count, pattern, streamLbls),
320320
newLbls,

‎pkg/pattern/ingester.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ func (i *Ingester) GetOrCreateInstance(instanceID string) (*instance, error) { /
424424
var patternWriter aggregation.EntryWriter
425425
patternCfg := i.cfg.PatternPersistence
426426
if i.limits.PatternPersistenceEnabled(instanceID) {
427-
metricWriter, err = aggregation.NewPush(
427+
patternWriter, err = aggregation.NewPush(
428428
patternCfg.LokiAddr,
429429
instanceID,
430430
patternCfg.WriteTimeout,
@@ -487,6 +487,9 @@ func (i *Ingester) stopWriters() {
487487
if instance.metricWriter != nil {
488488
instance.metricWriter.Stop()
489489
}
490+
if instance.patternWriter != nil {
491+
instance.patternWriter.Stop()
492+
}
490493
}
491494
}
492495

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package pattern
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/go-kit/log"
9+
"github.com/grafana/dskit/flagext"
10+
"github.com/grafana/dskit/kv"
11+
"github.com/grafana/dskit/ring"
12+
"github.com/grafana/dskit/services"
13+
"github.com/grafana/dskit/user"
14+
"github.com/stretchr/testify/mock"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
// TestPatternPersistenceConfiguration tests that pattern persistence writers
19+
// are correctly configured based on the limits configuration.
20+
func TestPatternPersistenceConfiguration(t *testing.T) {
21+
tests := []struct {
22+
name string
23+
patternPersistenceEnabled bool
24+
metricAggregationEnabled bool
25+
expectPatternWriter bool
26+
expectMetricWriter bool
27+
}{
28+
{
29+
name: "both disabled",
30+
patternPersistenceEnabled: false,
31+
metricAggregationEnabled: false,
32+
expectPatternWriter: false,
33+
expectMetricWriter: false,
34+
},
35+
{
36+
name: "only pattern persistence enabled",
37+
patternPersistenceEnabled: true,
38+
metricAggregationEnabled: false,
39+
expectPatternWriter: true,
40+
expectMetricWriter: false,
41+
},
42+
{
43+
name: "only metric aggregation enabled",
44+
patternPersistenceEnabled: false,
45+
metricAggregationEnabled: true,
46+
expectPatternWriter: false,
47+
expectMetricWriter: true,
48+
},
49+
{
50+
name: "both enabled",
51+
patternPersistenceEnabled: true,
52+
metricAggregationEnabled: true,
53+
expectPatternWriter: true,
54+
expectMetricWriter: true,
55+
},
56+
}
57+
58+
for _, tt := range tests {
59+
t.Run(tt.name, func(t *testing.T) {
60+
// Setup mock limits
61+
limits := &configurableLimits{
62+
patternPersistenceEnabled: tt.patternPersistenceEnabled,
63+
metricAggregationEnabled: tt.metricAggregationEnabled,
64+
}
65+
66+
// Setup ring
67+
replicationSet := ring.ReplicationSet{
68+
Instances: []ring.InstanceDesc{
69+
{Id: "localhost", Addr: "ingester0"},
70+
},
71+
}
72+
73+
fakeRing := &fakeRing{}
74+
fakeRing.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
75+
Return(replicationSet, nil)
76+
77+
ringClient := &fakeRingClient{
78+
ring: fakeRing,
79+
}
80+
81+
// Create ingester with the specified configuration
82+
cfg := testIngesterConfig(t)
83+
ing, err := New(cfg, limits, ringClient, "test", nil, log.NewNopLogger())
84+
require.NoError(t, err)
85+
defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
86+
87+
err = services.StartAndAwaitRunning(context.Background(), ing)
88+
require.NoError(t, err)
89+
90+
// Create test instance
91+
_ = user.InjectOrgID(context.Background(), "test-tenant")
92+
instance, err := ing.GetOrCreateInstance("test-tenant")
93+
require.NoError(t, err)
94+
95+
// Verify writer configuration based on test expectations
96+
if tt.expectPatternWriter {
97+
require.NotNil(t, instance.patternWriter, "pattern writer should be configured when pattern persistence is enabled")
98+
} else {
99+
require.Nil(t, instance.patternWriter, "pattern writer should be nil when pattern persistence is disabled")
100+
}
101+
102+
if tt.expectMetricWriter {
103+
require.NotNil(t, instance.metricWriter, "metric writer should be configured when metric aggregation is enabled")
104+
} else {
105+
require.Nil(t, instance.metricWriter, "metric writer should be nil when metric aggregation is disabled")
106+
}
107+
108+
// Verify they are different instances when both are enabled
109+
if tt.expectPatternWriter && tt.expectMetricWriter {
110+
require.NotEqual(t, instance.patternWriter, instance.metricWriter,
111+
"pattern writer and metric writer should be different instances")
112+
}
113+
})
114+
}
115+
}
116+
117+
// TestPatternPersistenceStopWriters tests that both pattern and metric writers
118+
// are properly stopped when the ingester shuts down
119+
func TestPatternPersistenceStopWriters(t *testing.T) {
120+
mockPatternWriter := &mockEntryWriter{}
121+
mockMetricWriter := &mockEntryWriter{}
122+
123+
mockPatternWriter.On("Stop").Return()
124+
mockMetricWriter.On("Stop").Return()
125+
126+
replicationSet := ring.ReplicationSet{
127+
Instances: []ring.InstanceDesc{
128+
{Id: "localhost", Addr: "ingester0"},
129+
},
130+
}
131+
132+
fakeRing := &fakeRing{}
133+
fakeRing.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
134+
Return(replicationSet, nil)
135+
136+
ringClient := &fakeRingClient{ring: fakeRing}
137+
138+
cfg := testIngesterConfig(t)
139+
ing, err := New(cfg, &configurableLimits{
140+
patternPersistenceEnabled: true,
141+
metricAggregationEnabled: true,
142+
}, ringClient, "test", nil, log.NewNopLogger())
143+
require.NoError(t, err)
144+
145+
err = services.StartAndAwaitRunning(context.Background(), ing)
146+
require.NoError(t, err)
147+
148+
// Create instance and replace writers with mocks
149+
instance, err := ing.GetOrCreateInstance("test-tenant")
150+
require.NoError(t, err)
151+
152+
instance.patternWriter = mockPatternWriter
153+
instance.metricWriter = mockMetricWriter
154+
155+
// Stop the ingester - this should call stopWriters
156+
err = services.StopAndAwaitTerminated(context.Background(), ing)
157+
require.NoError(t, err)
158+
159+
// Verify both writers were stopped
160+
mockPatternWriter.AssertCalled(t, "Stop")
161+
mockMetricWriter.AssertCalled(t, "Stop")
162+
}
163+
164+
// configurableLimits implements the Limits interface with configurable pattern persistence
165+
type configurableLimits struct {
166+
patternPersistenceEnabled bool
167+
metricAggregationEnabled bool
168+
}
169+
170+
var _ Limits = &configurableLimits{}
171+
172+
func (c *configurableLimits) PatternIngesterTokenizableJSONFields(_ string) []string {
173+
return []string{"log", "message", "msg", "msg_", "_msg", "content"}
174+
}
175+
176+
func (c *configurableLimits) PatternPersistenceEnabled(_ string) bool {
177+
return c.patternPersistenceEnabled
178+
}
179+
180+
func (c *configurableLimits) MetricAggregationEnabled(_ string) bool {
181+
return c.metricAggregationEnabled
182+
}
183+
184+
func testIngesterConfig(t testing.TB) Config {
185+
kvClient, err := kv.NewClient(kv.Config{Store: "inmemory"}, ring.GetCodec(), nil, log.NewNopLogger())
186+
require.NoError(t, err)
187+
188+
cfg := Config{}
189+
flagext.DefaultValues(&cfg)
190+
cfg.FlushCheckPeriod = 99999 * time.Hour
191+
cfg.ConcurrentFlushes = 1
192+
cfg.LifecyclerConfig.RingConfig.KVStore.Mock = kvClient
193+
cfg.LifecyclerConfig.NumTokens = 1
194+
cfg.LifecyclerConfig.ListenPort = 0
195+
cfg.LifecyclerConfig.Addr = "localhost"
196+
cfg.LifecyclerConfig.ID = "localhost"
197+
cfg.LifecyclerConfig.FinalSleep = 0
198+
cfg.LifecyclerConfig.MinReadyDuration = 0
199+
200+
// Configure pattern persistence
201+
cfg.PatternPersistence.LokiAddr = "http://localhost:3100"
202+
cfg.PatternPersistence.WriteTimeout = 30 * time.Second
203+
cfg.PatternPersistence.PushPeriod = 10 * time.Second
204+
cfg.PatternPersistence.BatchSize = 1000
205+
206+
// Configure metric aggregation
207+
cfg.MetricAggregation.LokiAddr = "http://localhost:3100"
208+
cfg.MetricAggregation.WriteTimeout = 30 * time.Second
209+
cfg.MetricAggregation.SamplePeriod = 10 * time.Second
210+
211+
return cfg
212+
}

‎pkg/pattern/stream.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func newStream(
4040
instanceID string,
4141
drainCfg *drain.Config,
4242
drainLimits drain.Limits,
43-
writer aggregation.EntryWriter,
43+
patternWriter aggregation.EntryWriter,
4444
) (*stream, error) {
4545
linesSkipped, err := metrics.linesSkipped.CurryWith(prometheus.Labels{"tenant": instanceID})
4646
if err != nil {
@@ -49,7 +49,7 @@ func newStream(
4949

5050
patterns := make(map[string]*drain.Drain, len(constants.LogLevels))
5151
for _, lvl := range constants.LogLevels {
52-
patterns[lvl] = drain.New(instanceID, drainCfg, drainLimits, guessedFormat, writer, &drain.Metrics{
52+
patterns[lvl] = drain.New(instanceID, drainCfg, drainLimits, guessedFormat, patternWriter, &drain.Metrics{
5353
PatternsEvictedTotal: metrics.patternsDiscardedTotal.WithLabelValues(instanceID, guessedFormat, "false"),
5454
PatternsPrunedTotal: metrics.patternsDiscardedTotal.WithLabelValues(instanceID, guessedFormat, "true"),
5555
PatternsDetectedTotal: metrics.patternsDetectedTotal.WithLabelValues(instanceID, guessedFormat),

‎pkg/querier/http.go‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -447,18 +447,27 @@ func (q *QuerierAPI) PatternsHandler(ctx context.Context, req *logproto.QueryPat
447447
}
448448

449449
// Query store for older data by converting to LogQL query
450+
// Only query the store if pattern persistence is enabled for this tenant
450451
if storeQueryInterval != nil && !q.cfg.QueryIngesterOnly && q.engineV1 != nil {
451-
g.Go(func() error {
452-
storeReq := *req
453-
storeReq.Start = storeQueryInterval.start
454-
storeReq.End = storeQueryInterval.end
455-
resp, err := q.queryStoreForPatterns(ctx, &storeReq)
456-
if err != nil {
457-
return err
458-
}
459-
responses.add(resp)
460-
return nil
461-
})
452+
tenantID, err := tenant.TenantID(ctx)
453+
if err != nil {
454+
return nil, err
455+
}
456+
457+
// Only query the store if pattern persistence is enabled for this tenant
458+
if q.limits.PatternPersistenceEnabled(tenantID) {
459+
g.Go(func() error {
460+
storeReq := *req
461+
storeReq.Start = storeQueryInterval.start
462+
storeReq.End = storeQueryInterval.end
463+
resp, err := q.queryStoreForPatterns(ctx, &storeReq)
464+
if err != nil {
465+
return err
466+
}
467+
responses.add(resp)
468+
return nil
469+
})
470+
}
462471
}
463472

464473
if err := g.Wait(); err != nil {

0 commit comments

Comments
 (0)