Files
tidb/table/tables/cache.go

259 lines
7.0 KiB
Go

// Copyright 2021 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tables
import (
"context"
"sync/atomic"
"time"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/types"
"github.com/tikv/client-go/v2/oracle"
"github.com/tikv/client-go/v2/tikv"
)
// RenewLeaseType define the type for renew lease.
type RenewLeaseType int
const (
// RenewReadLease means renew read lease.
RenewReadLease RenewLeaseType = iota + 1
// RenewWriteLease means renew write lease.
RenewWriteLease
)
var (
_ table.Table = &cachedTable{}
_ table.CachedTable = &cachedTable{}
)
type cachedTable struct {
TableCommon
cacheData atomic.Value
handle StateRemote
renewCh chan func()
}
// cacheData pack the cache data and lease.
type cacheData struct {
Start uint64
Lease uint64
kv.MemBuffer
}
func leaseFromTS(ts uint64) uint64 {
// TODO make this configurable in the following PRs
const defaultLeaseDuration time.Duration = 3 * time.Second
physicalTime := oracle.GetTimeFromTS(ts)
lease := oracle.GoTimeToTS(physicalTime.Add(defaultLeaseDuration))
return lease
}
func newMemBuffer(store kv.Storage) (kv.MemBuffer, error) {
// Here is a trick to get a MemBuffer data, because the internal API is not exposed.
// Create a transaction with start ts 0, and take the MemBuffer out.
buffTxn, err := store.Begin(tikv.WithStartTS(0))
if err != nil {
return nil, err
}
return buffTxn.GetMemBuffer(), nil
}
func (c *cachedTable) TryReadFromCache(ts uint64) kv.MemBuffer {
tmp := c.cacheData.Load()
if tmp == nil {
return nil
}
data := tmp.(*cacheData)
if ts >= data.Start && ts < data.Lease {
leaseTime := oracle.GetTimeFromTS(data.Lease)
nowTime := oracle.GetTimeFromTS(ts)
distance := leaseTime.Sub(nowTime)
// TODO make this configurable in the following PRs
if distance >= 0 && distance <= (1*time.Second) {
c.renewCh <- c.renewLease(ts, RenewReadLease, data)
}
return data
}
return nil
}
// MockStateRemote represents the information of stateRemote.
// Exported it only for testing.
var MockStateRemote = struct {
Ch chan remoteTask
Data *mockStateRemoteData
}{}
// NewCachedTable creates a new CachedTable Instance
func NewCachedTable(tbl *TableCommon) (table.Table, error) {
if MockStateRemote.Data == nil {
MockStateRemote.Data = newMockStateRemoteData()
MockStateRemote.Ch = make(chan remoteTask, 100)
go mockRemoteService(MockStateRemote.Data, MockStateRemote.Ch)
}
ret := &cachedTable{
TableCommon: *tbl,
handle: &mockStateRemoteHandle{MockStateRemote.Ch},
renewCh: make(chan func()),
}
return ret, nil
}
// Init is an extra operation for cachedTable after TableFromMeta,
// Because cachedTable need some additional parameter that can't be passed in TableFromMeta.
func (c *cachedTable) Init(renewCh chan func()) error {
c.renewCh = renewCh
return nil
}
func (c *cachedTable) loadDataFromOriginalTable(store kv.Storage, lease uint64) (kv.MemBuffer, uint64, error) {
buffer, err := newMemBuffer(store)
if err != nil {
return nil, 0, err
}
var startTS uint64
err = kv.RunInNewTxn(context.Background(), store, true, func(ctx context.Context, txn kv.Transaction) error {
prefix := tablecodec.GenTablePrefix(c.tableID)
if err != nil {
return errors.Trace(err)
}
startTS = txn.StartTS()
if startTS >= lease {
return errors.New("the loaded data is outdated for caching")
}
it, err := txn.Iter(prefix, prefix.PrefixNext())
if err != nil {
return errors.Trace(err)
}
defer it.Close()
for it.Valid() && it.Key().HasPrefix(prefix) {
value := it.Value()
err = buffer.Set(it.Key(), value)
if err != nil {
return errors.Trace(err)
}
err = it.Next()
if err != nil {
return errors.Trace(err)
}
}
return nil
})
if err != nil {
return nil, 0, err
}
return buffer, startTS, nil
}
func (c *cachedTable) UpdateLockForRead(store kv.Storage, ts uint64) error {
// Load data from original table and the update lock information.
tid := c.Meta().ID
lease := leaseFromTS(ts)
succ, err := c.handle.LockForRead(context.Background(), tid, ts, lease)
if err != nil {
return errors.Trace(err)
}
if succ {
mb, startTS, err := c.loadDataFromOriginalTable(store, lease)
if err != nil {
return errors.Trace(err)
}
c.cacheData.Store(&cacheData{
Start: startTS,
Lease: lease,
MemBuffer: mb,
})
}
// Current status is not suitable to cache.
return nil
}
// AddRecord implements the AddRecord method for the table.Table interface.
func (c *cachedTable) AddRecord(ctx sessionctx.Context, r []types.Datum, opts ...table.AddRecordOption) (recordID kv.Handle, err error) {
txn, err := ctx.Txn(true)
if err != nil {
return nil, err
}
now := txn.StartTS()
start := time.Now()
err = c.handle.LockForWrite(context.Background(), c.Meta().ID, now, leaseFromTS(now))
if err != nil {
return nil, errors.Trace(err)
}
ctx.GetSessionVars().StmtCtx.WaitLockLeaseTime += time.Since(start)
return c.TableCommon.AddRecord(ctx, r, opts...)
}
// UpdateRecord implements table.Table
func (c *cachedTable) UpdateRecord(ctx context.Context, sctx sessionctx.Context, h kv.Handle, oldData, newData []types.Datum, touched []bool) error {
txn, err := sctx.Txn(true)
if err != nil {
return err
}
now := txn.StartTS()
start := time.Now()
err = c.handle.LockForWrite(ctx, c.Meta().ID, now, leaseFromTS(now))
if err != nil {
return errors.Trace(err)
}
sctx.GetSessionVars().StmtCtx.WaitLockLeaseTime += time.Since(start)
return c.TableCommon.UpdateRecord(ctx, sctx, h, oldData, newData, touched)
}
// RemoveRecord implements table.Table RemoveRecord interface.
func (c *cachedTable) RemoveRecord(ctx sessionctx.Context, h kv.Handle, r []types.Datum) error {
txn, err := ctx.Txn(true)
if err != nil {
return err
}
now := txn.StartTS()
start := time.Now()
err = c.handle.LockForWrite(context.Background(), c.Meta().ID, now, leaseFromTS(now))
if err != nil {
return errors.Trace(err)
}
ctx.GetSessionVars().StmtCtx.WaitLockLeaseTime += time.Since(start)
return c.TableCommon.RemoveRecord(ctx, h, r)
}
func (c *cachedTable) renewLease(ts uint64, op RenewLeaseType, data *cacheData) func() {
return func() {
tid := c.Meta().ID
lease := leaseFromTS(ts)
succ, err := c.handle.RenewLease(context.Background(), tid, ts, lease, op)
if err != nil {
log.Warn("Renew read lease error")
}
if succ {
c.cacheData.Store(&cacheData{
Start: data.Start,
Lease: lease,
MemBuffer: data,
})
}
}
}