From e8b3f717045bf92dd1421b831c9432bf20030392 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Mon, 6 Jan 2025 15:16:10 -0800 Subject: [PATCH 01/13] Introduce ToChunker interface --- .../doltcore/remotestorage/chunk_cache.go | 8 +++--- .../doltcore/remotestorage/chunk_fetcher.go | 12 ++++---- .../doltcore/remotestorage/chunk_store.go | 16 +++++------ .../doltcore/remotestorage/map_chunk_cache.go | 28 +++++++++---------- .../remotestorage/map_chunk_cache_test.go | 6 ++-- .../remotestorage/noop_chunk_cache.go | 10 +++---- go/store/datas/pull/pull_chunk_fetcher.go | 12 ++++---- .../datas/pull/pull_chunk_fetcher_test.go | 23 +++++++++------ go/store/datas/pull/puller.go | 14 ++++++---- go/store/nbs/archive_chunk_source.go | 2 +- go/store/nbs/archive_test.go | 2 +- go/store/nbs/chunk_fetcher.go | 2 +- go/store/nbs/cmp_chunk_table_writer.go | 11 ++++++-- go/store/nbs/cmp_chunk_table_writer_test.go | 4 +-- go/store/nbs/empty_chunk_source.go | 2 +- go/store/nbs/gc_copier.go | 2 +- go/store/nbs/generational_chunk_store.go | 9 +++--- go/store/nbs/ghost_store.go | 4 +-- go/store/nbs/journal_chunk_source.go | 4 +-- go/store/nbs/mem_table.go | 2 +- go/store/nbs/mem_table_test.go | 2 +- go/store/nbs/nbs_metrics_wrapper.go | 2 +- go/store/nbs/store.go | 16 +++++------ go/store/nbs/table.go | 2 +- go/store/nbs/table_reader.go | 26 +++++++++++++---- go/store/nbs/table_set.go | 2 +- 26 files changed, 127 insertions(+), 96 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/chunk_cache.go b/go/libraries/doltcore/remotestorage/chunk_cache.go index 0412b20a0ab..0d1e968844d 100644 --- a/go/libraries/doltcore/remotestorage/chunk_cache.go +++ b/go/libraries/doltcore/remotestorage/chunk_cache.go @@ -22,20 +22,20 @@ import ( // ChunkCache is an interface used for caching chunks type ChunkCache interface { // Put puts a slice of chunks into the cache. - Put(c []nbs.CompressedChunk) bool + Put(c []nbs.ToChunker) bool // Get gets a map of hash to chunk for a set of hashes. In the event that a chunk is not in the cache, chunks.Empty. // is put in it's place - Get(h hash.HashSet) map[hash.Hash]nbs.CompressedChunk + Get(h hash.HashSet) map[hash.Hash]nbs.ToChunker // Has takes a set of hashes and returns the set of hashes that the cache currently does not have in it. Has(h hash.HashSet) (absent hash.HashSet) // PutChunk puts a single chunk in the cache. true returns in the event that the chunk was cached successfully // and false is returned if that chunk is already is the cache. - PutChunk(chunk nbs.CompressedChunk) bool + PutChunk(chunk nbs.ToChunker) bool // GetAndClearChunksToFlush gets a map of hash to chunk which includes all the chunks that were put in the cache // between the last time GetAndClearChunksToFlush was called and now. - GetAndClearChunksToFlush() map[hash.Hash]nbs.CompressedChunk + GetAndClearChunksToFlush() map[hash.Hash]nbs.ToChunker } diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 51bd4bac614..5ca776d5940 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -49,7 +49,7 @@ type ChunkFetcher struct { egCtx context.Context toGetCh chan hash.HashSet - resCh chan nbs.CompressedChunk + resCh chan nbs.ToChunker abortCh chan struct{} stats StatsRecorder @@ -69,7 +69,7 @@ func NewChunkFetcher(ctx context.Context, dcs *DoltChunkStore) *ChunkFetcher { egCtx: ctx, toGetCh: make(chan hash.HashSet), - resCh: make(chan nbs.CompressedChunk), + resCh: make(chan nbs.ToChunker), abortCh: make(chan struct{}), stats: StatsFactory(), @@ -123,7 +123,7 @@ func (f *ChunkFetcher) CloseSend() error { // by |Get|. Returns |io.EOF| after |CloseSend| is called and all requested // chunks have been successfully received. Returns an error if this // |ChunkFetcher| is terminally failed or if the supplied |ctx| is |Done|. -func (f *ChunkFetcher) Recv(ctx context.Context) (nbs.CompressedChunk, error) { +func (f *ChunkFetcher) Recv(ctx context.Context) (nbs.ToChunker, error) { select { case <-ctx.Done(): return nbs.CompressedChunk{}, context.Cause(ctx) @@ -219,7 +219,7 @@ func fetcherHashSetToGetDlLocsReqsThread(ctx context.Context, reqCh chan hash.Ha // delivered in |reqCh|, and they will be delivered in order. // // This function handles backoff and retries for the underlying streaming RPC. -func fetcherRPCDownloadLocsThread(ctx context.Context, reqCh chan *remotesapi.GetDownloadLocsRequest, resCh chan []*remotesapi.DownloadLoc, client remotesapi.ChunkStoreServiceClient, storeRepoToken func(string), missingChunkCh chan nbs.CompressedChunk, host string) error { +func fetcherRPCDownloadLocsThread(ctx context.Context, reqCh chan *remotesapi.GetDownloadLocsRequest, resCh chan []*remotesapi.DownloadLoc, client remotesapi.ChunkStoreServiceClient, storeRepoToken func(string), missingChunkCh chan nbs.ToChunker, host string) error { stream, err := reliable.MakeCall[*remotesapi.GetDownloadLocsRequest, *remotesapi.GetDownloadLocsResponse]( ctx, reliable.CallOptions[*remotesapi.GetDownloadLocsRequest, *remotesapi.GetDownloadLocsResponse]{ @@ -527,7 +527,7 @@ func (cc *ConcurrencyControl) Run(ctx context.Context, done <-chan struct{}, ss } } -func fetcherDownloadURLThreads(ctx context.Context, fetchReqCh chan fetchReq, doneCh chan struct{}, chunkCh chan nbs.CompressedChunk, client remotesapi.ChunkStoreServiceClient, stats StatsRecorder, fetcher HTTPFetcher, params NetworkRequestParams) error { +func fetcherDownloadURLThreads(ctx context.Context, fetchReqCh chan fetchReq, doneCh chan struct{}, chunkCh chan nbs.ToChunker, client remotesapi.ChunkStoreServiceClient, stats StatsRecorder, fetcher HTTPFetcher, params NetworkRequestParams) error { eg, ctx := errgroup.WithContext(ctx) cc := &ConcurrencyControl{ MaxConcurrency: params.MaximumConcurrentDownloads, @@ -559,7 +559,7 @@ func fetcherDownloadURLThreads(ctx context.Context, fetchReqCh chan fetchReq, do return nil } -func fetcherDownloadURLThread(ctx context.Context, fetchReqCh chan fetchReq, doneCh <-chan struct{}, chunkCh chan nbs.CompressedChunk, client remotesapi.ChunkStoreServiceClient, stats StatsRecorder, health reliable.HealthRecorder, fetcher HTTPFetcher, params NetworkRequestParams) error { +func fetcherDownloadURLThread(ctx context.Context, fetchReqCh chan fetchReq, doneCh <-chan struct{}, chunkCh chan nbs.ToChunker, client remotesapi.ChunkStoreServiceClient, stats StatsRecorder, health reliable.HealthRecorder, fetcher HTTPFetcher, params NetworkRequestParams) error { respCh := make(chan fetchResp) cancelCh := make(chan struct{}) for { diff --git a/go/libraries/doltcore/remotestorage/chunk_store.go b/go/libraries/doltcore/remotestorage/chunk_store.go index c0ca1777995..72241b2d312 100644 --- a/go/libraries/doltcore/remotestorage/chunk_store.go +++ b/go/libraries/doltcore/remotestorage/chunk_store.go @@ -317,7 +317,7 @@ func (dcs *DoltChunkStore) Get(ctx context.Context, h hash.Hash) (chunks.Chunk, func (dcs *DoltChunkStore) GetMany(ctx context.Context, hashes hash.HashSet, found func(context.Context, *chunks.Chunk)) error { ae := atomicerr.New() decompressedSize := uint64(0) - err := dcs.GetManyCompressed(ctx, hashes, func(ctx context.Context, cc nbs.CompressedChunk) { + err := dcs.GetManyCompressed(ctx, hashes, func(ctx context.Context, cc nbs.ToChunker) { if ae.IsSet() { return } @@ -340,7 +340,7 @@ func (dcs *DoltChunkStore) GetMany(ctx context.Context, hashes hash.HashSet, fou // GetMany gets the Chunks with |hashes| from the store. On return, |foundChunks| will have been fully sent all chunks // which have been found. Any non-present chunks will silently be ignored. -func (dcs *DoltChunkStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error { +func (dcs *DoltChunkStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error { ctx, span := tracer.Start(ctx, "remotestorage.GetManyCompressed") defer span.End() @@ -353,7 +353,7 @@ func (dcs *DoltChunkStore) GetManyCompressed(ctx context.Context, hashes hash.Ha for h := range hashes { c := hashToChunk[h] - if c.IsEmpty() { + if c == nil || c.IsEmpty() { notCached = append(notCached, h) } else { found(ctx, c) @@ -432,7 +432,7 @@ func sortRangesBySize(ranges []*GetRange) { type resourcePathToUrlFunc func(ctx context.Context, lastError error, resourcePath string) (url string, err error) -func (gr *GetRange) GetDownloadFunc(ctx context.Context, stats StatsRecorder, health reliable.HealthRecorder, fetcher HTTPFetcher, params NetworkRequestParams, chunkChan chan nbs.CompressedChunk, pathToUrl resourcePathToUrlFunc) func() error { +func (gr *GetRange) GetDownloadFunc(ctx context.Context, stats StatsRecorder, health reliable.HealthRecorder, fetcher HTTPFetcher, params NetworkRequestParams, chunkChan chan nbs.ToChunker, pathToUrl resourcePathToUrlFunc) func() error { if len(gr.Ranges) == 0 { return func() error { return nil } } @@ -574,7 +574,7 @@ type RepoRequest interface { SetRepoPath(string) } -func (dcs *DoltChunkStore) readChunksAndCache(ctx context.Context, hashes []hash.Hash, found func(context.Context, nbs.CompressedChunk)) (err error) { +func (dcs *DoltChunkStore) readChunksAndCache(ctx context.Context, hashes []hash.Hash, found func(context.Context, nbs.ToChunker)) (err error) { toSend := hash.NewHashSet(hashes...) fetcher := dcs.ChunkFetcher(ctx) @@ -603,7 +603,7 @@ func (dcs *DoltChunkStore) readChunksAndCache(ctx context.Context, hashes []hash return err } // Don't forward on empty/not found chunks. - if len(cc.CompressedData) > 0 { + if !cc.IsEmpty() { if dcs.cache.PutChunk(cc) { return ErrCacheCapacityExceeded } @@ -644,7 +644,7 @@ func (dcs *DoltChunkStore) HasMany(ctx context.Context, hashes hash.HashSet) (ha hashSl, byteSl := HashSetToSlices(notCached) absent := make(hash.HashSet) - var found []nbs.CompressedChunk + var found []nbs.ToChunker var err error batchItr(len(hashSl), maxHasManyBatchSize, func(st, end int) (stop bool) { @@ -738,7 +738,7 @@ func (dcs *DoltChunkStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chu } cc := nbs.ChunkToCompressedChunk(c) - if dcs.cache.Put([]nbs.CompressedChunk{cc}) { + if dcs.cache.Put([]nbs.ToChunker{cc}) { return ErrCacheCapacityExceeded } return nil diff --git a/go/libraries/doltcore/remotestorage/map_chunk_cache.go b/go/libraries/doltcore/remotestorage/map_chunk_cache.go index ea5dad323ef..64a0fcf6e6e 100644 --- a/go/libraries/doltcore/remotestorage/map_chunk_cache.go +++ b/go/libraries/doltcore/remotestorage/map_chunk_cache.go @@ -24,16 +24,16 @@ import ( // mapChunkCache is a ChunkCache implementation that stores everything in an in memory map. type mapChunkCache struct { mu *sync.Mutex - hashToChunk map[hash.Hash]nbs.CompressedChunk - toFlush map[hash.Hash]nbs.CompressedChunk + hashToChunk map[hash.Hash]nbs.ToChunker + toFlush map[hash.Hash]nbs.ToChunker cm CapacityMonitor } func newMapChunkCache() *mapChunkCache { return &mapChunkCache{ &sync.Mutex{}, - make(map[hash.Hash]nbs.CompressedChunk), - make(map[hash.Hash]nbs.CompressedChunk), + make(map[hash.Hash]nbs.ToChunker), + make(map[hash.Hash]nbs.ToChunker), NewUncappedCapacityMonitor(), } } @@ -42,14 +42,14 @@ func newMapChunkCache() *mapChunkCache { func NewMapChunkCacheWithMaxCapacity(maxCapacity int64) *mapChunkCache { return &mapChunkCache{ &sync.Mutex{}, - make(map[hash.Hash]nbs.CompressedChunk), - make(map[hash.Hash]nbs.CompressedChunk), + make(map[hash.Hash]nbs.ToChunker), + make(map[hash.Hash]nbs.ToChunker), NewFixedCapacityMonitor(maxCapacity), } } // Put puts a slice of chunks into the cache. -func (mcc *mapChunkCache) Put(chnks []nbs.CompressedChunk) bool { +func (mcc *mapChunkCache) Put(chnks []nbs.ToChunker) bool { mcc.mu.Lock() defer mcc.mu.Unlock() @@ -63,7 +63,7 @@ func (mcc *mapChunkCache) Put(chnks []nbs.CompressedChunk) bool { } } - if mcc.cm.CapacityExceeded(len(c.FullCompressedChunk)) { + if mcc.cm.CapacityExceeded(int(c.FullCompressedChunkLen())) { return true } @@ -79,8 +79,8 @@ func (mcc *mapChunkCache) Put(chnks []nbs.CompressedChunk) bool { // Get gets a map of hash to chunk for a set of hashes. In the event that a chunk is not in the cache, chunks.Empty. // is put in it's place -func (mcc *mapChunkCache) Get(hashes hash.HashSet) map[hash.Hash]nbs.CompressedChunk { - hashToChunk := make(map[hash.Hash]nbs.CompressedChunk) +func (mcc *mapChunkCache) Get(hashes hash.HashSet) map[hash.Hash]nbs.ToChunker { + hashToChunk := make(map[hash.Hash]nbs.ToChunker) mcc.mu.Lock() defer mcc.mu.Unlock() @@ -112,13 +112,13 @@ func (mcc *mapChunkCache) Has(hashes hash.HashSet) (absent hash.HashSet) { return absent } -func (mcc *mapChunkCache) PutChunk(ch nbs.CompressedChunk) bool { +func (mcc *mapChunkCache) PutChunk(ch nbs.ToChunker) bool { mcc.mu.Lock() defer mcc.mu.Unlock() h := ch.Hash() if existing, ok := mcc.hashToChunk[h]; !ok || existing.IsEmpty() { - if mcc.cm.CapacityExceeded(len(ch.FullCompressedChunk)) { + if mcc.cm.CapacityExceeded(int(ch.FullCompressedChunkLen())) { return true } mcc.hashToChunk[h] = ch @@ -130,8 +130,8 @@ func (mcc *mapChunkCache) PutChunk(ch nbs.CompressedChunk) bool { // GetAndClearChunksToFlush gets a map of hash to chunk which includes all the chunks that were put in the cache // between the last time GetAndClearChunksToFlush was called and now. -func (mcc *mapChunkCache) GetAndClearChunksToFlush() map[hash.Hash]nbs.CompressedChunk { - newToFlush := make(map[hash.Hash]nbs.CompressedChunk) +func (mcc *mapChunkCache) GetAndClearChunksToFlush() map[hash.Hash]nbs.ToChunker { + newToFlush := make(map[hash.Hash]nbs.ToChunker) mcc.mu.Lock() defer mcc.mu.Unlock() diff --git a/go/libraries/doltcore/remotestorage/map_chunk_cache_test.go b/go/libraries/doltcore/remotestorage/map_chunk_cache_test.go index 6944eb98c35..8cb5e118d1c 100644 --- a/go/libraries/doltcore/remotestorage/map_chunk_cache_test.go +++ b/go/libraries/doltcore/remotestorage/map_chunk_cache_test.go @@ -27,8 +27,8 @@ import ( "github.com/dolthub/dolt/go/store/nbs" ) -func genRandomChunks(rng *rand.Rand, n int) (hash.HashSet, []nbs.CompressedChunk) { - chks := make([]nbs.CompressedChunk, n) +func genRandomChunks(rng *rand.Rand, n int) (hash.HashSet, []nbs.ToChunker) { + chks := make([]nbs.ToChunker, n) hashes := make(hash.HashSet) for i := 0; i < n; i++ { size := int(rng.Int31n(99) + 1) @@ -88,7 +88,7 @@ func TestMapChunkCache(t *testing.T) { toFlush = mapChunkCache.GetAndClearChunksToFlush() - expected := map[hash.Hash]nbs.CompressedChunk{moreChks[0].Hash(): moreChks[0]} + expected := map[hash.Hash]nbs.ToChunker{moreChks[0].Hash(): moreChks[0]} eq := reflect.DeepEqual(toFlush, expected) assert.True(t, eq, "Missing or unexpected chunks to flush (seed %d)", seed) } diff --git a/go/libraries/doltcore/remotestorage/noop_chunk_cache.go b/go/libraries/doltcore/remotestorage/noop_chunk_cache.go index 8edf589c31b..6b86aa11188 100644 --- a/go/libraries/doltcore/remotestorage/noop_chunk_cache.go +++ b/go/libraries/doltcore/remotestorage/noop_chunk_cache.go @@ -29,22 +29,22 @@ var noopChunkCache = &noopChunkCacheImpl{} type noopChunkCacheImpl struct { } -func (*noopChunkCacheImpl) Put(chnks []nbs.CompressedChunk) bool { +func (*noopChunkCacheImpl) Put(chnks []nbs.ToChunker) bool { return false } -func (*noopChunkCacheImpl) Get(hashes hash.HashSet) map[hash.Hash]nbs.CompressedChunk { - return make(map[hash.Hash]nbs.CompressedChunk) +func (*noopChunkCacheImpl) Get(hashes hash.HashSet) map[hash.Hash]nbs.ToChunker { + return make(map[hash.Hash]nbs.ToChunker) } func (*noopChunkCacheImpl) Has(hashes hash.HashSet) (absent hash.HashSet) { return hashes } -func (*noopChunkCacheImpl) PutChunk(ch nbs.CompressedChunk) bool { +func (*noopChunkCacheImpl) PutChunk(ch nbs.ToChunker) bool { return false } -func (*noopChunkCacheImpl) GetAndClearChunksToFlush() map[hash.Hash]nbs.CompressedChunk { +func (*noopChunkCacheImpl) GetAndClearChunksToFlush() map[hash.Hash]nbs.ToChunker { panic("noopChunkCache does not support GetAndClearChunksToFlush().") } diff --git a/go/store/datas/pull/pull_chunk_fetcher.go b/go/store/datas/pull/pull_chunk_fetcher.go index 2e0d58a0f87..3d2c6d556aa 100644 --- a/go/store/datas/pull/pull_chunk_fetcher.go +++ b/go/store/datas/pull/pull_chunk_fetcher.go @@ -26,7 +26,7 @@ import ( ) type GetManyer interface { - GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error + GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error } type ChunkFetcherable interface { @@ -52,7 +52,7 @@ type PullChunkFetcher struct { batchCh chan hash.HashSet doneCh chan struct{} - resCh chan nbs.CompressedChunk + resCh chan nbs.ToChunker } func NewPullChunkFetcher(ctx context.Context, getter GetManyer) *PullChunkFetcher { @@ -63,7 +63,7 @@ func NewPullChunkFetcher(ctx context.Context, getter GetManyer) *PullChunkFetche getter: getter, batchCh: make(chan hash.HashSet), doneCh: make(chan struct{}), - resCh: make(chan nbs.CompressedChunk), + resCh: make(chan nbs.ToChunker), } ret.eg.Go(func() error { return ret.fetcherThread(func() { @@ -86,9 +86,9 @@ func (f *PullChunkFetcher) fetcherThread(finalize func()) error { missing := batch.Copy() // Blocking get, no concurrency, only one fetcher. - err := f.getter.GetManyCompressed(f.ctx, batch, func(ctx context.Context, chk nbs.CompressedChunk) { + err := f.getter.GetManyCompressed(f.ctx, batch, func(ctx context.Context, chk nbs.ToChunker) { mu.Lock() - missing.Remove(chk.H) + missing.Remove(chk.Hash()) mu.Unlock() select { case <-ctx.Done(): @@ -139,7 +139,7 @@ func (f *PullChunkFetcher) Close() error { return f.eg.Wait() } -func (f *PullChunkFetcher) Recv(ctx context.Context) (nbs.CompressedChunk, error) { +func (f *PullChunkFetcher) Recv(ctx context.Context) (nbs.ToChunker, error) { select { case res, ok := <-f.resCh: if !ok { diff --git a/go/store/datas/pull/pull_chunk_fetcher_test.go b/go/store/datas/pull/pull_chunk_fetcher_test.go index 2fa3bd4e187..f2975efb679 100644 --- a/go/store/datas/pull/pull_chunk_fetcher_test.go +++ b/go/store/datas/pull/pull_chunk_fetcher_test.go @@ -70,8 +70,12 @@ func TestPullChunkFetcher(t *testing.T) { defer wg.Done() cmp, err := f.Recv(context.Background()) assert.NoError(t, err) - assert.Equal(t, cmp.H, gm.C.H) - assert.Equal(t, cmp.FullCompressedChunk, gm.C.FullCompressedChunk) + assert.Equal(t, cmp.Hash(), gm.C.H) + + cc, ok := cmp.(nbs.CompressedChunk) + assert.True(t, ok) + + assert.Equal(t, cc.FullCompressedChunk, gm.C.FullCompressedChunk) _, err = f.Recv(context.Background()) assert.ErrorIs(t, err, io.EOF) assert.NoError(t, f.Close()) @@ -92,8 +96,11 @@ func TestPullChunkFetcher(t *testing.T) { defer wg.Done() cmp, err := f.Recv(context.Background()) assert.NoError(t, err) - assert.Equal(t, cmp.H, h) - assert.Nil(t, cmp.FullCompressedChunk) + assert.Equal(t, cmp.Hash(), h) + + cc, ok := cmp.(nbs.CompressedChunk) + assert.True(t, ok) + assert.Nil(t, cc.FullCompressedChunk) _, err = f.Recv(context.Background()) assert.ErrorIs(t, err, io.EOF) assert.NoError(t, f.Close()) @@ -136,7 +143,7 @@ func TestPullChunkFetcher(t *testing.T) { type emptyGetManyer struct { } -func (emptyGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error { +func (emptyGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error { return nil } @@ -144,7 +151,7 @@ type deliveringGetManyer struct { C nbs.CompressedChunk } -func (d deliveringGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error { +func (d deliveringGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error { for _ = range hashes { found(ctx, d.C) } @@ -155,7 +162,7 @@ type blockingGetManyer struct { block chan struct{} } -func (b blockingGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error { +func (b blockingGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error { <-b.block return nil } @@ -165,6 +172,6 @@ type errorGetManyer struct { var getManyerErr = fmt.Errorf("always return an error") -func (errorGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.CompressedChunk)) error { +func (errorGetManyer) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, nbs.ToChunker)) error { return getManyerErr } diff --git a/go/store/datas/pull/puller.go b/go/store/datas/pull/puller.go index 9401b58d7e6..3e6a277c3e6 100644 --- a/go/store/datas/pull/puller.go +++ b/go/store/datas/pull/puller.go @@ -346,11 +346,11 @@ func (p *Puller) Pull(ctx context.Context) error { if cChk.IsGhost() { return fmt.Errorf("attempted to push or pull ghost chunk: %w", nbs.ErrGhostChunkRequested) } - if len(cChk.FullCompressedChunk) == 0 { + if cChk.FullCompressedChunkLen() == 0 { return errors.New("failed to get all chunks.") } - atomic.AddUint64(&p.stats.fetchedSourceBytes, uint64(len(cChk.FullCompressedChunk))) + atomic.AddUint64(&p.stats.fetchedSourceBytes, uint64(cChk.FullCompressedChunkLen())) atomic.AddUint64(&p.stats.fetchedSourceChunks, uint64(1)) chnk, err := cChk.ToChunk() @@ -366,9 +366,13 @@ func (p *Puller) Pull(ctx context.Context) error { } tracker.TickProcessed() - err = p.wr.AddCompressedChunk(ctx, cChk) - if err != nil { - return err + if compressedChunk, ok := cChk.(nbs.CompressedChunk); ok { + err = p.wr.AddCompressedChunk(ctx, compressedChunk) + if err != nil { + return err + } + } else { + panic("TODO: handle ZStd-CompressedChunk") // NM4. } } }) diff --git a/go/store/nbs/archive_chunk_source.go b/go/store/nbs/archive_chunk_source.go index 3d3a59ec4d6..271252f5718 100644 --- a/go/store/nbs/archive_chunk_source.go +++ b/go/store/nbs/archive_chunk_source.go @@ -171,7 +171,7 @@ func (acs archiveChunkSource) getRecordRanges(_ context.Context, _ []getRecord, return nil, gcBehavior_Continue, errors.New("Archive chunk source does not support getRecordRanges") } -func (acs archiveChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (acs archiveChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { return acs.getMany(ctx, eg, reqs, func(ctx context.Context, chk *chunks.Chunk) { found(ctx, ChunkToCompressedChunk(*chk)) }, keeper, stats) diff --git a/go/store/nbs/archive_test.go b/go/store/nbs/archive_test.go index 4de745e6a5c..fd9e1dee118 100644 --- a/go/store/nbs/archive_test.go +++ b/go/store/nbs/archive_test.go @@ -676,7 +676,7 @@ func (tcs *testChunkSource) getMany(ctx context.Context, eg *errgroup.Group, req panic("never used") } -func (tcs *testChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (tcs *testChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { panic("never used") } diff --git a/go/store/nbs/chunk_fetcher.go b/go/store/nbs/chunk_fetcher.go index 2be1a0aa78d..8b4aae88a45 100644 --- a/go/store/nbs/chunk_fetcher.go +++ b/go/store/nbs/chunk_fetcher.go @@ -46,7 +46,7 @@ type ChunkFetcher interface { CloseSend() error - Recv(context.Context) (CompressedChunk, error) + Recv(context.Context) (ToChunker, error) Close() error } diff --git a/go/store/nbs/cmp_chunk_table_writer.go b/go/store/nbs/cmp_chunk_table_writer.go index 28cdde95630..64112ab19d1 100644 --- a/go/store/nbs/cmp_chunk_table_writer.go +++ b/go/store/nbs/cmp_chunk_table_writer.go @@ -75,17 +75,22 @@ func (tw *CmpChunkTableWriter) GetMD5() []byte { } // AddCmpChunk adds a compressed chunk -func (tw *CmpChunkTableWriter) AddCmpChunk(c CompressedChunk) error { - if c.IsGhost() { +func (tw *CmpChunkTableWriter) AddCmpChunk(tc ToChunker) error { + if tc.IsGhost() { // Ghost chunks cannot be written to a table file. They should // always be filtered by the write processes before landing // here. return ErrGhostChunkRequested } - if len(c.CompressedData) == 0 { + if tc.IsEmpty() { panic("NBS blocks cannot be zero length") } + c, ok := tc.(CompressedChunk) + if !ok { + panic("Require a CompressedChunk") // NM4 + } + uncmpLen, err := snappy.DecodedLen(c.CompressedData) if err != nil { diff --git a/go/store/nbs/cmp_chunk_table_writer_test.go b/go/store/nbs/cmp_chunk_table_writer_test.go index 6f0508b2e8e..592ca683e45 100644 --- a/go/store/nbs/cmp_chunk_table_writer_test.go +++ b/go/store/nbs/cmp_chunk_table_writer_test.go @@ -48,10 +48,10 @@ func TestCmpChunkTableWriter(t *testing.T) { } reqs := toGetRecords(hashes) - found := make([]CompressedChunk, 0) + found := make([]ToChunker, 0) eg, egCtx := errgroup.WithContext(ctx) - _, _, err = tr.getManyCompressed(egCtx, eg, reqs, func(ctx context.Context, c CompressedChunk) { found = append(found, c) }, nil, &Stats{}) + _, _, err = tr.getManyCompressed(egCtx, eg, reqs, func(ctx context.Context, c ToChunker) { found = append(found, c) }, nil, &Stats{}) require.NoError(t, err) require.NoError(t, eg.Wait()) diff --git a/go/store/nbs/empty_chunk_source.go b/go/store/nbs/empty_chunk_source.go index 60b7f953d21..8ea39c869ca 100644 --- a/go/store/nbs/empty_chunk_source.go +++ b/go/store/nbs/empty_chunk_source.go @@ -50,7 +50,7 @@ func (ecs emptyChunkSource) getMany(ctx context.Context, eg *errgroup.Group, req return true, gcBehavior_Continue, nil } -func (ecs emptyChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (ecs emptyChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { return true, gcBehavior_Continue, nil } diff --git a/go/store/nbs/gc_copier.go b/go/store/nbs/gc_copier.go index 44c02df8ef7..53a7105e4e6 100644 --- a/go/store/nbs/gc_copier.go +++ b/go/store/nbs/gc_copier.go @@ -56,7 +56,7 @@ func newGarbageCollectionCopier(tfp tableFilePersister) (*gcCopier, error) { return &gcCopier{writer, tfp}, nil } -func (gcc *gcCopier) addChunk(ctx context.Context, c CompressedChunk) error { +func (gcc *gcCopier) addChunk(ctx context.Context, c ToChunker) error { return gcc.writer.AddCmpChunk(c) } diff --git a/go/store/nbs/generational_chunk_store.go b/go/store/nbs/generational_chunk_store.go index c858ab683b0..04b32b4e4a8 100644 --- a/go/store/nbs/generational_chunk_store.go +++ b/go/store/nbs/generational_chunk_store.go @@ -28,6 +28,7 @@ import ( ) var _ chunks.ChunkStore = (*GenerationalNBS)(nil) +var _ chunks.GenerationalCS = (*GenerationalNBS)(nil) var _ chunks.TableFileStore = (*GenerationalNBS)(nil) var _ chunks.GenerationalCS = (*GenerationalNBS)(nil) var _ chunks.ChunkStoreGarbageCollector = (*GenerationalNBS)(nil) @@ -144,14 +145,14 @@ func (gcs *GenerationalNBS) GetMany(ctx context.Context, hashes hash.HashSet, fo return gcs.ghostGen.GetMany(ctx, notFound, found) } -func (gcs *GenerationalNBS) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk)) error { +func (gcs *GenerationalNBS) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker)) error { return gcs.getManyCompressed(ctx, hashes, found, gcDependencyMode_TakeDependency) } -func (gcs *GenerationalNBS) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk), gcDepMode gcDependencyMode) error { +func (gcs *GenerationalNBS) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker), gcDepMode gcDependencyMode) error { var mu sync.Mutex notInOldGen := hashes.Copy() - err := gcs.oldGen.getManyCompressed(ctx, hashes, func(ctx context.Context, chunk CompressedChunk) { + err := gcs.oldGen.getManyCompressed(ctx, hashes, func(ctx context.Context, chunk ToChunker) { mu.Lock() delete(notInOldGen, chunk.Hash()) mu.Unlock() @@ -165,7 +166,7 @@ func (gcs *GenerationalNBS) getManyCompressed(ctx context.Context, hashes hash.H } notFound := notInOldGen.Copy() - err = gcs.newGen.getManyCompressed(ctx, notInOldGen, func(ctx context.Context, chunk CompressedChunk) { + err = gcs.newGen.getManyCompressed(ctx, notInOldGen, func(ctx context.Context, chunk ToChunker) { mu.Lock() delete(notFound, chunk.Hash()) mu.Unlock() diff --git a/go/store/nbs/ghost_store.go b/go/store/nbs/ghost_store.go index 38fdf5247c4..b6d86ee04c5 100644 --- a/go/store/nbs/ghost_store.go +++ b/go/store/nbs/ghost_store.go @@ -90,11 +90,11 @@ func (g GhostBlockStore) GetMany(ctx context.Context, hashes hash.HashSet, found return nil } -func (g GhostBlockStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk)) error { +func (g GhostBlockStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker)) error { return g.getManyCompressed(ctx, hashes, found, gcDependencyMode_TakeDependency) } -func (g GhostBlockStore) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk), gcDepMode gcDependencyMode) error { +func (g GhostBlockStore) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker), gcDepMode gcDependencyMode) error { for h := range hashes { if g.skippedRefs.Has(h) { found(ctx, NewGhostCompressedChunk(h)) diff --git a/go/store/nbs/journal_chunk_source.go b/go/store/nbs/journal_chunk_source.go index 10a5548b0c8..a35c6cd21e8 100644 --- a/go/store/nbs/journal_chunk_source.go +++ b/go/store/nbs/journal_chunk_source.go @@ -96,7 +96,7 @@ type journalRecord struct { } func (s journalChunkSource) getMany(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, *chunks.Chunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { - return s.getManyCompressed(ctx, eg, reqs, func(ctx context.Context, cc CompressedChunk) { + return s.getManyCompressed(ctx, eg, reqs, func(ctx context.Context, cc ToChunker) { ch, err := cc.ToChunk() if err != nil { eg.Go(func() error { @@ -115,7 +115,7 @@ func (s journalChunkSource) getMany(ctx context.Context, eg *errgroup.Group, req // and then (4) asynchronously perform reads. We release the journal read // lock after returning when all reads are completed, which can be after the // function returns. -func (s journalChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (s journalChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { defer trace.StartRegion(ctx, "journalChunkSource.getManyCompressed").End() var remaining bool diff --git a/go/store/nbs/mem_table.go b/go/store/nbs/mem_table.go index 1fd8c0ffcda..ada8b669612 100644 --- a/go/store/nbs/mem_table.go +++ b/go/store/nbs/mem_table.go @@ -193,7 +193,7 @@ func (mt *memTable) getMany(ctx context.Context, eg *errgroup.Group, reqs []getR return remaining, gcBehavior_Continue, nil } -func (mt *memTable) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (mt *memTable) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { var remaining bool for i, r := range reqs { data := mt.chunks[*r.a] diff --git a/go/store/nbs/mem_table_test.go b/go/store/nbs/mem_table_test.go index 42a1888a98f..3e97be099c0 100644 --- a/go/store/nbs/mem_table_test.go +++ b/go/store/nbs/mem_table_test.go @@ -306,7 +306,7 @@ func (crg chunkReaderGroup) getMany(ctx context.Context, eg *errgroup.Group, req return true, gcBehavior_Continue, nil } -func (crg chunkReaderGroup) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (crg chunkReaderGroup) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { for _, haver := range crg { remaining, gcb, err := haver.getManyCompressed(ctx, eg, reqs, found, keeper, stats) if err != nil { diff --git a/go/store/nbs/nbs_metrics_wrapper.go b/go/store/nbs/nbs_metrics_wrapper.go index 36b262075b7..1da178f8843 100644 --- a/go/store/nbs/nbs_metrics_wrapper.go +++ b/go/store/nbs/nbs_metrics_wrapper.go @@ -99,7 +99,7 @@ func (nbsMW *NBSMetricWrapper) PruneTableFiles(ctx context.Context) error { // GetManyCompressed gets the compressed Chunks with |hashes| from the store. On return, // |found| will have been fully sent all chunks which have been // found. Any non-present chunks will silently be ignored. -func (nbsMW *NBSMetricWrapper) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk)) error { +func (nbsMW *NBSMetricWrapper) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker)) error { atomic.AddInt32(&nbsMW.TotalChunkGets, int32(len(hashes))) return nbsMW.nbs.GetManyCompressed(ctx, hashes, found) } diff --git a/go/store/nbs/store.go b/go/store/nbs/store.go index e49daa85956..b52f0bc37a8 100644 --- a/go/store/nbs/store.go +++ b/go/store/nbs/store.go @@ -86,7 +86,7 @@ func makeGlobalCaches() { type NBSCompressedChunkStore interface { chunks.ChunkStore - GetManyCompressed(context.Context, hash.HashSet, func(context.Context, CompressedChunk)) error + GetManyCompressed(context.Context, hash.HashSet, func(context.Context, ToChunker)) error } type gcDependencyMode int @@ -97,7 +97,7 @@ const ( ) type CompressedChunkStoreForGC interface { - getManyCompressed(context.Context, hash.HashSet, func(context.Context, CompressedChunk), gcDependencyMode) error + getManyCompressed(context.Context, hash.HashSet, func(context.Context, ToChunker), gcDependencyMode) error } type NomsBlockStore struct { @@ -896,11 +896,11 @@ func (nbs *NomsBlockStore) GetMany(ctx context.Context, hashes hash.HashSet, fou ) } -func (nbs *NomsBlockStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk)) error { +func (nbs *NomsBlockStore) GetManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker)) error { return nbs.getManyCompressed(ctx, hashes, found, gcDependencyMode_TakeDependency) } -func (nbs *NomsBlockStore) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, CompressedChunk), gcDepMode gcDependencyMode) error { +func (nbs *NomsBlockStore) getManyCompressed(ctx context.Context, hashes hash.HashSet, found func(context.Context, ToChunker), gcDepMode gcDependencyMode) error { ctx, span := tracer.Start(ctx, "nbs.GetManyCompressed", trace.WithAttributes(attribute.Int("num_hashes", len(hashes)))) defer span.End() return nbs.getManyWithFunc(ctx, hashes, gcDepMode, @@ -1844,25 +1844,25 @@ func (i *markAndSweeper) SaveHashes(ctx context.Context, hashes []hash.Hash) err found := 0 var addErr error - err = i.src.getManyCompressed(ctx, toVisit, func(ctx context.Context, cc CompressedChunk) { + err = i.src.getManyCompressed(ctx, toVisit, func(ctx context.Context, tc ToChunker) { mu.Lock() defer mu.Unlock() if addErr != nil { return } found += 1 - if cc.IsGhost() { + if tc.IsGhost() { // Ghost chunks encountered on the walk can be left alone --- they // do not bring their dependencies, and because of how generational // store works, they will still be ghost chunks // in the store after the GC is finished. return } - addErr = i.gcc.addChunk(ctx, cc) + addErr = i.gcc.addChunk(ctx, tc) if addErr != nil { return } - c, err := cc.ToChunk() + c, err := tc.ToChunk() if err != nil { addErr = err return diff --git a/go/store/nbs/table.go b/go/store/nbs/table.go index 234163be86e..6abb43be65c 100644 --- a/go/store/nbs/table.go +++ b/go/store/nbs/table.go @@ -221,7 +221,7 @@ type chunkReader interface { // getManyCompressed sets getRecord.found to true, and calls |found| for each present getRecord query. // It returns true if any getRecord query was not found in this chunkReader. - getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) + getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) // count returns the chunk count for this chunkReader. count() (uint32, error) diff --git a/go/store/nbs/table_reader.go b/go/store/nbs/table_reader.go index de66ffd27bd..f3456ae58fc 100644 --- a/go/store/nbs/table_reader.go +++ b/go/store/nbs/table_reader.go @@ -38,6 +38,14 @@ import ( // Do not read more than 128MB at a time. const maxReadSize = 128 * 1024 * 1024 +type ToChunker interface { + Hash() hash.Hash + ToChunk() (chunks.Chunk, error) + FullCompressedChunkLen() uint32 + IsEmpty() bool + IsGhost() bool +} + // CompressedChunk represents a chunk of data in a table file which is still compressed via snappy. type CompressedChunk struct { // H is the hash of the chunk @@ -53,6 +61,8 @@ type CompressedChunk struct { ghost bool } +var _ ToChunker = CompressedChunk{} + // NewCompressedChunk creates a CompressedChunk func NewCompressedChunk(h hash.Hash, buff []byte) (CompressedChunk, error) { dataLen := uint64(len(buff)) - checksumSize @@ -114,6 +124,10 @@ func (cmp CompressedChunk) CompressedSize() int { return len(cmp.CompressedData) } +func (cmp CompressedChunk) FullCompressedChunkLen() uint32 { + return uint32(len(cmp.FullCompressedChunk)) +} + var EmptyCompressedChunk CompressedChunk func init() { @@ -323,10 +337,10 @@ var _ chunkReader = tableReader{} func (tr tableReader) readCompressedAtOffsets( ctx context.Context, rb readBatch, - found func(context.Context, CompressedChunk), + found func(context.Context, ToChunker), stats *Stats, ) error { - return tr.readAtOffsetsWithCB(ctx, rb, stats, func(ctx context.Context, cmp CompressedChunk) error { + return tr.readAtOffsetsWithCB(ctx, rb, stats, func(ctx context.Context, cmp ToChunker) error { found(ctx, cmp) return nil }) @@ -338,7 +352,7 @@ func (tr tableReader) readAtOffsets( found func(context.Context, *chunks.Chunk), stats *Stats, ) error { - return tr.readAtOffsetsWithCB(ctx, rb, stats, func(ctx context.Context, cmp CompressedChunk) error { + return tr.readAtOffsetsWithCB(ctx, rb, stats, func(ctx context.Context, cmp ToChunker) error { chk, err := cmp.ToChunk() if err != nil { @@ -354,7 +368,7 @@ func (tr tableReader) readAtOffsetsWithCB( ctx context.Context, rb readBatch, stats *Stats, - cb func(ctx context.Context, cmp CompressedChunk) error, + cb func(ctx context.Context, cmp ToChunker) error, ) error { readLength := rb.End() - rb.Start() buff := make([]byte, readLength) @@ -405,7 +419,7 @@ func (tr tableReader) getMany( err = tr.getManyAtOffsets(ctx, eg, offsetRecords, found, stats) return remaining, gcBehavior_Continue, err } -func (tr tableReader) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (tr tableReader) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { // Pass #1: Iterate over |reqs| and |tr.prefixes| (both sorted by address) and build the set // of table locations which must be read in order to satisfy the getMany operation. offsetRecords, remaining, gcb, err := tr.findOffsets(reqs, keeper) @@ -419,7 +433,7 @@ func (tr tableReader) getManyCompressed(ctx context.Context, eg *errgroup.Group, return remaining, gcBehavior_Continue, err } -func (tr tableReader) getManyCompressedAtOffsets(ctx context.Context, eg *errgroup.Group, offsetRecords offsetRecSlice, found func(context.Context, CompressedChunk), stats *Stats) error { +func (tr tableReader) getManyCompressedAtOffsets(ctx context.Context, eg *errgroup.Group, offsetRecords offsetRecSlice, found func(context.Context, ToChunker), stats *Stats) error { return tr.getManyAtOffsetsWithReadFunc(ctx, eg, offsetRecords, stats, func( ctx context.Context, rb readBatch, diff --git a/go/store/nbs/table_set.go b/go/store/nbs/table_set.go index 88fd92de587..a664a2fc100 100644 --- a/go/store/nbs/table_set.go +++ b/go/store/nbs/table_set.go @@ -228,7 +228,7 @@ func (ts tableSet) getMany(ctx context.Context, eg *errgroup.Group, reqs []getRe return f(ts.upstream) } -func (ts tableSet) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, CompressedChunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { +func (ts tableSet) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { f := func(css chunkSourceSet) (bool, gcBehavior, error) { for _, haver := range css { remaining, gcb, err := haver.getManyCompressed(ctx, eg, reqs, found, keeper, stats) From fc2976a2147e20939efa018d7f6fa158b08bc5b9 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Tue, 7 Jan 2025 11:14:22 -0800 Subject: [PATCH 02/13] Simplest possible update to gRPC interface for archive spans --- proto/dolt/services/remotesapi/v1alpha1/chunkstore.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proto/dolt/services/remotesapi/v1alpha1/chunkstore.proto b/proto/dolt/services/remotesapi/v1alpha1/chunkstore.proto index 519bd616b03..04ff5804567 100644 --- a/proto/dolt/services/remotesapi/v1alpha1/chunkstore.proto +++ b/proto/dolt/services/remotesapi/v1alpha1/chunkstore.proto @@ -89,6 +89,8 @@ message RangeChunk { bytes hash = 1; uint64 offset = 2; uint32 length = 3; + uint64 dictionary_offset = 4; + uint32 dictionary_length = 5; } message HttpGetRange { From 52356e47ec0cfb294542e04f4c2424e62119aba3 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Tue, 7 Jan 2025 11:16:12 -0800 Subject: [PATCH 03/13] Generated Proto code --- .../remotesapi/v1alpha1/chunkstore.pb.go | 789 +++++++++--------- .../remotesapi/v1alpha1/chunkstore_grpc.pb.go | 1 - 2 files changed, 405 insertions(+), 385 deletions(-) diff --git a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go index 4fb6fcd3d98..ffd2289f70c 100644 --- a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go +++ b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go @@ -21,12 +21,11 @@ package remotesapi import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" ) const ( @@ -402,9 +401,11 @@ type RangeChunk struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - Length uint32 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"` + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Length uint32 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"` + DictionaryOffset uint64 `protobuf:"varint,4,opt,name=dictionary_offset,json=dictionaryOffset,proto3" json:"dictionary_offset,omitempty"` + DictionaryLength uint32 `protobuf:"varint,5,opt,name=dictionary_length,json=dictionaryLength,proto3" json:"dictionary_length,omitempty"` } func (x *RangeChunk) Reset() { @@ -460,6 +461,20 @@ func (x *RangeChunk) GetLength() uint32 { return 0 } +func (x *RangeChunk) GetDictionaryOffset() uint64 { + if x != nil { + return x.DictionaryOffset + } + return 0 +} + +func (x *RangeChunk) GetDictionaryLength() uint32 { + if x != nil { + return x.DictionaryLength + } + return 0 +} + type HttpGetRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2233,418 +2248,424 @@ var file_dolt_services_remotesapi_v1alpha1_chunkstore_proto_rawDesc = []byte{ 0x74, 0x70, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x68, 0x61, - 0x73, 0x68, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x67, 0x0a, 0x0c, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, - 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, - 0xe9, 0x02, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x12, - 0x4c, 0x0a, 0x08, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x48, 0x00, 0x52, 0x07, 0x68, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x12, 0x57, 0x0a, - 0x0e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, - 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x47, 0x65, - 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x66, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x3d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x73, 0x68, 0x65, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x0a, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x68, + 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x10, 0x64, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x72, 0x79, 0x4f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x72, 0x79, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x10, 0x64, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x22, 0x67, 0x0a, 0x0c, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x75, 0x72, 0x6c, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x52, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x0b, 0x44, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x12, 0x4c, 0x0a, 0x08, 0x68, 0x74, + 0x74, 0x70, 0x5f, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, + 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x48, 0x00, 0x52, + 0x07, 0x68, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x12, 0x57, 0x0a, 0x0e, 0x68, 0x74, 0x74, 0x70, + 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, - 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, - 0x0a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x25, 0x0a, 0x11, 0x48, - 0x74, 0x74, 0x70, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x22, 0x94, 0x01, 0x0a, 0x09, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, - 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x53, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, - 0x5f, 0x70, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x64, 0x6f, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x48, 0x00, 0x52, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x61, 0x66, 0x74, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x41, 0x66, 0x74, + 0x65, 0x72, 0x12, 0x66, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x48, 0x74, 0x74, 0x70, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, - 0x65, 0x48, 0x00, 0x52, 0x08, 0x68, 0x74, 0x74, 0x70, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x0a, 0x0a, - 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xbb, 0x01, 0x0a, 0x16, 0x47, 0x65, - 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, - 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x75, 0x6e, - 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, - 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, - 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x7c, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x44, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x42, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2e, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, - 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8b, 0x01, 0x0a, 0x10, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, - 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, - 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x75, 0x6e, - 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x22, 0xa9, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, - 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, - 0x12, 0x2e, 0x0a, 0x11, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, - 0x0f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, - 0x12, 0x61, 0x0a, 0x12, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, - 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x10, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, - 0x78, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x25, 0x0a, 0x11, 0x48, 0x74, 0x74, 0x70, 0x50, 0x6f, + 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x94, 0x01, + 0x0a, 0x09, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x12, 0x26, 0x0a, 0x0f, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x53, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x70, 0x6f, 0x73, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x4c, 0x6f, 0x63, 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x50, + 0x6f, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x08, + 0x68, 0x74, 0x74, 0x70, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xbb, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, + 0x6f, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, + 0x74, 0x68, 0x22, 0x7c, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, + 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, 0x6f, + 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x52, 0x04, 0x6c, 0x6f, 0x63, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x8b, 0x01, 0x0a, 0x10, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x1d, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0xa9, + 0x02, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, + 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x11, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x12, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x10, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x78, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2c, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x52, + 0x04, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8f, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x62, 0x61, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, + 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8f, 0x01, 0x0a, 0x0d, 0x52, 0x65, - 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, - 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, - 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, - 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x2f, 0x0a, 0x0e, 0x52, - 0x65, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8d, 0x01, 0x0a, - 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, + 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, + 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, + 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x2f, 0x0a, 0x0e, 0x52, 0x65, 0x62, 0x61, 0x73, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, + 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8d, 0x01, 0x0a, 0x0b, 0x52, 0x6f, 0x6f, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, + 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x4a, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x45, 0x0a, 0x0e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xde, 0x02, 0x0a, 0x0d, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x4a, 0x0a, 0x0c, - 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, - 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x45, 0x0a, 0x0e, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0xde, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, - 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6c, - 0x61, 0x73, 0x74, 0x12, 0x5b, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, - 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x61, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, - 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, - 0x22, 0x2a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xfb, 0x01, 0x0a, - 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, + 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x12, 0x5b, + 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, - 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x61, 0x0a, 0x12, 0x63, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x61, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x10, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x92, 0x02, 0x0a, 0x17, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x66, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x66, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x73, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, - 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, - 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x73, 0x0a, 0x18, 0x70, 0x75, - 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x64, - 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x16, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, - 0x54, 0x0a, 0x10, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x66, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x73, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, - 0x6f, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x5f, - 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, - 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x82, 0x02, 0x0a, 0x0d, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, - 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x75, 0x72, 0x6c, 0x12, 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, - 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x66, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, - 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, - 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x72, - 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb5, 0x01, - 0x0a, 0x1a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, - 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, - 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, - 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x2a, 0x0a, 0x0e, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xfb, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, + 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x61, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, + 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, - 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, - 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x8f, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, - 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x99, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x58, 0x0a, 0x0f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x69, 0x0a, 0x18, 0x61, 0x70, 0x70, - 0x65, 0x6e, 0x64, 0x69, 0x78, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, - 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x6f, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, + 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x92, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x73, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x73, 0x0a, 0x18, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x63, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, + 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x52, 0x16, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x54, 0x0a, 0x10, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x6e, 0x62, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1f, 0x0a, 0x0b, 0x6e, 0x62, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x62, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, + 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15, 0x61, - 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x22, 0xba, 0x03, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, - 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, - 0x12, 0x61, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, - 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x12, 0x5b, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, - 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x62, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x27, + 0x0a, 0x0d, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x70, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x78, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, + 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, + 0x61, 0x74, 0x68, 0x22, 0x82, 0x02, 0x0a, 0x0d, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x41, 0x66, 0x74, 0x65, 0x72, + 0x12, 0x66, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x61, - 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, + 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, + 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, + 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, - 0x22, 0x50, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x2a, 0xa4, 0x01, 0x0a, 0x16, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x28, 0x0a, - 0x24, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, - 0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x55, 0x53, 0x48, 0x5f, - 0x43, 0x4f, 0x4e, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x54, - 0x52, 0x4f, 0x4c, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, - 0x4e, 0x47, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x55, 0x53, 0x48, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, + 0x22, 0x8f, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x12, 0x3f, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x61, 0x66, + 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x41, 0x66, + 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x99, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x58, 0x0a, 0x0f, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x69, 0x0a, 0x18, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, + 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x78, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xba, + 0x03, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, + 0x6f, 0x49, 0x64, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x61, 0x0a, 0x12, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x10, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x5b, + 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x62, 0x0a, 0x0f, 0x61, + 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, + 0x74, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x50, 0x61, 0x74, 0x68, 0x22, 0x50, 0x0a, 0x15, 0x41, + 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2a, 0xa4, 0x01, + 0x0a, 0x16, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x24, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x43, 0x4f, 0x4e, - 0x54, 0x52, 0x4f, 0x4c, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, - 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x02, 0x2a, 0x89, 0x01, 0x0a, 0x16, 0x4d, 0x61, - 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x24, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, - 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x58, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, - 0x0a, 0x1c, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, - 0x44, 0x49, 0x58, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, - 0x12, 0x23, 0x0a, 0x1f, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x50, 0x50, - 0x45, 0x4e, 0x44, 0x49, 0x58, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x50, 0x50, - 0x45, 0x4e, 0x44, 0x10, 0x02, 0x32, 0xb2, 0x0b, 0x0a, 0x11, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x64, 0x6f, 0x6c, + 0x54, 0x52, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x55, + 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x4f, 0x4c, 0x5f, 0x49, + 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x45, + 0x54, 0x10, 0x01, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x43, 0x4f, 0x4e, 0x43, + 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x4f, 0x4c, 0x5f, + 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x5f, 0x53, + 0x45, 0x54, 0x10, 0x02, 0x2a, 0x89, 0x01, 0x0a, 0x16, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, + 0x74, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x78, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x28, 0x0a, 0x24, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x50, 0x50, 0x45, + 0x4e, 0x44, 0x49, 0x58, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x4d, 0x41, 0x4e, + 0x49, 0x46, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x58, 0x5f, 0x4f, + 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x4d, + 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x58, + 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02, + 0x32, 0xb2, 0x0b, 0x0a, 0x11, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x09, 0x48, 0x61, 0x73, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x12, 0x33, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x76, 0x0a, 0x09, 0x48, 0x61, 0x73, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x33, + 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x48, 0x61, 0x73, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x61, 0x73, 0x43, 0x68, 0x75, 0x6e, 0x6b, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x61, 0x73, - 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, - 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x94, - 0x01, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, - 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x64, 0x6f, 0x6c, - 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, + 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x17, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x87, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x2e, 0x64, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3a, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, + 0x12, 0x87, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x38, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, + 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x06, 0x52, 0x65, + 0x62, 0x61, 0x73, 0x65, 0x12, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x62, 0x61, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x62, 0x61, 0x73, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x04, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x2e, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6d, 0x0a, 0x06, 0x52, 0x65, 0x62, 0x61, 0x73, 0x65, 0x12, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, - 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, 0x6f, - 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x52, 0x65, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, - 0x0a, 0x04, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2e, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x12, 0x30, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, + 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x85, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x12, 0x38, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, + 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x13, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, + 0x6c, 0x12, 0x3d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x38, 0x2e, 0x64, 0x6f, 0x6c, 0x74, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x94, - 0x01, 0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, - 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x3d, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x37, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x38, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3e, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x53, 0x5a, 0x51, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x68, 0x75, 0x62, - 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x3b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, + 0x65, 0x73, 0x12, 0x37, 0x2e, 0x64, 0x6f, 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x64, 0x6f, + 0x6c, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x41, 0x64, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x53, 0x5a, 0x51, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x68, 0x75, 0x62, 0x2f, 0x64, 0x6f, 0x6c, 0x74, + 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x6f, + 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go index 4801776b6da..98b6424bdf4 100644 --- a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go +++ b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go @@ -22,7 +22,6 @@ package remotesapi import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" From be6de190c050097b9dc8833ac6ca4f45798367ae Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 9 Jan 2025 08:36:18 -0800 Subject: [PATCH 04/13] First successful clone. No tests yet. --- .../remotesapi/v1alpha1/chunkstore.pb.go | 5 +- .../remotesapi/v1alpha1/chunkstore_grpc.pb.go | 1 + go/libraries/doltcore/remotesrv/grpc.go | 7 ++- go/libraries/doltcore/remotesrv/http.go | 10 +++- .../doltcore/remotestorage/chunk_fetcher.go | 8 +++- go/store/cmd/noms/noms_manifest.go | 4 +- go/store/datas/pull/clone.go | 12 +++-- go/store/nbs/archive_build.go | 8 ++-- go/store/nbs/archive_chunk_source.go | 33 ++++++++++++- go/store/nbs/archive_test.go | 4 ++ go/store/nbs/chunk_source_adapter.go | 4 ++ go/store/nbs/conjoiner.go | 16 +++---- go/store/nbs/conjoiner_test.go | 8 ++-- go/store/nbs/dynamo_manifest_test.go | 22 ++++----- go/store/nbs/empty_chunk_source.go | 4 ++ go/store/nbs/file_manifest_test.go | 6 +-- go/store/nbs/file_table_reader.go | 4 ++ go/store/nbs/gc_copier.go | 6 +-- go/store/nbs/generational_chunk_store.go | 2 +- go/store/nbs/ghost_store_test.go | 4 +- go/store/nbs/journal.go | 6 +-- go/store/nbs/journal_chunk_source.go | 8 +++- go/store/nbs/manifest.go | 39 +++++++++++----- go/store/nbs/metadata.go | 2 +- go/store/nbs/root_tracker_test.go | 2 +- go/store/nbs/store.go | 46 +++++++++++-------- go/store/nbs/table.go | 4 ++ go/store/nbs/table_set.go | 16 ++++--- go/store/nbs/table_set_test.go | 2 +- 29 files changed, 199 insertions(+), 94 deletions(-) diff --git a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go index ffd2289f70c..1f2b6e24774 100644 --- a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go +++ b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore.pb.go @@ -21,11 +21,12 @@ package remotesapi import ( + reflect "reflect" + sync "sync" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" - sync "sync" ) const ( diff --git a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go index 98b6424bdf4..4801776b6da 100644 --- a/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go +++ b/go/gen/proto/dolt/services/remotesapi/v1alpha1/chunkstore_grpc.pb.go @@ -22,6 +22,7 @@ package remotesapi import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" diff --git a/go/libraries/doltcore/remotesrv/grpc.go b/go/libraries/doltcore/remotesrv/grpc.go index d158f47e05b..89f53780bc5 100644 --- a/go/libraries/doltcore/remotesrv/grpc.go +++ b/go/libraries/doltcore/remotesrv/grpc.go @@ -295,7 +295,12 @@ func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStore var ranges []*remotesapi.RangeChunk for h, r := range hashToRange { hCpy := h - ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length}) + ranges = append(ranges, &remotesapi.RangeChunk{ + Hash: hCpy[:], + Offset: r.Offset, + Length: r.Length, + DictionaryOffset: r.DictOffset, + DictionaryLength: r.DictLength}) } url := rs.getDownloadUrl(md, prefix+"/"+loc) diff --git a/go/libraries/doltcore/remotesrv/http.go b/go/libraries/doltcore/remotesrv/http.go index eedbf926148..1c815e8fcce 100644 --- a/go/libraries/doltcore/remotesrv/http.go +++ b/go/libraries/doltcore/remotesrv/http.go @@ -94,12 +94,18 @@ func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) { respWr.WriteHeader(http.StatusBadRequest) return } - _, ok := hash.MaybeParse(path[i+1:]) + + fileName := path[i+1:] + if strings.HasSuffix(fileName, ".darc") { + fileName = fileName[:len(fileName)-5] + } + _, ok := hash.MaybeParse(fileName) if !ok { - logger.WithField("last_path_component", path[i+1:]).Warn("bad request with unparseable last path component") + logger.WithField("last_path_component", fileName).Warn("bad request with unparseable last path component") respWr.WriteHeader(http.StatusBadRequest) return } + abs, err := fh.fs.Abs(path) if err != nil { logger.WithError(err).Error("could not get absolute path") diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 5ca776d5940..830c90d35e0 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -48,8 +48,14 @@ type ChunkFetcher struct { eg *errgroup.Group egCtx context.Context + // toGetCh is the channel used to request chunks. This will be initially given a root, + // and as refs are found, they will be added to the channel for workers to batch and request. NM4. toGetCh chan hash.HashSet - resCh chan nbs.ToChunker + + // resCh is the results channel for the fetcher. It is used both to return + // chunks themselves, and to indicate which chunks were requested but missing + // buy having a Hash, but are empty. NM4. + resCh chan nbs.ToChunker abortCh chan struct{} stats StatsRecorder diff --git a/go/store/cmd/noms/noms_manifest.go b/go/store/cmd/noms/noms_manifest.go index 4ece88cb030..1f979ef64c3 100644 --- a/go/store/cmd/noms/noms_manifest.go +++ b/go/store/cmd/noms/noms_manifest.go @@ -103,7 +103,7 @@ func runManifest(ctx context.Context, args []string) int { nbsFiles := make([]NbsFile, numSpecs) for i := 0; i < numSpecs; i++ { tableSpecInfo := manifest.GetTableSpecInfo(i) - path := filepath.Join(spec.DatabaseName, tableSpecInfo.GetName()) + path := filepath.Join(spec.DatabaseName, tableSpecInfo.GetFileName()) fileInfo, err := os.Stat(path) nbsFiles[i] = NbsFile{tableSpecInfo, fileInfo, err} } @@ -130,7 +130,7 @@ func runManifest(ctx context.Context, args []string) int { fmt.Println(" referenced nbs files:") for _, nbsFile := range nbsFiles { - name := nbsFile.manifestSpec.GetName() + name := nbsFile.manifestSpec.GetFileName() chunkCnt := nbsFile.manifestSpec.GetChunkCount() sizeStr := nbsFile.sizeStr() existsStr := nbsFile.fileInfoErr == nil diff --git a/go/store/datas/pull/clone.go b/go/store/datas/pull/clone.go index 9cad9f5e514..a1cae4e9df2 100644 --- a/go/store/datas/pull/clone.go +++ b/go/store/datas/pull/clone.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "strings" "github.com/cenkalti/backoff/v4" "golang.org/x/sync/errgroup" @@ -81,9 +82,14 @@ func mapTableFiles(tblFiles []chunks.TableFile) ([]string, map[string]chunks.Tab fileIDtoNumChunks := make(map[string]int) for i, tblFile := range tblFiles { - fileIDtoTblFile[tblFile.FileID()] = tblFile - fileIds[i] = tblFile.FileID() - fileIDtoNumChunks[tblFile.FileID()] = tblFile.NumChunks() + fileId := tblFile.FileID() + if strings.HasSuffix(fileId, ".darc") { + fileId = fileId[:len(fileId)-5] // NM4. + } + + fileIDtoTblFile[fileId] = tblFile + fileIds[i] = fileId + fileIDtoNumChunks[fileId] = tblFile.NumChunks() } return fileIds, fileIDtoTblFile, fileIDtoNumChunks diff --git a/go/store/nbs/archive_build.go b/go/store/nbs/archive_build.go index 349bf239d11..071521b8c74 100644 --- a/go/store/nbs/archive_build.go +++ b/go/store/nbs/archive_build.go @@ -99,8 +99,8 @@ func UnArchive(ctx context.Context, cs chunks.ChunkStore, smd StorageMetadata, p specs, err := gs.oldGen.tables.toSpecs() newSpecs := make([]tableSpec, 0, len(specs)) for _, spec := range specs { - if newSpec, exists := swapMap[spec.name]; exists { - newSpecs = append(newSpecs, tableSpec{newSpec, spec.chunkCount}) + if newSpec, exists := swapMap[spec.hash]; exists { + newSpecs = append(newSpecs, tableSpec{typeNoms, newSpec, spec.chunkCount}) } else { newSpecs = append(newSpecs, spec) } @@ -169,8 +169,8 @@ func BuildArchive(ctx context.Context, cs chunks.ChunkStore, dagGroups *ChunkRel specs, err := gs.oldGen.tables.toSpecs() newSpecs := make([]tableSpec, 0, len(specs)) for _, spec := range specs { - if newSpec, exists := swapMap[spec.name]; exists { - newSpecs = append(newSpecs, tableSpec{newSpec, spec.chunkCount}) + if newSpec, exists := swapMap[spec.hash]; exists { + newSpecs = append(newSpecs, tableSpec{typeArchive, newSpec, spec.chunkCount}) } else { newSpecs = append(newSpecs, spec) } diff --git a/go/store/nbs/archive_chunk_source.go b/go/store/nbs/archive_chunk_source.go index 271252f5718..ebf1a854156 100644 --- a/go/store/nbs/archive_chunk_source.go +++ b/go/store/nbs/archive_chunk_source.go @@ -141,6 +141,10 @@ func (acs archiveChunkSource) hash() hash.Hash { return acs.aRdr.footer.hash } +func (acs archiveChunkSource) name() string { + return acs.hash().String() + ".darc" // NM4 - second time this const is defined. Fix! +} + func (acs archiveChunkSource) currentSize() uint64 { return acs.aRdr.footer.fileSize } @@ -167,12 +171,37 @@ func (acs archiveChunkSource) clone() (chunkSource, error) { return archiveChunkSource{acs.file, rdr}, nil } -func (acs archiveChunkSource) getRecordRanges(_ context.Context, _ []getRecord, _ keeperF) (map[hash.Hash]Range, gcBehavior, error) { - return nil, gcBehavior_Continue, errors.New("Archive chunk source does not support getRecordRanges") +func (acs archiveChunkSource) getRecordRanges(_ context.Context, requests []getRecord, _ keeperF) (map[hash.Hash]Range, gcBehavior, error) { + result := make(map[hash.Hash]Range, len(requests)) + for _, req := range requests { + hAddr := *req.a + if acs.aRdr.has(hAddr) { + idx := acs.aRdr.search(hAddr) + if idx < 0 { + // Chunk not found. + continue + } + + dictId, dataId := acs.aRdr.getChunkRef(idx) + dataSpan := acs.aRdr.getByteSpanByID(dataId) + dictSpan := acs.aRdr.getByteSpanByID(dictId) + + rng := Range{ + Offset: dataSpan.offset, + Length: uint32(dataSpan.length), + DictOffset: dictSpan.offset, + DictLength: uint32(dictSpan.length), + } + + result[hAddr] = rng + } + } + return result, gcBehavior_Block, nil // NM4 - FIXME. Merging. This is wrong. Use the keeperF } func (acs archiveChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { return acs.getMany(ctx, eg, reqs, func(ctx context.Context, chk *chunks.Chunk) { + // NM4 - UPDATE. this is def wrong. Not sure why I did this! found(ctx, ChunkToCompressedChunk(*chk)) }, keeper, stats) } diff --git a/go/store/nbs/archive_test.go b/go/store/nbs/archive_test.go index fd9e1dee118..ac0c1c70eb3 100644 --- a/go/store/nbs/archive_test.go +++ b/go/store/nbs/archive_test.go @@ -696,6 +696,10 @@ func (tcs *testChunkSource) hash() hash.Hash { panic("never used") } +func (tcs *testChunkSource) name() string { + panic("never used") +} + func (tcs *testChunkSource) reader(ctx context.Context) (io.ReadCloser, uint64, error) { panic("never used") } diff --git a/go/store/nbs/chunk_source_adapter.go b/go/store/nbs/chunk_source_adapter.go index f86d7527b43..013351ef6c5 100644 --- a/go/store/nbs/chunk_source_adapter.go +++ b/go/store/nbs/chunk_source_adapter.go @@ -29,6 +29,10 @@ func (csa chunkSourceAdapter) hash() hash.Hash { return csa.h } +func (csa chunkSourceAdapter) name() string { + return csa.h.String() +} + func newReaderFromIndexData(ctx context.Context, q MemoryQuotaProvider, idxData []byte, name hash.Hash, tra tableReaderAt, blockSize uint64) (cs chunkSource, err error) { index, err := parseTableIndexByCopy(ctx, idxData, q) if err != nil { diff --git a/go/store/nbs/conjoiner.go b/go/store/nbs/conjoiner.go index ad2a12ac61f..0721a49a65f 100644 --- a/go/store/nbs/conjoiner.go +++ b/go/store/nbs/conjoiner.go @@ -162,7 +162,7 @@ func conjoin(ctx context.Context, s conjoinStrategy, upstream manifestContents, return upstream, func() {}, nil } for i := range upstream.appendix { - if upstream.appendix[i].name != appendixSpecs[i].name { + if upstream.appendix[i].hash != appendixSpecs[i].hash { return upstream, func() {}, nil } } @@ -176,19 +176,19 @@ func conjoin(ctx context.Context, s conjoinStrategy, upstream manifestContents, conjoineeSet := map[hash.Hash]struct{}{} upstreamNames := map[hash.Hash]struct{}{} for _, spec := range upstream.specs { - upstreamNames[spec.name] = struct{}{} + upstreamNames[spec.hash] = struct{}{} } for _, c := range conjoinees { - if _, present := upstreamNames[c.name]; !present { + if _, present := upstreamNames[c.hash]; !present { return upstream, func() {}, nil // Bail! } - conjoineeSet[c.name] = struct{}{} + conjoineeSet[c.hash] = struct{}{} } // Filter conjoinees out of upstream.specs to generate new set of keepers keepers = make([]tableSpec, 0, len(upstream.specs)-len(conjoinees)) for _, spec := range upstream.specs { - if _, present := conjoineeSet[spec.name]; !present { + if _, present := conjoineeSet[spec.hash]; !present { keepers = append(keepers, spec) } } @@ -202,7 +202,7 @@ func conjoinTables(ctx context.Context, conjoinees []tableSpec, p tablePersister for idx := range conjoinees { i, spec := idx, conjoinees[idx] eg.Go(func() (err error) { - toConjoin[i], err = p.Open(ectx, spec.name, spec.chunkCount, stats) + toConjoin[i], err = p.Open(ectx, spec.hash, spec.chunkCount, stats) return }) } @@ -240,7 +240,7 @@ func conjoinTables(ctx context.Context, conjoinees []tableSpec, p tablePersister if err != nil { return tableSpec{}, nil, err } - return tableSpec{h, cnt}, cleanup, nil + return tableSpec{typeNoms, h, cnt}, cleanup, nil } func toSpecs(srcs chunkSources) ([]tableSpec, error) { @@ -258,7 +258,7 @@ func toSpecs(srcs chunkSources) ([]tableSpec, error) { if err != nil { return nil, err } - specs[i] = tableSpec{h, cnt} + specs[i] = tableSpec{typeNoms, h, cnt} } return specs, nil diff --git a/go/store/nbs/conjoiner_test.go b/go/store/nbs/conjoiner_test.go index a9f64aa2220..9b2480ec0e2 100644 --- a/go/store/nbs/conjoiner_test.go +++ b/go/store/nbs/conjoiner_test.go @@ -42,7 +42,7 @@ func (ts tableSpecsByAscendingCount) Len() int { return len(ts) } func (ts tableSpecsByAscendingCount) Less(i, j int) bool { tsI, tsJ := ts[i], ts[j] if tsI.chunkCount == tsJ.chunkCount { - return bytes.Compare(tsI.name[:], tsJ.name[:]) < 0 + return bytes.Compare(tsI.hash[:], tsJ.hash[:]) < 0 } return tsI.chunkCount < tsJ.chunkCount } @@ -76,7 +76,7 @@ func makeTestSrcs(t *testing.T, tableSizes []uint32, p tablePersister) (srcs chu // Makes a tableSet with len(tableSizes) upstream tables containing tableSizes[N] unique chunks func makeTestTableSpecs(t *testing.T, tableSizes []uint32, p tablePersister) (specs []tableSpec) { for _, src := range makeTestSrcs(t, tableSizes, p) { - specs = append(specs, tableSpec{src.hash(), mustUint32(src.count())}) + specs = append(specs, tableSpec{typeNoms, src.hash(), mustUint32(src.count())}) err := src.close() require.NoError(t, err) } @@ -134,7 +134,7 @@ func testConjoin(t *testing.T, factory func(t *testing.T) tablePersister) { assertContainAll := func(t *testing.T, p tablePersister, expect, actual []tableSpec) { open := func(specs []tableSpec) (sources chunkSources) { for _, sp := range specs { - cs, err := p.Open(context.Background(), sp.name, sp.chunkCount, stats) + cs, err := p.Open(context.Background(), sp.hash, sp.chunkCount, stats) if err != nil { require.NoError(t, err) } @@ -183,7 +183,7 @@ func testConjoin(t *testing.T, factory func(t *testing.T) tablePersister) { src, _, err := p.Persist(context.Background(), mt, nil, nil, &Stats{}) require.NoError(t, err) defer src.close() - return tableSpec{src.hash(), mustUint32(src.count())} + return tableSpec{typeNoms, src.hash(), mustUint32(src.count())} } tc := []struct { diff --git a/go/store/nbs/dynamo_manifest_test.go b/go/store/nbs/dynamo_manifest_test.go index e682c35c7be..1c1ba194156 100644 --- a/go/store/nbs/dynamo_manifest_test.go +++ b/go/store/nbs/dynamo_manifest_test.go @@ -68,13 +68,13 @@ func TestDynamoManifestParseIfExists(t *testing.T) { assert.Equal(newLock, contents.lock) assert.Equal(newRoot, contents.root) if assert.Len(contents.appendix, 1) { - assert.Equal(tableName.String(), contents.specs[0].name.String()) + assert.Equal(tableName.String(), contents.specs[0].hash.String()) assert.Equal(uint32(0), contents.specs[0].chunkCount) - assert.Equal(tableName.String(), contents.appendix[0].name.String()) + assert.Equal(tableName.String(), contents.appendix[0].hash.String()) assert.Equal(uint32(0), contents.appendix[0].chunkCount) } if assert.Len(contents.specs, 2) { - assert.Equal(tableName.String(), contents.specs[1].name.String()) + assert.Equal(tableName.String(), contents.specs[1].hash.String()) assert.Equal(uint32(0), contents.specs[1].chunkCount) } } @@ -109,7 +109,7 @@ func TestDynamoManifestUpdate(t *testing.T) { stats := &Stats{} // First, test winning the race against another process. - contents := makeContents("locker", "nuroot", []tableSpec{{computeAddr([]byte("a")), 3}}, nil) + contents := makeContents("locker", "nuroot", []tableSpec{{typeNoms, computeAddr([]byte("a")), 3}}, nil) upstream, err := mm.Update(context.Background(), hash.Hash{}, contents, stats, func() error { // This should fail to get the lock, and therefore _not_ clobber the manifest. So the Update should succeed. lock := computeAddr([]byte("nolock")) @@ -145,7 +145,7 @@ func TestDynamoManifestUpdate(t *testing.T) { require.NoError(t, err) assert.Equal(jerkLock, upstream.lock) assert.Equal(rejected.root, upstream.root) - assert.Equal([]tableSpec{{tableName, 1}}, upstream.specs) + assert.Equal([]tableSpec{{typeNoms, tableName, 1}}, upstream.specs) } func TestDynamoManifestUpdateAppendix(t *testing.T) { @@ -155,11 +155,11 @@ func TestDynamoManifestUpdateAppendix(t *testing.T) { // First, test winning the race against another process. specs := []tableSpec{ - {computeAddr([]byte("app-a")), 3}, - {computeAddr([]byte("a")), 3}, + {typeNoms, computeAddr([]byte("app-a")), 3}, + {typeNoms, computeAddr([]byte("a")), 3}, } - app := []tableSpec{{computeAddr([]byte("app-a")), 3}} + app := []tableSpec{{typeNoms, computeAddr([]byte("app-a")), 3}} contents := makeContents("locker", "nuroot", specs, app) upstream, err := mm.Update(context.Background(), hash.Hash{}, contents, stats, func() error { @@ -204,8 +204,8 @@ func TestDynamoManifestUpdateAppendix(t *testing.T) { require.NoError(t, err) assert.Equal(jerkLock, upstream.lock) assert.Equal(rejected.root, upstream.root) - assert.Equal([]tableSpec{{appTableName, 1}, {tableName, 1}}, upstream.specs) - assert.Equal([]tableSpec{{appTableName, 1}}, upstream.appendix) + assert.Equal([]tableSpec{{typeNoms, appTableName, 1}, {typeNoms, tableName, 1}}, upstream.specs) + assert.Equal([]tableSpec{{typeNoms, appTableName, 1}}, upstream.appendix) } func TestDynamoManifestCaching(t *testing.T) { @@ -231,7 +231,7 @@ func TestDynamoManifestCaching(t *testing.T) { // When failing the optimistic lock, we should hit persistent storage. reads = ddb.NumGets() - contents := makeContents("lock2", "nuroot", []tableSpec{{computeAddr([]byte("a")), 3}}, nil) + contents := makeContents("lock2", "nuroot", []tableSpec{{typeNoms, computeAddr([]byte("a")), 3}}, nil) upstream, err := mm.Update(context.Background(), hash.Hash{}, contents, stats, nil) require.NoError(t, err) assert.NotEqual(contents.lock, upstream.lock) diff --git a/go/store/nbs/empty_chunk_source.go b/go/store/nbs/empty_chunk_source.go index 8ea39c869ca..a341251ff7e 100644 --- a/go/store/nbs/empty_chunk_source.go +++ b/go/store/nbs/empty_chunk_source.go @@ -66,6 +66,10 @@ func (ecs emptyChunkSource) hash() hash.Hash { return hash.Hash{} } +func (ecs emptyChunkSource) name() string { + return ecs.hash().String() +} + func (ecs emptyChunkSource) index() (tableIndex, error) { return onHeapTableIndex{}, nil } diff --git a/go/store/nbs/file_manifest_test.go b/go/store/nbs/file_manifest_test.go index 9cb5d51830f..d4cad682dfd 100644 --- a/go/store/nbs/file_manifest_test.go +++ b/go/store/nbs/file_manifest_test.go @@ -73,7 +73,7 @@ func TestFileManifestLoadIfExists(t *testing.T) { assert.Equal(jerk, upstream.lock) assert.Equal(newRoot, upstream.root) if assert.Len(upstream.specs, 1) { - assert.Equal(tableName.String(), upstream.specs[0].name.String()) + assert.Equal(tableName.String(), upstream.specs[0].hash.String()) assert.Equal(uint32(0), upstream.specs[0].chunkCount) } } @@ -134,7 +134,7 @@ func TestFileManifestUpdate(t *testing.T) { nbfVers: constants.FormatLD1String, lock: computeAddr([]byte("locker")), root: hash.Of([]byte("new root")), - specs: []tableSpec{{computeAddr([]byte("a")), 3}}, + specs: []tableSpec{{typeNoms, computeAddr([]byte("a")), 3}}, } upstream, err := fm.Update(context.Background(), hash.Hash{}, contents, stats, func() error { // This should fail to get the lock, and therefore _not_ clobber the manifest. So the Update should succeed. @@ -177,7 +177,7 @@ func TestFileManifestUpdate(t *testing.T) { require.NoError(t, err) assert.Equal(jerkLock, upstream.lock) assert.Equal(contents2.root, upstream.root) - assert.Equal([]tableSpec{{tableName, 1}}, upstream.specs) + assert.Equal([]tableSpec{{typeNoms, tableName, 1}}, upstream.specs) } // tryClobberManifest simulates another process trying to access dir/manifestFileName concurrently. To avoid deadlock, it does a non-blocking lock of dir/lockFileName. If it can get the lock, it clobbers the manifest. diff --git a/go/store/nbs/file_table_reader.go b/go/store/nbs/file_table_reader.go index a95c2109c32..57dee611661 100644 --- a/go/store/nbs/file_table_reader.go +++ b/go/store/nbs/file_table_reader.go @@ -169,6 +169,10 @@ func (ftr *fileTableReader) hash() hash.Hash { return ftr.h } +func (ftr *fileTableReader) name() string { + return ftr.h.String() +} + func (ftr *fileTableReader) Close() error { return ftr.tableReader.close() } diff --git a/go/store/nbs/gc_copier.go b/go/store/nbs/gc_copier.go index 53a7105e4e6..203588b98e6 100644 --- a/go/store/nbs/gc_copier.go +++ b/go/store/nbs/gc_copier.go @@ -94,7 +94,7 @@ func (gcc *gcCopier) copyTablesToDir(ctx context.Context) (ts []tableSpec, err e if exists { return []tableSpec{ { - name: addr, + hash: addr, chunkCount: uint32(gcc.writer.ChunkCount()), }, }, nil @@ -106,7 +106,7 @@ func (gcc *gcCopier) copyTablesToDir(ctx context.Context) (ts []tableSpec, err e if err == nil { return []tableSpec{ { - name: addr, + hash: addr, chunkCount: uint32(gcc.writer.ChunkCount()), }, }, nil @@ -128,7 +128,7 @@ func (gcc *gcCopier) copyTablesToDir(ctx context.Context) (ts []tableSpec, err e return []tableSpec{ { - name: addr, + hash: addr, chunkCount: uint32(gcc.writer.ChunkCount()), }, }, nil diff --git a/go/store/nbs/generational_chunk_store.go b/go/store/nbs/generational_chunk_store.go index 04b32b4e4a8..380ffd8cb3d 100644 --- a/go/store/nbs/generational_chunk_store.go +++ b/go/store/nbs/generational_chunk_store.go @@ -459,7 +459,7 @@ func (gcs *GenerationalNBS) GetChunkLocationsWithPaths(ctx context.Context, hash return res, nil } -func (gcs *GenerationalNBS) GetChunkLocations(ctx context.Context, hashes hash.HashSet) (map[hash.Hash]map[hash.Hash]Range, error) { +func (gcs *GenerationalNBS) GetChunkLocations(ctx context.Context, hashes hash.HashSet) (map[string]map[hash.Hash]Range, error) { res, err := gcs.newGen.GetChunkLocations(ctx, hashes) if err != nil { return nil, err diff --git a/go/store/nbs/ghost_store_test.go b/go/store/nbs/ghost_store_test.go index e6fc6146f7e..aa347b1a6bf 100644 --- a/go/store/nbs/ghost_store_test.go +++ b/go/store/nbs/ghost_store_test.go @@ -74,8 +74,8 @@ func TestGhostBlockStore(t *testing.T) { require.Equal(t, ghost, got[0].Hash()) }) t.Run("GetManyCompressed", func(t *testing.T) { - var got []CompressedChunk - err := bs.GetManyCompressed(ctx, hash.NewHashSet(absent, ghost), func(_ context.Context, c CompressedChunk) { + var got []ToChunker + err := bs.GetManyCompressed(ctx, hash.NewHashSet(absent, ghost), func(_ context.Context, c ToChunker) { got = append(got, c) }) require.NoError(t, err) diff --git a/go/store/nbs/journal.go b/go/store/nbs/journal.go index dc8deff8e19..a07229102c2 100644 --- a/go/store/nbs/journal.go +++ b/go/store/nbs/journal.go @@ -503,7 +503,7 @@ func (c journalConjoiner) chooseConjoinees(upstream []tableSpec) (conjoinees, ke var stash tableSpec // don't conjoin journal pruned := make([]tableSpec, 0, len(upstream)) for _, ts := range upstream { - if isJournalAddr(ts.name) { + if isJournalAddr(ts.hash) { stash = ts } else { pruned = append(pruned, ts) @@ -513,7 +513,7 @@ func (c journalConjoiner) chooseConjoinees(upstream []tableSpec) (conjoinees, ke if err != nil { return nil, nil, err } - if !hash.Hash(stash.name).IsEmpty() { + if !hash.Hash(stash.hash).IsEmpty() { keepers = append(keepers, stash) } return @@ -646,7 +646,7 @@ func (jm *journalManifest) Close() (err error) { func containsJournalSpec(specs []tableSpec) (ok bool) { for _, spec := range specs { - if spec.name == journalAddr { + if spec.hash == journalAddr { ok = true break } diff --git a/go/store/nbs/journal_chunk_source.go b/go/store/nbs/journal_chunk_source.go index a35c6cd21e8..7d55f93f068 100644 --- a/go/store/nbs/journal_chunk_source.go +++ b/go/store/nbs/journal_chunk_source.go @@ -182,6 +182,10 @@ func (s journalChunkSource) hash() hash.Hash { return journalAddr } +func (s journalChunkSource) name() string { + return s.hash().String() +} + // reader implements chunkSource. func (s journalChunkSource) reader(ctx context.Context) (io.ReadCloser, uint64, error) { rdr, sz, err := s.journal.snapshot(ctx) @@ -298,10 +302,10 @@ func equalSpecs(left, right []tableSpec) bool { } l := make(map[hash.Hash]struct{}, len(left)) for _, s := range left { - l[s.name] = struct{}{} + l[s.hash] = struct{}{} } for _, s := range right { - if _, ok := l[s.name]; !ok { + if _, ok := l[s.hash]; !ok { return false } } diff --git a/go/store/nbs/manifest.go b/go/store/nbs/manifest.go index f125353849d..e4ab6841acf 100644 --- a/go/store/nbs/manifest.go +++ b/go/store/nbs/manifest.go @@ -177,7 +177,7 @@ func (mc manifestContents) removeAppendixSpecs() (manifestContents, []tableSpec) filtered := make([]tableSpec, 0) removed := make([]tableSpec, 0) for _, s := range mc.specs { - if _, ok := appendixSet[s.name]; ok { + if _, ok := appendixSet[s.hash]; ok { removed = append(removed, s) } else { filtered = append(filtered, s) @@ -204,7 +204,7 @@ func (mc manifestContents) getAppendixSet() (ss map[hash.Hash]struct{}) { func toSpecSet(specs []tableSpec) (ss map[hash.Hash]struct{}) { ss = make(map[hash.Hash]struct{}, len(specs)) for _, ts := range specs { - ss[ts.name] = struct{}{} + ss[ts.hash] = struct{}{} } return ss } @@ -212,7 +212,7 @@ func toSpecSet(specs []tableSpec) (ss map[hash.Hash]struct{}) { func (mc manifestContents) size() (size uint64) { size += uint64(len(mc.nbfVers)) + hash.ByteLen + hash.ByteLen for _, sp := range mc.specs { - size += uint64(len(sp.name)) + uint32Size // for sp.chunkCount + size += uint64(len(sp.hash)) + uint32Size // for sp.chunkCount } return } @@ -439,17 +439,32 @@ func (mm manifestManager) Name() string { // TableSpecInfo is an interface for retrieving data from a tableSpec outside of this package type TableSpecInfo interface { - GetName() string + GetFileName() string GetChunkCount() uint32 } +type tableFileType int + +const ( + typeNoms tableFileType = iota + typeArchive +) + type tableSpec struct { - name hash.Hash + fileType tableFileType + hash hash.Hash chunkCount uint32 } -func (ts tableSpec) GetName() string { - return ts.name.String() +func (ts tableSpec) GetFileName() string { + switch ts.fileType { + case typeNoms: + return ts.hash.String() + case typeArchive: + return ts.hash.String() + ".darc" // NM4 - common code for this??? + default: + panic(fmt.Sprintf("runtime error: unknown table file type: %d", ts.fileType)) + } } func (ts tableSpec) GetChunkCount() uint32 { @@ -459,7 +474,7 @@ func (ts tableSpec) GetChunkCount() uint32 { func tableSpecsToMap(specs []tableSpec) map[string]int { m := make(map[string]int) for _, spec := range specs { - m[spec.name.String()] = int(spec.chunkCount) + m[spec.hash.String()] = int(spec.chunkCount) } return m @@ -470,7 +485,7 @@ func parseSpecs(tableInfo []string) ([]tableSpec, error) { for i := range specs { var err error var ok bool - specs[i].name, ok = hash.MaybeParse(tableInfo[2*i]) + specs[i].hash, ok = hash.MaybeParse(tableInfo[2*i]) if !ok { return nil, fmt.Errorf("invalid table file name: %s", tableInfo[2*i]) } @@ -490,7 +505,7 @@ func parseSpecs(tableInfo []string) ([]tableSpec, error) { func formatSpecs(specs []tableSpec, tableInfo []string) { d.Chk.True(len(tableInfo) == 2*len(specs)) for i, t := range specs { - tableInfo[2*i] = t.name.String() + tableInfo[2*i] = t.hash.String() tableInfo[2*i+1] = strconv.FormatUint(uint64(t.chunkCount), 10) } } @@ -505,11 +520,11 @@ func generateLockHash(root hash.Hash, specs []tableSpec, appendix []tableSpec, e blockHash := sha512.New() blockHash.Write(root[:]) for _, spec := range appendix { - blockHash.Write(spec.name[:]) + blockHash.Write(spec.hash[:]) } blockHash.Write([]byte{0}) for _, spec := range specs { - blockHash.Write(spec.name[:]) + blockHash.Write(spec.hash[:]) } if len(extra) > 0 { blockHash.Write([]byte{0}) diff --git a/go/store/nbs/metadata.go b/go/store/nbs/metadata.go index 6f443efb0b5..efb0e2a6bd1 100644 --- a/go/store/nbs/metadata.go +++ b/go/store/nbs/metadata.go @@ -118,7 +118,7 @@ func GetStorageMetadata(path string) (StorageMetadata, error) { tableSpecInfo := manifest.GetTableSpecInfo(i) // If the oldgen/name exists, it's not an archive. If it exists with a .darc suffix, then it's an archive. - tfName := tableSpecInfo.GetName() + tfName := tableSpecInfo.GetFileName() fullPath := filepath.Join(oldgen, tfName) _, err := os.Stat(fullPath) if err == nil { diff --git a/go/store/nbs/root_tracker_test.go b/go/store/nbs/root_tracker_test.go index 37d82483743..e8fff9ad62b 100644 --- a/go/store/nbs/root_tracker_test.go +++ b/go/store/nbs/root_tracker_test.go @@ -404,7 +404,7 @@ func interloperWrite(fm *fakeManifest, p tablePersister, rootChunk []byte, chunk return hash.Hash{}, nil, err } - fm.set(constants.FormatLD1String, newLock, newRoot, []tableSpec{{src.hash(), uint32(len(chunks) + 1)}}, nil) + fm.set(constants.FormatLD1String, newLock, newRoot, []tableSpec{{typeNoms, src.hash(), uint32(len(chunks) + 1)}}, nil) if err = src.close(); err != nil { return [20]byte{}, nil, err diff --git a/go/store/nbs/store.go b/go/store/nbs/store.go index b52f0bc37a8..1ce515d8fb1 100644 --- a/go/store/nbs/store.go +++ b/go/store/nbs/store.go @@ -68,7 +68,6 @@ const ( defaultMaxTables = 256 defaultManifestCacheSize = 1 << 23 // 8MB - preflushChunkCount = 8 ) var ( @@ -144,8 +143,10 @@ var _ chunks.ChunkStoreGarbageCollector = &NomsBlockStore{} const hasCacheSize = 100000 type Range struct { - Offset uint64 - Length uint32 + Offset uint64 + Length uint32 + DictOffset uint64 + DictLength uint32 } // ChunkJournal returns the ChunkJournal in use by this NomsBlockStore, or nil if no ChunkJournal is being used. @@ -161,15 +162,15 @@ func (nbs *NomsBlockStore) GetChunkLocationsWithPaths(ctx context.Context, hashe if err != nil { return nil, err } - toret := make(map[string]map[hash.Hash]Range, len(locs)) + toret := make(map[string]map[hash.Hash]Range, len(locs)) // NM4 for k, v := range locs { - toret[k.String()] = v + toret[k] = v } return toret, nil } -func (nbs *NomsBlockStore) GetChunkLocations(ctx context.Context, hashes hash.HashSet) (map[hash.Hash]map[hash.Hash]Range, error) { - fn := func(css chunkSourceSet, gr []getRecord, ranges map[hash.Hash]map[hash.Hash]Range, keeper keeperF) (gcBehavior, error) { +func (nbs *NomsBlockStore) GetChunkLocations(ctx context.Context, hashes hash.HashSet) (map[string]map[hash.Hash]Range, error) { + fn := func(css chunkSourceSet, gr []getRecord, ranges map[string]map[hash.Hash]Range, keeper keeperF) (gcBehavior, error) { for _, cs := range css { rng, gcb, err := cs.getRecordRanges(ctx, gr, keeper) if err != nil { @@ -178,8 +179,11 @@ func (nbs *NomsBlockStore) GetChunkLocations(ctx context.Context, hashes hash.Ha if gcb != gcBehavior_Continue { return gcb, nil } + if len(rng) == 0 { + continue + } - h := hash.Hash(cs.hash()) + h := cs.name() if m, ok := ranges[h]; ok { for k, v := range rng { m[k] = v @@ -197,7 +201,7 @@ func (nbs *NomsBlockStore) GetChunkLocations(ctx context.Context, hashes hash.Ha nbs.mu.Unlock() gr := toGetRecords(hashes) - ranges := make(map[hash.Hash]map[hash.Hash]Range) + ranges := make(map[string]map[hash.Hash]Range) gcb, err := fn(tables.upstream, gr, ranges, keeper) if needsContinue, err := nbs.handleUnlockedRead(ctx, gcb, endRead, err); err != nil { @@ -317,15 +321,15 @@ func (nbs *NomsBlockStore) updateManifestAddFiles(ctx context.Context, updates m if appendixOption == nil { if _, ok := currSpecs[h]; !ok { hasWork = true - contents.specs = append(contents.specs, tableSpec{h, count}) + contents.specs = append(contents.specs, tableSpec{typeNoms, h, count}) } } else if *appendixOption == ManifestAppendixOption_Set { hasWork = true - appendixSpecs = append(appendixSpecs, tableSpec{h, count}) + appendixSpecs = append(appendixSpecs, tableSpec{typeNoms, h, count}) } else if *appendixOption == ManifestAppendixOption_Append { if _, ok := currAppendixSpecs[h]; !ok { hasWork = true - appendixSpecs = append(appendixSpecs, tableSpec{h, count}) + appendixSpecs = append(appendixSpecs, tableSpec{typeNoms, h, count}) } } else { return manifestContents{}, ErrUnsupportedManifestAppendixOption @@ -465,12 +469,13 @@ func OverwriteStoreManifest(ctx context.Context, store *NomsBlockStore, root has } // Appendix table files should come first in specs for h, c := range appendixTableFiles { - s := tableSpec{name: h, chunkCount: c} + // NM4 - not sure on this one.... + s := tableSpec{fileType: typeNoms, hash: h, chunkCount: c} contents.appendix = append(contents.appendix, s) contents.specs = append(contents.specs, s) } for h, c := range tableFiles { - s := tableSpec{name: h, chunkCount: c} + s := tableSpec{fileType: typeNoms, hash: h, chunkCount: c} contents.specs = append(contents.specs, s) } contents.lock = generateLockHash(contents.root, contents.specs, contents.appendix, nil) @@ -1399,7 +1404,7 @@ func (nbs *NomsBlockStore) updateManifest(ctx context.Context, current, last has filtered := make([]tableSpec, 0, len(specs)) for _, s := range specs { - if _, present := appendixSet[s.name]; !present { + if _, present := appendixSet[s.hash]; !present { filtered = append(filtered, s) } } @@ -1488,7 +1493,7 @@ func (tf tableFile) LocationPrefix() string { // FileID gets the id of the file func (tf tableFile) FileID() string { - return tf.info.GetName() + return tf.info.GetFileName() } // NumChunks returns the number of chunks in a table file @@ -1546,10 +1551,15 @@ func getTableFiles(css map[hash.Hash]chunkSource, contents manifestContents, num } for i := 0; i < numSpecs; i++ { info := specFunc(contents, i) - cs, ok := css[info.name] + cs, ok := css[info.hash] if !ok { return nil, ErrSpecWithoutChunkSource } + + if _, ok := cs.(archiveChunkSource); ok { + info.fileType = typeArchive + } + tableFiles = append(tableFiles, newTableFile(cs, info)) } return tableFiles, nil @@ -1917,7 +1927,7 @@ func (gcf gcFinalizer) AddChunksToStore(ctx context.Context) (chunks.HasManyFunc fileIdToNumChunks := tableSpecsToMap(gcf.specs) var addrs []hash.Hash for _, spec := range gcf.specs { - addrs = append(addrs, spec.name) + addrs = append(addrs, spec.hash) } f := func(ctx context.Context, hashes hash.HashSet) (hash.HashSet, error) { return gcf.nbs.hasManyInSources(addrs, hashes) diff --git a/go/store/nbs/table.go b/go/store/nbs/table.go index 6abb43be65c..bc7e2d3636c 100644 --- a/go/store/nbs/table.go +++ b/go/store/nbs/table.go @@ -239,6 +239,10 @@ type chunkSource interface { // hash returns the hash address of this chunkSource. hash() hash.Hash + // name is the on disk short name for this chunkSource. Classically, this was a hash. Having files + // with suffixes (eg darc) was useful. + name() string + // opens a Reader to the first byte of the chunkData segment of this table. reader(context.Context) (io.ReadCloser, uint64, error) diff --git a/go/store/nbs/table_set.go b/go/store/nbs/table_set.go index a664a2fc100..ab523bffa48 100644 --- a/go/store/nbs/table_set.go +++ b/go/store/nbs/table_set.go @@ -433,10 +433,10 @@ func (ts tableSet) rebase(ctx context.Context, specs []tableSpec, stats *Stats) specs = make([]tableSpec, 0, len(orig)) seen := map[hash.Hash]struct{}{} for _, spec := range orig { - if _, ok := seen[spec.name]; ok { + if _, ok := seen[spec.hash]; ok { continue } - seen[spec.name] = struct{}{} + seen[spec.hash] = struct{}{} // keep specs in order to play nicely with // manifest appendix optimization specs = append(specs, spec) @@ -464,7 +464,7 @@ func (ts tableSet) rebase(ctx context.Context, specs []tableSpec, stats *Stats) upstream := make(chunkSourceSet, len(specs)) for _, s := range specs { // clone tables that we have already opened - if cs, ok := ts.upstream[s.name]; ok { + if cs, ok := ts.upstream[s.hash]; ok { cl, err := cs.clone() if err != nil { _ = eg.Wait() @@ -482,7 +482,7 @@ func (ts tableSet) rebase(ctx context.Context, specs []tableSpec, stats *Stats) // open missing tables in parallel spec := s eg.Go(func() error { - cs, err := ts.p.Open(ctx, spec.name, spec.chunkCount, stats) // NM4 - spec.name is the tf/arch name. + cs, err := ts.p.Open(ctx, spec.hash, spec.chunkCount, stats) if err != nil { return err } @@ -521,8 +521,9 @@ func (ts tableSet) toSpecs() ([]tableSpec, error) { if err != nil { return nil, err } else if cnt > 0 { + // NM4 - New to choose between classic or archive here. h := src.hash() - tableSpecs = append(tableSpecs, tableSpec{h, cnt}) + tableSpecs = append(tableSpecs, tableSpec{typeNoms, h, cnt}) } } for _, src := range ts.upstream { @@ -532,11 +533,12 @@ func (ts tableSet) toSpecs() ([]tableSpec, error) { } else if cnt <= 0 { return nil, errors.New("no upstream chunks") } + // NM4 - New to choose between classic or archive here. h := src.hash() - tableSpecs = append(tableSpecs, tableSpec{h, cnt}) + tableSpecs = append(tableSpecs, tableSpec{typeNoms, h, cnt}) } sort.Slice(tableSpecs, func(i, j int) bool { - return bytes.Compare(tableSpecs[i].name[:], tableSpecs[j].name[:]) < 0 + return bytes.Compare(tableSpecs[i].hash[:], tableSpecs[j].hash[:]) < 0 }) return tableSpecs, nil } diff --git a/go/store/nbs/table_set_test.go b/go/store/nbs/table_set_test.go index e1cfcef3dad..cc19f34fe45 100644 --- a/go/store/nbs/table_set_test.go +++ b/go/store/nbs/table_set_test.go @@ -238,7 +238,7 @@ func TestTableSetClosesOpenedChunkSourcesOnErr(t *testing.T) { p.sourcesToFail[a] = true } once = false - specs = append(specs, tableSpec{a, 1}) + specs = append(specs, tableSpec{typeNoms, a, 1}) } ts := newTableSet(p, q) From 0c1beef81828a2d8845b32b4a30a682eec44fdff Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 23 Jan 2025 13:23:50 -0800 Subject: [PATCH 05/13] Add test for cloning archived repository. --- go/libraries/doltcore/remotesrv/server.go | 10 --- .../noms/8p5e2m6skovfdjlh4jg3llr8sfvu384l | Bin 0 -> 458 bytes .../bats/archive-test-repo/noms/LOCK | 0 .../bats/archive-test-repo/noms/manifest | 1 + .../29o8a3uevcpr15tilcemb3s438edmoog.darc | Bin 0 -> 58783 bytes .../bats/archive-test-repo/noms/oldgen/LOCK | 0 .../dnu4lr5j8sstbj5usbld7alsnuj5nf23.darc | Bin 0 -> 113409 bytes .../archive-test-repo/noms/oldgen/manifest | 1 + .../bats/archive-test-repo/repo_state.json | 6 ++ integration-tests/bats/archive.bats | 71 ++++++++++++++++++ 10 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 integration-tests/bats/archive-test-repo/noms/8p5e2m6skovfdjlh4jg3llr8sfvu384l create mode 100644 integration-tests/bats/archive-test-repo/noms/LOCK create mode 100644 integration-tests/bats/archive-test-repo/noms/manifest create mode 100644 integration-tests/bats/archive-test-repo/noms/oldgen/29o8a3uevcpr15tilcemb3s438edmoog.darc create mode 100644 integration-tests/bats/archive-test-repo/noms/oldgen/LOCK create mode 100644 integration-tests/bats/archive-test-repo/noms/oldgen/dnu4lr5j8sstbj5usbld7alsnuj5nf23.darc create mode 100644 integration-tests/bats/archive-test-repo/noms/oldgen/manifest create mode 100755 integration-tests/bats/archive-test-repo/repo_state.json diff --git a/go/libraries/doltcore/remotesrv/server.go b/go/libraries/doltcore/remotesrv/server.go index 46b7ec5182f..6b451a63109 100644 --- a/go/libraries/doltcore/remotesrv/server.go +++ b/go/libraries/doltcore/remotesrv/server.go @@ -17,7 +17,6 @@ package remotesrv import ( "context" "crypto/tls" - "errors" "net" "net/http" "strings" @@ -29,7 +28,6 @@ import ( "google.golang.org/grpc" remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/env" "github.com/dolthub/dolt/go/libraries/utils/filesys" ) @@ -80,14 +78,6 @@ func NewServer(args ServerArgs) (*Server, error) { args.Logger = logrus.NewEntry(logrus.StandardLogger()) } - storageMetadata, err := env.GetMultiEnvStorageMetadata(args.FS) - if err != nil { - return nil, err - } - if storageMetadata.ArchiveFilesPresent() { - return nil, errors.New("archive files present. Please run `dolt archive --revert` before running the server.") - } - s := new(Server) s.stopChan = make(chan struct{}) diff --git a/integration-tests/bats/archive-test-repo/noms/8p5e2m6skovfdjlh4jg3llr8sfvu384l b/integration-tests/bats/archive-test-repo/noms/8p5e2m6skovfdjlh4jg3llr8sfvu384l new file mode 100644 index 0000000000000000000000000000000000000000..aa14b7ce0ebf3b5dd87200955663ee16aca918bf GIT binary patch literal 458 zcmZ3(xImhLVTB9>14DRFa0rlM5MWSYP+*W@kYEr2vRQzbfnndZN$CuXvK~OD2oS3P zu?Y}605PMf2S1Q1F3eFVDN4*MPE0Pz%+F&LDM?B!DRxNBP0Y;EOH0g7W#9s;asrAe zgrpXi6m$ASr2p8&RGbuf@Y?#_`(&F8q6|16h5}6wl3-xlRE{0zzrmO#kBP?VZhte=sZm{P0{^k`lYBmbPtypq(Syu=*+ zZT?S+_0`V*$eu8YX#kRb8 d4EH>XQ&v0%`JV}hfq-$s|E)I;sU*J(007Hxd@}$5 literal 0 HcmV?d00001 diff --git a/integration-tests/bats/archive-test-repo/noms/LOCK b/integration-tests/bats/archive-test-repo/noms/LOCK new file mode 100644 index 00000000000..e69de29bb2d diff --git a/integration-tests/bats/archive-test-repo/noms/manifest b/integration-tests/bats/archive-test-repo/noms/manifest new file mode 100644 index 00000000000..924d8f97c52 --- /dev/null +++ b/integration-tests/bats/archive-test-repo/noms/manifest @@ -0,0 +1 @@ +5:__DOLT__:6eseiohaelofp485e95ti1pgqe5qrseg:att0kn4lt0uqce5mdqf01ni9ediahoo4:6eseiohaelofp485e95ti1pgqe5qrseg:8p5e2m6skovfdjlh4jg3llr8sfvu384l:2 \ No newline at end of file diff --git a/integration-tests/bats/archive-test-repo/noms/oldgen/29o8a3uevcpr15tilcemb3s438edmoog.darc b/integration-tests/bats/archive-test-repo/noms/oldgen/29o8a3uevcpr15tilcemb3s438edmoog.darc new file mode 100644 index 0000000000000000000000000000000000000000..1b938008b809647af36ea6b64af337491ebee815 GIT binary patch literal 58783 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%0D+pPU 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 literal 0 HcmV?d00001 diff --git a/integration-tests/bats/archive-test-repo/noms/oldgen/LOCK b/integration-tests/bats/archive-test-repo/noms/oldgen/LOCK new file mode 100644 index 00000000000..e69de29bb2d diff --git a/integration-tests/bats/archive-test-repo/noms/oldgen/dnu4lr5j8sstbj5usbld7alsnuj5nf23.darc b/integration-tests/bats/archive-test-repo/noms/oldgen/dnu4lr5j8sstbj5usbld7alsnuj5nf23.darc new file mode 100644 index 0000000000000000000000000000000000000000..c13a757ab4108fa44f12cae460f020d8dd3f4ab4 GIT binary patch literal 113409 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 FRi5Y5=5 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 $$9IyfBvm7 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%0A5Mi;&rLe5kJRLl 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�$ZAmX~eh6 zH{#hNih9~c_*FA^>a&nWHxd?Fd3-HIYLG*mb=d2KckkZ7pL~xW-M4jlm2hLfNpMp= zju`(mnC9H)x$y;KlpOVm^CsSrp%f33_5*CxU#cCT{up+sa4;@s9K*lo{6H(&zxAbw zsn66F7kb`UE)#Ty*#Ku?uwfcIKAnmK2wi77>>ak(4r% zl#-W5$xF#uaSL-h`FaJ~frlPGN)K zPCo7z#T^4t-ZDPUf#N6+PqYJC)Pg-Gby~;sKZE)Z@4v%FWsdK*F_d{;E?q3cs=*jp_dF0eeyN6hiT9HYbXJOtlt`i3_y7s^R99lgi1b2T`AXaXbrGCbAOt zv+(ps!Z-Ij8tW8P=#d(F)*flxO*Yo=;)=3i)K0g3 Date: Thu, 23 Jan 2025 15:05:53 -0800 Subject: [PATCH 06/13] Remove tests which not longer make sense --- integration-tests/bats/archive.bats | 33 ----------------------------- 1 file changed, 33 deletions(-) diff --git a/integration-tests/bats/archive.bats b/integration-tests/bats/archive.bats index 45be7d02109..c6b3e6710fc 100755 --- a/integration-tests/bats/archive.bats +++ b/integration-tests/bats/archive.bats @@ -143,39 +143,6 @@ mutations_and_gc_statement() { [ "$commits" -eq "66" ] } -@test "archive: archive backup no go" { - dolt sql -q "$(mutations_and_gc_statement)" - dolt archive - - dolt backup add bac1 file://../bac1 - run dolt backup sync bac1 - - [ "$status" -eq 1 ] - [[ "$output" =~ "archive files present" ]] || false - - # currently the cli and stored procedures are different code paths. - run dolt sql -q "call dolt_backup('sync', 'bac1')" - [ "$status" -eq 1 ] - # NM4 - TODO. This message is cryptic, but plumbing the error through is awkward. - [[ "$output" =~ "Archive chunk source" ]] || false -} - -@test "archive: clone archived database fails" { - mkdir remote - cd remote - dolt init - dolt sql -q "$(mutations_and_gc_statement)" - dolt archive - cd .. - - dolt clone file://./remote clone_test - [ "$status" -eq 1 ] - [[ "$output" =~ "archive files present" ]] || false - - rm -rf remote - rm -rf clone_test -} - @test "archive: can clone archived repository" { mkdir -p remote/.dolt mkdir cloned From 41675cfde090b934944247323690ebd7e3adf730 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 23 Jan 2025 14:41:36 -0800 Subject: [PATCH 07/13] DO NOT SHIP Notes to myself. --- go/libraries/doltcore/remotestorage/chunk_fetcher.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 830c90d35e0..91c7ef835f0 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -261,6 +261,7 @@ func fetcherRPCDownloadLocsThread(ctx context.Context, reqCh chan *remotesapi.Ge }) eg.Go(func() error { for { + // NM4 - Where there responses come back - resp is an rpc struct. NM4. resp, err := stream.Recv() if err == io.EOF { close(resCh) @@ -306,6 +307,7 @@ func getMissingChunks(req *remotesapi.GetDownloadLocsRequest, resp *remotesapi.G numRequested := len(req.ChunkHashes) numResponded := 0 for _, loc := range resp.Locs { + // NM4 - Looky here. hgr := loc.Location.(*remotesapi.DownloadLoc_HttpGetRange).HttpGetRange numResponded += len(hgr.Ranges) } @@ -368,6 +370,7 @@ func (d downloads) Add(resp *remotesapi.DownloadLoc) { d.refreshes[path] = refresh } for _, r := range gr.Ranges { + // NM4 - this is where the offset is read!! do something here or nearby. d.ranges.Insert(gr.Url, r.Hash, r.Offset, r.Length) } } From c1b6c0979b07c579c04a9a2a201d508983975dc1 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Tue, 28 Jan 2025 11:04:15 -0800 Subject: [PATCH 08/13] another clone test --- integration-tests/bats/archive.bats | 38 ++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/integration-tests/bats/archive.bats b/integration-tests/bats/archive.bats index c6b3e6710fc..90ced9566cf 100755 --- a/integration-tests/bats/archive.bats +++ b/integration-tests/bats/archive.bats @@ -11,6 +11,12 @@ setup() { } teardown() { + if [ -n "$remotesrv_pid" ]; then + kill "$remotesrv_pid" + wait "$remotesrv_pid" || : + remotesrv_pid="" + fi + assert_feature_version teardown_common } @@ -190,10 +196,40 @@ mutations_and_gc_statement() { run dolt sql -q 'select sum(i) from tbl;' [[ "$status" -eq 0 ]] || false [[ "$output" =~ "138075" ]] || false # i = 1 - 525, sum is 138075 +} + +@test "archive: can clone respiratory with mixed types" { + mkdir -p remote/.dolt + mkdir cloned + + # Copy the archive test repo to remote directory + cp -R $BATS_TEST_DIRNAME/archive-test-repo/* remote/.dolt + cd remote + + # Insert data (commits automatically), but don't gc/archive yet. Want to make sure we can still clone it. + dolt sql -q "$(insert_statement)" + + port=$( definePORT ) + + remotesrv --http-port $port --grpc-port $port --repo-mode & + remotesrv_pid=$! + [[ "$remotesrv_pid" -gt 0 ]] || false + + cd ../cloned + run dolt clone http://localhost:$port/test-org/test-repo repo1 + [ "$status" -eq 0 ] + cd repo1 + + # verify new data is there. + run dolt sql -q 'select sum(i) from tbl;' + [[ "$status" -eq 0 ]] || false + + [[ "$output" =~ "151525" ]] || false # i = 1 - 550, sum is 151525 +} - teardown_common kill $remotesrv_pid wait $remotesrv_pid || : remotesrv_pid="" } + From 2d578558456431d74f45ce5beec5755e8cc06d28 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Tue, 4 Feb 2025 10:17:04 -0800 Subject: [PATCH 09/13] First steel thread for archive fetch. --- .../doltcore/remotestorage/chunk_fetcher.go | 142 +++++++++++++++++- .../doltcore/remotestorage/chunk_store.go | 88 +++++++++-- .../remotestorage/internal/ranges/ranges.go | 17 ++- go/store/datas/pull/puller.go | 11 +- go/store/nbs/archive.go | 2 +- go/store/nbs/archive_build.go | 2 +- go/store/nbs/archive_chunk_source.go | 2 +- integration-tests/bats/archive.bats | 54 +++++-- 8 files changed, 278 insertions(+), 40 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 91c7ef835f0..24bf2d5bab7 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -18,9 +18,12 @@ import ( "context" "errors" "io" + "sync" "sync/atomic" "time" + "github.com/cenkalti/backoff/v4" + "github.com/dolthub/gozstd" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -57,8 +60,9 @@ type ChunkFetcher struct { // buy having a Hash, but are empty. NM4. resCh chan nbs.ToChunker - abortCh chan struct{} - stats StatsRecorder + abortCh chan struct{} + stats StatsRecorder + dictCache *DictionaryCache } const ( @@ -68,7 +72,14 @@ const ( reliableCallDeliverRespTimeout = 15 * time.Second ) +var globalDictCache *DictionaryCache +var once sync.Once + func NewChunkFetcher(ctx context.Context, dcs *DoltChunkStore) *ChunkFetcher { + once.Do(func() { + globalDictCache = NewDictionaryCache(newDownloads(), dcs.csClient) + }) + eg, ctx := errgroup.WithContext(ctx) ret := &ChunkFetcher{ eg: eg, @@ -370,19 +381,27 @@ func (d downloads) Add(resp *remotesapi.DownloadLoc) { d.refreshes[path] = refresh } for _, r := range gr.Ranges { - // NM4 - this is where the offset is read!! do something here or nearby. - d.ranges.Insert(gr.Url, r.Hash, r.Offset, r.Length) + // NM4 - Split at this point? Break the dictionary into its own request. + d.ranges.Insert(gr.Url, r.Hash[:], r.Offset, r.Length, r.DictionaryOffset, r.DictionaryLength) + // if r.DictionaryLength == 0 { + // // NM4 - maybe invert the hash, and add it to a set of..... not sure. + // d.ranges.Insert(gr.Url, r.Hash, r.DictionaryOffset, r.DictionaryLength) + // } } } +// NM4 - On the client side, we only request HttpRanges for raw bytes. The struct includes the dictionary offset and length, +// but those only make sense in the response of DownloadLocations. func toGetRange(rs []*ranges.GetRange) *GetRange { ret := new(GetRange) for _, r := range rs { ret.Url = r.Url ret.Ranges = append(ret.Ranges, &remotesapi.RangeChunk{ - Hash: r.Hash, - Offset: r.Offset, - Length: r.Length, + Hash: r.Hash, + Offset: r.Offset, + Length: r.Length, + DictionaryOffset: r.DictionaryOffset, + DictionaryLength: r.DictionaryLength, }) } return ret @@ -596,3 +615,112 @@ func fetcherDownloadURLThread(ctx context.Context, fetchReqCh chan fetchReq, don } } } + +/////// + +type DictionaryKey struct { + url string + off uint64 + len uint32 +} + +type DictionaryCache struct { + mu sync.Mutex + cache map[DictionaryKey]*gozstd.DDict + client remotesapi.ChunkStoreServiceClient + dlds downloads +} + +func NewDictionaryCache(downloads downloads, client remotesapi.ChunkStoreServiceClient) *DictionaryCache { + return &DictionaryCache{ + mu: sync.Mutex{}, + cache: make(map[DictionaryKey]*gozstd.DDict), + client: client, + dlds: downloads, + } +} + +func (dc *DictionaryCache) Get(rang *GetRange, idx int, stats StatsRecorder, recorder reliable.HealthRecorder) (*gozstd.DDict, error) { + // Way too granular... but I'll use a real cache for production. prototype maddddddneeesssss + dc.mu.Lock() + defer dc.mu.Unlock() + + path := rang.ResourcePath() + off := rang.Ranges[idx].DictionaryOffset + ln := rang.Ranges[idx].DictionaryLength + + key := DictionaryKey{path, off, ln} + if v, ok := dc.cache[key]; ok { + return v, nil + } else { + + pathToUrl := dc.dlds.refreshes[path] + if pathToUrl == nil { + // Kinda do what Add does.... + refresh := new(locationRefresh) + + sRang := &remotesapi.HttpGetRange{} + sRang.Url = rang.Url + sRang.Ranges = append(sRang.Ranges, &remotesapi.RangeChunk{Offset: off, Length: ln}) + rang := &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: sRang} + dl := &remotesapi.DownloadLoc{Location: rang} + + refresh.Add(dl) + dc.dlds.refreshes[path] = refresh + + pathToUrl = refresh + } + + ctx := context.Background() + fetcher := globalHttpFetcher + + urlF := func(lastError error) (string, error) { + earl, err := pathToUrl.GetURL(ctx, lastError, dc.client) + if err != nil { + return "", err + } + if earl == "" { + earl = path + } + return earl, nil + } + + resp := reliable.StreamingRangeDownload(ctx, reliable.StreamingRangeRequest{ + Fetcher: fetcher, + Offset: off, + Length: uint64(ln), + UrlFact: urlF, + Stats: stats, + Health: recorder, + BackOffFact: func(ctx context.Context) backoff.BackOff { + return downloadBackOff(ctx, 3) // params.DownloadRetryCount) + }, + Throughput: reliable.MinimumThroughputCheck{ + CheckInterval: defaultRequestParams.ThroughputMinimumCheckInterval, + BytesPerCheck: defaultRequestParams.ThroughputMinimumBytesPerCheck, + NumIntervals: defaultRequestParams.ThroughputMinimumNumIntervals, + }, + RespHeadersTimeout: defaultRequestParams.RespHeadersTimeout, + }) + defer resp.Close() + + buf := make([]byte, ln) + _, err := io.ReadFull(resp.Body, buf) + if err != nil { + return nil, err + } + + rawDict, err := gozstd.Decompress(nil, buf) + if err != nil { + return nil, err + } + + dict, err := gozstd.NewDDict(rawDict) + if err != nil { + return nil, err + } + + dc.cache[key] = dict + return dict, nil + } +} diff --git a/go/libraries/doltcore/remotestorage/chunk_store.go b/go/libraries/doltcore/remotestorage/chunk_store.go index 72241b2d312..777febd4f54 100644 --- a/go/libraries/doltcore/remotestorage/chunk_store.go +++ b/go/libraries/doltcore/remotestorage/chunk_store.go @@ -32,6 +32,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "github.com/dolthub/gozstd" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -371,6 +372,7 @@ func (dcs *DoltChunkStore) GetManyCompressed(ctx context.Context, hashes hash.Ha return nil } +// NM4 - Extending the protobuf isn't not really necesary. Possible split this out into a new struct. type GetRange remotesapi.HttpGetRange func (gr *GetRange) ResourcePath() string { @@ -436,6 +438,7 @@ func (gr *GetRange) GetDownloadFunc(ctx context.Context, stats StatsRecorder, he if len(gr.Ranges) == 0 { return func() error { return nil } } + return func() error { urlF := func(lastError error) (string, error) { url, err := pathToUrl(ctx, lastError, gr.ResourcePath()) @@ -466,9 +469,9 @@ func (gr *GetRange) GetDownloadFunc(ctx context.Context, stats StatsRecorder, he RespHeadersTimeout: params.RespHeadersTimeout, }) defer resp.Close() - reader := &RangeChunkReader{GetRange: gr, Reader: resp.Body} + reader := &RangeChunkReader{Path: gr.ResourcePath(), GetRange: gr, Reader: resp.Body} for { - cc, err := reader.ReadChunk() + cc, err := reader.ReadChunk(stats, health) if errors.Is(err, io.EOF) { return nil } @@ -484,14 +487,59 @@ func (gr *GetRange) GetDownloadFunc(ctx context.Context, stats StatsRecorder, he } } +type ArchiveToChunker struct { + h hash.Hash + dictionary *gozstd.DDict + chunkData []byte +} + +func (a ArchiveToChunker) Hash() hash.Hash { + return a.h +} + +func (a ArchiveToChunker) ToChunk() (chunks.Chunk, error) { + dict := a.dictionary + data := a.chunkData + rawChunk, err := gozstd.DecompressDict(nil, data, dict) + // NM4 - calculate chunk addr for safety while testing. + newChunk := chunks.NewChunk(rawChunk) + + if newChunk.Hash() != a.h { + panic("Hash Mismatch!!") + } + + return newChunk, err + +} + +func (a ArchiveToChunker) FullCompressedChunkLen() uint32 { + //TODO Not sure what the right impl for this is.... NM4. + return uint32(len(a.chunkData)) // + dictionary??? +} + +func (a ArchiveToChunker) IsEmpty() bool { + //TODO implement me + return len(a.chunkData) == 0 +} + +func (a ArchiveToChunker) IsGhost() bool { + //TODO implement me + // NM4 - yes, need to. Or maybe not???? + return false +} + +var _ nbs.ToChunker = (*ArchiveToChunker)(nil) + type RangeChunkReader struct { + Path string GetRange *GetRange Reader io.Reader i int skip int } -func (r *RangeChunkReader) ReadChunk() (nbs.CompressedChunk, error) { +// NM4 - THis is the place where we need to intercept responses and conjour the "full" chunk. +func (r *RangeChunkReader) ReadChunk(stats StatsRecorder, health reliable.HealthRecorder) (nbs.ToChunker, error) { if r.skip > 0 { _, err := io.CopyN(io.Discard, r.Reader, int64(r.skip)) if err != nil { @@ -499,21 +547,41 @@ func (r *RangeChunkReader) ReadChunk() (nbs.CompressedChunk, error) { } r.skip = 0 } - if r.i >= len(r.GetRange.Ranges) { + + idx := r.i + r.i += 1 + + if idx >= len(r.GetRange.Ranges) { return nbs.CompressedChunk{}, io.EOF } - if r.i < len(r.GetRange.Ranges)-1 { - r.skip = int(r.GetRange.GapBetween(r.i, r.i+1)) + if idx < len(r.GetRange.Ranges)-1 { + r.skip = int(r.GetRange.GapBetween(idx, idx+1)) } - l := r.GetRange.Ranges[r.i].Length - h := hash.New(r.GetRange.Ranges[r.i].Hash) - r.i += 1 + + rang := r.GetRange.Ranges[idx] + l := rang.Length + h := hash.New(rang.Hash) + + if strings.HasPrefix(h.String(), "eh9e0b3ou") { + _ = h.String() + } + buf := make([]byte, l) _, err := io.ReadFull(r.Reader, buf) if err != nil { return nbs.CompressedChunk{}, err } else { - return nbs.NewCompressedChunk(h, buf) + if rang.DictionaryLength == 0 { + // NOMS snappy compressed chunk. + return nbs.NewCompressedChunk(h, buf) + } else { + dict, err := globalDictCache.Get(r.GetRange, idx, stats, health) + if err != nil { + return nbs.CompressedChunk{}, err + } + + return ArchiveToChunker{h: h, dictionary: dict, chunkData: buf}, nil + } } } diff --git a/go/libraries/doltcore/remotestorage/internal/ranges/ranges.go b/go/libraries/doltcore/remotestorage/internal/ranges/ranges.go index de1600bd0a4..9e3ebe16236 100644 --- a/go/libraries/doltcore/remotestorage/internal/ranges/ranges.go +++ b/go/libraries/doltcore/remotestorage/internal/ranges/ranges.go @@ -33,6 +33,11 @@ type GetRange struct { Offset uint64 Length uint32 Region *Region + + // Archive file format requires the url/dictionary offset/length to be carried through to fully resolve the chunk. + // This information is not used withing the range calculations at all, as the range is not related to the chunk content. + DictionaryOffset uint64 + DictionaryLength uint32 } // A |Region| represents a continuous range of bytes within in a Url. @@ -145,12 +150,14 @@ func (t *Tree) Len() int { return t.t.Len() } -func (t *Tree) Insert(url string, hash []byte, offset uint64, length uint32) { +func (t *Tree) Insert(url string, hash []byte, offset uint64, length uint32, dictOffset uint64, dictLength uint32) { ins := &GetRange{ - Url: t.intern(url), - Hash: hash, - Offset: offset, - Length: length, + Url: t.intern(url), + Hash: hash, + Offset: offset, + Length: length, + DictionaryOffset: dictOffset, + DictionaryLength: dictLength, } t.t.ReplaceOrInsert(ins) diff --git a/go/store/datas/pull/puller.go b/go/store/datas/pull/puller.go index 3e6a277c3e6..53beec0adbd 100644 --- a/go/store/datas/pull/puller.go +++ b/go/store/datas/pull/puller.go @@ -28,6 +28,7 @@ import ( "sync/atomic" "time" + "github.com/dolthub/dolt/go/libraries/doltcore/remotestorage" "golang.org/x/sync/errgroup" "github.com/dolthub/dolt/go/libraries/doltcore/dconfig" @@ -371,8 +372,14 @@ func (p *Puller) Pull(ctx context.Context) error { if err != nil { return err } - } else { - panic("TODO: handle ZStd-CompressedChunk") // NM4. + } else if _, ok := cChk.(remotestorage.ArchiveToChunker); ok { + // NM4 - Until we can write quickly to archives..... + cc := nbs.ChunkToCompressedChunk(chnk) + + err = p.wr.AddCompressedChunk(ctx, cc) + if err != nil { + return err + } } } }) diff --git a/go/store/nbs/archive.go b/go/store/nbs/archive.go index 650b45a0c96..65c4f3e6e11 100644 --- a/go/store/nbs/archive.go +++ b/go/store/nbs/archive.go @@ -27,7 +27,7 @@ Chunks from the Archive. ByteSpans are arbitrary offset/lengths into the file which store (1) zstd dictionary data, and (2) compressed chunk data. Each Chunk is stored as a pair of ByteSpans (dict,data). Dictionary ByteSpans can (should) be used by multiple Chunks, so there are more ByteSpans than Chunks. The Index is used to map Chunks to ByteSpan pairs. These pairs are -called ChunkRefs, and were store them as [uint32,uint32] on disk. This allows us to quickly find the ByteSpans for a +called ChunkRefs, and we store them as [uint32,uint32] on disk. This allows us to quickly find the ByteSpans for a given Chunk with minimal processing at load time. A Dolt Archive file follows the following format: diff --git a/go/store/nbs/archive_build.go b/go/store/nbs/archive_build.go index 071521b8c74..8a8ecf58737 100644 --- a/go/store/nbs/archive_build.go +++ b/go/store/nbs/archive_build.go @@ -57,7 +57,7 @@ func UnArchive(ctx context.Context, cs chunks.ChunkStore, smd StorageMetadata, p return err } if exists { - // We have a fast path to follow because oritinal table file is still on disk. + // We have a fast path to follow because original table file is still on disk. swapMap[arc.hash()] = orginTfId } else { // We don't have the original table file id, so we have to create a new one. diff --git a/go/store/nbs/archive_chunk_source.go b/go/store/nbs/archive_chunk_source.go index ebf1a854156..8ef6f0d710f 100644 --- a/go/store/nbs/archive_chunk_source.go +++ b/go/store/nbs/archive_chunk_source.go @@ -196,7 +196,7 @@ func (acs archiveChunkSource) getRecordRanges(_ context.Context, requests []getR result[hAddr] = rng } } - return result, gcBehavior_Block, nil // NM4 - FIXME. Merging. This is wrong. Use the keeperF + return result, gcBehavior_Continue, nil // NM4 - FIXME. Merging. This is wrong. Use the keeperF } func (acs archiveChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) { diff --git a/integration-tests/bats/archive.bats b/integration-tests/bats/archive.bats index 90ced9566cf..32100da5d30 100755 --- a/integration-tests/bats/archive.bats +++ b/integration-tests/bats/archive.bats @@ -115,19 +115,6 @@ mutations_and_gc_statement() { [ "$files" -eq "2" ] } -@test "archive: archive with remotesrv no go" { - dolt sql -q "$(mutations_and_gc_statement)" - dolt archive - - run dolt sql-server --remotesapi-port=12321 - [ "$status" -eq 1 ] - [[ "$output" =~ "archive files present" ]] || false - - run remotesrv --repo-mode - [ "$status" -eq 1 ] - [[ "$output" =~ "archive files present" ]] || false -} - @test "archive: archive --revert (fast)" { dolt sql -q "$(mutations_and_gc_statement)" dolt archive @@ -227,9 +214,50 @@ mutations_and_gc_statement() { [[ "$output" =~ "151525" ]] || false # i = 1 - 550, sum is 151525 } +@test "archive: can fetch chunks from an archived repo" { + mkdir -p remote/.dolt + mkdir cloned + + # Copy the archive test repo to remote directory + cp -R $BATS_TEST_DIRNAME/archive-test-repo/* remote/.dolt + cd remote + + port=$( definePORT ) + + remotesrv --http-port $port --grpc-port $port --repo-mode & + remotesrv_pid=$! + [[ "$remotesrv_pid" -gt 0 ]] || false + + cd ../cloned + dolt clone http://localhost:$port/test-org/test-repo repo1 + # Fetch when there are no changes. + cd repo1 + dolt fetch + + ## update the remote repo directly. Need to run the archive command when the server is stopped. + ## This will result in achived files on the remote, which we will need to read chunks from when we fetch. + cd ../../remote kill $remotesrv_pid wait $remotesrv_pid || : remotesrv_pid="" + dolt sql -q "$(mutations_and_gc_statement)" + dolt archive + + remotesrv --http-port $port --grpc-port $port --repo-mode & + remotesrv_pid=$! + [[ "$remotesrv_pid" -gt 0 ]] || false + + cd ../cloned/repo1 + + run dolt fetch + [ "$status" -eq 0 ] + + run dolt status + [ "$status" -eq 0 ] + + [[ "$output" =~ "Your branch is behind 'origin/main' by 20 commits, and can be fast-forwarded" ]] || false + # Verify the repo has integrity. + dolt fsck } From 3abab624853980fb09d4f3f540bfa0c462c0311a Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 5 Feb 2025 12:59:52 -0800 Subject: [PATCH 10/13] go tests build fixes --- .../internal/ranges/ranges_test.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/internal/ranges/ranges_test.go b/go/libraries/doltcore/remotestorage/internal/ranges/ranges_test.go index 9ec6b04e338..1f36ec296fb 100644 --- a/go/libraries/doltcore/remotestorage/internal/ranges/ranges_test.go +++ b/go/libraries/doltcore/remotestorage/internal/ranges/ranges_test.go @@ -77,11 +77,11 @@ func TestTree(t *testing.T) { tree := NewTree(8 * 1024) // Insert 1KB ranges every 16 KB. for i, j := 0, 0; i < 16; i, j = i+1, j+16*1024 { - tree.Insert("A", []byte{}, uint64(j), 1024) + tree.Insert("A", []byte{}, uint64(j), 1024, 0, 0) } // Insert 1KB ranges every 16 KB, offset by 8KB. for i := 15*16*1024 + 8*1024; i >= 0; i -= 16 * 1024 { - tree.Insert("A", []byte{}, uint64(i), 1024) + tree.Insert("A", []byte{}, uint64(i), 1024, 0, 0) } assertTree(t, tree) }) @@ -89,11 +89,11 @@ func TestTree(t *testing.T) { tree := NewTree(8 * 1024) // Insert 1KB ranges every 16 KB, offset by 8KB. for i := 15*16*1024 + 8*1024; i >= 0; i -= 16 * 1024 { - tree.Insert("A", []byte{}, uint64(i), 1024) + tree.Insert("A", []byte{}, uint64(i), 1024, 0, 0) } // Insert 1KB ranges every 16 KB. for i, j := 0, 0; i < 16; i, j = i+1, j+16*1024 { - tree.Insert("A", []byte{}, uint64(j), 1024) + tree.Insert("A", []byte{}, uint64(j), 1024, 0, 0) } assertTree(t, tree) }) @@ -111,7 +111,7 @@ func TestTree(t *testing.T) { }) tree := NewTree(8 * 1024) for _, offset := range entries { - tree.Insert("A", []byte{}, offset, 1024) + tree.Insert("A", []byte{}, offset, 1024, 0, 0) } assertTree(t, tree) } @@ -126,7 +126,7 @@ func TestTree(t *testing.T) { "B", "A", "9", "8", } for i, j := 0, 0; i < 16; i, j = i+1, j+1024 { - tree.Insert(files[i], []byte{}, uint64(j), 1024) + tree.Insert(files[i], []byte{}, uint64(j), 1024, 0, 0) } assert.Equal(t, 16, tree.regions.Len()) assert.Equal(t, 16, tree.t.Len()) @@ -134,17 +134,17 @@ func TestTree(t *testing.T) { t.Run("MergeInMiddle", func(t *testing.T) { tree := NewTree(8 * 1024) // 1KB chunk at byte 0 - tree.Insert("A", []byte{}, 0, 1024) + tree.Insert("A", []byte{}, 0, 1024, 0, 0) // 1KB chunk at byte 16KB - tree.Insert("A", []byte{}, 16384, 1024) + tree.Insert("A", []byte{}, 16384, 1024, 0, 0) assert.Equal(t, 2, tree.regions.Len()) assert.Equal(t, 2, tree.t.Len()) // 1KB chunk at byte 8KB - tree.Insert("A", []byte{}, 8192, 1024) + tree.Insert("A", []byte{}, 8192, 1024, 0, 0) assert.Equal(t, 1, tree.regions.Len()) assert.Equal(t, 3, tree.t.Len()) - tree.Insert("A", []byte{}, 4096, 1024) - tree.Insert("A", []byte{}, 12228, 1024) + tree.Insert("A", []byte{}, 4096, 1024, 0, 0) + tree.Insert("A", []byte{}, 12228, 1024, 0, 0) assert.Equal(t, 1, tree.regions.Len()) assert.Equal(t, 5, tree.t.Len()) e, _ := tree.t.Min() @@ -184,7 +184,7 @@ func TestTree(t *testing.T) { t.Run("InsertAscending", func(t *testing.T) { tree := NewTree(4 * 1024) for _, e := range entries { - tree.Insert(e.url, []byte{e.id}, e.offset, e.length) + tree.Insert(e.url, []byte{e.id}, e.offset, e.length, 0, 0) } assertTree(t, tree) }) @@ -192,7 +192,7 @@ func TestTree(t *testing.T) { tree := NewTree(4 * 1024) for i := len(entries) - 1; i >= 0; i-- { e := entries[i] - tree.Insert(e.url, []byte{e.id}, e.offset, e.length) + tree.Insert(e.url, []byte{e.id}, e.offset, e.length, 0, 0) } assertTree(t, tree) }) @@ -205,7 +205,7 @@ func TestTree(t *testing.T) { }) tree := NewTree(4 * 1024) for _, e := range entries { - tree.Insert(e.url, []byte{e.id}, e.offset, e.length) + tree.Insert(e.url, []byte{e.id}, e.offset, e.length, 0, 0) } assertTree(t, tree) } From 35cca9275166547e6f94701b25bc6ee0d01d4f0b Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 5 Feb 2025 13:38:22 -0800 Subject: [PATCH 11/13] Fixup --- go/libraries/doltcore/remotestorage/chunk_fetcher.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 24bf2d5bab7..97efc5eb4e4 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -60,9 +60,8 @@ type ChunkFetcher struct { // buy having a Hash, but are empty. NM4. resCh chan nbs.ToChunker - abortCh chan struct{} - stats StatsRecorder - dictCache *DictionaryCache + abortCh chan struct{} + stats StatsRecorder } const ( From 0ca8cf88bd4c328763aa4a64dc1cbd168170412b Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 5 Feb 2025 15:55:07 -0800 Subject: [PATCH 12/13] Use better dictionary cache --- .../doltcore/remotestorage/chunk_fetcher.go | 193 ++++++++++-------- .../doltcore/remotestorage/chunk_store.go | 13 +- go/store/nbs/store.go | 3 +- 3 files changed, 110 insertions(+), 99 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index 97efc5eb4e4..a2c550f364b 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -33,6 +33,8 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/remotestorage/internal/reliable" "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/dolt/go/store/nbs" + + "github.com/hashicorp/golang-lru/v2" ) // A remotestorage.ChunkFetcher is a pipelined chunk fetcher for fetching a @@ -52,12 +54,12 @@ type ChunkFetcher struct { egCtx context.Context // toGetCh is the channel used to request chunks. This will be initially given a root, - // and as refs are found, they will be added to the channel for workers to batch and request. NM4. + // and as refs are found, they will be added to the channel for workers to batch and request. toGetCh chan hash.HashSet // resCh is the results channel for the fetcher. It is used both to return // chunks themselves, and to indicate which chunks were requested but missing - // buy having a Hash, but are empty. NM4. + // by having a Hash, but are empty. resCh chan nbs.ToChunker abortCh chan struct{} @@ -271,7 +273,6 @@ func fetcherRPCDownloadLocsThread(ctx context.Context, reqCh chan *remotesapi.Ge }) eg.Go(func() error { for { - // NM4 - Where there responses come back - resp is an rpc struct. NM4. resp, err := stream.Recv() if err == io.EOF { close(resCh) @@ -317,7 +318,6 @@ func getMissingChunks(req *remotesapi.GetDownloadLocsRequest, resp *remotesapi.G numRequested := len(req.ChunkHashes) numResponded := 0 for _, loc := range resp.Locs { - // NM4 - Looky here. hgr := loc.Location.(*remotesapi.DownloadLoc_HttpGetRange).HttpGetRange numResponded += len(hgr.Ranges) } @@ -380,17 +380,10 @@ func (d downloads) Add(resp *remotesapi.DownloadLoc) { d.refreshes[path] = refresh } for _, r := range gr.Ranges { - // NM4 - Split at this point? Break the dictionary into its own request. d.ranges.Insert(gr.Url, r.Hash[:], r.Offset, r.Length, r.DictionaryOffset, r.DictionaryLength) - // if r.DictionaryLength == 0 { - // // NM4 - maybe invert the hash, and add it to a set of..... not sure. - // d.ranges.Insert(gr.Url, r.Hash, r.DictionaryOffset, r.DictionaryLength) - // } } } -// NM4 - On the client side, we only request HttpRanges for raw bytes. The struct includes the dictionary offset and length, -// but those only make sense in the response of DownloadLocations. func toGetRange(rs []*ranges.GetRange) *GetRange { ret := new(GetRange) for _, r := range rs { @@ -615,111 +608,137 @@ func fetcherDownloadURLThread(ctx context.Context, fetchReqCh chan fetchReq, don } } -/////// - -type DictionaryKey struct { - url string - off uint64 - len uint32 +type DictionaryCache struct { + cache *lru.TwoQueueCache[DictionaryKey, *gozstd.DDict] + pending sync.Map // DictionaryKey -> chan struct{} + client remotesapi.ChunkStoreServiceClient + dlds downloads } -type DictionaryCache struct { - mu sync.Mutex - cache map[DictionaryKey]*gozstd.DDict - client remotesapi.ChunkStoreServiceClient - dlds downloads +type DictionaryKey struct { + path string + off uint64 + len uint32 } func NewDictionaryCache(downloads downloads, client remotesapi.ChunkStoreServiceClient) *DictionaryCache { + c, err := lru.New2Q[DictionaryKey, *gozstd.DDict](1024) + if err != nil { + panic(err) + } + return &DictionaryCache{ - mu: sync.Mutex{}, - cache: make(map[DictionaryKey]*gozstd.DDict), + cache: c, client: client, dlds: downloads, } } func (dc *DictionaryCache) Get(rang *GetRange, idx int, stats StatsRecorder, recorder reliable.HealthRecorder) (*gozstd.DDict, error) { - // Way too granular... but I'll use a real cache for production. prototype maddddddneeesssss - dc.mu.Lock() - defer dc.mu.Unlock() - path := rang.ResourcePath() off := rang.Ranges[idx].DictionaryOffset ln := rang.Ranges[idx].DictionaryLength key := DictionaryKey{path, off, ln} - if v, ok := dc.cache[key]; ok { - return v, nil - } else { + if dict, ok := dc.cache.Get(key); ok { + return dict, nil + } - pathToUrl := dc.dlds.refreshes[path] - if pathToUrl == nil { - // Kinda do what Add does.... - refresh := new(locationRefresh) + // Check for an in-flight request. Default dictionary will be requested many times, so we want to avoid + // making multiple requests for the same resource. + if ch, loaded := dc.pending.LoadOrStore(key, make(chan struct{})); loaded { + // There's an ongoing fetch, wait for its completion + <-ch.(chan struct{}) + if dict, ok := dc.cache.Get(key); ok { + return dict, nil + } + return nil, errors.New("failed to fetch dictionary due to in-flight request") + } + // When update is done, regardless of success or failure, we need to unblock anyone waiting. + defer func() { + if ch, found := dc.pending.LoadAndDelete(key); found { + close(ch.(chan *gozstd.DDict)) + } + }() - sRang := &remotesapi.HttpGetRange{} - sRang.Url = rang.Url - sRang.Ranges = append(sRang.Ranges, &remotesapi.RangeChunk{Offset: off, Length: ln}) - rang := &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: sRang} - dl := &remotesapi.DownloadLoc{Location: rang} + // Fetch the dictionary + ddict, err := dc.fetchDictionary(rang, idx, stats, recorder) + if err != nil { + return nil, err + } - refresh.Add(dl) - dc.dlds.refreshes[path] = refresh + // Store the dictionary in the cache + dc.cache.Add(key, ddict) - pathToUrl = refresh - } + return ddict, nil +} - ctx := context.Background() - fetcher := globalHttpFetcher +func (dc *DictionaryCache) fetchDictionary(rang *GetRange, idx int, stats StatsRecorder, recorder reliable.HealthRecorder) (*gozstd.DDict, error) { + path := rang.ResourcePath() + off := rang.Ranges[idx].DictionaryOffset + ln := rang.Ranges[idx].DictionaryLength - urlF := func(lastError error) (string, error) { - earl, err := pathToUrl.GetURL(ctx, lastError, dc.client) - if err != nil { - return "", err - } - if earl == "" { - earl = path - } - return earl, nil - } + ctx := context.Background() + pathToUrl := dc.dlds.refreshes[path] + if pathToUrl == nil { + // Kinda do what Add does.... + refresh := new(locationRefresh) - resp := reliable.StreamingRangeDownload(ctx, reliable.StreamingRangeRequest{ - Fetcher: fetcher, - Offset: off, - Length: uint64(ln), - UrlFact: urlF, - Stats: stats, - Health: recorder, - BackOffFact: func(ctx context.Context) backoff.BackOff { - return downloadBackOff(ctx, 3) // params.DownloadRetryCount) - }, - Throughput: reliable.MinimumThroughputCheck{ - CheckInterval: defaultRequestParams.ThroughputMinimumCheckInterval, - BytesPerCheck: defaultRequestParams.ThroughputMinimumBytesPerCheck, - NumIntervals: defaultRequestParams.ThroughputMinimumNumIntervals, - }, - RespHeadersTimeout: defaultRequestParams.RespHeadersTimeout, - }) - defer resp.Close() + sRang := &remotesapi.HttpGetRange{} + sRang.Url = rang.Url + sRang.Ranges = append(sRang.Ranges, &remotesapi.RangeChunk{Offset: off, Length: ln}) + rang := &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: sRang} + dl := &remotesapi.DownloadLoc{Location: rang} - buf := make([]byte, ln) - _, err := io.ReadFull(resp.Body, buf) - if err != nil { - return nil, err - } + refresh.Add(dl) + dc.dlds.refreshes[path] = refresh - rawDict, err := gozstd.Decompress(nil, buf) - if err != nil { - return nil, err - } + pathToUrl = refresh + } + + fetcher := globalHttpFetcher - dict, err := gozstd.NewDDict(rawDict) + urlF := func(lastError error) (string, error) { + earl, err := pathToUrl.GetURL(ctx, lastError, dc.client) if err != nil { - return nil, err + return "", err } + if earl == "" { + earl = path + } + return earl, nil + } - dc.cache[key] = dict - return dict, nil + resp := reliable.StreamingRangeDownload(ctx, reliable.StreamingRangeRequest{ + Fetcher: fetcher, + Offset: off, + Length: uint64(ln), + UrlFact: urlF, + Stats: stats, + Health: recorder, + BackOffFact: func(ctx context.Context) backoff.BackOff { + return downloadBackOff(ctx, 3) // params.DownloadRetryCount) + }, + Throughput: reliable.MinimumThroughputCheck{ + CheckInterval: defaultRequestParams.ThroughputMinimumCheckInterval, + BytesPerCheck: defaultRequestParams.ThroughputMinimumBytesPerCheck, + NumIntervals: defaultRequestParams.ThroughputMinimumNumIntervals, + }, + RespHeadersTimeout: defaultRequestParams.RespHeadersTimeout, + }) + defer resp.Close() + + buf := make([]byte, ln) + _, err := io.ReadFull(resp.Body, buf) + if err != nil { + return nil, err + } + + // Dictionaries are compressed with no dictionary. + rawDict, err := gozstd.Decompress(nil, buf) + if err != nil { + return nil, err } + + return gozstd.NewDDict(rawDict) } diff --git a/go/libraries/doltcore/remotestorage/chunk_store.go b/go/libraries/doltcore/remotestorage/chunk_store.go index 777febd4f54..0e18d5c9fcb 100644 --- a/go/libraries/doltcore/remotestorage/chunk_store.go +++ b/go/libraries/doltcore/remotestorage/chunk_store.go @@ -372,7 +372,8 @@ func (dcs *DoltChunkStore) GetManyCompressed(ctx context.Context, hashes hash.Ha return nil } -// NM4 - Extending the protobuf isn't not really necesary. Possible split this out into a new struct. +// GetRange is structurally the same as remotesapi.HttpGetRange, but with added functions. Instances of GetRange +// don't get sent over the wire, so it is not necessary to use the remotesapi, just convenient. type GetRange remotesapi.HttpGetRange func (gr *GetRange) ResourcePath() string { @@ -509,7 +510,6 @@ func (a ArchiveToChunker) ToChunk() (chunks.Chunk, error) { } return newChunk, err - } func (a ArchiveToChunker) FullCompressedChunkLen() uint32 { @@ -523,8 +523,7 @@ func (a ArchiveToChunker) IsEmpty() bool { } func (a ArchiveToChunker) IsGhost() bool { - //TODO implement me - // NM4 - yes, need to. Or maybe not???? + // archives are never ghosts. They are only instantiated when the chunk is found. return false } @@ -538,7 +537,6 @@ type RangeChunkReader struct { skip int } -// NM4 - THis is the place where we need to intercept responses and conjour the "full" chunk. func (r *RangeChunkReader) ReadChunk(stats StatsRecorder, health reliable.HealthRecorder) (nbs.ToChunker, error) { if r.skip > 0 { _, err := io.CopyN(io.Discard, r.Reader, int64(r.skip)) @@ -562,17 +560,12 @@ func (r *RangeChunkReader) ReadChunk(stats StatsRecorder, health reliable.Health l := rang.Length h := hash.New(rang.Hash) - if strings.HasPrefix(h.String(), "eh9e0b3ou") { - _ = h.String() - } - buf := make([]byte, l) _, err := io.ReadFull(r.Reader, buf) if err != nil { return nbs.CompressedChunk{}, err } else { if rang.DictionaryLength == 0 { - // NOMS snappy compressed chunk. return nbs.NewCompressedChunk(h, buf) } else { dict, err := globalDictCache.Get(r.GetRange, idx, stats, health) diff --git a/go/store/nbs/store.go b/go/store/nbs/store.go index 1ce515d8fb1..e0c43e15106 100644 --- a/go/store/nbs/store.go +++ b/go/store/nbs/store.go @@ -162,7 +162,7 @@ func (nbs *NomsBlockStore) GetChunkLocationsWithPaths(ctx context.Context, hashe if err != nil { return nil, err } - toret := make(map[string]map[hash.Hash]Range, len(locs)) // NM4 + toret := make(map[string]map[hash.Hash]Range, len(locs)) for k, v := range locs { toret[k] = v } @@ -469,7 +469,6 @@ func OverwriteStoreManifest(ctx context.Context, store *NomsBlockStore, root has } // Appendix table files should come first in specs for h, c := range appendixTableFiles { - // NM4 - not sure on this one.... s := tableSpec{fileType: typeNoms, hash: h, chunkCount: c} contents.appendix = append(contents.appendix, s) contents.specs = append(contents.specs, s) From 1601fe146c9e4dc18b0be452fdf90e8feeed1a7a Mon Sep 17 00:00:00 2001 From: macneale4 Date: Thu, 6 Feb 2025 00:04:02 +0000 Subject: [PATCH 13/13] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/remotestorage/chunk_fetcher.go | 3 +-- go/store/datas/pull/puller.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/go/libraries/doltcore/remotestorage/chunk_fetcher.go b/go/libraries/doltcore/remotestorage/chunk_fetcher.go index a2c550f364b..855c0218f0e 100644 --- a/go/libraries/doltcore/remotestorage/chunk_fetcher.go +++ b/go/libraries/doltcore/remotestorage/chunk_fetcher.go @@ -24,6 +24,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/dolthub/gozstd" + lru "github.com/hashicorp/golang-lru/v2" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -33,8 +34,6 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/remotestorage/internal/reliable" "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/dolt/go/store/nbs" - - "github.com/hashicorp/golang-lru/v2" ) // A remotestorage.ChunkFetcher is a pipelined chunk fetcher for fetching a diff --git a/go/store/datas/pull/puller.go b/go/store/datas/pull/puller.go index 53beec0adbd..826c4ba7a0e 100644 --- a/go/store/datas/pull/puller.go +++ b/go/store/datas/pull/puller.go @@ -28,10 +28,10 @@ import ( "sync/atomic" "time" - "github.com/dolthub/dolt/go/libraries/doltcore/remotestorage" "golang.org/x/sync/errgroup" "github.com/dolthub/dolt/go/libraries/doltcore/dconfig" + "github.com/dolthub/dolt/go/libraries/doltcore/remotestorage" "github.com/dolthub/dolt/go/store/chunks" "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/dolt/go/store/nbs"