Skip to content

Commit 5e86cfa

Browse files
authored
Merge pull request #36493 from vespa-engine/boeker/ann-doom-class
Add AnnDoom class specifically for ANN searches
2 parents c33faf6 + 0441c62 commit 5e86cfa

29 files changed

Lines changed: 479 additions & 95 deletions

searchcore/src/tests/proton/matching/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
2+
vespa_add_executable(searchcore_ann_deadline_configuration_test_app TEST
3+
SOURCES
4+
ann_deadline_configuration_test.cpp
5+
DEPENDS
6+
searchcore_matching
7+
GTest::gtest
8+
)
9+
vespa_add_test(NAME searchcore_ann_deadline_configuration_test_app COMMAND searchcore_ann_deadline_configuration_test_app)
210
vespa_add_executable(searchcore_matching_test_app TEST
311
SOURCES
412
matching_test.cpp
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
2+
3+
#include <vespa/searchcore/proton/matching/ann_deadline_configuration.h>
4+
#include <vespa/vespalib/gtest/gtest.h>
5+
#include <vespa/vespalib/util/doom.h>
6+
#include <vespa/vespalib/util/time.h>
7+
#include <cstdint>
8+
9+
using proton::matching::AnnDeadlineConfiguration;
10+
using vespalib::Deadline;
11+
using vespalib::Doom;
12+
using vespalib::steady_time;
13+
14+
class AnnDeadlineConfigurationTest : public ::testing::Test {
15+
protected:
16+
std::atomic<steady_time> time;
17+
Doom doom;
18+
19+
public:
20+
AnnDeadlineConfigurationTest();
21+
~AnnDeadlineConfigurationTest() override;
22+
23+
};
24+
25+
AnnDeadlineConfigurationTest::AnnDeadlineConfigurationTest()
26+
: time(vespalib::steady_clock::now()),
27+
doom(time, time.load() + 1s) {
28+
}
29+
30+
AnnDeadlineConfigurationTest::~AnnDeadlineConfigurationTest() = default;
31+
32+
TEST_F(AnnDeadlineConfigurationTest, soft_doom_becomes_deadline) {
33+
for (auto duration : {50ms, 100ms, 1000ms}) {
34+
AnnDeadlineConfiguration config(time.load() + duration);
35+
36+
for (uint32_t remaining_searches : {1, 2, 3, 5, 10}) {
37+
Deadline deadline = config.make_ann_deadline(doom, remaining_searches);
38+
EXPECT_EQ(Deadline::Type::BUDGET, deadline.type());
39+
EXPECT_EQ(duration, deadline.time_left());
40+
}
41+
}
42+
}
43+
44+
TEST_F(AnnDeadlineConfigurationTest, ann_timebudget_becomes_deadline) {
45+
for (auto budget : {50ms, 100ms, 1000ms}) {
46+
for (auto ann_timeout : {10ms, 20ms, 70ms, 500ms}) { // timeout disabled, acts as soft-timeout
47+
AnnDeadlineConfiguration config(budget, false, time.load() + ann_timeout);
48+
49+
for (uint32_t remaining_searches : {1, 2, 3, 5, 10}) {
50+
Deadline deadline = config.make_ann_deadline(doom, remaining_searches);
51+
EXPECT_EQ(Deadline::Type::BUDGET, deadline.type());
52+
EXPECT_EQ(std::min(budget, ann_timeout), deadline.time_left());
53+
}
54+
}
55+
}
56+
}
57+
58+
TEST_F(AnnDeadlineConfigurationTest, ann_timeout_becomes_deadline) {
59+
for (auto ann_timeout : {10ms, 20ms, 80ms, 500ms}) { // Less than time budget of 2s
60+
AnnDeadlineConfiguration config(2s, true, time.load() + ann_timeout);
61+
62+
for (uint32_t remaining_searches : {1, 2, 5, 10}) {
63+
Deadline deadline = config.make_ann_deadline(doom, remaining_searches);
64+
EXPECT_EQ(Deadline::Type::TIMEOUT, deadline.type());
65+
EXPECT_EQ(ann_timeout / remaining_searches, deadline.time_left());
66+
}
67+
}
68+
}
69+
70+
TEST_F(AnnDeadlineConfigurationTest, ann_budget_ann_timeout_interaction_is_handled) {
71+
AnnDeadlineConfiguration config(50ms, true, time.load() + 100ms);
72+
Deadline deadline = config.make_ann_deadline(doom, 1);
73+
EXPECT_EQ(Deadline::Type::BUDGET, deadline.type());
74+
EXPECT_EQ(50ms, deadline.time_left());
75+
76+
Deadline deadline2 = config.make_ann_deadline(doom, 2);
77+
EXPECT_EQ(Deadline::Type::TIMEOUT, deadline2.type());
78+
EXPECT_EQ(50ms, deadline2.time_left());
79+
80+
Deadline deadline3 = config.make_ann_deadline(doom, 4);
81+
EXPECT_EQ(Deadline::Type::TIMEOUT, deadline3.type());
82+
EXPECT_EQ(25ms, deadline3.time_left());
83+
84+
85+
time.store(time.load() + 50ms);
86+
Deadline deadline4 = config.make_ann_deadline(doom, 1);
87+
EXPECT_EQ(Deadline::Type::TIMEOUT, deadline4.type());
88+
EXPECT_EQ(50ms, deadline4.time_left());
89+
90+
Deadline deadline5 = config.make_ann_deadline(doom, 2);
91+
EXPECT_EQ(Deadline::Type::TIMEOUT, deadline5.type());
92+
EXPECT_EQ(25ms, deadline5.time_left());
93+
}
94+
95+
GTEST_MAIN_RUN_ALL_TESTS()

searchcore/src/tests/proton/matching/query_test.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Unit tests for query.
33

44
#include <vespa/searchlib/common/serialized_query_tree.h>
5+
#include <vespa/searchcore/proton/matching/ann_deadline_configuration.h>
56
#include <vespa/searchcore/proton/matching/fakesearchcontext.h>
67
#include <vespa/searchcore/proton/matching/matchdatareservevisitor.h>
78
#include <vespa/searchcore/proton/matching/blueprintbuilder.h>
@@ -1151,26 +1152,28 @@ GlobalFilterBlueprint::~GlobalFilterBlueprint() = default;
11511152

11521153
TEST(QueryTest, global_filter_is_calculated_and_handled)
11531154
{
1155+
vespalib::Doom doom(vespalib::Doom::never());
1156+
proton::matching::AnnDeadlineConfiguration ann_deadline_config(vespalib::steady_time::max());
11541157
// estimated hits = 3, estimated hit ratio = 0.3
11551158
auto result = SimpleResult().addHit(3).addHit(5).addHit(7);
11561159
uint32_t docid_limit = 10;
11571160
{ // global filter is not wanted
11581161
GlobalFilterBlueprint bp(result, false);
1159-
auto res = Query::handle_global_filter(bp, docid_limit, 0, 1, ttb(), nullptr);
1162+
auto res = Query::handle_global_filter(bp, doom, ann_deadline_config, docid_limit, 0, 1, ttb(), nullptr);
11601163
EXPECT_FALSE(res);
11611164
EXPECT_FALSE(bp.filter);
11621165
EXPECT_EQ(-1.0, bp.estimated_hit_ratio);
11631166
}
11641167
{ // estimated_hit_ratio < global_filter_lower_limit
11651168
GlobalFilterBlueprint bp(result, true);
1166-
auto res = Query::handle_global_filter(bp, docid_limit, 0.31, 1, ttb(), nullptr);
1169+
auto res = Query::handle_global_filter(bp, doom, ann_deadline_config, docid_limit, 0.31, 1, ttb(), nullptr);
11671170
EXPECT_FALSE(res);
11681171
EXPECT_FALSE(bp.filter);
11691172
EXPECT_EQ(-1.0, bp.estimated_hit_ratio);
11701173
}
11711174
{ // estimated_hit_ratio <= global_filter_upper_limit
11721175
GlobalFilterBlueprint bp(result, true);
1173-
auto res = Query::handle_global_filter(bp, docid_limit, 0, 0.3, ttb(), nullptr);
1176+
auto res = Query::handle_global_filter(bp, doom, ann_deadline_config, docid_limit, 0, 0.3, ttb(), nullptr);
11741177
EXPECT_TRUE(res);
11751178
EXPECT_TRUE(bp.filter);
11761179
EXPECT_TRUE(bp.filter->is_active());
@@ -1183,7 +1186,7 @@ TEST(QueryTest, global_filter_is_calculated_and_handled)
11831186
}
11841187
{ // estimated_hit_ratio > global_filter_upper_limit
11851188
GlobalFilterBlueprint bp(result, true);
1186-
auto res = Query::handle_global_filter(bp, docid_limit, 0, 0.29, ttb(), nullptr);
1189+
auto res = Query::handle_global_filter(bp, doom, ann_deadline_config, docid_limit, 0, 0.29, ttb(), nullptr);
11871190
EXPECT_TRUE(res);
11881191
EXPECT_TRUE(bp.filter);
11891192
EXPECT_FALSE(bp.filter->is_active());

searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
22
vespa_add_library(searchcore_matching STATIC
33
SOURCES
4+
ann_deadline_configuration.cpp
45
attribute_limiter.cpp
56
blueprintbuilder.cpp
67
docid_range_scheduler.cpp
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
2+
3+
#include "ann_deadline_configuration.h"
4+
#include <cassert>
5+
6+
using vespalib::Deadline;
7+
using vespalib::Doom;
8+
9+
namespace proton::matching {
10+
11+
AnnDeadlineConfiguration::AnnDeadlineConfiguration(vespalib::steady_time soft_doom)
12+
: AnnDeadlineConfiguration(vespalib::duration::max(), false, soft_doom) {
13+
}
14+
15+
AnnDeadlineConfiguration::AnnDeadlineConfiguration(vespalib::duration timebudget, bool timeout_enabled, vespalib::steady_time timeout) noexcept
16+
: _timebudget(timebudget),
17+
_timeout_enabled(timeout_enabled),
18+
_timeout(timeout) {
19+
}
20+
21+
const vespalib::Deadline AnnDeadlineConfiguration::make_ann_deadline(const vespalib::Doom& doom, uint32_t remaining_searches) const noexcept {
22+
assert(remaining_searches > 0);
23+
vespalib::steady_time now(doom.getTimeNS());
24+
// ANN might hit the deadline due to a depleted time budget or a timeout,
25+
// whatever happens first. The timeout might be the ANN-specific timeout
26+
// or the soft-timeout. The soft-timeout is used when ANN timeouts
27+
// are not enabled, and in this case, reaching the soft-timeout
28+
// is not reported as an ANN timeout.
29+
vespalib::duration timeout_left = _timeout_enabled ? ((_timeout - now) / remaining_searches)
30+
: _timeout - now;
31+
32+
if (_timebudget < timeout_left) {
33+
return doom.make_deadline(now + _timebudget, Deadline::Type::BUDGET);
34+
} else {
35+
return doom.make_deadline(now + timeout_left, _timeout_enabled ? Deadline::Type::TIMEOUT : Deadline::Type::BUDGET);
36+
}
37+
}
38+
39+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
2+
3+
#pragma once
4+
5+
#include <vespa/vespalib/util/deadline.h>
6+
#include <vespa/vespalib/util/doom.h>
7+
#include <vespa/vespalib/util/time.h>
8+
9+
namespace proton::matching {
10+
11+
/**
12+
* Class that stores the ANN time budget and ANN timeout.
13+
* Used to create Deadline objects for the individual ANN searches.
14+
*/
15+
class AnnDeadlineConfiguration {
16+
public:
17+
AnnDeadlineConfiguration(vespalib::steady_time soft_doom);
18+
AnnDeadlineConfiguration(vespalib::duration timebudget, bool timeout_enabled, vespalib::steady_time timeout) noexcept;
19+
20+
/**
21+
* Returns a Deadline for the next ANN search.
22+
* This deadline is the minimum of the time budget and the time until the timeout,
23+
* where the time until the timeout is distributed equally between all remaining searches if
24+
* the timeout is enabled.
25+
*/
26+
const vespalib::Deadline make_ann_deadline(const vespalib::Doom& doom, uint32_t remaining_searches) const noexcept;
27+
28+
private:
29+
vespalib::duration _timebudget;
30+
bool _timeout_enabled;
31+
vespalib::steady_time _timeout;
32+
};
33+
34+
35+
}

searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ MatchTools::setup_dump()
171171
MatchToolsFactory::
172172
MatchToolsFactory(QueryLimiter & queryLimiter,
173173
const vespalib::Doom & doom,
174+
const AnnDeadlineConfiguration& ann_deadline_config,
174175
ISearchContext & searchContext,
175176
IAttributeContext & attributeContext,
176177
search::engine::Trace & root_trace,
@@ -231,7 +232,7 @@ MatchToolsFactory(QueryLimiter & queryLimiter,
231232
_query.fetchPostings(ExecuteInfo::create(in_flow.rate(), _requestContext.getDoom(), thread_bundle));
232233
if (is_search) {
233234
bool use_lazy_filter = LazyFilter::check(_queryEnv.getProperties());
234-
_query.handle_global_filter(_requestContext, searchContext.getDocIdLimit(),
235+
_query.handle_global_filter(_requestContext, ann_deadline_config, searchContext.getDocIdLimit(),
235236
_create_blueprint_params.global_filter_lower_limit,
236237
_create_blueprint_params.global_filter_upper_limit, trace, sort_by_cost, use_lazy_filter);
237238
}

searchcore/src/vespa/searchcore/proton/matching/match_tools.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#pragma once
44

5+
#include "ann_deadline_configuration.h"
56
#include "field_id_to_name_mapper.h"
67
#include "handlerecorder.h"
78
#include "isearchcontext.h"
@@ -151,6 +152,7 @@ class MatchToolsFactory
151152

152153
MatchToolsFactory(QueryLimiter & queryLimiter,
153154
const vespalib::Doom & softDoom,
155+
const AnnDeadlineConfiguration& ann_deadline_config,
154156
ISearchContext &searchContext,
155157
IAttributeContext &attributeContext,
156158
search::engine::Trace & root_trace,

searchcore/src/vespa/searchcore/proton/matching/matcher.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
22

33
#include "matcher.h"
4+
#include "ann_deadline_configuration.h"
45
#include "isearchcontext.h"
56
#include "match_master.h"
67
#include "match_context.h"
@@ -165,8 +166,9 @@ Matcher::create_match_tools_factory(const search::engine::Request &request, ISea
165166
_stats.softDoomFactor(), factor, hasFactorOverride, vespalib::count_ns(safeLeft));
166167
}
167168
vespalib::Doom doom(_now_ref, safeDoom, request.getTimeOfDoom(), hasFactorOverride);
169+
AnnDeadlineConfiguration ann_deadline_config(safeDoom);
168170
const auto& queryTree = request.getSerializedQueryTree();
169-
return std::make_unique<MatchToolsFactory>(_queryLimiter, doom, searchContext, attrContext,
171+
return std::make_unique<MatchToolsFactory>(_queryLimiter, doom, ann_deadline_config, searchContext, attrContext,
170172
request.trace(), queryTree, request.location,
171173
_viewResolver, metaStore, _indexEnv, *_rankSetup,
172174
rankProperties, feature_overrides, thread_bundle,

searchcore/src/vespa/searchcore/proton/matching/query.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
22

33
#include "query.h"
4+
#include "ann_deadline_configuration.h"
45
#include "blueprintbuilder.h"
56
#include "matchdatareservevisitor.h"
67
#include "resolveviewvisitor.h"
@@ -273,11 +274,11 @@ Query::fetchPostings(const ExecuteInfo & executeInfo)
273274
}
274275

275276
void
276-
Query::handle_global_filter(const IRequestContext & requestContext, uint32_t docid_limit,
277+
Query::handle_global_filter(const IRequestContext & requestContext, const AnnDeadlineConfiguration& ann_deadline_config, uint32_t docid_limit,
277278
double global_filter_lower_limit, double global_filter_upper_limit,
278279
search::engine::Trace& trace, bool sort_by_cost, bool use_lazy_filter)
279280
{
280-
if (!handle_global_filter(*_blueprint, docid_limit, global_filter_lower_limit, global_filter_upper_limit,
281+
if (!handle_global_filter(*_blueprint, requestContext.getDoom(), ann_deadline_config, docid_limit, global_filter_lower_limit, global_filter_upper_limit,
281282
requestContext.thread_bundle(), &trace, use_lazy_filter))
282283
{
283284
return;
@@ -292,7 +293,7 @@ Query::handle_global_filter(const IRequestContext & requestContext, uint32_t doc
292293
}
293294

294295
bool
295-
Query::handle_global_filter(Blueprint& blueprint, uint32_t docid_limit,
296+
Query::handle_global_filter(Blueprint& blueprint, const vespalib::Doom& doom, const AnnDeadlineConfiguration& ann_deadline_config, uint32_t docid_limit,
296297
double global_filter_lower_limit, double global_filter_upper_limit,
297298
vespalib::ThreadBundle &thread_bundle, search::engine::Trace* trace, bool use_lazy_filter)
298299
{
@@ -359,12 +360,12 @@ Query::handle_global_filter(Blueprint& blueprint, uint32_t docid_limit,
359360
trace->addEvent(5, "Handle global filter in query execution plan");
360361
}
361362
blueprint.set_global_filter(*global_filter, estimated_hit_ratio);
362-
perform_ann_searches(blueprint);
363+
perform_ann_searches(blueprint, doom, ann_deadline_config);
363364
return true;
364365
}
365366

366367
void
367-
Query::perform_ann_searches(Blueprint& blueprint)
368+
Query::perform_ann_searches(Blueprint& blueprint, const vespalib::Doom& doom, const AnnDeadlineConfiguration& ann_deadline_config)
368369
{
369370
std::queue<search::queryeval::NearestNeighborBlueprint*> ann_blueprints;
370371
blueprint.each_node_post_order([&ann_blueprints](Blueprint& bp) {
@@ -375,7 +376,8 @@ Query::perform_ann_searches(Blueprint& blueprint)
375376
}
376377
});
377378
while (!ann_blueprints.empty()) {
378-
ann_blueprints.front()->perform_index_search();
379+
const vespalib::Deadline deadline = ann_deadline_config.make_ann_deadline(doom, ann_blueprints.size());
380+
ann_blueprints.front()->perform_index_search(deadline);
379381
ann_blueprints.pop();
380382
}
381383
}

0 commit comments

Comments
 (0)