Skip to content

(improvement) cache namedtuple class in named_tuple_factory to avoid repeated exec() calls#738

Closed
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/cache-named-tuple-factory
Closed

(improvement) cache namedtuple class in named_tuple_factory to avoid repeated exec() calls#738
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/cache-named-tuple-factory

Conversation

@mykaul

@mykaul mykaul commented Mar 13, 2026

Copy link
Copy Markdown

Summary

  • Cache the Row namedtuple class in named_tuple_factory() keyed on tuple(colnames), so Python's namedtuple() (which internally calls exec()) is only invoked once per unique column schema

Motivation

named_tuple_factory is the default row_factory in the driver. Every call to namedtuple('Row', columns) internally calls exec() to generate a new class — this is surprisingly expensive. For prepared statements executing the same query repeatedly, the column names never change, yet we pay the namedtuple() + exec() cost on every result set.

Benchmark results

100-row result sets (5 columns):

Variant Min Mean Median Ops/sec
Cached 18.6 us 20.3 us 19.7 us 49,200/s
Uncached 78.9 us 83.7 us 81.6 us 11,900/s

100-row result sets (20 columns):

Variant Min Mean Median Ops/sec
Cached 55.1 us 63.3 us 59.3 us 15,800/s
Uncached 126.3 us 167.9 us 140.3 us 5,960/s

Single-row result (5 columns, isolates cache overhead):

Variant Min Mean Median Ops/sec
Cached 357 ns 415 ns 393 ns 2,410 Kops/s
Uncached 57.5 us 65.1 us 62.2 us 15.4 Kops/s

The single-row case shows the true per-call savings most clearly: 415 ns (dict lookup) vs 65 us (exec()-based namedtuple creation).

Design notes

  • Cache is a plain dict keyed on tuple(colnames) (the raw column names before cleaning)
  • Error handling paths (SyntaxError, Exception) are preserved — on SyntaxError the function falls back to pseudo_namedtuple_factory without caching; on other exceptions, sanitized identifiers are used and the result is cached
  • Cache is naturally bounded by the number of distinct column-name tuples, which is bounded by the number of distinct queries — typically tens to low hundreds in a real application

Tests

All existing unit tests pass (16 row factory + resultset tests, 30 cluster tests).

…repeated exec() calls

Cache the Row namedtuple class keyed on column names so that repeated
queries with the same schema skip the expensive namedtuple() call (which
internally uses exec()). This reduces per-result-set overhead by 3-5x
for typical column counts, and up to 157x for single-row results where
class creation dominates.
@mykaul mykaul closed this Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant