Skip to content

Commit 0378eff

Browse files
authored
Invalidate query compilation cache entries with outdated VIEWs (#1960) (#2479)
KIKIMR-21002 In this PR we add the following algorithm for invalidating cache entries for outdated VIEWs: 1. Store path ids and schema versions of the views that were used in the query in the cache entries, so they can be accessed later. 2. Whenever we retrieve a compilation result from cache, send a request for SchemeCache to check if the schema version of the views used in this query (if any) has not changed since we compiled this query. 3. Send a recompilation request if any view is outdated. There are two important things to note about this solution: - We make a SchemeCache request for each repeated query and there is a lot of these in an OLTP-focused database like YDB. However, we have already been sending these request for preliminary (this is not the last check of schema version mismatch (at least for tables)) cache invalidation for tables, so views should not incur an additional performance impact here. - This solution does not guarantee strong consistency for queries using views, because query cache invalidation will not happen instantly after the view definition is updated. The node should get an update from the SchemeCache, which takes some time.
1 parent 5635e2b commit 0378eff

File tree

5 files changed

+107
-2
lines changed

5 files changed

+107
-2
lines changed

ydb/core/kqp/provider/yql_kikimr_datasource.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,18 @@ class TKiSourceLoadTableMetadataTransformer : public TGraphTransformerBase {
351351
if (!AddCluster(table, res, input, ctx)) {
352352
return TStatus::Error;
353353
}
354+
355+
if (const auto& preparingQuery = SessionCtx->Query().PreparingQuery;
356+
preparingQuery
357+
&& res.Metadata->Kind == EKikimrTableKind::View
358+
) {
359+
const auto& viewMetadata = *res.Metadata;
360+
auto* viewInfo = preparingQuery->MutablePhysicalQuery()->MutableViewInfos()->Add();
361+
auto* pathId = viewInfo->MutableTableId();
362+
pathId->SetOwnerId(viewMetadata.PathId.OwnerId());
363+
pathId->SetTableId(viewMetadata.PathId.TableId());
364+
viewInfo->SetSchemaVersion(viewMetadata.SchemaVersion);
365+
}
354366
} else {
355367
TIssueScopeGuard issueScope(ctx.IssueManager, [input, &table, &ctx]() {
356368
return MakeIntrusive<TIssue>(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder()

ydb/core/kqp/session_actor/kqp_query_state.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,24 @@ bool TKqpQueryState::EnsureTableVersions(const TEvTxProxySchemeCache::TEvNavigat
7171
return true;
7272
}
7373

74+
void TKqpQueryState::FillViews(const google::protobuf::RepeatedPtrField< ::NKqpProto::TKqpTableInfo>& views) {
75+
for (const auto& view : views) {
76+
const auto& pathId = view.GetTableId();
77+
const auto schemaVersion = view.GetSchemaVersion();
78+
auto [it, isInserted] = TableVersions.emplace(TTableId(pathId.GetOwnerId(), pathId.GetTableId()), schemaVersion);
79+
if (!isInserted) {
80+
Y_ENSURE(it->second == schemaVersion);
81+
}
82+
}
83+
}
7484

7585
std::unique_ptr<TEvTxProxySchemeCache::TEvNavigateKeySet> TKqpQueryState::BuildNavigateKeySet() {
7686
TableVersions.clear();
7787

7888
for (const auto& tx : PreparedQuery->GetPhysicalQuery().GetTransactions()) {
7989
FillTables(tx);
8090
}
91+
FillViews(PreparedQuery->GetPhysicalQuery().GetViewInfos());
8192

8293
auto navigate = MakeHolder<NSchemeCache::TSchemeCacheNavigate>();
8394
navigate->DatabaseName = Database;

ydb/core/kqp/session_actor/kqp_query_state.h

+2
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ class TKqpQueryState : public TNonCopyable {
234234
}
235235
}
236236

237+
void FillViews(const google::protobuf::RepeatedPtrField< ::NKqpProto::TKqpTableInfo>& views);
238+
237239
bool NeedCheckTableVersions() const {
238240
return CompileStats.FromCache;
239241
}

ydb/core/kqp/ut/view/view_ut.cpp

+80-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#include <ydb/core/kqp/ut/common/kqp_ut_common.h>
22
#include <ydb/library/yql/sql/sql.h>
33
#include <ydb/library/yql/utils/log/log.h>
4+
#include <ydb/public/sdk/cpp/client/ydb_proto/accessor.h>
45

56
#include <util/folder/filelist.h>
67

78
#include <format>
89

910
using namespace NKikimr;
1011
using namespace NKikimr::NKqp;
12+
using namespace NYdb;
1113
using namespace NYdb::NTable;
1214

1315
namespace {
@@ -60,17 +62,46 @@ void ExecuteDataDefinitionQuery(TSession& session, const TString& script) {
6062
<< script << "\nThe issues:\n" << result.GetIssues().ToString());
6163
}
6264

63-
TDataQueryResult ExecuteDataModificationQuery(TSession& session, const TString& script) {
65+
TDataQueryResult ExecuteDataModificationQuery(TSession& session,
66+
const TString& script,
67+
const TExecDataQuerySettings& settings = {}
68+
) {
6469
const auto result = session.ExecuteDataQuery(
6570
script,
66-
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx()
71+
TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(),
72+
settings
6773
).ExtractValueSync();
6874
UNIT_ASSERT_C(result.IsSuccess(), "Failed to execute the following DML script:\n"
6975
<< script << "\nThe issues:\n" << result.GetIssues().ToString());
7076

7177
return result;
7278
}
7379

80+
TValue GetSingleResult(const TDataQueryResult& rawResults) {
81+
auto resultSetParser = rawResults.GetResultSetParser(0);
82+
UNIT_ASSERT(resultSetParser.TryNextRow());
83+
return resultSetParser.GetValue(0);
84+
}
85+
86+
int GetInteger(const TValue& value) {
87+
return TValueParser(value).GetInt32();
88+
}
89+
90+
TMaybe<bool> GetFromCacheStat(const TQueryStats& stats) {
91+
const auto& proto = TProtoAccessor::GetProto(stats);
92+
if (!proto.Hascompilation()) {
93+
return Nothing();
94+
}
95+
return proto.Getcompilation().Getfrom_cache();
96+
}
97+
98+
void AssertFromCache(const TMaybe<TQueryStats>& stats, bool expectedValue) {
99+
UNIT_ASSERT(stats.Defined());
100+
const auto isFromCache = GetFromCacheStat(*stats);
101+
UNIT_ASSERT_C(isFromCache.Defined(), stats->ToString());
102+
UNIT_ASSERT_VALUES_EQUAL_C(*isFromCache, expectedValue, stats->ToString());
103+
}
104+
74105
void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) {
75106
const auto& firstResults = first.GetResultSets();
76107
const auto& secondResults = second.GetResultSets();
@@ -376,4 +407,51 @@ Y_UNIT_TEST_SUITE(TSelectFromViewTest) {
376407
ExecuteDataDefinitionQuery(session, ReadWholeFile(pathPrefix + "drop_view.sql"));
377408
}
378409
}
410+
411+
Y_UNIT_TEST(QueryCacheIsUpdated) {
412+
TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false));
413+
EnableViewsFeatureFlag(kikimr);
414+
auto session = kikimr.GetTableClient().CreateSession().GetValueSync().GetSession();
415+
416+
constexpr const char* viewName = "TheView";
417+
418+
const auto getCreationQuery = [&viewName](const char* innerQuery) -> TString {
419+
return std::format(R"(
420+
CREATE VIEW {} WITH (security_invoker = TRUE) AS {};
421+
)",
422+
viewName,
423+
innerQuery
424+
);
425+
};
426+
constexpr const char* firstInnerQuery = "SELECT 1";
427+
ExecuteDataDefinitionQuery(session, getCreationQuery(firstInnerQuery));
428+
429+
const TString selectFromViewQuery = std::format(R"(
430+
SELECT * FROM {};
431+
)",
432+
viewName
433+
);
434+
TExecDataQuerySettings queryExecutionSettings;
435+
queryExecutionSettings.KeepInQueryCache(true);
436+
queryExecutionSettings.CollectQueryStats(ECollectQueryStatsMode::Basic);
437+
ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings);
438+
// make sure the server side cache is working by calling the same query twice
439+
const auto cachedQueryRawResult = ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings);
440+
AssertFromCache(cachedQueryRawResult.GetStats(), true);
441+
UNIT_ASSERT_VALUES_EQUAL(GetInteger(GetSingleResult(cachedQueryRawResult)), 1);
442+
443+
// recreate the view with a different query inside
444+
ExecuteDataDefinitionQuery(session, std::format(R"(
445+
DROP VIEW {};
446+
)",
447+
viewName
448+
)
449+
);
450+
constexpr const char* secondInnerQuery = "SELECT 2";
451+
ExecuteDataDefinitionQuery(session, getCreationQuery(secondInnerQuery));
452+
453+
const auto secondCallRawResult = ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings);
454+
AssertFromCache(secondCallRawResult.GetStats(), false);
455+
UNIT_ASSERT_VALUES_EQUAL(GetInteger(GetSingleResult(secondCallRawResult)), 2);
456+
}
379457
}

ydb/core/protos/kqp_physical.proto

+2
Original file line numberDiff line numberDiff line change
@@ -507,4 +507,6 @@ message TKqpPhyQuery {
507507
bool HasUncommittedChangesRead = 9;
508508

509509
string QueryDiagnostics = 10;
510+
511+
repeated TKqpTableInfo ViewInfos = 11;
510512
}

0 commit comments

Comments
 (0)