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 }