Esempio n. 1
0
    /// <summary>
    /// Add tablet locations to the cache. Already known tablet locations will
    /// have their entry updated and expiration extended.
    /// </summary>
    /// <param name="tablets">The discovered tablets to cache.</param>
    /// <param name="requestPartitionKey">The lookup partition key.</param>
    /// <param name="requestedBatchSize">
    /// The number of tablet locations requested from the master in the
    /// original request.
    /// </param>
    /// <param name="ttl">
    /// The time in milliseconds that the tablets may be cached for.
    /// </param>
    public void CacheTabletLocations(
        List <RemoteTablet> tablets,
        ReadOnlySpan <byte> requestPartitionKey,
        int requestedBatchSize,
        long ttl)
    {
        long expiration = _systemClock.CurrentMilliseconds + ttl;
        var  newEntries = new List <TableLocationEntry>();

        if (tablets.Count == 0)
        {
            // If there are no tablets in the response, then the table is empty. If
            // there were any tablets in the table they would have been returned, since
            // the master guarantees that if the partition key falls in a non-covered
            // range, the previous tablet will be returned, and we did not set an upper
            // bound partition key on the request.

            newEntries.Add(TableLocationEntry.NewNonCoveredRange(
                               Array.Empty <byte>(),
                               Array.Empty <byte>(),
                               expiration));
        }
        else
        {
            // The comments below will reference the following diagram:
            //
            //   +---+   +---+---+
            //   |   |   |   |   |
            // A | B | C | D | E | F
            //   |   |   |   |   |
            //   +---+   +---+---+
            //
            // It depicts a tablet locations response from the master containing three
            // tablets: B, D and E. Three non-covered ranges are present: A, C, and F.
            // An RPC response containing B, D and E could occur if the lookup partition
            // key falls in A, B, or C, although the existence of A as an initial
            // non-covered range can only be inferred if the lookup partition key falls
            // in A.

            byte[] firstLowerBound = tablets[0].Partition.PartitionKeyStart;

            if (requestPartitionKey.SequenceCompareTo(firstLowerBound) < 0)
            {
                // If the first tablet is past the requested partition key, then the
                // partition key falls in an initial non-covered range, such as A.
                newEntries.Add(TableLocationEntry.NewNonCoveredRange(
                                   Array.Empty <byte>(), firstLowerBound, expiration));
            }

            // lastUpperBound tracks the upper bound of the previously processed
            // entry, so that we can determine when we have found a non-covered range.
            byte[] lastUpperBound = firstLowerBound;

            foreach (var tablet in tablets)
            {
                byte[] tabletLowerBound = tablet.Partition.PartitionKeyStart;
                byte[] tabletUpperBound = tablet.Partition.PartitionKeyEnd;

                if (lastUpperBound.SequenceCompareTo(tabletLowerBound) < 0)
                {
                    // There is a non-covered range between the previous tablet and this tablet.
                    // This will discover C while processing the tablet location for D.
                    newEntries.Add(TableLocationEntry.NewNonCoveredRange(
                                       lastUpperBound, tabletLowerBound, expiration));
                }

                lastUpperBound = tabletUpperBound;

                // Now add the tablet itself (such as B, D, or E).
                newEntries.Add(TableLocationEntry.NewCoveredRange(tablet, expiration));
            }

            if (lastUpperBound.Length > 0 &&
                tablets.Count < requestedBatchSize)
            {
                // There is a non-covered range between the last tablet and the end
                // of the partition key space, such as F.
                newEntries.Add(TableLocationEntry.NewNonCoveredRange(
                                   lastUpperBound, Array.Empty <byte>(), expiration));
            }
        }

        byte[] discoveredlowerBound = newEntries[0].LowerBoundPartitionKey;
        byte[] discoveredUpperBound = newEntries[newEntries.Count - 1].UpperBoundPartitionKey;

        _lock.EnterWriteLock();
        try
        {
            // Remove all existing overlapping entries, and add the new entries.
            TableLocationEntry?floorEntry = _cache.FloorEntry(discoveredlowerBound);
            if (floorEntry is not null &&
                requestPartitionKey.SequenceCompareTo(floorEntry.UpperBoundPartitionKey) < 0)
            {
                discoveredlowerBound = floorEntry.LowerBoundPartitionKey;
            }

            bool upperBoundActive = discoveredUpperBound.Length > 0;
            _cache.ClearRange(discoveredlowerBound, discoveredUpperBound, upperBoundActive);

            foreach (var entry in newEntries)
            {
                _cache.Insert(entry);
            }
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }