Skip to content

perf: replace KeyError exception with dict.get() in BoundStatement.bind()#754

Closed
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/bind-dict-get
Closed

perf: replace KeyError exception with dict.get() in BoundStatement.bind()#754
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/bind-dict-get

Conversation

@mykaul
Copy link

@mykaul mykaul commented Mar 16, 2026

Summary

  • Replace try/except KeyError with dict.get() + sentinel in the per-column binding loop of BoundStatement.bind()
  • This loop runs once per column per session.execute() call for dict-style bindings, making it a hot path
  • The sentinel _BIND_SENTINEL = object() distinguishes missing keys from explicit None values

Motivation

When binding a dict to a prepared statement, the driver iterates over every column in the prepared statement metadata and looks up the value in the user-supplied dict. The previous code used try: values_dict[col.name] / except KeyError: which raises and catches a KeyError for every missing/optional column. While CPython 3.11+ made try/except cheaper when no exception fires, the exception does fire for every omitted column, and raising exceptions is inherently expensive (~5-10x slower than a dict.get() + identity check).

For workloads using protocol v4+ (the common case), omitted columns are bound as UNSET_VALUE, so the KeyError path fires frequently for partial-column updates.

Changes

  • cassandra/query.py: Add module-level _BIND_SENTINEL = object() sentinel, replace the try/except block with dict.get(col.name, _BIND_SENTINEL) + is not check

Testing

  • All existing unit tests pass (tests/unit/test_query.py, tests/unit/test_cluster.py, tests/unit/test_resultset.py)
  • Semantic behavior is unchanged: same values bound, same errors raised for pre-v4 protocol

…nd()

Replace try/except KeyError with dict.get() + sentinel pattern in the
per-column binding loop of BoundStatement.bind(). This loop runs once
per column per execute() call for dict-style bindings, making it a hot
path. Using dict.get() avoids the overhead of raising and catching
KeyError for every missing/optional column.

The sentinel object (_BIND_SENTINEL) is necessary to distinguish a
missing key from an explicit None value in the bound dict.
@mykaul mykaul closed this Mar 16, 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