github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/logtail/logtailer.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  import (
    18  	"context"
    19  	"math"
    20  
    21  	pkgcatalog "github.com/matrixorigin/matrixone/pkg/catalog"
    22  	"github.com/matrixorigin/matrixone/pkg/container/types"
    23  	"github.com/matrixorigin/matrixone/pkg/logutil"
    24  	"github.com/matrixorigin/matrixone/pkg/pb/api"
    25  	"github.com/matrixorigin/matrixone/pkg/pb/logtail"
    26  	"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
    27  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  // Logtailer provides logtail for the specified table.
    32  type Logtailer interface {
    33  	// RangeLogtail returns logtail for all tables within the range (from, to].
    34  	// NOTE: caller should keep time range monotonous, or there would be a checkpoint.
    35  	RangeLogtail(
    36  		ctx context.Context, from, to timestamp.Timestamp,
    37  	) ([]logtail.TableLogtail, []func(), error)
    38  
    39  	RegisterCallback(cb func(from, to timestamp.Timestamp, closeCB func(), tails ...logtail.TableLogtail) error)
    40  
    41  	// TableLogtail returns logtail for the specified table.
    42  	//
    43  	// NOTE: If table not exist, logtail.TableLogtail shouldn't be a simple zero value.
    44  	TableLogtail(
    45  		ctx context.Context, table api.TableID, from, to timestamp.Timestamp,
    46  	) (logtail.TableLogtail, func(), error)
    47  
    48  	// Now is a time getter from TxnManager. Users of Logtailer should get a timestamp
    49  	// from Now and use the timestamp to collect logtail, in that case, all txn prepared
    50  	// before it are visible.
    51  	Now() (timestamp.Timestamp, timestamp.Timestamp)
    52  }
    53  
    54  var _ Logtailer = (*LogtailerImpl)(nil)
    55  
    56  type LogtailerImpl struct {
    57  	ctx       context.Context
    58  	ckpClient CheckpointClient
    59  	mgr       *Manager
    60  	c         *catalog.Catalog
    61  }
    62  
    63  func NewLogtailer(
    64  	ctx context.Context,
    65  	ckpClient CheckpointClient,
    66  	mgr *Manager,
    67  	c *catalog.Catalog) *LogtailerImpl {
    68  	return &LogtailerImpl{
    69  		ctx:       ctx,
    70  		ckpClient: ckpClient,
    71  		mgr:       mgr,
    72  		c:         c,
    73  	}
    74  }
    75  
    76  // Now is a time getter from TxnManager. Users of Logtailer should get a timestamp
    77  // from Now and use the timestamp to collect logtail, in that case, all txn prepared
    78  // before it are visible.
    79  func (l *LogtailerImpl) Now() (timestamp.Timestamp, timestamp.Timestamp) {
    80  	ts := l.mgr.nowClock() // now in logtail manager is the same with the one in TxnManager
    81  
    82  	return ts.ToTimestamp(), timestamp.Timestamp{}
    83  }
    84  
    85  // TableLogtail returns logtail for the specified table.
    86  // It boils down to calling `HandleSyncLogTailReq`
    87  func (l *LogtailerImpl) TableLogtail(
    88  	ctx context.Context, table api.TableID, from, to timestamp.Timestamp,
    89  ) (logtail.TableLogtail, func(), error) {
    90  	req := api.SyncLogTailReq{
    91  		CnHave: &from,
    92  		CnWant: &to,
    93  		Table:  &table,
    94  	}
    95  	resp, closeCB, err := HandleSyncLogTailReq(ctx, l.ckpClient, l.mgr, l.c, req, true)
    96  	ret := logtail.TableLogtail{}
    97  	if err != nil {
    98  		return ret, closeCB, err
    99  	}
   100  	ret.CkpLocation = resp.CkpLocation
   101  	ret.Ts = &to
   102  	ret.Table = &table
   103  	ret.Commands = nonPointerEntryList(resp.Commands)
   104  	return ret, closeCB, nil
   105  }
   106  func (l *LogtailerImpl) RegisterCallback(cb func(from, to timestamp.Timestamp, closeCB func(), tails ...logtail.TableLogtail) error) {
   107  	l.mgr.RegisterCallback(cb)
   108  }
   109  
   110  // RangeLogtail returns logtail for all tables that are modified within the range (from, to].
   111  // Check out all dirty tables in the time window and collect logtails for every table
   112  func (l *LogtailerImpl) RangeLogtail(
   113  	ctx context.Context, from, to timestamp.Timestamp,
   114  ) ([]logtail.TableLogtail, []func(), error) {
   115  	start := types.BuildTS(from.PhysicalTime, from.LogicalTime)
   116  	end := types.BuildTS(to.PhysicalTime, to.LogicalTime)
   117  
   118  	ckpLoc, checkpointed, err := l.ckpClient.CollectCheckpointsInRange(ctx, start, end)
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  
   123  	if checkpointed.GreaterEq(&end) {
   124  		return []logtail.TableLogtail{{
   125  			CkpLocation: ckpLoc,
   126  			Ts:          &to,
   127  			Table:       &api.TableID{DbId: math.MaxUint64, TbId: math.MaxUint64},
   128  		}}, nil, nil
   129  	} else if ckpLoc != "" {
   130  		start = checkpointed.Next()
   131  	}
   132  
   133  	reader := l.mgr.GetReader(start, end)
   134  	resps := make([]logtail.TableLogtail, 0, 8)
   135  
   136  	closeCBs := make([]func(), 0)
   137  	// collect resp for the three system tables
   138  	if reader.HasCatalogChanges() {
   139  		for _, scope := range []Scope{ScopeDatabases, ScopeTables, ScopeColumns} {
   140  			resp, closeCB, err := l.getCatalogRespBuilder(scope, reader, ckpLoc).build()
   141  			if err != nil {
   142  				for _, cb := range closeCBs {
   143  					if cb != nil {
   144  						cb()
   145  					}
   146  				}
   147  				return nil, nil, err
   148  			}
   149  			closeCBs = append(closeCBs, closeCB)
   150  			resps = append(resps, resp)
   151  		}
   152  	}
   153  
   154  	// collect resp for every dirty normal table
   155  	dirties, _ := reader.GetDirty()
   156  	for _, table := range dirties.Tables {
   157  		did, tid := table.DbID, table.ID
   158  		resp, closeCB, err := l.getTableRespBuilder(did, tid, reader, ckpLoc).build()
   159  		if err != nil {
   160  			for _, cb := range closeCBs {
   161  				if cb != nil {
   162  					cb()
   163  				}
   164  			}
   165  			return resps, nil, err
   166  		}
   167  		closeCBs = append(closeCBs, closeCB)
   168  		resps = append(resps, resp)
   169  	}
   170  	return resps, closeCBs, nil
   171  }
   172  
   173  func (l *LogtailerImpl) getTableRespBuilder(did, tid uint64, reader *Reader, ckpLoc string) *tableRespBuilder {
   174  	return &tableRespBuilder{
   175  		did:    did,
   176  		tid:    tid,
   177  		scope:  ScopeUserTables,
   178  		reader: reader,
   179  		c:      l.c,
   180  	}
   181  }
   182  
   183  func (l *LogtailerImpl) getCatalogRespBuilder(scope Scope, reader *Reader, ckpLoc string) *tableRespBuilder {
   184  	b := &tableRespBuilder{
   185  		did:    pkgcatalog.MO_CATALOG_ID,
   186  		scope:  scope,
   187  		reader: reader,
   188  		c:      l.c,
   189  	}
   190  	switch scope {
   191  	case ScopeDatabases:
   192  		b.tid = pkgcatalog.MO_DATABASE_ID
   193  	case ScopeTables:
   194  		b.tid = pkgcatalog.MO_TABLES_ID
   195  	case ScopeColumns:
   196  		b.tid = pkgcatalog.MO_COLUMNS_ID
   197  	}
   198  	return b
   199  }
   200  
   201  type tableRespBuilder struct {
   202  	ctx      context.Context
   203  	did, tid uint64
   204  	ckpLoc   string
   205  	scope    Scope
   206  	reader   *Reader
   207  	c        *catalog.Catalog
   208  }
   209  
   210  func (b *tableRespBuilder) build() (logtail.TableLogtail, func(), error) {
   211  	resp, closeCB, err := b.collect()
   212  	if err != nil {
   213  		return logtail.TableLogtail{}, closeCB, err
   214  	}
   215  	ret := logtail.TableLogtail{}
   216  	ret.CkpLocation = resp.CkpLocation
   217  	to := b.reader.to.ToTimestamp()
   218  	ret.Ts = &to
   219  	ret.Table = &api.TableID{DbId: b.did, TbId: b.tid}
   220  	ret.Commands = nonPointerEntryList(resp.Commands)
   221  	return ret, closeCB, nil
   222  }
   223  
   224  func (b *tableRespBuilder) collect() (api.SyncLogTailResp, func(), error) {
   225  	var builder RespBuilder
   226  	if b.scope == ScopeUserTables {
   227  		dbEntry, err := b.c.GetDatabaseByID(b.did)
   228  		if err != nil {
   229  			logutil.Info("[Logtail] not found", zap.Any("db_id", b.did))
   230  			return api.SyncLogTailResp{}, nil, nil
   231  		}
   232  		tableEntry, err := dbEntry.GetTableEntryByID(b.tid)
   233  		if err != nil {
   234  			logutil.Info("[Logtail] not found", zap.Any("t_id", b.tid))
   235  			return api.SyncLogTailResp{}, nil, nil
   236  		}
   237  		builder = NewTableLogtailRespBuilder(b.ctx, b.ckpLoc, b.reader.from, b.reader.to, tableEntry)
   238  	} else {
   239  		builder = NewCatalogLogtailRespBuilder(b.ctx, b.scope, b.ckpLoc, b.reader.from, b.reader.to)
   240  	}
   241  	op := NewBoundTableOperator(b.c, b.reader, b.scope, b.did, b.tid, builder)
   242  	err := op.Run()
   243  	if err != nil {
   244  		return api.SyncLogTailResp{}, builder.Close, err
   245  	}
   246  
   247  	resp, err := builder.BuildResp()
   248  	return resp, builder.Close, err
   249  }
   250  
   251  // TODO: remvove this after push mode is stable
   252  func nonPointerEntryList(src []*api.Entry) []api.Entry {
   253  	es := make([]api.Entry, len(src))
   254  	for i, e := range src {
   255  		es[i] = *e
   256  	}
   257  	return es
   258  }