Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/ml_ops/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,73 @@ To include soft-deleted records in the listing:
region="us-west-2",
)

**Feature-level writes with UpdateRecord (Standard_V2):**

``UpdateRecord`` performs a partial write to a record in a feature group whose online store uses
the ``Standard_V2`` or ``InMemory`` storage type. Only the features you supply are written; features
you do not list are preserved. This avoids the ``GetRecord`` -> merge -> ``PutRecord`` round trip and
prevents lost writes when independent pipelines own different features on the same record. The record
must already exist in the online store (use ``PutRecord`` to create it).

Create the feature group with ``Standard_V2`` storage (feature-level writes require ``Standard_V2``
or ``InMemory``; they are not supported on the default ``Standard`` tier):

.. code-block:: python

from sagemaker.mlops.feature_store import FeatureGroupManager, OnlineStoreStorageTypeEnum
from sagemaker.core.shapes import OnlineStoreConfig

feature_group = FeatureGroupManager.create(
feature_group_name="customer-features",
record_identifier_feature_name="customer_id",
event_time_feature_name="event_time",
feature_definitions=feature_definitions,
online_store_config=OnlineStoreConfig(
enable_online_store=True,
storage_type=OnlineStoreStorageTypeEnum.STANDARD_V2.value,
),
role_arn=role,
)

You can migrate an existing ``Standard`` feature group to ``Standard_V2`` with ``UpdateFeatureGroup``.
This migration is one-way and cannot be reversed:

.. code-block:: python

from sagemaker.core.resources import FeatureGroup
from sagemaker.core.shapes import OnlineStoreConfigUpdate

feature_group = FeatureGroup.get(feature_group_name="customer-features")
feature_group.update(
online_store_config=OnlineStoreConfigUpdate(storage_type="Standard_V2"),
)

Use ``update_record`` to write only the features that changed. Pass ``EventTime`` as a feature
(not a top-level parameter); features you do not include are preserved:

.. code-block:: python

from sagemaker.mlops.feature_store import update_record

update_record(
feature_group_name="customer-features",
record_identifier_value_as_string="cust-1",
features=[
{"feature_name": "purchase_count", "value_as_string": "11"},
{"feature_name": "event_time", "value_as_string": "2026-01-02T00:00:00Z"},
],
region="us-west-2",
)

Notes:

* Supply at most 100 features per call. If the supplied ``EventTime`` is not greater than the
record's current ``EventTime``, the update is rejected with a ``ConflictException``.
* ``ttl_duration`` requires the record's event-time feature to be present in ``features``.
``target_stores`` defaults to all stores on the feature group; a value resolving to the
``OfflineStore`` only is rejected.
* ``UpdateRecord`` is not supported on ``Standard`` (V1) feature groups.



Migration from V2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@
{"shape":"AccessForbidden"}
],
"documentation":"<p>The <code>PutRecord</code> API is used to ingest a list of <code>Records</code> into your feature group. </p> <p>If a new record’s <code>EventTime</code> is greater, the new record is written to both the <code>OnlineStore</code> and <code>OfflineStore</code>. Otherwise, the record is a historic record and it is written only to the <code>OfflineStore</code>. </p> <p>You can specify the ingestion to be applied to the <code>OnlineStore</code>, <code>OfflineStore</code>, or both by using the <code>TargetStores</code> request parameter. </p> <p>You can set the ingested record to expire at a given time to live (TTL) duration after the record’s event time, <code>ExpiresAt</code> = <code>EventTime</code> + <code>TtlDuration</code>, by specifying the <code>TtlDuration</code> parameter. A record level <code>TtlDuration</code> is set when specifying the <code>TtlDuration</code> parameter using the <code>PutRecord</code> API call. If the input <code>TtlDuration</code> is <code>null</code> or unspecified, <code>TtlDuration</code> is set to the default feature group level <code>TtlDuration</code>. A record level <code>TtlDuration</code> supersedes the group level <code>TtlDuration</code>.</p>"
},
"UpdateRecord":{
"name":"UpdateRecord",
"http":{
"method":"POST",
"requestUri":"/FeatureGroup/{FeatureGroupName}/Record"
},
"input":{"shape":"UpdateRecordRequest"},
"errors":[
{"shape":"ValidationError"},
{"shape":"InternalFailure"},
{"shape":"ServiceUnavailable"},
{"shape":"AccessForbidden"},
{"shape":"ResourceNotFound"},
{"shape":"ConflictException"}
],
"documentation":"<p>Updates one or more feature values for an existing record in the specified feature group. Features that you do not include in the request remain unchanged. You can update up to 100 features per call. This operation requires the online store and is available only for feature groups that use the <code>Standard_V2</code> or <code>InMemory</code> online store type.</p> <p>The record must already exist in the online store; if it does not exist or has been soft-deleted, the operation returns a <code>ResourceNotFound</code> error. To create a record, use <code>PutRecord</code>.</p> <p>Pass <code>EventTime</code> as a feature in the <code>Features</code> list rather than as a top-level parameter. If you provide an <code>EventTime</code> that is older than the record's current <code>EventTime</code>, the update is rejected with a <code>ConflictException</code>; if it is equal to or newer, the update is applied; if you omit it, the record's existing <code>EventTime</code> is kept. If you specify <code>TtlDuration</code> you must also provide an <code>EventTime</code>, otherwise a <code>ValidationError</code> is returned. <code>TargetStores</code> must include the <code>OnlineStore</code>; a value that resolves to the <code>OfflineStore</code> only is rejected.</p>"
}
},
"shapes":{
Expand All @@ -124,6 +141,15 @@
"exception":true,
"synthetic":true
},
"ConflictException":{
"type":"structure",
"members":{
"Message":{"shape":"Message"}
},
"documentation":"<p>The request conflicts with the current state of the record. This is returned by <code>UpdateRecord</code> when the supplied <code>EventTime</code> is not greater than the record's current <code>EventTime</code>.</p>",
"error":{"httpStatusCode":409},
"exception":true
},
"BatchGetRecordError":{
"type":"structure",
"required":[
Expand Down Expand Up @@ -662,6 +688,38 @@
"member":{"shape":"BatchGetRecordIdentifier"},
"min":0
},
"UpdateRecordRequest":{
"type":"structure",
"required":[
"FeatureGroupName",
"RecordIdentifierValueAsString",
"Features"
],
"members":{
"FeatureGroupName":{
"shape":"FeatureGroupNameOrArn",
"documentation":"<p>The name or Amazon Resource Name (ARN) of the feature group that contains the record you want to update. The feature group must use an <code>OnlineStoreConfig</code> <code>StorageType</code> of <code>Standard_V2</code> or <code>InMemory</code>.</p>",
"location":"uri",
"locationName":"FeatureGroupName"
},
"RecordIdentifierValueAsString":{
"shape":"ValueAsString",
"documentation":"<p>The value for the <code>RecordIdentifier</code> that uniquely identifies the record to update, in string format.</p>"
},
"Features":{
"shape":"Record",
"documentation":"<p>The feature values to write to the record. Only the features included here are written; features that are not listed are preserved. Pass <code>EventTime</code> as a feature in this list. You can update up to 100 features per call.</p>"
},
"TargetStores":{
"shape":"TargetStores",
"documentation":"<p>A list of stores to which the update is applied. By default, Feature Store applies the update to all of the stores that you're using for the <code>FeatureGroup</code>. A value that resolves to the <code>OfflineStore</code> only is rejected.</p>"
},
"TtlDuration":{
"shape":"TtlDuration",
"documentation":"<p>Time to live duration, where the record is hard deleted after the expiration time is reached; <code>ExpiresAt</code> = <code>EventTime</code> + <code>TtlDuration</code>. Specifying <code>TtlDuration</code> requires <code>EventTime</code> to be present in <code>Features</code>.</p>"
}
}
},
"ValidationError":{
"type":"structure",
"members":{
Expand Down
5 changes: 5 additions & 0 deletions sagemaker-core/sample/sagemaker/2017-07-24/service-2.json
Original file line number Diff line number Diff line change
Expand Up @@ -38648,6 +38648,10 @@
"TtlDuration":{
"shape":"TtlDuration",
"documentation":"<p>Time to live duration, where the record is hard deleted after the expiration time is reached; <code>ExpiresAt</code> = <code>EventTime</code> + <code>TtlDuration</code>. For information on HardDelete, see the <a href=\"https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_feature_store_DeleteRecord.html\">DeleteRecord</a> API in the Amazon SageMaker API Reference guide.</p>"
},
"StorageType":{
"shape":"StorageType",
"documentation":"<p>The online store storage type to migrate the feature group to. Use this parameter to migrate an existing feature group from <code>Standard</code> to <code>Standard_V2</code> storage format, enabling support for the <a href=\"https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_feature_store_UpdateRecord.html\">UpdateRecord</a> operation. Migration is a one-way operation and cannot be reversed.</p>"
}
},
"documentation":"<p>Updates the feature group online store configuration.</p>"
Expand Down Expand Up @@ -45368,6 +45372,7 @@
"type":"string",
"enum":[
"Standard",
"Standard_V2",
"InMemory"
]
},
Expand Down
58 changes: 58 additions & 0 deletions sagemaker-core/src/sagemaker/core/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -12282,6 +12282,64 @@ def put_record(
response = client.put_record(**operation_input_args)
logger.debug(f"Response: {response}")

@Base.add_validate_call
def update_record(
self,
record_identifier_value_as_string: StrPipeVar,
features: List[FeatureValue],
target_stores: Optional[List[StrPipeVar]] = Unassigned(),
ttl_duration: Optional[TtlDuration] = Unassigned(),
session: Optional[Session] = None,
region: Optional[str] = None,
) -> None:
"""
The UpdateRecord API performs a feature-level write to a Record in a feature group whose OnlineStoreConfig StorageType is Standard_V2 or InMemory. Only the supplied Features are written; features not included are preserved. The record must already exist in the online store.

Parameters:
record_identifier_value_as_string: The value for the RecordIdentifier that uniquely identifies the record to update, in string format.
features: The list of FeatureValues to update. Only the features included here are written; features that are not listed are preserved. Pass EventTime as a feature in this list. A maximum of 100 features can be updated in a single request.
target_stores: A list of stores to which the update is applied. By default, Feature Store applies the update to all of the stores that you're using for the FeatureGroup. A value that resolves to the OfflineStore only is rejected.
ttl_duration: Time to live duration, where the record is hard deleted after the expiration time is reached; ExpiresAt = EventTime + TtlDuration. Specifying TtlDuration requires EventTime to be present in Features.
session: Boto3 session.
region: Region name.

Raises:
botocore.exceptions.ClientError: This exception is raised for AWS service related errors.
The error message and error code can be parsed from the exception as follows:
```
try:
# AWS service call here
except botocore.exceptions.ClientError as e:
error_message = e.response['Error']['Message']
error_code = e.response['Error']['Code']
```
AccessForbidden: You do not have permission to perform an action.
ConflictException: There was a conflict when you attempted to modify a record; the supplied EventTime was not greater than the record's current EventTime.
InternalFailure: An internal failure occurred. Try your request again. If the problem persists, contact Amazon Web Services customer support.
ResourceNotFound: A resource that is required to perform an action was not found.
ServiceUnavailable: The service is currently unavailable.
ValidationError: There was an error validating your request.
"""

operation_input_args = {
"FeatureGroupName": self.feature_group_name,
"RecordIdentifierValueAsString": record_identifier_value_as_string,
"Features": features,
"TargetStores": target_stores,
"TtlDuration": ttl_duration,
}
# serialize the input request
operation_input_args = serialize(operation_input_args)
logger.debug(f"Serialized input request: {operation_input_args}")

client = Base.get_sagemaker_client(
session=session, region_name=region, service_name="sagemaker-featurestore-runtime"
)

logger.debug(f"Calling update_record API")
response = client.update_record(**operation_input_args)
logger.debug(f"Response: {response}")

@Base.add_validate_call
def delete_record(
self,
Expand Down
2 changes: 2 additions & 0 deletions sagemaker-core/src/sagemaker/core/shapes/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14626,9 +14626,11 @@ class OnlineStoreConfigUpdate(Base):
Attributes
----------------------
ttl_duration: Time to live duration, where the record is hard deleted after the expiration time is reached; ExpiresAt = EventTime + TtlDuration. For information on HardDelete, see the DeleteRecord API in the Amazon SageMaker API Reference guide.
storage_type: The online store storage type to migrate the feature group to. Use this parameter to migrate an existing feature group from Standard to Standard_V2 storage format, enabling support for the UpdateRecord operation. Migration is a one-way operation and cannot be reversed.
"""

ttl_duration: Optional[TtlDuration] = Unassigned()
storage_type: Optional[StrPipeVar] = Unassigned()


class Parent(Base):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,14 @@
"method_type": "object",
"service_name": "sagemaker-featurestore-runtime"
},
"UpdateRecord": {
"operation_name": "UpdateRecord",
"resource_name": "FeatureGroup",
"method_name": "update_record",
"return_type": "None",
"method_type": "object",
"service_name": "sagemaker-featurestore-runtime"
},
"DeleteRecord": {
"operation_name": "DeleteRecord",
"resource_name": "FeatureGroup",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14100,7 +14100,10 @@
"type": "structure",
},
"OnlineStoreConfigUpdate": {
"members": [{"name": "TtlDuration", "shape": "TtlDuration", "type": "structure"}],
"members": [
{"name": "TtlDuration", "shape": "TtlDuration", "type": "structure"},
{"name": "StorageType", "shape": "StorageType", "type": "string"},
],
"type": "structure",
},
"OnlineStoreSecurityConfig": {
Expand Down Expand Up @@ -18687,6 +18690,16 @@
],
"type": "structure",
},
"UpdateRecordRequest": {
"members": [
{"name": "FeatureGroupName", "shape": "FeatureGroupNameOrArn", "type": "string"},
{"name": "RecordIdentifierValueAsString", "shape": "ValueAsString", "type": "string"},
{"name": "Features", "shape": "Record", "type": "list"},
{"name": "TargetStores", "shape": "TargetStores", "type": "list"},
{"name": "TtlDuration", "shape": "TtlDuration", "type": "structure"},
],
"type": "structure",
},
"UpdateTrialComponentResponse": {
"members": [{"name": "TrialComponentArn", "shape": "TrialComponentArn", "type": "string"}],
"type": "structure",
Expand Down
Loading
Loading