Example #1
0
  ImmutableSet<ListenableFuture<?>> index(List<Span> spans) {
    // First parse each span into partition keys used to support query requests
    Builder<PartitionKeyToTraceId, Long> parsed = ImmutableSetMultimap.builder();
    for (Span span : spans) {
      Long timestamp = guessTimestamp(span);
      if (timestamp == null) continue;
      for (String partitionKey : index.partitionKeys(span)) {
        parsed.put(
            new PartitionKeyToTraceId(index.table(), partitionKey, span.traceId),
            1000 * (timestamp / 1000)); // index precision is millis
      }
    }

    // The parsed results may include inserts that already occur, or are redundant as they don't
    // impact QueryRequest.endTs or QueryRequest.loopback. For example, a parsed timestamp could
    // be between timestamps of rows that already exist for a particular trace.
    ImmutableSetMultimap<PartitionKeyToTraceId, Long> maybeInsert = parsed.build();

    ImmutableSetMultimap<PartitionKeyToTraceId, Long> toInsert;
    if (sharedState == null) { // special-case when caching is disabled.
      toInsert = maybeInsert;
    } else {
      // Optimized results will be smaller when the input includes traces with local spans, or when
      // other threads indexed the same trace.
      toInsert = entriesThatIncreaseGap(sharedState, maybeInsert);

      if (maybeInsert.size() > toInsert.size() && LOG.isDebugEnabled()) {
        int delta = maybeInsert.size() - toInsert.size();
        LOG.debug("optimized out {}/{} inserts into {}", delta, maybeInsert.size(), index.table());
      }
    }

    // For each entry, insert a new row in the index table asynchronously
    ImmutableSet.Builder<ListenableFuture<?>> result = ImmutableSet.builder();
    for (Map.Entry<PartitionKeyToTraceId, Long> entry : toInsert.entries()) {
      BoundStatement bound =
          bindWithName(prepared, boundName)
              .setLong("trace_id", entry.getKey().traceId)
              .setBytesUnsafe("ts", timestampCodec.serialize(entry.getValue()));
      if (indexTtl != null) {
        bound.setInt("ttl_", indexTtl);
      }
      index.bindPartitionKey(bound, entry.getKey().partitionKey);
      result.add(session.executeAsync(bound));
    }
    return result.build();
  }
Example #2
0
  @VisibleForTesting
  static ImmutableSetMultimap<PartitionKeyToTraceId, Long> entriesThatIncreaseGap(
      ConcurrentMap<PartitionKeyToTraceId, Pair<Long>> sharedState,
      ImmutableSetMultimap<PartitionKeyToTraceId, Long> updates) {
    ImmutableSet.Builder<PartitionKeyToTraceId> toUpdate = ImmutableSet.builder();

    // Enter a loop that affects shared state when an update widens the time interval for a key.
    for (Map.Entry<PartitionKeyToTraceId, Long> input : updates.entries()) {
      PartitionKeyToTraceId key = input.getKey();
      long timestamp = input.getValue();
      for (; ; ) {
        Pair<Long> oldRange = sharedState.get(key);
        if (oldRange == null) {
          // Initial state is where this key has a single timestamp.
          oldRange = sharedState.putIfAbsent(key, Pair.create(timestamp, timestamp));

          // If there was no previous value, we need to update the index
          if (oldRange == null) {
            toUpdate.add(key);
            break;
          }
        }

        long first = timestamp < oldRange._1 ? timestamp : oldRange._1;
        long last = timestamp > oldRange._2 ? timestamp : oldRange._2;

        Pair<Long> newRange = Pair.create(first, last);
        if (oldRange.equals(newRange)) {
          break; // the current timestamp is contained
        } else if (sharedState.replace(key, oldRange, newRange)) {
          toUpdate.add(key); // The range was extended
          break;
        }
      }
    }

    // When the loop completes, we'll know one of our updates widened the interval of a trace, if
    // it is the first or last timestamp. By ignoring those between an existing interval, we can
    // end up with less Cassandra writes.
    Builder<PartitionKeyToTraceId, Long> result = ImmutableSetMultimap.builder();
    for (PartitionKeyToTraceId needsUpdate : toUpdate.build()) {
      Pair<Long> firstLast = sharedState.get(needsUpdate);
      if (updates.containsEntry(needsUpdate, firstLast._1)) result.put(needsUpdate, firstLast._1);
      if (updates.containsEntry(needsUpdate, firstLast._2)) result.put(needsUpdate, firstLast._2);
    }
    return result.build();
  }