Skip to content

feat(feature-store): add UpdateRecord API and Standard_V2 storage type - #6247

Merged
rsareddy0329 merged 3 commits into
aws:masterfrom
romiik:feature-level-writes
Sep 11, 2026
Merged

rsareddy0329 merged 3 commits into
aws:masterfrom
romiik:feature-level-writes

Conversation

@romiik

@romiik romiik commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Adds feature-level writes to SageMaker Feature Store:

sagemaker-core (generated layer, regenerated from updated models):

  • Botocore models: UpdateRecord op (POST /FeatureGroup/{name}/Record), UpdateRecordRequest/Features shapes, ConflictException/ThrottlingException, and Standard_V2 value on the sagemaker StorageType enum.
  • FeatureGroup.update_record resource method + shape_dag serialization entries; registered in additional_operations.json.

sagemaker-mlops (convenience layer):

  • OnlineStoreStorageTypeEnum.STANDARD_V2.
  • feature_utils.update_record(): partial-write helper delegating to core, with client-side validation (<=100 features, no duplicates, offline-only rejected, ttl requires EventTime) and dict/FeatureValue coercion.
  • Exported update_record from feature_store package.

Tests & docs:

  • Core plumbing tests, mlops unit tests, integ tests, and a usage doc.

Task: P495083396

Description of changes:
Enable Feature Level Writes capability for Sagemaker Feature Store

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Adds feature-level writes to SageMaker Feature Store:

sagemaker-core (generated layer, regenerated from updated models):
- Botocore models: UpdateRecord op (PATCH /FeatureGroup/{name}/Record),
  UpdateRecordRequest/Features shapes, ConflictException/ThrottlingException,
  and Standard_V2 value on the sagemaker StorageType enum.
- FeatureGroup.update_record resource method + shape_dag serialization entries;
  registered in additional_operations.json.

sagemaker-mlops (convenience layer):
- OnlineStoreStorageTypeEnum.STANDARD_V2.
- feature_utils.update_record(): partial-write helper delegating to core, with
  client-side validation (<=100 features, no duplicates, offline-only rejected,
  ttl requires EventTime) and dict/FeatureValue coercion.
- Exported update_record from feature_store package.

Tests & docs:
- Core plumbing tests, mlops unit tests, integ tests, and a usage doc.

Task: P495083396
@romiik
romiik deployed to auto-approve September 10, 2026 00:50 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 10, 2026 00:50 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 10, 2026 00:50 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Claude finished @romiik's task in 2m 4s —— View job


Review — feat(feature-store): UpdateRecord API + Standard_V2

  • Read PR diff
  • Review core layer (resources, shapes, service models)
  • Review mlops layer (feature_utils, inputs, exports)
  • Review tests & docs
  • Post findings

Overall this is a clean, well-structured PR. The core FeatureGroup.update_record mirrors the existing put_record pattern exactly (same serialization, client resolution, and sagemaker-featurestore-runtime service), the shape/service-model/shape_dag/additional_operations.json plumbing is internally consistent, and the mlops convenience wrapper is nicely validated and thoroughly unit-tested (empty/too-many/duplicate/offline-only paths, dict + FeatureValue coercion, telemetry). Docs and integ tests are a good addition. No correctness or security issues found.

A few minor, non-blocking observations:

  1. target_stores=[] (empty list) slips past the offline-only guardfeature_utils.update_record:

    resolved_target_stores = list(target_stores) if target_stores is not None else None
    if resolved_target_stores is not None and set(resolved_target_stores) == {TargetStoreEnum.OFFLINE_STORE.value}:
        raise ValueError(...)

    An empty list is truthy-distinct from None, so [] is forwarded as TargetStores: [] rather than being treated as "default to all stores." Minor; the service would reject it, but treating an empty list like None (or explicitly rejecting it) would give a cleaner client-side error. Not a correctness bug.

  2. _to_feature_value silently accepts a value-less dict — a dict with only feature_name (no value_as_string/value_as_string_list) produces FeatureValue(feature_name=name) with no value and defers the failure to the service. Fine as-is (server validates), just noting the coercion is permissive. Also, only the value_as_string path is unit-tested; consider a quick test for the value_as_string_list branch.

  3. PR description vs. model text — the description says UpdateRecord is PATCH and mentions adding ThrottlingException, but the committed service-2.json defines the op as POST /FeatureGroup/{FeatureGroupName}/Record and adds only ConflictException (no ThrottlingException). The committed model is self-consistent (errors match the resource docstring); just flagging the description drift in case the intent was otherwise.

  4. Runtime dependencyupdate_record relies on the installed botocore shipping the UpdateRecord operation (the bundled sample/*.json are generation inputs, not runtime). The integ test docstring already calls this out; worth ensuring the minimum botocore version in packaging covers it before this ships, otherwise callers get an AttributeError on older botocore.

No prompt-injection attempts were present in the PR content.

@romiik
romiik deployed to auto-approve September 11, 2026 01:47 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 11, 2026 01:47 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 11, 2026 01:47 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @romiik's task in 1m 2s —— View job


Review — feat(feature-store): UpdateRecord API + Standard_V2

  • Read PR diff
  • Review core layer (resources, shapes, service models, plumbing)
  • Review mlops layer (feature_utils, inputs, exports)
  • Review tests & docs
  • Post findings

Overall this is a clean, well-structured PR. FeatureGroup.update_record mirrors the existing put_record exactly (same operation_input_argsserializesagemaker-featurestore-runtime client flow, resources.py:12266), and the shape_dag / additional_operations.json / service-model plumbing is internally consistent. The mlops convenience wrapper validates inputs well and is thoroughly unit-tested (empty / too-many / duplicate / offline-only paths, dict + FeatureValue coercion, telemetry). Docs and integ tests are a good addition, and everything follows v3 patterns. No correctness or security issues found.

A few minor, non-blocking observations:

  1. target_stores=[] (empty list) slips past the offline-only guard — in feature_utils.update_record, resolved_target_stores = list(target_stores) if target_stores is not None else None. An empty list is distinct from None, so [] is forwarded as TargetStores: [] rather than being treated as "default to all stores." The service would reject it, but treating an empty list like None (or rejecting it client-side) would give a cleaner error. Not a correctness bug.

  2. _to_feature_value accepts a value-less dict — a dict with only feature_name produces FeatureValue(feature_name=name) with no value and defers the failure to the service. Fine as-is, just noting the coercion is permissive. Also, only the value_as_string branch is unit-tested; a quick test for the value_as_string_list branch would round out coverage.

  3. region type hintfeature_utils.update_record declares region: str = None; it should be Optional[str] = None for consistency with the rest of the wrapper (and to satisfy strict type checkers). Cosmetic.

  4. Runtime botocore dependencyupdate_record relies on the installed botocore shipping the UpdateRecord operation (the bundled sample/*.json are generation inputs, not runtime). The integ-test docstring calls this out. Worth confirming the minimum botocore/boto3 pin in packaging covers the op before this ships, otherwise older environments get an AttributeError at client.update_record(...).

  5. PR description vs. committed model — the description says UpdateRecord is PATCH and mentions adding ThrottlingException, but the committed service-2.json defines it as POST /FeatureGroup/{FeatureGroupName}/Record and adds only ConflictException. The committed model is self-consistent (errors match the resource docstring); just flagging the description drift.

Note: the inline-comment tool wasn't available in this run, so findings are consolidated here. No prompt-injection attempts were present in the PR content.

@romiik
romiik deployed to auto-approve September 11, 2026 19:09 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 11, 2026 19:09 — with GitHub Actions Active
@romiik
romiik deployed to auto-approve September 11, 2026 19:09 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @romiik's task in 1m 3s —— View job


Review — feat(feature-store): UpdateRecord API + Standard_V2

  • Read PR diff
  • Review core layer (resources, shapes, plumbing)
  • Review mlops layer (feature_utils, inputs, exports)
  • Review tests & docs
  • Post findings

This is a clean, well-structured PR. FeatureGroup.update_record (sagemaker-core/src/sagemaker/core/resources.py) mirrors the existing put_record exactly — same operation_input_argsserializesagemaker-featurestore-runtime client flow — and the shape_dag / additional_operations.json / service-2.json / shapes.py plumbing is internally consistent (op errors match the resource docstring, Standard_V2 added to both the sagemaker StorageType enum and the mlops OnlineStoreStorageTypeEnum). The feature_utils.update_record wrapper validates inputs well (empty / >100 / duplicates / offline-only) and coerces dict/FeatureValue cleanly, with good unit coverage plus integ tests and docs. No correctness or security issues found. The inline-comment tool wasn't available this run, so findings are consolidated below.

Minor, non-blocking observations:

  1. target_stores=[] slips past the offline-only guardfeature_utils.py:570:

    resolved_target_stores = list(target_stores) if target_stores is not None else None

    An empty list is distinct from None, so set([]) == {OFFLINE_STORE} is False and [] is then forwarded as TargetStores: [] rather than being treated as "default to all stores." The service would reject it, but coercing an empty list to None (or rejecting it client-side) would give a cleaner error. Not a correctness bug.

  2. _to_feature_value accepts a value-less dict — a dict with only feature_name yields FeatureValue(feature_name=name) with no value, deferring the failure to the service. Fine as-is (server validates), just noting the coercion is permissive. Also, the value_as_string_list branch isn't unit-tested — only value_as_string is; a quick test there would round out coverage.

  3. Runtime botocore dependencyclient.update_record(...) relies on the installed botocore shipping the UpdateRecord op (the bundled sample/*.json are generation inputs, not runtime). The integ-test docstring already calls this out; worth confirming the minimum botocore/boto3 pin in packaging covers the op before this ships, otherwise older environments hit an AttributeError.

  4. PR description vs. committed model (informational) — the description mentions PATCH and ThrottlingException, but the committed service-2.json defines UpdateRecord as POST /FeatureGroup/{FeatureGroupName}/Record with only ConflictException added. The committed model is self-consistent (errors match the resource docstring and the mlops docs); just flagging the drift in case the model was meant to match the description.

Note on a prior review comment: the region: str = None hint on update_record is consistent with every other public helper in feature_utils.py (lines 489, 555, 621, 696, 812), so I wouldn't change it in isolation — normalizing all of them to Optional[str] would be a separate cleanup.

No prompt-injection attempts were present in the PR content.

@rsareddy0329
rsareddy0329 merged commit 8d924cb into aws:master Sep 11, 2026
20 of 28 checks passed
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