github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/block_writer_v2.go (about) 1 package symdb 2 3 import ( 4 "context" 5 "fmt" 6 "hash/crc32" 7 "io" 8 "math" 9 "os" 10 "path/filepath" 11 12 "github.com/grafana/dskit/multierror" 13 "github.com/parquet-go/parquet-go" 14 "golang.org/x/sync/errgroup" 15 16 "github.com/grafana/pyroscope/pkg/phlaredb/block" 17 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 18 "github.com/grafana/pyroscope/pkg/util/build" 19 ) 20 21 type writerV2 struct { 22 config *Config 23 24 index IndexFile 25 indexWriter *fileWriter 26 stacktraces *fileWriter 27 files []block.File 28 29 // Parquet tables. 30 mappings parquetWriter[schemav1.InMemoryMapping, schemav1.MappingPersister] 31 functions parquetWriter[schemav1.InMemoryFunction, schemav1.FunctionPersister] 32 locations parquetWriter[schemav1.InMemoryLocation, schemav1.LocationPersister] 33 strings parquetWriter[string, schemav1.StringPersister] 34 } 35 36 func newWriterV2(c *Config) *writerV2 { 37 return &writerV2{ 38 config: c, 39 index: IndexFile{ 40 Header: IndexHeader{ 41 Magic: symdbMagic, 42 Version: FormatV2, 43 }, 44 }, 45 } 46 } 47 48 func (w *writerV2) writePartitions(partitions []*PartitionWriter) error { 49 if err := w.createDir(); err != nil { 50 return err 51 } 52 53 g, _ := errgroup.WithContext(context.Background()) 54 g.Go(func() (err error) { 55 if w.stacktraces, err = w.newFile(StacktracesFileName); err != nil { 56 return err 57 } 58 for _, partition := range partitions { 59 if err = w.writeStacktraces(partition); err != nil { 60 return err 61 } 62 } 63 return w.stacktraces.Close() 64 }) 65 66 g.Go(func() (err error) { 67 if err = w.strings.init(w.config.Dir, w.config.Parquet); err != nil { 68 return err 69 } 70 for _, partition := range partitions { 71 if partition.header.V2.Strings, err = w.strings.readFrom(partition.strings.slice); err != nil { 72 return err 73 } 74 } 75 return w.strings.Close() 76 }) 77 78 g.Go(func() (err error) { 79 if err = w.functions.init(w.config.Dir, w.config.Parquet); err != nil { 80 return err 81 } 82 for _, partition := range partitions { 83 if partition.header.V2.Functions, err = w.functions.readFrom(partition.functions.slice); err != nil { 84 return err 85 } 86 } 87 return w.functions.Close() 88 }) 89 90 g.Go(func() (err error) { 91 if err = w.mappings.init(w.config.Dir, w.config.Parquet); err != nil { 92 return err 93 } 94 for _, partition := range partitions { 95 if partition.header.V2.Mappings, err = w.mappings.readFrom(partition.mappings.slice); err != nil { 96 return err 97 } 98 } 99 return w.mappings.Close() 100 }) 101 102 g.Go(func() (err error) { 103 if err = w.locations.init(w.config.Dir, w.config.Parquet); err != nil { 104 return err 105 } 106 for _, partition := range partitions { 107 if partition.header.V2.Locations, err = w.locations.readFrom(partition.locations.slice); err != nil { 108 return err 109 } 110 } 111 return w.locations.Close() 112 }) 113 114 if err := g.Wait(); err != nil { 115 return err 116 } 117 118 for _, partition := range partitions { 119 w.index.PartitionHeaders = append(w.index.PartitionHeaders, &partition.header) 120 } 121 122 return w.Flush() 123 } 124 125 func (w *writerV2) Flush() (err error) { 126 if err = w.writeIndexFile(); err != nil { 127 return err 128 } 129 w.files = []block.File{ 130 w.indexWriter.meta(), 131 w.stacktraces.meta(), 132 w.locations.meta(), 133 w.mappings.meta(), 134 w.functions.meta(), 135 w.strings.meta(), 136 } 137 return nil 138 } 139 140 func (w *writerV2) writeStacktraces(partition *PartitionWriter) (err error) { 141 h := StacktraceBlockHeader{ 142 Offset: w.stacktraces.w.offset, 143 Partition: partition.header.Partition, 144 Encoding: StacktraceEncodingGroupVarint, 145 Stacktraces: uint32(len(partition.stacktraces.hashToIdx)), 146 StacktraceNodes: partition.stacktraces.tree.len(), 147 StacktraceMaxNodes: math.MaxUint32, 148 } 149 crc := crc32.New(castagnoli) 150 if h.Size, err = partition.stacktraces.WriteTo(io.MultiWriter(crc, w.stacktraces)); err != nil { 151 return fmt.Errorf("writing stacktrace chunk data: %w", err) 152 } 153 h.CRC = crc.Sum32() 154 partition.header.Stacktraces = append(partition.header.Stacktraces, h) 155 return nil 156 } 157 158 func (w *writerV2) createDir() error { 159 if err := os.MkdirAll(w.config.Dir, 0o755); err != nil { 160 return fmt.Errorf("failed to create directory %q: %w", w.config.Dir, err) 161 } 162 return nil 163 } 164 165 func (w *writerV2) writeIndexFile() (err error) { 166 // Write the index file only after all the files were flushed. 167 if w.indexWriter, err = w.newFile(IndexFileName); err != nil { 168 return err 169 } 170 defer func() { 171 err = multierror.New(err, w.indexWriter.Close()).Err() 172 }() 173 if _, err = w.index.WriteTo(w.indexWriter); err != nil { 174 return fmt.Errorf("failed to write index file: %w", err) 175 } 176 return err 177 } 178 179 func (w *writerV2) newFile(path string) (f *fileWriter, err error) { 180 path = filepath.Join(w.config.Dir, path) 181 if f, err = newFileWriter(path); err != nil { 182 return nil, fmt.Errorf("failed to create %q: %w", path, err) 183 } 184 return f, err 185 } 186 187 func (w *writerV2) meta() []block.File { return w.files } 188 189 type parquetWriter[M schemav1.Models, P schemav1.Persister[M]] struct { 190 persister P 191 config ParquetConfig 192 193 currentRowGroup uint32 194 currentRows uint32 195 rowsTotal uint64 196 197 buffer *parquet.Buffer 198 rowsBatch []parquet.Row 199 200 writer *parquet.GenericWriter[P] 201 file *os.File 202 path string 203 } 204 205 func (s *parquetWriter[M, P]) init(dir string, c ParquetConfig) (err error) { 206 s.config = c 207 s.path = filepath.Join(dir, s.persister.Name()+block.ParquetSuffix) 208 s.file, err = os.OpenFile(s.path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o644) 209 if err != nil { 210 return err 211 } 212 s.rowsBatch = make([]parquet.Row, 0, 128) 213 s.buffer = parquet.NewBuffer(s.persister.Schema()) 214 s.writer = parquet.NewGenericWriter[P](s.file, s.persister.Schema(), 215 parquet.CreatedBy("github.com/grafana/pyroscope/", build.Version, build.Revision), 216 parquet.PageBufferSize(3*1024*1024), 217 ) 218 return nil 219 } 220 221 func (s *parquetWriter[M, P]) readFrom(values []M) (ranges []RowRangeReference, err error) { 222 for len(values) > 0 { 223 var r RowRangeReference 224 if r, err = s.writeRows(values); err != nil { 225 return nil, err 226 } 227 ranges = append(ranges, r) 228 values = values[r.Rows:] 229 } 230 return ranges, nil 231 } 232 233 func (s *parquetWriter[M, P]) writeRows(values []M) (r RowRangeReference, err error) { 234 r.RowGroup = s.currentRowGroup 235 r.Index = s.currentRows 236 if len(values) == 0 { 237 return r, nil 238 } 239 var n int 240 for len(values) > 0 && int(s.currentRows) < s.config.MaxBufferRowCount { 241 s.fillBatch(values) 242 if n, err = s.buffer.WriteRows(s.rowsBatch); err != nil { 243 return r, err 244 } 245 s.currentRows += uint32(n) 246 r.Rows += uint32(n) 247 values = values[n:] 248 } 249 if int(s.currentRows)+cap(s.rowsBatch) >= s.config.MaxBufferRowCount { 250 if err = s.flushBuffer(); err != nil { 251 return r, err 252 } 253 } 254 return r, nil 255 } 256 257 func (s *parquetWriter[M, P]) fillBatch(values []M) int { 258 m := min(len(values), cap(s.rowsBatch)) 259 s.rowsBatch = s.rowsBatch[:m] 260 for i := 0; i < m; i++ { 261 row := s.rowsBatch[i][:0] 262 s.rowsBatch[i] = s.persister.Deconstruct(row, values[i]) 263 } 264 return m 265 } 266 267 func (s *parquetWriter[M, P]) flushBuffer() error { 268 if _, err := s.writer.WriteRowGroup(s.buffer); err != nil { 269 return err 270 } 271 s.rowsTotal += uint64(s.buffer.NumRows()) 272 s.currentRowGroup++ 273 s.currentRows = 0 274 s.buffer.Reset() 275 return nil 276 } 277 278 func (s *parquetWriter[M, P]) meta() block.File { 279 f := block.File{ 280 // Note that the path is relative to the symdb root dir. 281 RelPath: filepath.Base(s.path), 282 Parquet: &block.ParquetFile{ 283 NumRows: s.rowsTotal, 284 }, 285 } 286 if f.Parquet.NumRows > 0 { 287 f.Parquet.NumRowGroups = uint64(s.currentRowGroup + 1) 288 } 289 if stat, err := os.Stat(s.path); err == nil { 290 f.SizeBytes = uint64(stat.Size()) 291 } 292 return f 293 } 294 295 func (s *parquetWriter[M, P]) Close() error { 296 if err := s.flushBuffer(); err != nil { 297 return fmt.Errorf("flushing parquet buffer: %w", err) 298 } 299 if err := s.writer.Close(); err != nil { 300 return fmt.Errorf("closing parquet writer: %w", err) 301 } 302 if err := s.file.Close(); err != nil { 303 return fmt.Errorf("closing parquet file: %w", err) 304 } 305 return nil 306 }