github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/storage/storage_mem.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package storage
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"sort"
    17  	"strconv"
    18  	"time"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    23  	"github.com/olekukonko/tablewriter"
    24  )
    25  
    26  type memStorage struct {
    27  	mu struct {
    28  		syncutil.RWMutex
    29  		buckets []ctpb.Entry
    30  		scale   time.Duration
    31  	}
    32  }
    33  
    34  var _ SingleStorage = (*memStorage)(nil)
    35  
    36  // NewMemStorage initializes a SingleStorage backed by an in-memory slice that
    37  // represents the given number of buckets, where the i-th bucket holds a closed
    38  // timestamp approximately 2^i*scale in the past.
    39  func NewMemStorage(scale time.Duration, buckets int) SingleStorage {
    40  	m := &memStorage{}
    41  	m.mu.buckets = make([]ctpb.Entry, buckets)
    42  	m.mu.scale = scale
    43  	return m
    44  }
    45  
    46  func (m *memStorage) String() string {
    47  	m.mu.RLock()
    48  	defer m.mu.RUnlock()
    49  
    50  	var buf bytes.Buffer
    51  	tw := tablewriter.NewWriter(&buf)
    52  
    53  	header := make([]string, 1+len(m.mu.buckets))
    54  	header[0] = ""
    55  	align := make([]int, 1+len(m.mu.buckets))
    56  	align[0] = tablewriter.ALIGN_LEFT
    57  
    58  	for i := range m.mu.buckets {
    59  		header[1+i] = m.mu.buckets[i].ClosedTimestamp.String() + "\nage=" + time.Duration(
    60  			m.mu.buckets[0].ClosedTimestamp.WallTime-m.mu.buckets[i].ClosedTimestamp.WallTime,
    61  		).String() + " (target ≤" + m.bucketMaxAge(i).String() + ")\nepoch=" + fmt.Sprintf("%d", m.mu.buckets[i].Epoch)
    62  		align[1+i] = tablewriter.ALIGN_RIGHT
    63  	}
    64  	tw.SetAutoFormatHeaders(false)
    65  	tw.SetColumnAlignment(align)
    66  	tw.SetHeader(header)
    67  	tw.SetHeaderLine(true)
    68  	tw.SetRowLine(false)
    69  	tw.SetColumnSeparator(" ")
    70  	tw.SetBorder(true)
    71  	tw.SetTrimWhiteSpaceAtEOL(true)
    72  	rangeIDs := make([]roachpb.RangeID, 0, len(m.mu.buckets[0].MLAI))
    73  	for rangeID := range m.mu.buckets[0].MLAI {
    74  		rangeIDs = append(rangeIDs, rangeID)
    75  	}
    76  	sort.Slice(rangeIDs, func(i, j int) bool {
    77  		return rangeIDs[i] < rangeIDs[j]
    78  	})
    79  
    80  	row := make([]string, 1+len(m.mu.buckets))
    81  	for _, rangeID := range rangeIDs {
    82  		row[0] = "r" + strconv.FormatInt(int64(rangeID), 10)
    83  		for i, entry := range m.mu.buckets {
    84  			lai, ok := entry.MLAI[rangeID]
    85  			if ok {
    86  				row[1+i] = strconv.FormatInt(int64(lai), 10)
    87  			} else {
    88  				row[1+i] = ""
    89  			}
    90  		}
    91  		tw.Append(row)
    92  	}
    93  
    94  	tw.Render()
    95  	return buf.String()
    96  }
    97  
    98  func (m *memStorage) bucketMaxAge(index int) time.Duration {
    99  	if index == 0 {
   100  		return 0
   101  	}
   102  	return (1 << uint(index-1)) * m.mu.scale
   103  }
   104  
   105  func (m *memStorage) Add(e ctpb.Entry) {
   106  	m.mu.Lock()
   107  	defer m.mu.Unlock()
   108  
   109  	now := e.ClosedTimestamp.WallTime
   110  
   111  	for i := 0; i < len(m.mu.buckets); i++ {
   112  		if time.Duration(now-m.mu.buckets[i].ClosedTimestamp.WallTime) <= m.bucketMaxAge(i) {
   113  			break
   114  		}
   115  		mergedEntry := merge(m.mu.buckets[i], e)
   116  		e = m.mu.buckets[i]
   117  		m.mu.buckets[i] = mergedEntry
   118  	}
   119  }
   120  
   121  func (m *memStorage) VisitAscending(f func(ctpb.Entry) (done bool)) {
   122  	m.mu.RLock()
   123  	defer m.mu.RUnlock()
   124  
   125  	for i := len(m.mu.buckets) - 1; i >= 0; i-- {
   126  		entry := m.mu.buckets[i]
   127  		if entry.Epoch == 0 {
   128  			// Skip empty buckets.
   129  			continue
   130  		}
   131  		if f(entry) {
   132  			return
   133  		}
   134  	}
   135  }
   136  
   137  func (m *memStorage) VisitDescending(f func(ctpb.Entry) (done bool)) {
   138  	m.mu.RLock()
   139  	defer m.mu.RUnlock()
   140  
   141  	for l, i := len(m.mu.buckets), 0; i < l; i++ {
   142  		entry := m.mu.buckets[i]
   143  		// Stop once we hit an empty bucket (which implies that all further buckets
   144  		// are also empty), or once the visitor is satisfied.
   145  		if entry.Epoch == 0 || f(entry) {
   146  			return
   147  		}
   148  	}
   149  }
   150  
   151  func (m *memStorage) Clear() {
   152  	m.mu.Lock()
   153  	defer m.mu.Unlock()
   154  	for i := 0; i < len(m.mu.buckets); i++ {
   155  		m.mu.buckets[i] = ctpb.Entry{}
   156  	}
   157  }
   158  
   159  func merge(e, ee ctpb.Entry) ctpb.Entry {
   160  	// TODO(tschottdorf): if either of these hit, check that what we're
   161  	// returning has Full set. If we make it past, check that either of
   162  	// them has it set. The first Entry the Storage sees for an epoch must have it
   163  	// set, so the assertions should never fire.
   164  	if e.Epoch < ee.Epoch {
   165  		return ee
   166  	} else if e.Epoch > ee.Epoch {
   167  		return e
   168  	}
   169  
   170  	// Epochs match, so we can actually update.
   171  
   172  	// Initialize re as a deep copy of e.
   173  	re := e
   174  	re.MLAI = map[roachpb.RangeID]ctpb.LAI{}
   175  	for rangeID, mlai := range e.MLAI {
   176  		re.MLAI[rangeID] = mlai
   177  	}
   178  	// The result is full if either operand is.
   179  	re.Full = e.Full || ee.Full
   180  	// Use the larger of both timestamps with the union of the MLAIs, preferring larger
   181  	// ones on conflict.
   182  	re.ClosedTimestamp.Forward(ee.ClosedTimestamp)
   183  	for rangeID, mlai := range ee.MLAI {
   184  		if cur, found := re.MLAI[rangeID]; !found || cur < mlai {
   185  			re.MLAI[rangeID] = mlai
   186  		}
   187  	}
   188  	return re
   189  }