Skip to content

Commit 2ebeb7e

Browse files
authored
Merge pull request #584 from ydb-platform/query_service_stats
QueryService stats support
2 parents eeef3fa + b76a5dd commit 2ebeb7e

File tree

11 files changed

+193
-24
lines changed

11 files changed

+193
-24
lines changed

examples/query-stats/main.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import ydb
3+
4+
5+
SESSION_QUERY = "SELECT 1"
6+
TX_QUERY = "SELECT 1"
7+
8+
9+
def get_query_stats_from_session(pool: ydb.QuerySessionPool):
10+
def callee(session: ydb.QuerySession):
11+
with session.execute(SESSION_QUERY, stats_mode=ydb.QueryStatsMode.PROFILE):
12+
pass
13+
14+
print(session.last_query_stats)
15+
16+
pool.retry_operation_sync(callee)
17+
18+
19+
def get_query_stats_from_tx(pool: ydb.QuerySessionPool):
20+
def callee(tx: ydb.QueryTxContext):
21+
with tx.execute(TX_QUERY, stats_mode=ydb.QueryStatsMode.PROFILE):
22+
pass
23+
24+
print(tx.last_query_stats)
25+
26+
pool.retry_tx_sync(callee)
27+
28+
29+
def main():
30+
driver = ydb.Driver(
31+
endpoint=os.getenv("YDB_ENDPOINT", "grpc://localhost:2136"),
32+
database=os.getenv("YDB_DATABASE", "/local"),
33+
credentials=ydb.AnonymousCredentials(),
34+
)
35+
36+
with driver:
37+
# wait until driver become initialized
38+
driver.wait(fail_fast=True, timeout=5)
39+
with ydb.QuerySessionPool(driver) as pool:
40+
get_query_stats_from_session(pool)
41+
get_query_stats_from_tx(pool)
42+
43+
44+
main()

tests/query/test_query_session.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from concurrent.futures import _base as b
55
from unittest import mock
66

7-
7+
from ydb.query.base import QueryStatsMode
88
from ydb.query.session import QuerySession
99

1010

@@ -143,3 +143,34 @@ def cancel(self):
143143
assert "attach stream thread" not in thread_names
144144

145145
_check_session_state_empty(session)
146+
147+
@pytest.mark.parametrize(
148+
"stats_mode",
149+
[
150+
None,
151+
QueryStatsMode.UNSPECIFIED,
152+
QueryStatsMode.NONE,
153+
QueryStatsMode.BASIC,
154+
QueryStatsMode.FULL,
155+
QueryStatsMode.PROFILE,
156+
],
157+
)
158+
def test_stats_mode(self, session: QuerySession, stats_mode: QueryStatsMode):
159+
session.create()
160+
161+
for _ in session.execute("SELECT 1; SELECT 2; SELECT 3;", stats_mode=stats_mode):
162+
pass
163+
164+
stats = session.last_query_stats
165+
166+
if stats_mode in [None, QueryStatsMode.NONE, QueryStatsMode.UNSPECIFIED]:
167+
assert stats is None
168+
return
169+
170+
assert stats is not None
171+
assert len(stats.query_phases) > 0
172+
173+
if stats_mode != QueryStatsMode.BASIC:
174+
assert len(stats.query_plan) > 0
175+
else:
176+
assert stats.query_plan == ""

tests/query/test_query_transaction.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from ydb.query.base import QueryStatsMode
34
from ydb.query.transaction import QueryTxContext
45
from ydb.query.transaction import QueryTxStateEnum
56

@@ -104,3 +105,32 @@ def test_tx_identity_after_begin_works(self, tx: QueryTxContext):
104105

105106
assert identity.tx_id == tx.tx_id
106107
assert identity.session_id == tx.session_id
108+
109+
@pytest.mark.parametrize(
110+
"stats_mode",
111+
[
112+
None,
113+
QueryStatsMode.UNSPECIFIED,
114+
QueryStatsMode.NONE,
115+
QueryStatsMode.BASIC,
116+
QueryStatsMode.FULL,
117+
QueryStatsMode.PROFILE,
118+
],
119+
)
120+
def test_stats_mode(self, tx: QueryTxContext, stats_mode: QueryStatsMode):
121+
for _ in tx.execute("SELECT 1; SELECT 2; SELECT 3;", commit_tx=True, stats_mode=stats_mode):
122+
pass
123+
124+
stats = tx.last_query_stats
125+
126+
if stats_mode in [None, QueryStatsMode.NONE, QueryStatsMode.UNSPECIFIED]:
127+
assert stats is None
128+
return
129+
130+
assert stats is not None
131+
assert len(stats.query_phases) > 0
132+
133+
if stats_mode != QueryStatsMode.BASIC:
134+
assert len(stats.query_plan) > 0
135+
else:
136+
assert stats.query_plan == ""

ydb/aio/query/pool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async def retry_tx_async(
142142
"""Special interface to execute a bunch of commands with transaction in a safe, retriable way.
143143
144144
:param callee: A function, that works with session.
145-
:param tx_mode: Transaction mode, which is a one from the following choises:
145+
:param tx_mode: Transaction mode, which is a one from the following choices:
146146
1) QuerySerializableReadWrite() which is default mode;
147147
2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
148148
3) QuerySnapshotReadOnly();

ydb/aio/query/session.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,26 +117,34 @@ async def execute(
117117
exec_mode: base.QueryExecMode = None,
118118
concurrent_result_sets: bool = False,
119119
settings: Optional[BaseRequestSettings] = None,
120+
*,
121+
stats_mode: Optional[base.QueryStatsMode] = None,
120122
) -> AsyncResponseContextIterator:
121123
"""Sends a query to Query Service
122124
123125
:param query: (YQL or SQL text) to be executed.
124-
:param syntax: Syntax of the query, which is a one from the following choises:
126+
:param syntax: Syntax of the query, which is a one from the following choices:
125127
1) QuerySyntax.YQL_V1, which is default;
126128
2) QuerySyntax.PG.
127129
:param parameters: dict with parameters and YDB types;
128130
:param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
131+
:param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
132+
1) QueryStatsMode:NONE, which is default;
133+
2) QueryStatsMode.BASIC;
134+
3) QueryStatsMode.FULL;
135+
4) QueryStatsMode.PROFILE;
129136
130137
:return: Iterator with result sets
131138
"""
132139
self._state._check_session_ready_to_use()
133140

134141
stream_it = await self._execute_call(
135142
query=query,
143+
parameters=parameters,
136144
commit_tx=True,
137145
syntax=syntax,
138146
exec_mode=exec_mode,
139-
parameters=parameters,
147+
stats_mode=stats_mode,
140148
concurrent_result_sets=concurrent_result_sets,
141149
settings=settings,
142150
)
@@ -147,6 +155,7 @@ async def execute(
147155
rpc_state=None,
148156
response_pb=resp,
149157
session_state=self._state,
158+
session=self,
150159
settings=self._settings,
151160
),
152161
)

ydb/aio/query/transaction.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(self, driver, session_state, session, tx_mode):
2929
3030
:param driver: A driver instance
3131
:param session_state: A state of session
32-
:param tx_mode: Transaction mode, which is a one from the following choises:
32+
:param tx_mode: Transaction mode, which is a one from the following choices:
3333
1) QuerySerializableReadWrite() which is default mode;
3434
2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
3535
3) QuerySnapshotReadOnly();
@@ -142,32 +142,40 @@ async def execute(
142142
exec_mode: Optional[base.QueryExecMode] = None,
143143
concurrent_result_sets: Optional[bool] = False,
144144
settings: Optional[BaseRequestSettings] = None,
145+
*,
146+
stats_mode: Optional[base.QueryStatsMode] = None,
145147
) -> AsyncResponseContextIterator:
146148
"""Sends a query to Query Service
147149
148150
:param query: (YQL or SQL text) to be executed.
149151
:param parameters: dict with parameters and YDB types;
150152
:param commit_tx: A special flag that allows transaction commit.
151-
:param syntax: Syntax of the query, which is a one from the following choises:
153+
:param syntax: Syntax of the query, which is a one from the following choices:
152154
1) QuerySyntax.YQL_V1, which is default;
153155
2) QuerySyntax.PG.
154-
:param exec_mode: Exec mode of the query, which is a one from the following choises:
156+
:param exec_mode: Exec mode of the query, which is a one from the following choices:
155157
1) QueryExecMode.EXECUTE, which is default;
156158
2) QueryExecMode.EXPLAIN;
157159
3) QueryExecMode.VALIDATE;
158160
4) QueryExecMode.PARSE.
159161
:param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
162+
:param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
163+
1) QueryStatsMode:NONE, which is default;
164+
2) QueryStatsMode.BASIC;
165+
3) QueryStatsMode.FULL;
166+
4) QueryStatsMode.PROFILE;
160167
161168
:return: Iterator with result sets
162169
"""
163170
await self._ensure_prev_stream_finished()
164171

165172
stream_it = await self._execute_call(
166173
query=query,
174+
parameters=parameters,
167175
commit_tx=commit_tx,
168176
syntax=syntax,
169177
exec_mode=exec_mode,
170-
parameters=parameters,
178+
stats_mode=stats_mode,
171179
concurrent_result_sets=concurrent_result_sets,
172180
settings=settings,
173181
)

ydb/query/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
"QuerySessionPool",
88
"QueryClientSettings",
99
"QuerySession",
10+
"QueryStatsMode",
1011
"QueryTxContext",
1112
]
1213

1314
import logging
1415

1516
from .base import (
1617
QueryClientSettings,
18+
QueryStatsMode,
1719
)
1820

1921
from .session import QuerySession

ydb/query/base.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
if typing.TYPE_CHECKING:
2727
from .transaction import BaseQueryTxContext
28+
from .session import BaseQuerySession
2829

2930

3031
class QuerySyntax(enum.IntEnum):
@@ -41,7 +42,7 @@ class QueryExecMode(enum.IntEnum):
4142
EXECUTE = 50
4243

4344

44-
class StatsMode(enum.IntEnum):
45+
class QueryStatsMode(enum.IntEnum):
4546
UNSPECIFIED = 0
4647
NONE = 10
4748
BASIC = 20
@@ -132,12 +133,13 @@ def create_execute_query_request(
132133
tx_mode: Optional[BaseQueryTxMode],
133134
syntax: Optional[QuerySyntax],
134135
exec_mode: Optional[QueryExecMode],
136+
stats_mode: Optional[QueryStatsMode],
135137
parameters: Optional[dict],
136138
concurrent_result_sets: Optional[bool],
137139
) -> ydb_query.ExecuteQueryRequest:
138140
syntax = QuerySyntax.YQL_V1 if not syntax else syntax
139141
exec_mode = QueryExecMode.EXECUTE if not exec_mode else exec_mode
140-
stats_mode = StatsMode.NONE # TODO: choise is not supported yet
142+
stats_mode = QueryStatsMode.NONE if stats_mode is None else stats_mode
141143

142144
tx_control = None
143145
if not tx_id and not tx_mode:
@@ -189,6 +191,7 @@ def wrap_execute_query_response(
189191
response_pb: _apis.ydb_query.ExecuteQueryResponsePart,
190192
session_state: IQuerySessionState,
191193
tx: Optional["BaseQueryTxContext"] = None,
194+
session: Optional["BaseQuerySession"] = None,
192195
commit_tx: Optional[bool] = False,
193196
settings: Optional[QueryClientSettings] = None,
194197
) -> convert.ResultSet:
@@ -198,6 +201,12 @@ def wrap_execute_query_response(
198201
elif tx and response_pb.tx_meta and not tx.tx_id:
199202
tx._move_to_beginned(response_pb.tx_meta.id)
200203

204+
if response_pb.HasField("exec_stats"):
205+
if tx is not None:
206+
tx._last_query_stats = response_pb.exec_stats
207+
if session is not None:
208+
session._last_query_stats = response_pb.exec_stats
209+
201210
if response_pb.HasField("result_set"):
202211
return convert.ResultSet.from_message(response_pb.result_set, settings)
203212

ydb/query/pool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def retry_tx_sync(
151151
"""Special interface to execute a bunch of commands with transaction in a safe, retriable way.
152152
153153
:param callee: A function, that works with session.
154-
:param tx_mode: Transaction mode, which is a one from the following choises:
154+
:param tx_mode: Transaction mode, which is a one from the following choices:
155155
1) QuerySerializableReadWrite() which is default mode;
156156
2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
157157
3) QuerySnapshotReadOnly();

0 commit comments

Comments
 (0)