mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-12-30 04:57:55 +08:00
dc784bf217
* listing files to convert to parquet * write parquet files * save logs into parquet files * pass by value * compact logs into parquet format * can skip existing files * refactor * refactor * fix compilation * when no partition found * refactor * add untested parquet file read * rename package * refactor * rename files * remove unused * add merged log read func * parquet wants to know the file size * rewind by time * pass in stop ts * add stop ts * adjust log * minor * adjust log * skip .parquet files when reading message logs * skip non message files * Update subscriber_record.go * send messages * skip message data with only ts * skip non log files * update parquet-go package * ensure a valid record type * add new field to a record type * Update read_parquet_to_log.go * fix parquet file name generation * separating reading parquet and logs * add key field * add skipped logs * use in memory cache * refactor * refactor * refactor * refactor, and change compact log * refactor * rename * refactor * fix format * prefix v to version directory
163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
package logstore
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"github.com/parquet-go/parquet-go"
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/mq/schema"
|
|
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/chunk_cache"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
|
"google.golang.org/protobuf/proto"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
chunkCache = chunk_cache.NewChunkCacheInMemory(256) // 256 entries, 8MB max per entry
|
|
)
|
|
|
|
func GenParquetReadFunc(filerClient filer_pb.FilerClient, t topic.Topic, p topic.Partition) log_buffer.LogReadFromDiskFuncType {
|
|
partitionDir := topic.PartitionDir(t, p)
|
|
|
|
lookupFileIdFn := filer.LookupFn(filerClient)
|
|
|
|
// read topic conf from filer
|
|
var topicConf *mq_pb.ConfigureTopicResponse
|
|
var err error
|
|
if err := filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
topicConf, err = t.ReadConfFile(client)
|
|
return err
|
|
}); err != nil {
|
|
return nil
|
|
}
|
|
recordType := topicConf.GetRecordType()
|
|
recordType = schema.NewRecordTypeBuilder(recordType).
|
|
WithField(SW_COLUMN_NAME_TS, schema.TypeInt64).
|
|
WithField(SW_COLUMN_NAME_KEY, schema.TypeBytes).
|
|
RecordTypeEnd()
|
|
|
|
parquetSchema, err := schema.ToParquetSchema(t.Name, recordType)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
parquetLevels, err := schema.ToParquetLevels(recordType)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// eachFileFn reads a parquet file and calls eachLogEntryFn for each log entry
|
|
eachFileFn := func(entry *filer_pb.Entry, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
|
// create readerAt for the parquet file
|
|
fileSize := filer.FileSize(entry)
|
|
visibleIntervals, _ := filer.NonOverlappingVisibleIntervals(lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
|
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, 0, int64(fileSize))
|
|
readerCache := filer.NewReaderCache(32, chunkCache, lookupFileIdFn)
|
|
readerAt := filer.NewChunkReaderAtFromClient(readerCache, chunkViews, int64(fileSize))
|
|
|
|
// create parquet reader
|
|
parquetReader := parquet.NewReader(readerAt, parquetSchema)
|
|
rows := make([]parquet.Row, 128)
|
|
for {
|
|
rowCount, readErr := parquetReader.ReadRows(rows)
|
|
|
|
for i := 0; i < rowCount; i++ {
|
|
row := rows[i]
|
|
// convert parquet row to schema_pb.RecordValue
|
|
recordValue, err := schema.ToRecordValue(recordType, parquetLevels, row)
|
|
if err != nil {
|
|
return processedTsNs, fmt.Errorf("ToRecordValue failed: %v", err)
|
|
}
|
|
processedTsNs = recordValue.Fields[SW_COLUMN_NAME_TS].GetInt64Value()
|
|
if processedTsNs < starTsNs {
|
|
continue
|
|
}
|
|
if stopTsNs != 0 && processedTsNs >= stopTsNs {
|
|
return processedTsNs, nil
|
|
}
|
|
|
|
data, marshalErr := proto.Marshal(recordValue)
|
|
if marshalErr != nil {
|
|
return processedTsNs, fmt.Errorf("marshal record value: %v", marshalErr)
|
|
}
|
|
|
|
logEntry := &filer_pb.LogEntry{
|
|
Key: recordValue.Fields[SW_COLUMN_NAME_KEY].GetBytesValue(),
|
|
TsNs: processedTsNs,
|
|
Data: data,
|
|
}
|
|
|
|
// fmt.Printf(" parquet entry %s ts %v\n", string(logEntry.Key), time.Unix(0, logEntry.TsNs).UTC())
|
|
|
|
if _, err = eachLogEntryFn(logEntry); err != nil {
|
|
return processedTsNs, fmt.Errorf("process log entry %v: %v", logEntry, err)
|
|
}
|
|
}
|
|
|
|
if readErr != nil {
|
|
if readErr == io.EOF {
|
|
return processedTsNs, nil
|
|
}
|
|
return processedTsNs, readErr
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
return func(startPosition log_buffer.MessagePosition, stopTsNs int64, eachLogEntryFn log_buffer.EachLogEntryFuncType) (lastReadPosition log_buffer.MessagePosition, isDone bool, err error) {
|
|
startFileName := startPosition.UTC().Format(topic.TIME_FORMAT)
|
|
startTsNs := startPosition.Time.UnixNano()
|
|
var processedTsNs int64
|
|
|
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
|
|
return filer_pb.SeaweedList(client, partitionDir, "", func(entry *filer_pb.Entry, isLast bool) error {
|
|
if entry.IsDirectory {
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(entry.Name, ".parquet") {
|
|
return nil
|
|
}
|
|
if len(entry.Extended) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// read minTs from the parquet file
|
|
minTsBytes := entry.Extended["min"]
|
|
if len(minTsBytes) != 8 {
|
|
return nil
|
|
}
|
|
minTsNs := int64(binary.BigEndian.Uint64(minTsBytes))
|
|
|
|
// read max ts
|
|
maxTsBytes := entry.Extended["max"]
|
|
if len(maxTsBytes) != 8 {
|
|
return nil
|
|
}
|
|
maxTsNs := int64(binary.BigEndian.Uint64(maxTsBytes))
|
|
|
|
if stopTsNs != 0 && stopTsNs <= minTsNs {
|
|
isDone = true
|
|
return nil
|
|
}
|
|
|
|
if maxTsNs < startTsNs {
|
|
return nil
|
|
}
|
|
|
|
if processedTsNs, err = eachFileFn(entry, eachLogEntryFn, startTsNs, stopTsNs); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
|
|
}, startFileName, true, math.MaxInt32)
|
|
})
|
|
lastReadPosition = log_buffer.NewMessagePosition(processedTsNs, -2)
|
|
return
|
|
}
|
|
}
|