github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/acyclic/bindinfo/cache.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package bindinfo
    15  
    16  import (
    17  	"time"
    18  	"unsafe"
    19  
    20  	"github.com/whtcorpsinc/BerolinaSQL"
    21  	"github.com/whtcorpsinc/milevadb/metrics"
    22  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    23  	"github.com/whtcorpsinc/milevadb/types"
    24  	"github.com/whtcorpsinc/milevadb/soliton/hint"
    25  )
    26  
    27  const (
    28  	// Using is the bind info's in use status.
    29  	Using = "using"
    30  	// deleted is the bind info's deleted status.
    31  	deleted = "deleted"
    32  	// Invalid is the bind info's invalid status.
    33  	Invalid = "invalid"
    34  	// PendingVerify means the bind info needs to be verified.
    35  	PendingVerify = "pending verify"
    36  	// Rejected means that the bind has been rejected after verify process.
    37  	// We can retry it after certain time has passed.
    38  	Rejected = "rejected"
    39  	// Manual indicates the binding is created by ALLEGROALLEGROSQL like "create binding for ...".
    40  	Manual = "manual"
    41  	// Capture indicates the binding is captured by MilevaDB automatically.
    42  	Capture = "capture"
    43  	// Evolve indicates the binding is evolved by MilevaDB from old bindings.
    44  	Evolve = "evolve"
    45  )
    46  
    47  // Binding stores the basic bind hint info.
    48  type Binding struct {
    49  	BindALLEGROSQL string
    50  	// Status represents the status of the binding. It can only be one of the following values:
    51  	// 1. deleted: BindRecord is deleted, can not be used anymore.
    52  	// 2. using: Binding is in the normal active mode.
    53  	Status     string
    54  	CreateTime types.Time
    55  	UFIDelateTime types.Time
    56  	Source     string
    57  	Charset    string
    58  	DefCauslation  string
    59  	// Hint is the parsed hints, it is used to bind hints to stmt node.
    60  	Hint *hint.HintsSet
    61  	// ID is the string form of Hint. It would be non-empty only when the status is `Using` or `PendingVerify`.
    62  	ID string
    63  }
    64  
    65  func (b *Binding) isSame(rb *Binding) bool {
    66  	if b.ID != "" && rb.ID != "" {
    67  		return b.ID == rb.ID
    68  	}
    69  	// Sometimes we cannot construct `ID` because of the changed schemaReplicant, so we need to compare by bind allegrosql.
    70  	return b.BindALLEGROSQL == rb.BindALLEGROSQL
    71  }
    72  
    73  // SinceUFIDelateTime returns the duration since last uFIDelate time. Export for test.
    74  func (b *Binding) SinceUFIDelateTime() (time.Duration, error) {
    75  	uFIDelateTime, err := b.UFIDelateTime.GoTime(time.Local)
    76  	if err != nil {
    77  		return 0, err
    78  	}
    79  	return time.Since(uFIDelateTime), nil
    80  }
    81  
    82  // cache is a k-v map, key is original allegrosql, value is a slice of BindRecord.
    83  type cache map[string][]*BindRecord
    84  
    85  // BindRecord represents a allegrosql bind record retrieved from the storage.
    86  type BindRecord struct {
    87  	OriginalALLEGROSQL string
    88  	EDB          string
    89  
    90  	Bindings []Binding
    91  }
    92  
    93  // HasUsingBinding checks if there are any using bindings in bind record.
    94  func (br *BindRecord) HasUsingBinding() bool {
    95  	for _, binding := range br.Bindings {
    96  		if binding.Status == Using {
    97  			return true
    98  		}
    99  	}
   100  	return false
   101  }
   102  
   103  // FindBinding find bindings in BindRecord.
   104  func (br *BindRecord) FindBinding(hint string) *Binding {
   105  	for _, binding := range br.Bindings {
   106  		if binding.ID == hint {
   107  			return &binding
   108  		}
   109  	}
   110  	return nil
   111  }
   112  
   113  // prepareHints builds ID and Hint for BindRecord. If sctx is not nil, we check if
   114  // the BindALLEGROSQL is still valid.
   115  func (br *BindRecord) prepareHints(sctx stochastikctx.Context) error {
   116  	p := BerolinaSQL.New()
   117  	for i, bind := range br.Bindings {
   118  		if (bind.Hint != nil && bind.ID != "") || bind.Status == deleted {
   119  			continue
   120  		}
   121  		if sctx != nil {
   122  			_, err := getHintsForALLEGROSQL(sctx, bind.BindALLEGROSQL)
   123  			if err != nil {
   124  				return err
   125  			}
   126  		}
   127  		hintsSet, warns, err := hint.ParseHintsSet(p, bind.BindALLEGROSQL, bind.Charset, bind.DefCauslation, br.EDB)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		hintsStr, err := hintsSet.Restore()
   132  		if err != nil {
   133  			return err
   134  		}
   135  		// For `create global binding for select * from t using select * from t`, we allow it though hintsStr is empty.
   136  		// For `create global binding for select * from t using select /*+ non_exist_hint() */ * from t`,
   137  		// the hint is totally invalid, we escalate warning to error.
   138  		if hintsStr == "" && len(warns) > 0 {
   139  			return warns[0]
   140  		}
   141  		br.Bindings[i].Hint = hintsSet
   142  		br.Bindings[i].ID = hintsStr
   143  	}
   144  	return nil
   145  }
   146  
   147  // `merge` merges two BindRecord. It will replace old bindings with new bindings if there are new uFIDelates.
   148  func merge(lBindRecord, rBindRecord *BindRecord) *BindRecord {
   149  	if lBindRecord == nil {
   150  		return rBindRecord
   151  	}
   152  	if rBindRecord == nil {
   153  		return lBindRecord
   154  	}
   155  	result := lBindRecord.shallowCopy()
   156  	for _, rbind := range rBindRecord.Bindings {
   157  		found := false
   158  		for j, lbind := range lBindRecord.Bindings {
   159  			if lbind.isSame(&rbind) {
   160  				found = true
   161  				if rbind.UFIDelateTime.Compare(lbind.UFIDelateTime) >= 0 {
   162  					result.Bindings[j] = rbind
   163  				}
   164  				break
   165  			}
   166  		}
   167  		if !found {
   168  			result.Bindings = append(result.Bindings, rbind)
   169  		}
   170  	}
   171  	return result
   172  }
   173  
   174  func (br *BindRecord) remove(deleted *BindRecord) *BindRecord {
   175  	// Delete all bindings.
   176  	if len(deleted.Bindings) == 0 {
   177  		return &BindRecord{OriginalALLEGROSQL: br.OriginalALLEGROSQL, EDB: br.EDB}
   178  	}
   179  	result := br.shallowCopy()
   180  	for _, deletedBind := range deleted.Bindings {
   181  		for i, bind := range result.Bindings {
   182  			if bind.isSame(&deletedBind) {
   183  				result.Bindings = append(result.Bindings[:i], result.Bindings[i+1:]...)
   184  				break
   185  			}
   186  		}
   187  	}
   188  	return result
   189  }
   190  
   191  func (br *BindRecord) removeDeletedBindings() *BindRecord {
   192  	result := BindRecord{OriginalALLEGROSQL: br.OriginalALLEGROSQL, EDB: br.EDB, Bindings: make([]Binding, 0, len(br.Bindings))}
   193  	for _, binding := range br.Bindings {
   194  		if binding.Status != deleted {
   195  			result.Bindings = append(result.Bindings, binding)
   196  		}
   197  	}
   198  	return &result
   199  }
   200  
   201  // shallowCopy shallow copies the BindRecord.
   202  func (br *BindRecord) shallowCopy() *BindRecord {
   203  	result := BindRecord{
   204  		OriginalALLEGROSQL: br.OriginalALLEGROSQL,
   205  		EDB:          br.EDB,
   206  		Bindings:    make([]Binding, len(br.Bindings)),
   207  	}
   208  	copy(result.Bindings, br.Bindings)
   209  	return &result
   210  }
   211  
   212  func (br *BindRecord) isSame(other *BindRecord) bool {
   213  	return br.OriginalALLEGROSQL == other.OriginalALLEGROSQL && br.EDB == other.EDB
   214  }
   215  
   216  var statusIndex = map[string]int{
   217  	Using:   0,
   218  	deleted: 1,
   219  	Invalid: 2,
   220  }
   221  
   222  func (br *BindRecord) metrics() ([]float64, []int) {
   223  	sizes := make([]float64, len(statusIndex))
   224  	count := make([]int, len(statusIndex))
   225  	if br == nil {
   226  		return sizes, count
   227  	}
   228  	commonLength := float64(len(br.OriginalALLEGROSQL) + len(br.EDB))
   229  	// We treat it as deleted if there are no bindings. It could only occur in stochastik handles.
   230  	if len(br.Bindings) == 0 {
   231  		sizes[statusIndex[deleted]] = commonLength
   232  		count[statusIndex[deleted]] = 1
   233  		return sizes, count
   234  	}
   235  	// Make the common length counted in the first binding.
   236  	sizes[statusIndex[br.Bindings[0].Status]] = commonLength
   237  	for _, binding := range br.Bindings {
   238  		sizes[statusIndex[binding.Status]] += binding.size()
   239  		count[statusIndex[binding.Status]]++
   240  	}
   241  	return sizes, count
   242  }
   243  
   244  // size calculates the memory size of a bind info.
   245  func (b *Binding) size() float64 {
   246  	res := len(b.BindALLEGROSQL) + len(b.Status) + 2*int(unsafe.Sizeof(b.CreateTime)) + len(b.Charset) + len(b.DefCauslation)
   247  	return float64(res)
   248  }
   249  
   250  func uFIDelateMetrics(scope string, before *BindRecord, after *BindRecord, sizeOnly bool) {
   251  	beforeSize, beforeCount := before.metrics()
   252  	afterSize, afterCount := after.metrics()
   253  	for status, index := range statusIndex {
   254  		metrics.BindMemoryUsage.WithLabelValues(scope, status).Add(afterSize[index] - beforeSize[index])
   255  		if !sizeOnly {
   256  			metrics.BindTotalGauge.WithLabelValues(scope, status).Add(float64(afterCount[index] - beforeCount[index]))
   257  		}
   258  	}
   259  }