github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/tae/logtail/handle.go (about)

     1  // Copyright 2021 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package logtail
    16  
    17  /*
    18  
    19  an application on logtail mgr: build reponse to SyncLogTailRequest
    20  
    21  */
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"hash/fnv"
    27  	"strings"
    28  
    29  	pkgcatalog "github.com/matrixorigin/matrixone/pkg/catalog"
    30  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    31  	"github.com/matrixorigin/matrixone/pkg/container/batch"
    32  	"github.com/matrixorigin/matrixone/pkg/container/types"
    33  	"github.com/matrixorigin/matrixone/pkg/fileservice"
    34  	"github.com/matrixorigin/matrixone/pkg/logutil"
    35  	"github.com/matrixorigin/matrixone/pkg/pb/api"
    36  	"github.com/matrixorigin/matrixone/pkg/util/fault"
    37  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog"
    38  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
    39  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/containers"
    40  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/dataio/blockio"
    41  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/tasks"
    42  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/txn/txnimpl"
    43  	"go.uber.org/zap"
    44  )
    45  
    46  const Size90M = 90 * 1024 * 1024
    47  
    48  type CheckpointClient interface {
    49  	CollectCheckpointsInRange(ctx context.Context, start, end types.TS) (ckpLoc string, lastEnd types.TS, err error)
    50  	FlushTable(dbID, tableID uint64, ts types.TS) error
    51  }
    52  
    53  func DecideTableScope(tableID uint64) Scope {
    54  	var scope Scope
    55  	switch tableID {
    56  	case pkgcatalog.MO_DATABASE_ID:
    57  		scope = ScopeDatabases
    58  	case pkgcatalog.MO_TABLES_ID:
    59  		scope = ScopeTables
    60  	case pkgcatalog.MO_COLUMNS_ID:
    61  		scope = ScopeColumns
    62  	default:
    63  		scope = ScopeUserTables
    64  	}
    65  	return scope
    66  }
    67  
    68  func HandleSyncLogTailReq(
    69  	ctx context.Context,
    70  	ckpClient CheckpointClient,
    71  	mgr *Manager,
    72  	c *catalog.Catalog,
    73  	req api.SyncLogTailReq,
    74  	canRetry bool) (resp api.SyncLogTailResp, err error) {
    75  	logutil.Debugf("[Logtail] begin handle %+v", req)
    76  	defer func() {
    77  		logutil.Debugf("[Logtail] end handle %d entries[%q], err %v", len(resp.Commands), resp.CkpLocation, err)
    78  	}()
    79  	start := types.BuildTS(req.CnHave.PhysicalTime, req.CnHave.LogicalTime)
    80  	end := types.BuildTS(req.CnWant.PhysicalTime, req.CnWant.LogicalTime)
    81  	did, tid := req.Table.DbId, req.Table.TbId
    82  	dbEntry, err := c.GetDatabaseByID(did)
    83  	if err != nil {
    84  		return
    85  	}
    86  	tableEntry, err := dbEntry.GetTableEntryByID(tid)
    87  	if err != nil {
    88  		return
    89  	}
    90  	if start.Less(tableEntry.GetCreatedAt()) {
    91  		start = tableEntry.GetCreatedAt()
    92  	}
    93  
    94  	ckpLoc, checkpointed, err := ckpClient.CollectCheckpointsInRange(ctx, start, end)
    95  	if err != nil {
    96  		return
    97  	}
    98  
    99  	if checkpointed.GreaterEq(end) {
   100  		return api.SyncLogTailResp{
   101  			CkpLocation: ckpLoc,
   102  		}, err
   103  	} else if ckpLoc != "" {
   104  		start = checkpointed.Next()
   105  	}
   106  
   107  	scope := DecideTableScope(tid)
   108  
   109  	var visitor RespBuilder
   110  
   111  	if scope == ScopeUserTables {
   112  		visitor = NewTableLogtailRespBuilder(ckpLoc, start, end, tableEntry)
   113  	} else {
   114  		visitor = NewCatalogLogtailRespBuilder(scope, ckpLoc, start, end)
   115  	}
   116  	defer visitor.Close()
   117  
   118  	operator := mgr.GetTableOperator(start, end, c, did, tid, scope, visitor)
   119  	if err := operator.Run(); err != nil {
   120  		return api.SyncLogTailResp{}, err
   121  	}
   122  	resp, err = visitor.BuildResp()
   123  
   124  	if canRetry && scope == ScopeUserTables { // check simple conditions first
   125  		_, name, forceFlush := fault.TriggerFault("logtail_max_size")
   126  		if (forceFlush && name == tableEntry.GetSchema().Name) || resp.ProtoSize() > Size90M {
   127  			_ = ckpClient.FlushTable(did, tid, end)
   128  			// try again after flushing
   129  			newResp, err := HandleSyncLogTailReq(ctx, ckpClient, mgr, c, req, false)
   130  			logutil.Infof("[logtail] flush result: %d -> %d err: %v", resp.ProtoSize(), newResp.ProtoSize(), err)
   131  			return newResp, err
   132  		}
   133  	}
   134  	return
   135  }
   136  
   137  type RespBuilder interface {
   138  	catalog.Processor
   139  	BuildResp() (api.SyncLogTailResp, error)
   140  	Close()
   141  }
   142  
   143  // CatalogLogtailRespBuilder knows how to make api-entry from catalog entry
   144  // impl catalog.Processor interface, driven by LogtailCollector
   145  type CatalogLogtailRespBuilder struct {
   146  	*catalog.LoopProcessor
   147  	scope      Scope
   148  	start, end types.TS
   149  	checkpoint string
   150  	insBatch   *containers.Batch
   151  	delBatch   *containers.Batch
   152  }
   153  
   154  func NewCatalogLogtailRespBuilder(scope Scope, ckp string, start, end types.TS) *CatalogLogtailRespBuilder {
   155  	b := &CatalogLogtailRespBuilder{
   156  		LoopProcessor: new(catalog.LoopProcessor),
   157  		scope:         scope,
   158  		start:         start,
   159  		end:           end,
   160  		checkpoint:    ckp,
   161  	}
   162  	switch scope {
   163  	case ScopeDatabases:
   164  		b.insBatch = makeRespBatchFromSchema(catalog.SystemDBSchema)
   165  	case ScopeTables:
   166  		b.insBatch = makeRespBatchFromSchema(catalog.SystemTableSchema)
   167  	case ScopeColumns:
   168  		b.insBatch = makeRespBatchFromSchema(catalog.SystemColumnSchema)
   169  	}
   170  	b.delBatch = makeRespBatchFromSchema(DelSchema)
   171  	b.DatabaseFn = b.VisitDB
   172  	b.TableFn = b.VisitTbl
   173  
   174  	return b
   175  }
   176  
   177  func (b *CatalogLogtailRespBuilder) Close() {
   178  	if b.insBatch != nil {
   179  		b.insBatch.Close()
   180  		b.insBatch = nil
   181  	}
   182  	if b.delBatch != nil {
   183  		b.delBatch.Close()
   184  		b.delBatch = nil
   185  	}
   186  }
   187  
   188  func (b *CatalogLogtailRespBuilder) VisitDB(entry *catalog.DBEntry) error {
   189  	entry.RLock()
   190  	if shouldIgnoreDBInLogtail(entry.ID) {
   191  		entry.RUnlock()
   192  		return nil
   193  	}
   194  	mvccNodes := entry.ClonePreparedInRange(b.start, b.end)
   195  	entry.RUnlock()
   196  	for _, node := range mvccNodes {
   197  		if node.IsAborted() {
   198  			continue
   199  		}
   200  		dbNode := node.(*catalog.DBMVCCNode)
   201  		if dbNode.HasDropCommitted() {
   202  			// delScehma is empty, it will just fill rowid / commit ts
   203  			catalogEntry2Batch(b.delBatch, entry, DelSchema, txnimpl.FillDBRow, u64ToRowID(entry.GetID()), dbNode.GetEnd())
   204  		} else {
   205  			catalogEntry2Batch(b.insBatch, entry, catalog.SystemDBSchema, txnimpl.FillDBRow, u64ToRowID(entry.GetID()), dbNode.GetEnd())
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  func (b *CatalogLogtailRespBuilder) VisitTbl(entry *catalog.TableEntry) error {
   212  	entry.RLock()
   213  	if shouldIgnoreTblInLogtail(entry.ID) {
   214  		entry.RUnlock()
   215  		return nil
   216  	}
   217  	mvccNodes := entry.ClonePreparedInRange(b.start, b.end)
   218  	entry.RUnlock()
   219  	for _, node := range mvccNodes {
   220  		if node.IsAborted() {
   221  			continue
   222  		}
   223  		tblNode := node.(*catalog.TableMVCCNode)
   224  		if b.scope == ScopeColumns {
   225  			var dstBatch *containers.Batch
   226  			if !tblNode.HasDropCommitted() {
   227  				dstBatch = b.insBatch
   228  				// fill unique syscol fields if inserting
   229  				for _, syscol := range catalog.SystemColumnSchema.ColDefs {
   230  					txnimpl.FillColumnRow(entry, syscol.Name, b.insBatch.GetVectorByName(syscol.Name))
   231  				}
   232  			} else {
   233  				dstBatch = b.delBatch
   234  			}
   235  
   236  			// fill common syscol fields for every user column
   237  			rowidVec := dstBatch.GetVectorByName(catalog.AttrRowID)
   238  			commitVec := dstBatch.GetVectorByName(catalog.AttrCommitTs)
   239  			tableID := entry.GetID()
   240  			commitTs := tblNode.GetEnd()
   241  			for _, usercol := range entry.GetSchema().ColDefs {
   242  				rowidVec.Append(bytesToRowID([]byte(fmt.Sprintf("%d-%s", tableID, usercol.Name))))
   243  				commitVec.Append(commitTs)
   244  			}
   245  		} else {
   246  			if tblNode.HasDropCommitted() {
   247  				catalogEntry2Batch(b.delBatch, entry, DelSchema, txnimpl.FillTableRow, u64ToRowID(entry.GetID()), tblNode.GetEnd())
   248  			} else {
   249  				catalogEntry2Batch(b.insBatch, entry, catalog.SystemTableSchema, txnimpl.FillTableRow, u64ToRowID(entry.GetID()), tblNode.GetEnd())
   250  			}
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  func (b *CatalogLogtailRespBuilder) BuildResp() (api.SyncLogTailResp, error) {
   257  	entries := make([]*api.Entry, 0)
   258  	var tblID uint64
   259  	var tableName string
   260  	switch b.scope {
   261  	case ScopeDatabases:
   262  		tblID = pkgcatalog.MO_DATABASE_ID
   263  		tableName = pkgcatalog.MO_DATABASE
   264  	case ScopeTables:
   265  		tblID = pkgcatalog.MO_TABLES_ID
   266  		tableName = pkgcatalog.MO_TABLES
   267  	case ScopeColumns:
   268  		tblID = pkgcatalog.MO_COLUMNS_ID
   269  		tableName = pkgcatalog.MO_COLUMNS
   270  	}
   271  
   272  	if b.insBatch.Length() > 0 {
   273  		bat, err := containersBatchToProtoBatch(b.insBatch)
   274  		logutil.Debugf("[logtail] catalog insert to %d-%s, %s", tblID, tableName,
   275  			DebugBatchToString("catalog", b.insBatch, true, zap.DebugLevel))
   276  		if err != nil {
   277  			return api.SyncLogTailResp{}, err
   278  		}
   279  		insEntry := &api.Entry{
   280  			EntryType:    api.Entry_Insert,
   281  			TableId:      tblID,
   282  			TableName:    tableName,
   283  			DatabaseId:   pkgcatalog.MO_CATALOG_ID,
   284  			DatabaseName: pkgcatalog.MO_CATALOG,
   285  			Bat:          bat,
   286  		}
   287  		entries = append(entries, insEntry)
   288  	}
   289  	if b.delBatch.Length() > 0 {
   290  		bat, err := containersBatchToProtoBatch(b.delBatch)
   291  		logutil.Debugf("[logtail] catalog delete from %d-%s, %s", tblID, tableName,
   292  			DebugBatchToString("catalog", b.delBatch, false, zap.DebugLevel))
   293  		if err != nil {
   294  			return api.SyncLogTailResp{}, err
   295  		}
   296  		delEntry := &api.Entry{
   297  			EntryType:    api.Entry_Delete,
   298  			TableId:      tblID,
   299  			TableName:    tableName,
   300  			DatabaseId:   pkgcatalog.MO_CATALOG_ID,
   301  			DatabaseName: pkgcatalog.MO_CATALOG,
   302  			Bat:          bat,
   303  		}
   304  		entries = append(entries, delEntry)
   305  	}
   306  	return api.SyncLogTailResp{
   307  		CkpLocation: b.checkpoint,
   308  		Commands:    entries,
   309  	}, nil
   310  }
   311  
   312  // this is used to collect ONE ROW of db or table change
   313  func catalogEntry2Batch[T *catalog.DBEntry | *catalog.TableEntry](
   314  	dstBatch *containers.Batch,
   315  	e T,
   316  	schema *catalog.Schema,
   317  	fillDataRow func(e T, attr string, col containers.Vector, ts types.TS),
   318  	rowid types.Rowid,
   319  	commitTs types.TS,
   320  ) {
   321  	for _, col := range schema.ColDefs {
   322  		fillDataRow(e, col.Name, dstBatch.GetVectorByName(col.Name), commitTs)
   323  	}
   324  	dstBatch.GetVectorByName(catalog.AttrRowID).Append(rowid)
   325  	dstBatch.GetVectorByName(catalog.AttrCommitTs).Append(commitTs)
   326  }
   327  
   328  func u64ToRowID(v uint64) types.Rowid {
   329  	var rowid types.Rowid
   330  	bs := types.EncodeUint64(&v)
   331  	copy(rowid[0:], bs)
   332  	return rowid
   333  }
   334  
   335  func bytesToRowID(bs []byte) types.Rowid {
   336  	var rowid types.Rowid
   337  	if size := len(bs); size <= types.RowidSize {
   338  		copy(rowid[:size], bs[:size])
   339  	} else {
   340  		hasher := fnv.New128()
   341  		hasher.Write(bs)
   342  		hasher.Sum(rowid[:0])
   343  	}
   344  	return rowid
   345  }
   346  
   347  // make batch, append necessary field like commit ts
   348  func makeRespBatchFromSchema(schema *catalog.Schema) *containers.Batch {
   349  	bat := containers.NewBatch()
   350  
   351  	bat.AddVector(catalog.AttrRowID, containers.MakeVector(types.T_Rowid.ToType(), false))
   352  	bat.AddVector(catalog.AttrCommitTs, containers.MakeVector(types.T_TS.ToType(), false))
   353  	// Types() is not used, then empty schema can also be handled here
   354  	typs := schema.AllTypes()
   355  	attrs := schema.AllNames()
   356  	nullables := schema.AllNullables()
   357  	for i, attr := range attrs {
   358  		if attr == catalog.PhyAddrColumnName {
   359  			continue
   360  		}
   361  		bat.AddVector(attr, containers.MakeVector(typs[i], nullables[i]))
   362  	}
   363  	return bat
   364  }
   365  
   366  // consume containers.Batch to construct api batch
   367  func containersBatchToProtoBatch(bat *containers.Batch) (*api.Batch, error) {
   368  	mobat := containers.CopyToMoBatch(bat)
   369  	return batch.BatchToProtoBatch(mobat)
   370  }
   371  
   372  type TableLogtailRespBuilder struct {
   373  	*catalog.LoopProcessor
   374  	start, end      types.TS
   375  	did, tid        uint64
   376  	dname, tname    string
   377  	checkpoint      string
   378  	blkMetaInsBatch *containers.Batch
   379  	blkMetaDelBatch *containers.Batch
   380  	dataInsBatch    *containers.Batch
   381  	dataDelBatch    *containers.Batch
   382  }
   383  
   384  func NewTableLogtailRespBuilder(ckp string, start, end types.TS, tbl *catalog.TableEntry) *TableLogtailRespBuilder {
   385  	b := &TableLogtailRespBuilder{
   386  		LoopProcessor: new(catalog.LoopProcessor),
   387  		start:         start,
   388  		end:           end,
   389  		checkpoint:    ckp,
   390  	}
   391  	b.BlockFn = b.VisitBlk
   392  
   393  	schema := tbl.GetSchema()
   394  
   395  	b.did = tbl.GetDB().GetID()
   396  	b.tid = tbl.ID
   397  	b.dname = tbl.GetDB().GetName()
   398  	b.tname = schema.Name
   399  
   400  	b.dataInsBatch = makeRespBatchFromSchema(schema)
   401  	b.dataDelBatch = makeRespBatchFromSchema(DelSchema)
   402  	b.blkMetaInsBatch = makeRespBatchFromSchema(BlkMetaSchema)
   403  	b.blkMetaDelBatch = makeRespBatchFromSchema(DelSchema)
   404  	return b
   405  }
   406  
   407  func (b *TableLogtailRespBuilder) Close() {
   408  	if b.dataInsBatch != nil {
   409  		b.dataInsBatch.Close()
   410  		b.dataInsBatch = nil
   411  	}
   412  	if b.dataDelBatch != nil {
   413  		b.dataDelBatch.Close()
   414  		b.dataDelBatch = nil
   415  	}
   416  	if b.blkMetaInsBatch != nil {
   417  		b.blkMetaInsBatch.Close()
   418  		b.blkMetaInsBatch = nil
   419  	}
   420  	if b.blkMetaDelBatch != nil {
   421  		b.blkMetaDelBatch.Close()
   422  		b.blkMetaDelBatch = nil
   423  	}
   424  }
   425  
   426  func (b *TableLogtailRespBuilder) visitBlkMeta(e *catalog.BlockEntry) (skipData bool) {
   427  	newEnd := b.end
   428  	e.RLock()
   429  	// try to find new end
   430  	if newest := e.GetLatestCommittedNode(); newest != nil {
   431  		latestPrepareTs := newest.CloneAll().(*catalog.MetadataMVCCNode).GetPrepare()
   432  		if latestPrepareTs.Greater(b.end) {
   433  			newEnd = latestPrepareTs
   434  		}
   435  	}
   436  	mvccNodes := e.ClonePreparedInRange(b.start, newEnd)
   437  	e.RUnlock()
   438  
   439  	for _, node := range mvccNodes {
   440  		metaNode := node.(*catalog.MetadataMVCCNode)
   441  		if metaNode.MetaLoc != "" && !metaNode.IsAborted() {
   442  			b.appendBlkMeta(e, metaNode)
   443  		}
   444  	}
   445  
   446  	if n := len(mvccNodes); n > 0 {
   447  		newest := mvccNodes[n-1].(*catalog.MetadataMVCCNode)
   448  		if e.IsAppendable() {
   449  			if newest.MetaLoc != "" {
   450  				// appendable block has been flushed, no need to collect data
   451  				return true
   452  			}
   453  		} else {
   454  			if newest.DeltaLoc != "" && newest.GetEnd().GreaterEq(b.end) {
   455  				// non-appendable block has newer delta data on s3, no need to collect data
   456  				return true
   457  			}
   458  		}
   459  	}
   460  	return false
   461  }
   462  
   463  func (b *TableLogtailRespBuilder) appendBlkMeta(e *catalog.BlockEntry, metaNode *catalog.MetadataMVCCNode) {
   464  	logutil.Infof("[Logtail] record block meta row %s, %v, %s, %s, %s, %s",
   465  		e.AsCommonID().String(), e.IsAppendable(),
   466  		metaNode.CreatedAt.ToString(), metaNode.DeletedAt.ToString(), metaNode.MetaLoc, metaNode.DeltaLoc)
   467  	is_sorted := false
   468  	if !e.IsAppendable() && e.GetSchema().HasSortKey() {
   469  		is_sorted = true
   470  	}
   471  	insBatch := b.blkMetaInsBatch
   472  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_ID).Append(e.ID)
   473  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_EntryState).Append(e.IsAppendable())
   474  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_Sorted).Append(is_sorted)
   475  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_MetaLoc).Append([]byte(metaNode.MetaLoc))
   476  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_DeltaLoc).Append([]byte(metaNode.DeltaLoc))
   477  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_CommitTs).Append(metaNode.GetEnd())
   478  	insBatch.GetVectorByName(pkgcatalog.BlockMeta_SegmentID).Append(e.GetSegment().ID)
   479  	insBatch.GetVectorByName(catalog.AttrCommitTs).Append(metaNode.CreatedAt)
   480  	insBatch.GetVectorByName(catalog.AttrRowID).Append(u64ToRowID(e.ID))
   481  
   482  	if metaNode.HasDropCommitted() {
   483  		if metaNode.DeletedAt.IsEmpty() {
   484  			panic(moerr.NewInternalErrorNoCtx("no delete at time in a dropped entry"))
   485  		}
   486  		delBatch := b.blkMetaDelBatch
   487  		delBatch.GetVectorByName(catalog.AttrCommitTs).Append(metaNode.DeletedAt)
   488  		delBatch.GetVectorByName(catalog.AttrRowID).Append(u64ToRowID(e.ID))
   489  	}
   490  }
   491  
   492  func (b *TableLogtailRespBuilder) visitBlkData(e *catalog.BlockEntry) (err error) {
   493  	block := e.GetBlockData()
   494  	insBatch, err := block.CollectAppendInRange(b.start, b.end, false)
   495  	if err != nil {
   496  		return
   497  	}
   498  	if insBatch != nil && insBatch.Length() > 0 {
   499  		b.dataInsBatch.Extend(insBatch)
   500  		// insBatch is freed, don't use anymore
   501  	}
   502  	delBatch, err := block.CollectDeleteInRange(b.start, b.end, false)
   503  	if err != nil {
   504  		return
   505  	}
   506  	if delBatch != nil && delBatch.Length() > 0 {
   507  		b.dataDelBatch.Extend(delBatch)
   508  		// delBatch is freed, don't use anymore
   509  	}
   510  	return nil
   511  }
   512  
   513  func (b *TableLogtailRespBuilder) VisitBlk(entry *catalog.BlockEntry) error {
   514  	if b.visitBlkMeta(entry) {
   515  		// data has been flushed, no need to collect data
   516  		return nil
   517  	}
   518  	return b.visitBlkData(entry)
   519  }
   520  
   521  func (b *TableLogtailRespBuilder) BuildResp() (api.SyncLogTailResp, error) {
   522  	entries := make([]*api.Entry, 0)
   523  	tryAppendEntry := func(typ api.Entry_EntryType, metaChange bool, batch *containers.Batch) error {
   524  		if batch.Length() == 0 {
   525  			return nil
   526  		}
   527  		bat, err := containersBatchToProtoBatch(batch)
   528  		if err != nil {
   529  			return err
   530  		}
   531  
   532  		tableName := b.tname
   533  		if metaChange {
   534  			tableName = fmt.Sprintf("_%d_meta", b.tid)
   535  			logutil.Infof("[Logtail] send block meta for %q", b.tname)
   536  		}
   537  		if metaChange {
   538  			logutil.Infof("[logtail] table meta [%v] %d-%s: %s", typ, b.tid, b.tname,
   539  				DebugBatchToString("meta", batch, true, zap.InfoLevel))
   540  		} else {
   541  			logutil.Infof("[logtail] table data [%v] %d-%s: %s", typ, b.tid, b.tname,
   542  				DebugBatchToString("data", batch, false, zap.InfoLevel))
   543  		}
   544  
   545  		entry := &api.Entry{
   546  			EntryType:    typ,
   547  			TableId:      b.tid,
   548  			TableName:    tableName,
   549  			DatabaseId:   b.did,
   550  			DatabaseName: b.dname,
   551  			Bat:          bat,
   552  		}
   553  		entries = append(entries, entry)
   554  		return nil
   555  	}
   556  
   557  	empty := api.SyncLogTailResp{}
   558  	if err := tryAppendEntry(api.Entry_Insert, true, b.blkMetaInsBatch); err != nil {
   559  		return empty, err
   560  	}
   561  	if err := tryAppendEntry(api.Entry_Delete, true, b.blkMetaDelBatch); err != nil {
   562  		return empty, err
   563  	}
   564  	if err := tryAppendEntry(api.Entry_Insert, false, b.dataInsBatch); err != nil {
   565  		return empty, err
   566  	}
   567  	if err := tryAppendEntry(api.Entry_Delete, false, b.dataDelBatch); err != nil {
   568  		return empty, err
   569  	}
   570  
   571  	return api.SyncLogTailResp{
   572  		CkpLocation: b.checkpoint,
   573  		Commands:    entries,
   574  	}, nil
   575  }
   576  
   577  func LoadCheckpointEntries(
   578  	ctx context.Context,
   579  	metLoc string,
   580  	tableID uint64,
   581  	tableName string,
   582  	dbID uint64,
   583  	dbName string,
   584  	fs fileservice.FileService) (entries []*api.Entry, err error) {
   585  	if metLoc == "" {
   586  		return
   587  	}
   588  
   589  	locations := strings.Split(metLoc, ";")
   590  	datas := make([]*CheckpointData, len(locations))
   591  	jobs := make([]*tasks.Job, len(locations))
   592  	defer func() {
   593  		for idx, data := range datas {
   594  			if jobs[idx] != nil {
   595  				jobs[idx].WaitDone()
   596  			}
   597  			if data != nil {
   598  				data.Close()
   599  			}
   600  		}
   601  	}()
   602  
   603  	// TODO: using a global job scheduler
   604  	jobScheduler := tasks.NewParallelJobScheduler(200)
   605  	defer jobScheduler.Stop()
   606  
   607  	makeJob := func(i int) (job *tasks.Job) {
   608  		location := locations[i]
   609  		exec := func(ctx context.Context) (result *tasks.JobResult) {
   610  			result = &tasks.JobResult{}
   611  			reader, err := blockio.NewCheckpointReader(ctx, fs, location)
   612  			if err != nil {
   613  				result.Err = err
   614  				return
   615  			}
   616  			data := NewCheckpointData()
   617  			if err = data.ReadFrom(reader, nil, common.DefaultAllocator); err != nil {
   618  				result.Err = err
   619  				return
   620  			}
   621  			datas[i] = data
   622  			return
   623  		}
   624  		job = tasks.NewJob(
   625  			fmt.Sprintf("load-%s", location),
   626  			context.Background(),
   627  			exec)
   628  		return
   629  	}
   630  
   631  	for i := range locations {
   632  		jobs[i] = makeJob(i)
   633  		if err = jobScheduler.Schedule(jobs[i]); err != nil {
   634  			return
   635  		}
   636  	}
   637  
   638  	for _, job := range jobs {
   639  		result := job.WaitDone()
   640  		if err = result.Err; err != nil {
   641  			return
   642  		}
   643  	}
   644  
   645  	entries = make([]*api.Entry, 0)
   646  	for i := range locations {
   647  		data := datas[i]
   648  		ins, del, cnIns, err := data.GetTableData(tableID)
   649  		if err != nil {
   650  			return nil, err
   651  		}
   652  		if tableName != pkgcatalog.MO_DATABASE &&
   653  			tableName != pkgcatalog.MO_COLUMNS &&
   654  			tableName != pkgcatalog.MO_TABLES {
   655  			tableName = fmt.Sprintf("_%d_meta", tableID)
   656  		}
   657  		if ins != nil {
   658  			entry := &api.Entry{
   659  				EntryType:    api.Entry_Insert,
   660  				TableId:      tableID,
   661  				TableName:    tableName,
   662  				DatabaseId:   dbID,
   663  				DatabaseName: dbName,
   664  				Bat:          ins,
   665  			}
   666  			entries = append(entries, entry)
   667  		}
   668  		if cnIns != nil {
   669  			entry := &api.Entry{
   670  				EntryType:    api.Entry_Insert,
   671  				TableId:      tableID,
   672  				TableName:    tableName,
   673  				DatabaseId:   dbID,
   674  				DatabaseName: dbName,
   675  				Bat:          cnIns,
   676  			}
   677  			entries = append(entries, entry)
   678  		}
   679  		if del != nil {
   680  			entry := &api.Entry{
   681  				EntryType:    api.Entry_Delete,
   682  				TableId:      tableID,
   683  				TableName:    tableName,
   684  				DatabaseId:   dbID,
   685  				DatabaseName: dbName,
   686  				Bat:          del,
   687  			}
   688  			entries = append(entries, entry)
   689  		}
   690  	}
   691  	return
   692  }