|  | @@ -30,6 +30,7 @@ import org.elasticsearch.client.Client;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.logging.LoggerMessageFormat;
 | 
	
		
			
				|  |  |  import org.elasticsearch.core.Nullable;
 | 
	
		
			
				|  |  |  import org.elasticsearch.core.TimeValue;
 | 
	
		
			
				|  |  | +import org.elasticsearch.core.Tuple;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.engine.VersionConflictEngineException;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.mapper.MapperParsingException;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.reindex.BulkByScrollResponse;
 | 
	
	
		
			
				|  | @@ -59,6 +60,7 @@ import org.elasticsearch.xpack.transform.utils.ExceptionRootCauseFinder;
 | 
	
		
			
				|  |  |  import java.util.LinkedHashMap;
 | 
	
		
			
				|  |  |  import java.util.Map;
 | 
	
		
			
				|  |  |  import java.util.Map.Entry;
 | 
	
		
			
				|  |  | +import java.util.concurrent.ConcurrentHashMap;
 | 
	
		
			
				|  |  |  import java.util.concurrent.atomic.AtomicBoolean;
 | 
	
		
			
				|  |  |  import java.util.concurrent.atomic.AtomicReference;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -71,7 +73,7 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |      private final AtomicBoolean oldStatsCleanedUp = new AtomicBoolean(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private final AtomicReference<SeqNoPrimaryTermAndIndex> seqNoPrimaryTermAndIndex;
 | 
	
		
			
				|  |  | -    private volatile PointInTimeBuilder pit;
 | 
	
		
			
				|  |  | +    private final ConcurrentHashMap<String, PointInTimeBuilder> namedPits = new ConcurrentHashMap<>();
 | 
	
		
			
				|  |  |      private volatile long pitCheckpoint;
 | 
	
		
			
				|  |  |      private volatile boolean disablePit = false;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -250,11 +252,7 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      void doGetFieldMappings(ActionListener<Map<String, String>> fieldMappingsListener) {
 | 
	
		
			
				|  |  | -        SchemaUtil.getDestinationFieldMappings(
 | 
	
		
			
				|  |  | -            client,
 | 
	
		
			
				|  |  | -            getConfig().getDestination().getIndex(),
 | 
	
		
			
				|  |  | -            fieldMappingsListener
 | 
	
		
			
				|  |  | -        );
 | 
	
		
			
				|  |  | +        SchemaUtil.getDestinationFieldMappings(client, getConfig().getDestination().getIndex(), fieldMappingsListener);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -363,12 +361,20 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private void closePointInTime() {
 | 
	
		
			
				|  |  | +        for (String name : namedPits.keySet()) {
 | 
	
		
			
				|  |  | +            closePointInTime(name);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void closePointInTime(String name) {
 | 
	
		
			
				|  |  | +        PointInTimeBuilder pit = namedPits.remove(name);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          if (pit == null) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          String oldPit = pit.getEncodedId();
 | 
	
		
			
				|  |  | -        pit = null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          ClosePointInTimeRequest closePitRequest = new ClosePointInTimeRequest(oldPit);
 | 
	
		
			
				|  |  |          ClientHelper.executeWithHeadersAsync(
 | 
	
		
			
				|  |  |              transformConfig.getHeaders(),
 | 
	
	
		
			
				|  | @@ -383,20 +389,25 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |          );
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private void injectPointInTimeIfNeeded(SearchRequest searchRequest, ActionListener<SearchRequest> listener) {
 | 
	
		
			
				|  |  | +    private void injectPointInTimeIfNeeded(
 | 
	
		
			
				|  |  | +        Tuple<String, SearchRequest> namedSearchRequest,
 | 
	
		
			
				|  |  | +        ActionListener<Tuple<String, SearchRequest>> listener
 | 
	
		
			
				|  |  | +    ) {
 | 
	
		
			
				|  |  |          if (disablePit) {
 | 
	
		
			
				|  |  | -            listener.onResponse(searchRequest);
 | 
	
		
			
				|  |  | +            listener.onResponse(namedSearchRequest);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        SearchRequest searchRequest = namedSearchRequest.v2();
 | 
	
		
			
				|  |  | +        PointInTimeBuilder pit = namedPits.get(namedSearchRequest.v1());
 | 
	
		
			
				|  |  |          if (pit != null) {
 | 
	
		
			
				|  |  |              searchRequest.source().pointInTimeBuilder(pit);
 | 
	
		
			
				|  |  | -            listener.onResponse(searchRequest);
 | 
	
		
			
				|  |  | +            listener.onResponse(namedSearchRequest);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // no pit, create a new one
 | 
	
		
			
				|  |  | -        OpenPointInTimeRequest pitRequest = new OpenPointInTimeRequest(transformConfig.getSource().getIndex()).keepAlive(PIT_KEEP_ALIVE);
 | 
	
		
			
				|  |  | +        OpenPointInTimeRequest pitRequest = new OpenPointInTimeRequest(searchRequest.indices()).keepAlive(PIT_KEEP_ALIVE);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          ClientHelper.executeWithHeadersAsync(
 | 
	
		
			
				|  |  |              transformConfig.getHeaders(),
 | 
	
	
		
			
				|  | @@ -405,11 +416,17 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |              OpenPointInTimeAction.INSTANCE,
 | 
	
		
			
				|  |  |              pitRequest,
 | 
	
		
			
				|  |  |              ActionListener.wrap(response -> {
 | 
	
		
			
				|  |  | -                pit = new PointInTimeBuilder(response.getPointInTimeId()).setKeepAlive(PIT_KEEP_ALIVE);
 | 
	
		
			
				|  |  | -                searchRequest.source().pointInTimeBuilder(pit);
 | 
	
		
			
				|  |  | +                PointInTimeBuilder newPit = new PointInTimeBuilder(response.getPointInTimeId()).setKeepAlive(PIT_KEEP_ALIVE);
 | 
	
		
			
				|  |  | +                namedPits.put(namedSearchRequest.v1(), newPit);
 | 
	
		
			
				|  |  | +                searchRequest.source().pointInTimeBuilder(newPit);
 | 
	
		
			
				|  |  |                  pitCheckpoint = getNextCheckpoint().getCheckpoint();
 | 
	
		
			
				|  |  | -                logger.trace("[{}] using pit search context with id [{}]", getJobId(), pit.getEncodedId());
 | 
	
		
			
				|  |  | -                listener.onResponse(searchRequest);
 | 
	
		
			
				|  |  | +                logger.trace(
 | 
	
		
			
				|  |  | +                    "[{}] using pit search context with id [{}]; request [{}]",
 | 
	
		
			
				|  |  | +                    getJobId(),
 | 
	
		
			
				|  |  | +                    newPit.getEncodedId(),
 | 
	
		
			
				|  |  | +                    namedSearchRequest.v1()
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                listener.onResponse(namedSearchRequest);
 | 
	
		
			
				|  |  |              }, e -> {
 | 
	
		
			
				|  |  |                  Throwable unwrappedException = ExceptionsHelper.findSearchExceptionRootCause(e);
 | 
	
		
			
				|  |  |                  // if point in time is not supported, disable it but do not remember forever (stopping and starting will give it another
 | 
	
	
		
			
				|  | @@ -433,25 +450,27 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |                          e
 | 
	
		
			
				|  |  |                      );
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                listener.onResponse(searchRequest);
 | 
	
		
			
				|  |  | +                listener.onResponse(namedSearchRequest);
 | 
	
		
			
				|  |  |              })
 | 
	
		
			
				|  |  |          );
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private void doSearch(SearchRequest searchRequest, ActionListener<SearchResponse> listener) {
 | 
	
		
			
				|  |  | -        logger.trace("searchRequest: {}", searchRequest);
 | 
	
		
			
				|  |  | +    private void doSearch(Tuple<String, SearchRequest> namedSearchRequest, ActionListener<SearchResponse> listener) {
 | 
	
		
			
				|  |  | +        logger.trace(() -> new ParameterizedMessage("searchRequest: [{}]", namedSearchRequest.v2()));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        PointInTimeBuilder pit = namedSearchRequest.v2().pointInTimeBuilder();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          ClientHelper.executeWithHeadersAsync(
 | 
	
		
			
				|  |  |              transformConfig.getHeaders(),
 | 
	
		
			
				|  |  |              ClientHelper.TRANSFORM_ORIGIN,
 | 
	
		
			
				|  |  |              client,
 | 
	
		
			
				|  |  |              SearchAction.INSTANCE,
 | 
	
		
			
				|  |  | -            searchRequest,
 | 
	
		
			
				|  |  | +            namedSearchRequest.v2(),
 | 
	
		
			
				|  |  |              ActionListener.wrap(response -> {
 | 
	
		
			
				|  |  |                  // did the pit change?
 | 
	
		
			
				|  |  |                  if (response.pointInTimeId() != null && (pit == null || response.pointInTimeId() != pit.getEncodedId())) {
 | 
	
		
			
				|  |  | -                    pit = new PointInTimeBuilder(response.pointInTimeId()).setKeepAlive(PIT_KEEP_ALIVE);
 | 
	
		
			
				|  |  | -                    logger.trace("point in time handle has changed");
 | 
	
		
			
				|  |  | +                    namedPits.put(namedSearchRequest.v1(), new PointInTimeBuilder(response.pointInTimeId()).setKeepAlive(PIT_KEEP_ALIVE));
 | 
	
		
			
				|  |  | +                    logger.trace("point in time handle has changed; request [{}]", namedSearchRequest.v1());
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  listener.onResponse(response);
 | 
	
	
		
			
				|  | @@ -461,15 +480,22 @@ class ClientTransformIndexer extends TransformIndexer {
 | 
	
		
			
				|  |  |                  // succeeds a new pit gets created at the next run
 | 
	
		
			
				|  |  |                  Throwable unwrappedException = ExceptionsHelper.findSearchExceptionRootCause(e);
 | 
	
		
			
				|  |  |                  if (unwrappedException instanceof SearchContextMissingException) {
 | 
	
		
			
				|  |  | -                    logger.warn(new ParameterizedMessage("[{}] Search context missing, falling back to normal search.", getJobId()), e);
 | 
	
		
			
				|  |  | -                    pit = null;
 | 
	
		
			
				|  |  | -                    searchRequest.source().pointInTimeBuilder(null);
 | 
	
		
			
				|  |  | +                    logger.warn(
 | 
	
		
			
				|  |  | +                        new ParameterizedMessage(
 | 
	
		
			
				|  |  | +                            "[{}] Search context missing, falling back to normal search; request [{}]",
 | 
	
		
			
				|  |  | +                            getJobId(),
 | 
	
		
			
				|  |  | +                            namedSearchRequest.v1()
 | 
	
		
			
				|  |  | +                        ),
 | 
	
		
			
				|  |  | +                        e
 | 
	
		
			
				|  |  | +                    );
 | 
	
		
			
				|  |  | +                    namedPits.remove(namedSearchRequest.v1());
 | 
	
		
			
				|  |  | +                    namedSearchRequest.v2().source().pointInTimeBuilder(null);
 | 
	
		
			
				|  |  |                      ClientHelper.executeWithHeadersAsync(
 | 
	
		
			
				|  |  |                          transformConfig.getHeaders(),
 | 
	
		
			
				|  |  |                          ClientHelper.TRANSFORM_ORIGIN,
 | 
	
		
			
				|  |  |                          client,
 | 
	
		
			
				|  |  |                          SearchAction.INSTANCE,
 | 
	
		
			
				|  |  | -                        searchRequest,
 | 
	
		
			
				|  |  | +                        namedSearchRequest.v2(),
 | 
	
		
			
				|  |  |                          listener
 | 
	
		
			
				|  |  |                      );
 | 
	
		
			
				|  |  |                      return;
 |