github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/encoding.go (about) 1 package ingester 2 3 import ( 4 "time" 5 6 "github.com/pkg/errors" 7 "github.com/prometheus/prometheus/tsdb/chunks" 8 "github.com/prometheus/prometheus/tsdb/record" 9 10 "github.com/grafana/loki/pkg/logproto" 11 "github.com/grafana/loki/pkg/util/encoding" 12 ) 13 14 // RecordType represents the type of the WAL/Checkpoint record. 15 type RecordType byte 16 17 const ( 18 _ = iota // ignore first value so the zero value doesn't look like a record type. 19 // WALRecordSeries is the type for the WAL record for series. 20 WALRecordSeries RecordType = iota 21 // WALRecordEntriesV1 is the type for the WAL record for samples. 22 WALRecordEntriesV1 23 // CheckpointRecord is the type for the Checkpoint record based on protos. 24 CheckpointRecord 25 // WALRecordEntriesV2 is the type for the WAL record for samples with an 26 // additional counter value for use in replaying without the ordering constraint. 27 WALRecordEntriesV2 28 ) 29 30 // The current type of Entries that this distribution writes. 31 // Loki can read in a backwards compatible manner, but will write the newest variant. 32 const CurrentEntriesRec RecordType = WALRecordEntriesV2 33 34 // WALRecord is a struct combining the series and samples record. 35 type WALRecord struct { 36 UserID string 37 Series []record.RefSeries 38 39 // entryIndexMap coordinates the RefEntries index associated with a particular fingerprint. 40 // This is helpful for constant time lookups during ingestion and is ignored when restoring 41 // from the WAL. 42 entryIndexMap map[uint64]int 43 RefEntries []RefEntries 44 } 45 46 func (r *WALRecord) IsEmpty() bool { 47 return len(r.Series) == 0 && len(r.RefEntries) == 0 48 } 49 50 func (r *WALRecord) Reset() { 51 r.UserID = "" 52 if len(r.Series) > 0 { 53 r.Series = r.Series[:0] 54 } 55 56 for _, ref := range r.RefEntries { 57 recordPool.PutEntries(ref.Entries) 58 } 59 r.RefEntries = r.RefEntries[:0] 60 r.entryIndexMap = make(map[uint64]int) 61 } 62 63 func (r *WALRecord) AddEntries(fp uint64, counter int64, entries ...logproto.Entry) { 64 if idx, ok := r.entryIndexMap[fp]; ok { 65 r.RefEntries[idx].Entries = append(r.RefEntries[idx].Entries, entries...) 66 r.RefEntries[idx].Counter = counter 67 return 68 } 69 70 r.entryIndexMap[fp] = len(r.RefEntries) 71 r.RefEntries = append(r.RefEntries, RefEntries{ 72 Counter: counter, 73 Ref: chunks.HeadSeriesRef(fp), 74 Entries: entries, 75 }) 76 } 77 78 type RefEntries struct { 79 Counter int64 80 Ref chunks.HeadSeriesRef 81 Entries []logproto.Entry 82 } 83 84 func (r *WALRecord) encodeSeries(b []byte) []byte { 85 buf := encoding.EncWith(b) 86 buf.PutByte(byte(WALRecordSeries)) 87 buf.PutUvarintStr(r.UserID) 88 89 var enc record.Encoder 90 // The 'encoded' already has the type header and userID here, hence re-using 91 // the remaining part of the slice (i.e. encoded[len(encoded):])) to encode the series. 92 encoded := buf.Get() 93 encoded = append(encoded, enc.Series(r.Series, encoded[len(encoded):])...) 94 95 return encoded 96 } 97 98 func (r *WALRecord) encodeEntries(version RecordType, b []byte) []byte { 99 buf := encoding.EncWith(b) 100 buf.PutByte(byte(version)) 101 buf.PutUvarintStr(r.UserID) 102 103 // Placeholder for the first timestamp of any sample encountered. 104 // All others in this record will store their timestamps as diffs relative to this 105 // as a space optimization. 106 var first int64 107 108 outer: 109 for _, ref := range r.RefEntries { 110 for _, entry := range ref.Entries { 111 first = entry.Timestamp.UnixNano() 112 buf.PutBE64int64(first) 113 break outer 114 } 115 } 116 117 for _, ref := range r.RefEntries { 118 // ignore refs with 0 entries 119 if len(ref.Entries) < 1 { 120 continue 121 } 122 buf.PutBE64(uint64(ref.Ref)) // write fingerprint 123 124 if version >= WALRecordEntriesV2 { 125 buf.PutBE64int64(ref.Counter) // write highest counter value 126 } 127 128 buf.PutUvarint(len(ref.Entries)) // write number of entries 129 130 for _, s := range ref.Entries { 131 buf.PutVarint64(s.Timestamp.UnixNano() - first) 132 buf.PutUvarint(len(s.Line)) 133 buf.PutString(s.Line) 134 } 135 } 136 return buf.Get() 137 } 138 139 func decodeEntries(b []byte, version RecordType, rec *WALRecord) error { 140 if len(b) == 0 { 141 return nil 142 } 143 144 dec := encoding.DecWith(b) 145 baseTime := dec.Be64int64() 146 147 for len(dec.B) > 0 && dec.Err() == nil { 148 refEntries := RefEntries{ 149 Ref: chunks.HeadSeriesRef(dec.Be64()), 150 } 151 152 if version >= WALRecordEntriesV2 { 153 refEntries.Counter = dec.Be64int64() 154 } 155 156 nEntries := dec.Uvarint() 157 refEntries.Entries = make([]logproto.Entry, 0, nEntries) 158 rem := nEntries 159 for ; dec.Err() == nil && rem > 0; rem-- { 160 timeOffset := dec.Varint64() 161 lineLength := dec.Uvarint() 162 line := dec.Bytes(lineLength) 163 164 refEntries.Entries = append(refEntries.Entries, logproto.Entry{ 165 Timestamp: time.Unix(0, baseTime+timeOffset), 166 Line: string(line), 167 }) 168 } 169 170 if dec.Err() != nil { 171 return errors.Wrapf(dec.Err(), "entry decode error after %d RefEntries", nEntries-rem) 172 } 173 174 rec.RefEntries = append(rec.RefEntries, refEntries) 175 } 176 177 if dec.Err() != nil { 178 return errors.Wrap(dec.Err(), "refEntry decode error") 179 } 180 181 if len(dec.B) > 0 { 182 return errors.Errorf("unexpected %d bytes left in entry", len(dec.B)) 183 } 184 return nil 185 } 186 187 func decodeWALRecord(b []byte, walRec *WALRecord) (err error) { 188 var ( 189 userID string 190 dec record.Decoder 191 rSeries []record.RefSeries 192 193 decbuf = encoding.DecWith(b) 194 t = RecordType(decbuf.Byte()) 195 ) 196 197 switch t { 198 case WALRecordSeries: 199 userID = decbuf.UvarintStr() 200 rSeries, err = dec.Series(decbuf.B, walRec.Series) 201 case WALRecordEntriesV1, WALRecordEntriesV2: 202 userID = decbuf.UvarintStr() 203 err = decodeEntries(decbuf.B, t, walRec) 204 default: 205 return errors.New("unknown record type") 206 } 207 208 // We reach here only if its a record with type header. 209 if decbuf.Err() != nil { 210 return decbuf.Err() 211 } 212 213 if err != nil { 214 return err 215 } 216 217 walRec.UserID = userID 218 walRec.Series = rSeries 219 return nil 220 }