aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/prelude/fastsearch/IndexedBackend.java
blob: 294aff8d78b53b6a349226aacc670635784aa9e1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.fastsearch;

import com.yahoo.prelude.querytransform.QueryRewrite;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.dispatch.Dispatcher;
import com.yahoo.search.dispatch.FillInvoker;
import com.yahoo.search.dispatch.SearchInvoker;
import com.yahoo.search.grouping.GroupingRequest;
import com.yahoo.search.grouping.request.GroupingOperation;
import com.yahoo.search.query.Ranking;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
import com.yahoo.search.schema.SchemaInfo;

import java.io.IOException;
import java.util.Optional;

/**
 * The searcher which forwards queries to fdispatch nodes, using the fnet/fs4
 * network layer.
 *
 * @author  bratseth
 */
// TODO: Clean up all the duplication in the various search methods by
// switching to doing all the error handling using exceptions below doSearch2.
// Right now half is done by exceptions handled in doSearch2 and half by setting
// errors on results and returning them. It could be handy to create a QueryHandlingErrorException
// or similar which could wrap an error message, and then just always throw that and
// catch and unwrap into a results with an error in high level methods.  -Jon
public class IndexedBackend extends VespaBackend {

    /** Used to dispatch directly to search nodes over RPC, replacing the old fnet communication path */
    private final Dispatcher dispatcher;

    /**
     * Creates a Fastsearcher.
     *
     * @param serverId the resource pool used to create direct connections to the local search nodes when
     *                        bypassing the dispatch node
     * @param dispatcher the dispatcher used (when enabled) to send summary requests over the rpc protocol.
     *                   Eventually we will move everything to this protocol and never use dispatch nodes.
     *                   At that point we won't need a cluster searcher above this to select and pass the right
     *                   backend.
     * @param docSumParams  document summary parameters
     * @param clusterParams the cluster number, and other cluster backend parameters
     * @param documentdbInfoConfig document database parameters
     */
    public IndexedBackend(String serverId,
                          Dispatcher dispatcher,
                          SummaryParameters docSumParams,
                          ClusterParams clusterParams,
                          DocumentdbInfoConfig documentdbInfoConfig,
                          SchemaInfo schemaInfo) {
        init(serverId, docSumParams, clusterParams, documentdbInfoConfig, schemaInfo);
        this.dispatcher = dispatcher;
    }

    @Override
    protected void transformQuery(Query query) {
        QueryRewrite.rewriteSddocname(query);
    }

    private void injectSource(HitGroup hits) {
        for (Hit hit : hits.asUnorderedHits()) {
            if (hit instanceof FastHit) {
                hit.setSource(getName());
            }
        }
    }

    @Override
    public Result doSearch2(String schema, Query query) {
        if (dispatcher.allGroupsHaveSize1())
            forceSinglePassGrouping(query);
        try (SearchInvoker invoker = getSearchInvoker(query)) {
            Result result = invoker.search(query);
            injectSource(result.hits());

            if (query.properties().getBoolean(Ranking.RANKFEATURES, false)) {
                // There is currently no correct choice for which
                // summary class we want to fetch at this point. If we
                // fetch the one selected by the user it may not
                // contain the data we need. If we fetch the default
                // one we end up fetching docsums twice unless the
                // user also requested the default one.
                fill(result, query.getPresentation().getSummary()); // ARGH
            }
            return result;
        } catch (TimeoutException e) {
            return new Result(query,ErrorMessage.createTimeout(e.getMessage()));
        } catch (IOException e) {
            Result result = new Result(query);
            if (query.getTrace().getLevel() >= 1)
                query.trace(getName() + " error response: " + result, false, 1);
            result.hits().addError(ErrorMessage.createBackendCommunicationError(getName() + " failed: "+ e.getMessage()));
            return result;
        }
    }

    /**
     * Perform a partial docsum fill for a temporary result
     * representing a partition of the complete fill request.
     *
     * @param result result containing a partition of the unfilled hits
     * @param summaryClass the summary class we want to fill with
     **/
    @Override
    protected void doPartialFill(Result result, String summaryClass) {
        if (result.isFilled(summaryClass)) return;

        Query query = result.getQuery();
        traceQuery(getName(), "fill", query, query.getOffset(), query.getHits(), 1, quotedSummaryClass(summaryClass));

        try (FillInvoker invoker = getFillInvoker(result)) {
            invoker.fill(result, summaryClass);
        }
    }

    /** When we only search a single node, doing all grouping in one pass is more efficient */
    private void forceSinglePassGrouping(Query query) {
        for (GroupingRequest groupingRequest : query.getSelect().getGrouping())
            forceSinglePassGrouping(groupingRequest.getRootOperation());
    }

    private void forceSinglePassGrouping(GroupingOperation operation) {
        operation.setForceSinglePass(true);
        for (GroupingOperation childOperation : operation.getChildren())
            forceSinglePassGrouping(childOperation);
    }

    /**
     * Returns an invocation object for use in a single search request. The specific implementation returned
     * depends on query properties with the default being an invoker that interfaces with a dispatcher
     * on the same host.
     */
    private SearchInvoker getSearchInvoker(Query query) {
        return dispatcher.getSearchInvoker(query, this);
    }

    /**
     * Returns an invocation object for use in a single fill request. The specific implementation returned
     * depends on query properties with the default being an invoker that uses RPC to interface with
     * content nodes.
     */
    private FillInvoker getFillInvoker(Result result) {
        return dispatcher.getFillInvoker(result, this);
    }

    private static Optional<String> quotedSummaryClass(String summaryClass) {
        return Optional.of(summaryClass == null ? "[null]" : "'" + summaryClass + "'");
    }

    public String toString() {
        return "fast searcher (" + getName() + ") ";
    }

}