Skip to content

tdfs4ds.benchmark.runner

tdfs4ds.benchmark.runner

tdfs4ds.benchmark.runner

End-to-end latency / throughput benchmark for a tdfs4ds feature dataset view.

Uses raw teradatasql (NOT teradataml) on purpose: teradataml routes every statement through a single shared SQLAlchemy connection pool, which (a) corrupts under the concurrent-QPS thread fan-out ("N is not a valid connection pool handle") and (b) adds per-call wrapping latency that pollutes a latency benchmark. Connections are opened from the same TDFS4DS_TD_* environment the serve layer uses (tdfs4ds.utils.reconnect._connect_kwargs).

WHAT THIS MEASURES

CLIENT-PERCEIVED latency — wall-clock around cursor.execute() — which includes driver overhead, network round-trip, server parse/plan/AMP execution and result serialization. The optional DBQL pass isolates pure server time.

Two entry points
  • programmatic — :func:run_benchmark (returns the report dict), and
  • CLI — python -m tdfs4ds.benchmark.runner --host … --dataset … --entity-cols ….

_PHASE_WEIGHTS module-attribute

_PHASE_WEIGHTS = {'parse': 1, 'explain': 1, 'warm': 2, 'cold': 3, 'batch': 3, 'large_batch': 6, 'batch_strategies': 6, 'concurrent': 5, 'time_travel': 2, 'server_timing': 2}

_CONN_KWARGS module-attribute

_CONN_KWARGS: dict = {}

_MAIN_CONN module-attribute

_MAIN_CONN = None

_THREAD_LOCAL module-attribute

_THREAD_LOCAL = local()

_TYPE_NAMES module-attribute

_TYPE_NAMES = {'I': 'INTEGER', 'I1': 'BYTEINT', 'I2': 'SMALLINT', 'I8': 'BIGINT', 'D': 'DECIMAL', 'N': 'NUMBER', 'F': 'FLOAT', 'CF': 'CHAR', 'CV': 'VARCHAR', 'CO': 'CLOB', 'DA': 'DATE', 'AT': 'TIME', 'TS': 'TIMESTAMP', 'TZ': 'TIME WITH TZ', 'SZ': 'TIMESTAMP WITH TZ', 'BF': 'BYTE', 'BV': 'VARBYTE', 'BO': 'BLOB', 'YR': 'INTERVAL YEAR', 'MO': 'INTERVAL MONTH', 'DY': 'INTERVAL DAY', 'HR': 'INTERVAL HOUR', 'MI': 'INTERVAL MINUTE', 'SC': 'INTERVAL SECOND', 'PD': 'PERIOD(DATE)', 'PT': 'PERIOD(TIME)', 'PS': 'PERIOD(TIMESTAMP)', 'JN': 'JSON', 'XM': 'XML', 'UT': 'UDT'}

DEFAULT_MAX_PARSE_MS module-attribute

DEFAULT_MAX_PARSE_MS = 5000

_CURRENT_VALIDTIME_RE module-attribute

_CURRENT_VALIDTIME_RE = compile('CURRENT\\s+VALIDTIME', IGNORECASE)

build_where_clause

build_where_clause(entity_cols, key_values)

Build a SQL WHERE for ONE composite key (AND of equalities).

build_in_clause

build_in_clause(entity_cols, key_tuples)

Teradata-valid batch predicate for a list of (composite) key tuples.

Single-column key -> col IN (…); composite key -> (a=1 AND b=2) OR (a=3 AND b=4) OR ….

_fmt_val

_fmt_val(v)

Render one key value as a SQL literal (quote+escape strings, NULL keyword).

Dates and timestamps get typed literals. They used to fall through to str(v), so a date key rendered as col = 2016-01-01 - integer arithmetic, which never equals a date: every lookup on a date-keyed dataset silently returned no row (the benchmark then timed empty lookups).

in_list_fits

in_list_fits(entity_cols, key_tuples, max_bytes=SAFE_PREDICATE_BYTES)

True when the IN/OR predicate text stays safely under the parser limit.

predicate_byte_size

predicate_byte_size(entity_cols, key_tuples)

UTF-8 byte length of the IN/OR predicate for key_tuples.

_bench_connect_kwargs

_bench_connect_kwargs()

Resolve raw-connection kwargs from the TDFS4DS_TD_* environment.

Reuses the same resolver the serve bootstrap / reconnect use, so the benchmark works wherever the package is already configured.

_connect

_connect()

Open a fresh raw teradatasql connection from the global creds.

execute_sql

execute_sql(sql, conn=None)

Run a statement on a raw teradatasql cursor; return the cursor.

_thread_conn

_thread_conn()

Return this thread's own raw connection, opening it on first use.

percentiles

percentiles(timings_ms, n_runs)

time_query

time_query(callable_, n_runs=100, warmup=20)

_split_db_object

_split_db_object(name)

_format_column_type

_format_column_type(type_code, length, decimal_total, decimal_frac)

Render a Teradata type code into a SQL type. Falls back to the raw code (never an opaque '?') when unmapped.

_schema_via_help_column

_schema_via_help_column(dataset)

Resolve a view/table's columns via HELP COLUMN .* (works on VIEWS, unlike DBC.ColumnsV whose ColumnType is NULL for view columns).

_schema_via_columnsv

_schema_via_columnsv(dataset)

get_dataset_schema

get_dataset_schema(dataset, entity_cols)

Probe the dataset's resolved columns → feature count + per-column types.

Resolution order: HELP COLUMN first (authoritative for VIEWS), DBC.ColumnsV fallback. 'features' are the non-entity columns; entity columns are the grain keys. Returns a metadata dict or {error:…}.

test_parse_time

test_parse_time(dataset, n_runs=3)

Parse + optimise cost of the dataset view, measured with EXPLAIN.

EXPLAIN SELECT * FROM <dataset> parses the view, expands it and builds the plan, but executes nothing — so it isolates the cost a wide multi-join dataset pays on every request whose plan is not cached. Runs before the key sampling, which already executes the full view. Also reports the size of the view's DDL and of the plan: a long DDL is what the grouped strategy shortens.

test_explain

test_explain(dataset, entity_cols, sample_key)

test_single_key_warm

test_single_key_warm(dataset, entity_cols, hot_keys, n_runs=100)

test_single_key_cold

test_single_key_cold(dataset, entity_cols, all_keys, n_runs=200)

_batch_sql

_batch_sql(dataset, entity_cols, batch, pushdown)

The batch statement retrieve_batch would run (one SQL authority), or None when it would exceed Teradata's ~1 MB request parser limit.

_batch_query_metadata

_batch_query_metadata(dataset, entity_cols, sample_keys, pushdown)

What the batch phases run: query shape and, per storage table, the primary index and the partitioning entity columns the keys are matched on.

test_batch_lookup

test_batch_lookup(dataset, entity_cols, all_keys, batch_sizes, n_runs=30, pushdown='auto')

test_large_batch

test_large_batch(dataset, entity_cols, all_keys, sizes, pushdown='auto')

test_batch_strategies

test_batch_strategies(dataset, entity_cols, all_keys, sizes, n_runs=5, max_in_list_keys=None, pushdown='auto')

Compare the two batch-retrieval strategies side-by-side, per size.

For each size, time BOTH retrieve_batch(method='in_list') and retrieve_batch(method='volatile_join') over the same key sample, reusing the raw benchmark connection (_MAIN_CONN) so no teradataml pooling latency enters the measurement. An in_list predicate that would exceed the parser limit is recorded as {"error":"skipped_parser_limit"} rather than crashing — exactly the case the volatile join is meant to cover.

max_in_list_keys caps only the in_list arm: above it the in_list arm is recorded as {"error":"skipped_in_list_cap","cap":…} (a wide composite predicate's parse time would dominate the measurement) while the volatile_join arm still runs at the full size.

test_concurrent_qps

test_concurrent_qps(dataset, entity_cols, all_keys, concurrency_levels, n_queries_per_thread=50)

_as_of_literal

_as_of_literal(ts)

_build_as_of_view_ddl

_build_as_of_view_ddl(dataset, ts, temp_view)

test_time_travel

test_time_travel(dataset, entity_cols, all_keys, timestamps, n_runs=50)

A statement-level VALIDTIME AS OF prefix is ignored when the view bakes in CURRENT VALIDTIME, so we rewrite the view body into a temp view and query it.

_qrylog_columns

_qrylog_columns()

get_server_side_timings

get_server_side_timings(user, dataset, last_n=200)

Server execution time of the benchmark's OWN single-key lookups (scoped to the same query shape so client−server is a real overhead, not a mix).

_auto_entity_cols

_auto_entity_cols(dataset)

Derive the dataset's entity (grain) columns from the tdfs4ds catalog.

Requires an active teradataml context (the serve / programmatic case). Returns a list of column names, or None when it can't be resolved (the standalone CLI must then pass --entity-cols explicitly).

run_benchmark

run_benchmark(dataset, entity_cols=None, *, sample_size=10000, skip=(), batch_sizes=(1, 5, 10, 50, 100, 500), large_sizes=(10000, 100000, 1000000), strategy_sizes=(500, 5000, 50000), concurrency=(1, 2, 4), time_travel_dates=(), max_in_list_keys=None, max_parse_ms=DEFAULT_MAX_PARSE_MS, batch_pushdown=True, conn_kwargs=None, progress=None)

Run the benchmark against dataset and return the report dict.

Parameters

dataset : str Fully-qualified DB.dataset_view to benchmark. entity_cols : list[str] | None Grain/entity key columns. When None, auto-derived from the tdfs4ds dataset catalog (needs an active teradataml context). skip : set[str] Any of {'parse','explain','warm','cold','batch','large_batch', 'batch_strategies','concurrent','time_travel','server_timing'} to skip. max_parse_ms : float | None The parse phase runs FIRST, before anything executes the view: it times EXPLAIN SELECT * FROM <dataset> (parse + optimise, no execution). When its median exceeds max_parse_ms the run stops there — every request on such a view pays that cost before reading a row, so the latency phases would mostly measure parsing and could take very long. The report then explains why and what to try (the grouped strategy). Default 5000 ms; None or 0 never stops. strategy_sizes : tuple[int] Batch sizes at which the batch_strategies phase compares the in_list predicate vs the volatile_join retrieval side-by-side. max_in_list_keys : int | None Maximum number of keys put into an in_list predicate. When None it is inferred from the entity-column count as max(50, 500 // n_entity_cols) — wide composite keys produce large per-key disjuncts whose parse time dominates the measurement, so the cap keeps in_list batches cheap. It clamps batch_sizes and the in_list arm of batch_strategies ONLY; the volatile_join arm and large_batch keep their full sizes. batch_pushdown : bool True (default): the batch phases run what retrieve_batch runs - the dataset's query regenerated with the keys inside every storage-table read, so each FS_T_* table is read by primary index and only in the partitions the keys fall in. False: the previous SELECT * FROM <view> WHERE <keys> form, to measure the difference. conn_kwargs : dict | None Raw teradatasql kwargs; defaults to the TDFS4DS_TD_* environment. progress : callable(str) | None Receives one human-readable line per test phase (for a UI bar).

Returns the {"metadata":…, "results":…} report dict.

main

main()