Skip to content

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

Open
romiik wants to merge 1 commit into
aws:masterfrom
romiik:feature-level-writes
Open

feat(feature-store): add UpdateRecord API and Standard_V2 storage type#6247
romiik wants to merge 1 commit into
aws:masterfrom
romiik:feature-level-writes

Conversation

@romiik

@romiik romiik commented Sep 10, 2026

Copy link
Copy Markdown

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

Issue #, if available:

Description of changes:

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.

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