code.vegaprotocol.io/vega@v0.79.0/datanode/broker/buffered_event_source.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package broker 17 18 import ( 19 "context" 20 "encoding/binary" 21 "fmt" 22 "io" 23 "io/fs" 24 "os" 25 "path/filepath" 26 "sort" 27 "strings" 28 "time" 29 30 "code.vegaprotocol.io/vega/core/broker" 31 "code.vegaprotocol.io/vega/datanode/metrics" 32 "code.vegaprotocol.io/vega/datanode/utils" 33 "code.vegaprotocol.io/vega/logging" 34 ) 35 36 type FileBufferedEventSource struct { 37 log *logging.Logger 38 lastBufferedSeqNum chan uint64 39 sendChannelBufferSize int 40 source RawEventReceiver 41 bufferFilePath string 42 archiveFilesPath string 43 config BufferedEventSourceConfig 44 } 45 46 func NewBufferedEventSource(ctx context.Context, log *logging.Logger, config BufferedEventSourceConfig, 47 source RawEventReceiver, bufferFilesDir string, 48 archiveFilesDir string, 49 ) (*FileBufferedEventSource, error) { 50 err := os.RemoveAll(bufferFilesDir) 51 if err != nil { 52 return nil, fmt.Errorf("failed to remove old buffer files: %w", err) 53 } 54 55 err = os.Mkdir(bufferFilesDir, os.ModePerm) 56 if err != nil { 57 return nil, fmt.Errorf("failed to create buffer file directory: %w", err) 58 } 59 60 if config.Archive { 61 err = os.MkdirAll(archiveFilesDir, os.ModePerm) 62 if err != nil { 63 return nil, fmt.Errorf("failed to create buffer file archive directory: %w", err) 64 } 65 66 go func() { 67 ticker := time.NewTicker(1 * time.Minute) 68 defer ticker.Stop() 69 for { 70 select { 71 case <-ctx.Done(): 72 return 73 case <-ticker.C: 74 err := compressUncompressedFilesInDir(archiveFilesDir) 75 if err != nil { 76 log.Errorf("failed to compress uncompressed file in archive dir: %w", err) 77 } 78 79 err = removeOldArchiveFilesIfDirectoryFull(archiveFilesDir, config.ArchiveMaximumSizeBytes) 80 if err != nil { 81 log.Errorf("failed to remove old files from full archive directory: %w", err) 82 } 83 } 84 } 85 }() 86 } 87 88 fb := &FileBufferedEventSource{ 89 log: log.Named("buffered-event-source"), 90 source: source, 91 config: config, 92 lastBufferedSeqNum: make(chan uint64, 100), 93 bufferFilePath: bufferFilesDir, 94 archiveFilesPath: archiveFilesDir, 95 } 96 97 fb.log.Info("Starting buffered event source with a max buffered event, and events per buffer file size", 98 logging.Int("events-per-file", config.EventsPerFile)) 99 100 return fb, nil 101 } 102 103 func (m *FileBufferedEventSource) Listen() error { 104 return m.source.Listen() 105 } 106 107 func (m *FileBufferedEventSource) Receive(ctx context.Context) (<-chan []byte, <-chan error) { 108 sourceEventCh, sourceErrCh := m.source.Receive(ctx) 109 110 if m.config.EventsPerFile == 0 { 111 m.log.Info("events per file is set to 0, disabling event buffer") 112 return sourceEventCh, sourceErrCh 113 } 114 115 sinkEventCh := make(chan []byte, m.sendChannelBufferSize) 116 sinkErrorCh := make(chan error, 1) 117 118 ctxWithCancel, cancel := context.WithCancel(ctx) 119 go func() { 120 m.writeEventsToBuffer(ctx, sourceEventCh, sourceErrCh, sinkErrorCh) 121 cancel() 122 }() 123 124 go func() { 125 m.readEventsFromBuffer(ctxWithCancel, sinkEventCh, sinkErrorCh) 126 }() 127 128 return sinkEventCh, sinkErrorCh 129 } 130 131 func (m *FileBufferedEventSource) writeEventsToBuffer(ctx context.Context, sourceEventCh <-chan []byte, 132 sourceErrCh <-chan error, sinkErrorCh chan error, 133 ) { 134 bufferSeqNum := uint64(0) 135 136 var bufferFile *os.File 137 defer func() { 138 if bufferFile != nil { 139 err := bufferFile.Close() 140 if err != nil { 141 m.log.Errorf("failed to close event buffer file:%w", err) 142 } 143 } 144 }() 145 146 var err error 147 for { 148 select { 149 case event, ok := <-sourceEventCh: 150 if !ok { 151 return 152 } 153 if bufferSeqNum%uint64(m.config.EventsPerFile) == 0 { 154 bufferFile, err = m.rollBufferFile(bufferFile, bufferSeqNum) 155 if err != nil { 156 sinkErrorCh <- fmt.Errorf("failed to roll buffer file:%w", err) 157 } 158 } 159 160 bufferSeqNum++ 161 err = broker.WriteRawToBufferFile(bufferFile, bufferSeqNum, event) 162 metrics.EventBufferWrittenCountInc() 163 164 if err != nil { 165 sinkErrorCh <- fmt.Errorf("failed to write events to buffer:%w", err) 166 } 167 168 loop: 169 for { 170 select { 171 case <-m.lastBufferedSeqNum: 172 case <-ctx.Done(): 173 return 174 default: 175 break loop 176 } 177 } 178 m.lastBufferedSeqNum <- bufferSeqNum 179 180 case srcErr, ok := <-sourceErrCh: 181 if !ok { 182 return 183 } 184 sinkErrorCh <- srcErr 185 case <-ctx.Done(): 186 return 187 } 188 } 189 } 190 191 func (m *FileBufferedEventSource) readEventsFromBuffer(ctx context.Context, sinkEventCh chan []byte, sinkErrorCh chan error) { 192 var offset int64 193 var lastBufferSeqNum uint64 194 var lastSentBufferSeqNum uint64 195 var err error 196 197 var bufferFile *os.File 198 199 defer func() { 200 if bufferFile != nil { 201 err = bufferFile.Close() 202 if err != nil { 203 m.log.Errorf("failed to close event buffer file:%w", err) 204 } 205 } 206 close(sinkEventCh) 207 close(sinkErrorCh) 208 }() 209 210 for { 211 if ctx.Err() != nil { 212 return 213 } 214 215 if lastBufferSeqNum > lastSentBufferSeqNum { 216 if bufferFile == nil { 217 offset = 0 218 bufferFile, err = m.openBufferFile(lastSentBufferSeqNum+1, lastSentBufferSeqNum+uint64(m.config.EventsPerFile)) 219 if err != nil { 220 sinkErrorCh <- fmt.Errorf("failed to open buffer file:%w", err) 221 return 222 } 223 } 224 225 event, bufferSeqNum, read, err := ReadRawEvent(bufferFile, offset) 226 if err != nil { 227 sinkErrorCh <- fmt.Errorf("error when reading event from buffer file:%w", err) 228 return 229 } 230 231 offset += int64(read) 232 233 if event != nil { 234 sinkEventCh <- event 235 metrics.EventBufferReadCountInc() 236 lastSentBufferSeqNum = bufferSeqNum 237 238 if lastSentBufferSeqNum%uint64(m.config.EventsPerFile) == 0 { 239 if err = m.removeBufferFile(bufferFile); err != nil { 240 sinkErrorCh <- fmt.Errorf("failed to remove buffer file:%w", err) 241 } 242 bufferFile = nil 243 } 244 } else { 245 // Time for the buffer write to complete if we were unable to read a complete event for a given seq num 246 time.Sleep(10 * time.Millisecond) 247 } 248 } else { 249 // Wait until told there is new data written to the buffer 250 select { 251 case <-ctx.Done(): 252 return 253 case bufferSeqNum := <-m.lastBufferedSeqNum: 254 lastBufferSeqNum = bufferSeqNum 255 } 256 } 257 } 258 } 259 260 func (m *FileBufferedEventSource) rollBufferFile(currentBufferFile *os.File, seqNum uint64) (*os.File, error) { 261 if currentBufferFile != nil { 262 err := currentBufferFile.Close() 263 if err != nil { 264 return nil, fmt.Errorf("unable to create new buffer file, failed to close current events buffer file:%w", err) 265 } 266 } 267 268 newBufferFile, err := m.createFile(seqNum+1, seqNum+uint64(m.config.EventsPerFile)) 269 if err != nil { 270 return nil, fmt.Errorf("failed to create events buffer file:%w", err) 271 } 272 return newBufferFile, nil 273 } 274 275 func (m *FileBufferedEventSource) removeBufferFile(bufferFile *os.File) error { 276 err := bufferFile.Close() 277 if err != nil { 278 return fmt.Errorf("failed to close last event buffer file:%w", err) 279 } 280 281 if m.config.Archive { 282 err = m.moveBufferFileToArchive(bufferFile.Name()) 283 if err != nil { 284 return fmt.Errorf("failed to move buffer file to archive: %w", err) 285 } 286 } else { 287 err = os.Remove(bufferFile.Name()) 288 if err != nil { 289 return fmt.Errorf("failed to remove event buffer file: %w", err) 290 } 291 } 292 293 return nil 294 } 295 296 // moveBufferFileToArchive encodes the creation time into the archive file name to ensure that the correct order 297 // of files can always be determined even if the archive files are copied etc. 298 func (m *FileBufferedEventSource) moveBufferFileToArchive(bufferFilePath string) error { 299 bufferFileName := filepath.Base(bufferFilePath) 300 bufferSeqSpan := strings.ReplaceAll(bufferFileName, bufferFileNamePrepend, "") 301 timeNowUtc := time.Now().UTC() 302 303 archiveFileName := fmt.Sprintf("%s-%s-%d-seqnumspan%s", bufferFileNamePrepend, 304 timeNowUtc.Format("2006-01-02-15-04-05"), timeNowUtc.UnixNano(), bufferSeqSpan) 305 306 archiveFilePath := filepath.Join(m.archiveFilesPath, archiveFileName) 307 308 err := os.Rename(bufferFilePath, archiveFilePath) 309 if err != nil { 310 return fmt.Errorf("failed to rename file: %w", err) 311 } 312 return nil 313 } 314 315 func ReadRawEvent(eventFile *os.File, offset int64) (event []byte, seqNum uint64, 316 totalBytesRead uint32, err error, 317 ) { 318 sizeBytes := make([]byte, broker.NumberOfSizeBytes) 319 read, err := eventFile.ReadAt(sizeBytes, offset) 320 321 if err == io.EOF { 322 return nil, 0, 0, nil 323 } else if err != nil { 324 return nil, 0, 0, fmt.Errorf("error reading message size from events file:%w", err) 325 } 326 327 if read < broker.NumberOfSizeBytes { 328 return nil, 0, 0, nil 329 } 330 331 messageOffset := offset + broker.NumberOfSizeBytes 332 333 msgSize := binary.BigEndian.Uint32(sizeBytes) 334 seqNumAndMsgBytes := make([]byte, msgSize) 335 read, err = eventFile.ReadAt(seqNumAndMsgBytes, messageOffset) 336 if err == io.EOF { 337 return nil, 0, 0, nil 338 } else if err != nil { 339 return nil, 0, 0, fmt.Errorf("error reading message bytes from events file:%w", err) 340 } 341 342 if read < int(msgSize) { 343 return nil, 0, 0, nil 344 } 345 346 seqNumBytes := seqNumAndMsgBytes[:broker.NumberOfSeqNumBytes] 347 seqNum = binary.BigEndian.Uint64(seqNumBytes) 348 msgBytes := seqNumAndMsgBytes[broker.NumberOfSeqNumBytes:] 349 totalBytesRead = broker.NumberOfSizeBytes + msgSize 350 351 return msgBytes, seqNum, totalBytesRead, nil 352 } 353 354 const bufferFileNamePrepend = "datanode-buffer" 355 356 func (m *FileBufferedEventSource) getBufferFileName(fromSeqNum uint64, toSeqNum uint64) string { 357 return fmt.Sprintf("%s/%s-%d-%d.bevt", m.bufferFilePath, bufferFileNamePrepend, fromSeqNum, toSeqNum) 358 } 359 360 func (m *FileBufferedEventSource) createFile(fromSeqNum uint64, toSeqNum uint64) (*os.File, error) { 361 bufferFileName := m.getBufferFileName(fromSeqNum, toSeqNum) 362 bufferFile, err := os.Create(bufferFileName) 363 if err != nil { 364 return nil, fmt.Errorf("failed to create buffer file: %s :%w", bufferFileName, err) 365 } 366 return bufferFile, err 367 } 368 369 func (m *FileBufferedEventSource) openBufferFile(fromSeqNum uint64, toSeqNum uint64) (*os.File, error) { 370 bufferFileName := m.getBufferFileName(fromSeqNum, toSeqNum) 371 bufferFile, err := os.Open(bufferFileName) 372 if err != nil { 373 return nil, fmt.Errorf("failed to open buffer file: %s :%w", bufferFileName, err) 374 } 375 return bufferFile, nil 376 } 377 378 func compressUncompressedFilesInDir(dir string) error { 379 files, err := os.ReadDir(dir) 380 if err != nil { 381 return fmt.Errorf("failed to read dir: %w", err) 382 } 383 384 for _, file := range files { 385 if !file.IsDir() { 386 if !strings.HasSuffix(file.Name(), "gz") { 387 err = compressBufferedEventFile(file.Name(), dir) 388 if err != nil { 389 return fmt.Errorf("failed to compress file: %w", err) 390 } 391 } 392 } 393 } 394 395 return nil 396 } 397 398 func compressBufferedEventFile(bufferFileName string, archiveFilesDir string) error { 399 bufferFilePath := filepath.Join(archiveFilesDir, bufferFileName) 400 archiveFilePath := filepath.Join(archiveFilesDir, bufferFileName+".gz") 401 402 err := utils.CompressFile(bufferFilePath, archiveFilePath) 403 if err != nil { 404 return fmt.Errorf("failed to compress buffer file: %w", err) 405 } 406 407 err = os.Remove(bufferFilePath) 408 if err != nil { 409 return fmt.Errorf("failed to remove uncompressed buffer file: %w", err) 410 } 411 412 return nil 413 } 414 415 // removeOldArchiveFilesIfDirectoryFull intentionally uses the name of the file to figure out the relative age 416 // of the file, see moveBufferFileToArchive. 417 func removeOldArchiveFilesIfDirectoryFull(dir string, maximumDirSizeBytes int64) error { 418 var dirSizeBytes int64 419 var archiveFiles []fs.FileInfo 420 err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { 421 if err != nil || (info != nil && info.IsDir()) { 422 return nil //nolint:nilerr 423 } 424 dirSizeBytes += info.Size() 425 archiveFiles = append(archiveFiles, info) 426 return nil 427 }) 428 if err != nil { 429 return fmt.Errorf("failed to walk directory: %w", err) 430 } 431 432 if dirSizeBytes > maximumDirSizeBytes { 433 sort.Slice(archiveFiles, func(i, j int) bool { 434 return strings.Compare(archiveFiles[i].Name(), archiveFiles[j].Name()) < 0 435 }) 436 437 minimumBytesToRemove := dirSizeBytes - maximumDirSizeBytes 438 439 var bytesRemoved int64 440 for _, file := range archiveFiles { 441 err := os.Remove(filepath.Join(dir, file.Name())) 442 if err != nil { 443 return fmt.Errorf("failed to remove file: %w", err) 444 } 445 bytesRemoved += file.Size() 446 if bytesRemoved >= minimumBytesToRemove { 447 break 448 } 449 } 450 } 451 452 return nil 453 }