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  }