github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/recovery_test.go (about) 1 package ingester 2 3 import ( 4 "context" 5 fmt "fmt" 6 "runtime" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/pkg/errors" 12 "github.com/prometheus/prometheus/model/labels" 13 "github.com/prometheus/prometheus/tsdb/chunks" 14 "github.com/prometheus/prometheus/tsdb/record" 15 "github.com/stretchr/testify/require" 16 "github.com/weaveworks/common/user" 17 18 "github.com/grafana/loki/pkg/ingester/client" 19 "github.com/grafana/loki/pkg/logproto" 20 loki_runtime "github.com/grafana/loki/pkg/runtime" 21 "github.com/grafana/loki/pkg/storage/chunk" 22 "github.com/grafana/loki/pkg/validation" 23 ) 24 25 type MemoryWALReader struct { 26 xs [][]byte 27 28 initialized bool 29 } 30 31 func (m *MemoryWALReader) Next() bool { 32 if len(m.xs) < 1 { 33 return false 34 } 35 36 // don't advance on the first call 37 if !m.initialized { 38 m.initialized = true 39 return true 40 } 41 42 m.xs = m.xs[1:] 43 return len(m.xs) > 0 44 } 45 46 func (m *MemoryWALReader) Err() error { return nil } 47 48 func (m *MemoryWALReader) Record() []byte { return m.xs[0] } 49 50 func buildMemoryReader(users, totalStreams, entriesPerStream int) (*MemoryWALReader, []*WALRecord) { 51 var recs []*WALRecord 52 reader := &MemoryWALReader{} 53 for i := 0; i < totalStreams; i++ { 54 user := fmt.Sprintf("%d", i%users) 55 recs = append(recs, &WALRecord{ 56 UserID: user, 57 Series: []record.RefSeries{ 58 { 59 Ref: chunks.HeadSeriesRef(i), 60 Labels: labels.FromMap( 61 map[string]string{ 62 "stream": fmt.Sprint(i), 63 "user": user, 64 }, 65 ), 66 }, 67 }, 68 }) 69 70 var entries []logproto.Entry 71 for j := 0; j < entriesPerStream; j++ { 72 entries = append(entries, logproto.Entry{ 73 Timestamp: time.Unix(int64(j), 0), 74 Line: fmt.Sprintf("%d", j), 75 }) 76 } 77 recs = append(recs, &WALRecord{ 78 UserID: user, 79 RefEntries: []RefEntries{ 80 { 81 Ref: chunks.HeadSeriesRef(i), 82 Entries: entries, 83 }, 84 }, 85 }) 86 } 87 88 for _, rec := range recs { 89 if len(rec.Series) > 0 { 90 reader.xs = append(reader.xs, rec.encodeSeries(nil)) 91 } 92 93 if len(rec.RefEntries) > 0 { 94 reader.xs = append(reader.xs, rec.encodeEntries(CurrentEntriesRec, nil)) 95 } 96 } 97 98 return reader, recs 99 } 100 101 type MemRecoverer struct { 102 users map[string]map[chunks.HeadSeriesRef][]logproto.Entry 103 done chan struct{} 104 105 sync.Mutex 106 usersCt, streamsCt, seriesCt int 107 } 108 109 func NewMemRecoverer() *MemRecoverer { 110 return &MemRecoverer{ 111 users: make(map[string]map[chunks.HeadSeriesRef][]logproto.Entry), 112 done: make(chan struct{}), 113 } 114 } 115 116 func (r *MemRecoverer) NumWorkers() int { return runtime.GOMAXPROCS(0) } 117 118 func (r *MemRecoverer) Series(_ *Series) error { return nil } 119 120 func (r *MemRecoverer) SetStream(userID string, series record.RefSeries) error { 121 r.Lock() 122 defer r.Unlock() 123 user, ok := r.users[userID] 124 if !ok { 125 user = make(map[chunks.HeadSeriesRef][]logproto.Entry) 126 r.users[userID] = user 127 r.usersCt++ 128 } 129 130 if _, exists := user[series.Ref]; exists { 131 return errors.Errorf("stream (%d) already exists for user (%s)", series.Ref, userID) 132 } 133 134 user[series.Ref] = make([]logproto.Entry, 0) 135 r.streamsCt++ 136 return nil 137 } 138 139 func (r *MemRecoverer) Push(userID string, entries RefEntries) error { 140 r.Lock() 141 defer r.Unlock() 142 143 user, ok := r.users[userID] 144 if !ok { 145 return errors.Errorf("unexpected user access (%s)", userID) 146 } 147 148 stream, ok := user[entries.Ref] 149 if !ok { 150 return errors.Errorf("unexpected stream access") 151 } 152 153 r.seriesCt += len(entries.Entries) 154 user[entries.Ref] = append(stream, entries.Entries...) 155 return nil 156 } 157 158 func (r *MemRecoverer) Close() { close(r.done) } 159 160 func (r *MemRecoverer) Done() <-chan struct{} { return r.done } 161 162 func Test_InMemorySegmentRecover(t *testing.T) { 163 var ( 164 users = 10 165 streamsCt = 1000 166 entriesPerStream = 50 167 ) 168 reader, recs := buildMemoryReader(users, streamsCt, entriesPerStream) 169 170 recoverer := NewMemRecoverer() 171 172 require.Nil(t, RecoverWAL(reader, recoverer)) 173 recoverer.Close() 174 175 require.Equal(t, users, recoverer.usersCt) 176 require.Equal(t, streamsCt, recoverer.streamsCt) 177 require.Equal(t, streamsCt*entriesPerStream, recoverer.seriesCt) 178 179 for _, rec := range recs { 180 user, ok := recoverer.users[rec.UserID] 181 require.Equal(t, true, ok) 182 183 for _, s := range rec.Series { 184 _, ok := user[s.Ref] 185 require.Equal(t, true, ok) 186 } 187 188 for _, entries := range rec.RefEntries { 189 stream, ok := user[entries.Ref] 190 require.Equal(t, true, ok) 191 192 for i, entry := range entries.Entries { 193 require.Equal(t, entry, stream[i]) 194 } 195 } 196 } 197 } 198 199 func TestSeriesRecoveryNoDuplicates(t *testing.T) { 200 ingesterConfig := defaultIngesterTestConfig(t) 201 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 202 require.NoError(t, err) 203 204 store := &mockStore{ 205 chunks: map[string][]chunk.Chunk{}, 206 } 207 208 i, err := New(ingesterConfig, client.Config{}, store, limits, loki_runtime.DefaultTenantConfigs(), nil) 209 require.NoError(t, err) 210 211 mkSample := func(i int) *logproto.PushRequest { 212 return &logproto.PushRequest{ 213 Streams: []logproto.Stream{ 214 { 215 // Note: must use normalized labels here b/c we expect them 216 // sorted but use a string for efficiency. 217 Labels: `{bar="baz1", foo="bar"}`, 218 Entries: []logproto.Entry{ 219 { 220 Timestamp: time.Unix(int64(i), 0), 221 Line: fmt.Sprintf("line %d", i), 222 }, 223 }, 224 }, 225 }, 226 } 227 } 228 229 req := mkSample(1) 230 231 ctx := user.InjectOrgID(context.Background(), "test") 232 _, err = i.Push(ctx, req) 233 require.NoError(t, err) 234 235 iter := newIngesterSeriesIter(i).Iter() 236 require.Equal(t, true, iter.Next()) 237 238 series := iter.Stream() 239 require.Equal(t, false, iter.Next()) 240 241 // create a new ingester now 242 i, err = New(ingesterConfig, client.Config{}, store, limits, loki_runtime.DefaultTenantConfigs(), nil) 243 require.NoError(t, err) 244 245 // recover the checkpointed series 246 recoverer := newIngesterRecoverer(i) 247 require.NoError(t, recoverer.Series(series)) 248 249 _, err = i.Push(ctx, req) 250 require.NoError(t, err) // we don't error on duplicate pushes 251 252 result := mockQuerierServer{ 253 ctx: ctx, 254 } 255 256 // ensure no duplicate log lines exist 257 err = i.Query(&logproto.QueryRequest{ 258 Selector: `{foo="bar",bar="baz1"}`, 259 Limit: 100, 260 Start: time.Unix(0, 0), 261 End: time.Unix(10, 0), 262 }, &result) 263 require.NoError(t, err) 264 require.Len(t, result.resps, 1) 265 lbls := labels.Labels{{Name: "bar", Value: "baz1"}, {Name: "foo", Value: "bar"}} 266 expected := []logproto.Stream{ 267 { 268 Labels: lbls.String(), 269 Entries: []logproto.Entry{ 270 { 271 Timestamp: time.Unix(1, 0), 272 Line: "line 1", 273 }, 274 }, 275 Hash: lbls.Hash(), 276 }, 277 } 278 require.Equal(t, expected, result.resps[0].Streams) 279 }