Skip to content

Generate schemas as on-demand hydration from tuples - #841

Merged
kuhe merged 6 commits into
developfrom
kuhe/chore/schemas
Oct 13, 2026
Merged

kuhe merged 6 commits into
developfrom
kuhe/chore/schemas

Conversation

@kuhe

@kuhe kuhe commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

In the schemas.py file, generate schema declarations as a compact tuples instead of a constructor or factory invocation. This expands lazily to the Schema class object, but only on-use, rather than on import of the client.

The result is a reduction in the memory footprint of schemas (delayed init, compact standby mode), and faster startup.

Both the faster startup and the size-on-disk reduction help any caller, and usage on Lambda in particular.


Code example:

# compact tuple (new)
QUERY_INCOMPATIBLE_OPERATION_INPUT = (18, _s0, _s171, {_s72: _s73})

# Schema init (old)
QUERY_INCOMPATIBLE_OPERATION_INPUT = Schema(...)

effect on clients

client form whole-pkg .py size import+hydrate time peak mem (RSS Δ)
DDB baseline 3.08 MB 111 ms 14.3 MiB
compact 1.27 MB 104 ms 12.0 MiB
EC2 baseline 15.58 MB 736 ms 75.9 MiB
compact 11.83 MB 650 ms 62.8 MiB

picture of schemas.py only

client form size LOC exec time mem after load
DDB baseline 1.75 MB 31,821 44.9 ms 8.4 MiB
compact 0.16 MB 6,717 1.2 ms 0.8 MiB
EC2 baseline 3.75 MB 126,101 124.4 ms 27.8 MiB
compact 1.74 MB 87,510 12.1 ms 7.8 MiB

@kuhe
kuhe force-pushed the kuhe/chore/schemas branch 4 times, most recently from 8ffafa1 to 8dbd922 Compare October 9, 2026 19:55
@kuhe
kuhe marked this pull request as ready for review October 9, 2026 20:05
@kuhe
kuhe requested a review from a team as a code owner October 9, 2026 20:05
Comment on lines +54 to +57
reportUnknownVariableType = false
reportUnknownMemberType = false
reportUnknownArgumentType = false
reportUnknownLambdaType = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these needed and can we have more isolated suppressions (e.g. per-file)? Or does the entire package need them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into fixing, since I'd rather not add suppressions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suppressions are now inline

Comment thread packages/smithy-core/src/smithy_core/_schema_compact.py Outdated
Comment thread packages/smithy-core/src/smithy_core/_schema_compact.py
traits[tid] = Trait.new(tid)
bit >>= 1
i += 1
_traits_cache[indicator] = traits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is shared across schemas- would it be possible to return a copy or cache something immutable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already marked private - what is the scenario in which something mutates this incorrectly?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a comment on Schema.traits- the private cached dict escapes through this public field, so mutating one schema can affect unrelated schemas

Comment thread packages/smithy-core/src/smithy_core/_schema_compact.py
Comment thread packages/smithy-core/src/smithy_core/schemas.py
@kuhe
kuhe force-pushed the kuhe/chore/schemas branch 2 times, most recently from a39faa4 to 63a6307 Compare October 13, 2026 16:06
kuhe added 5 commits October 13, 2026 20:08
…wide

Replace the four reportUnknown* settings in the generated pyproject with
inline # pyright: ignore comments emitted only on the recursive/forward-ref
schema lines that pyright cannot type (the lambda member targets and the
assignment lines holding them). Keeps strict type checking everywhere else in
the generated client.
Use a thread-local in-progress map to break reference cycles instead of publishing an empty schema to the shared cache, so a reader never sees a half-built schema and a failed hydration leaves nothing cached. Add schema hydration tests.
@kuhe
kuhe force-pushed the kuhe/chore/schemas branch from c17caa2 to 0fc8991 Compare October 13, 2026 20:08
finally:
del stack[key]

_cache[key] = (data, schema)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this still looks like it can publish part of a recursive graph too early. if A → B → A, B can be cached while A is still being populated and another thread can cache an incomplete members_by_index for A. can we wait until the outermost hydration finishes before publishing anything from the graph?

@jonathan343 jonathan343 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two quick questions, not really suggestions. Otherwise looks good once Seans concerns are addressed

}
}

private int memberIndex(Shape parent) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - can this be shared? Or is it just easier to have the duplication? Commenting since I noticed this is repeated in the deserializer above

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be DRYed, yeah

@cached_property
def schema(self) -> Schema:
"""The schema of the operation."""
from ._schema_compact import hydrate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the imports here and not at the top of the file? Want to check my understanding

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a cycle-breaker.

stepping back a bit, I don't like how files have multiple exported symbols. I think class Schema should be in Schema.py and class APIOperation should be in APIOperation.py, but I'm leaving that existing style alone for now.

Hold every node of a connected schema graph out of the shared hydrate cache
until the outermost frame completes, so a cross-referenced sibling is never
published pointing at a half-built node. Extract AbstractMemberGenerator for
the writeSchema/memberIndex logic shared by the member ser/deser generators.

@stobrien89 stobrien89 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@kuhe
kuhe merged commit ad7cb7c into develop Oct 13, 2026
11 checks passed
@kuhe
kuhe deleted the kuhe/chore/schemas branch October 13, 2026 22:40
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.

3 participants