머신러닝 피처 스토어 시작하기
머신러닝 피처 스토어 시작하기
이 코드 예제에서는 AWS CLI로 Amazon SageMaker Feature Store에 피처 그룹(feature group)을 만들고 데이터를 넣는 방법을 보여드려요.
출처: 문서
본문
다음 작업을 다루어요.
- IAM 권한 설정
- SageMaker 실행 역할 생성
- 피처 그룹 생성
- 데이터 수집(ingest) 및 조회
- 리소스 정리
Bash (AWS CLI with Bash script)
Note GitHub에 더 많은 내용이 있어요. 전체 예제와 설정·실행 방법은 Sample developer tutorials 저장소에서 확인할 수 있어요.
이 스크립트는 SageMaker 실행 역할과 S3 버킷을 만들고, customers·orders 피처 그룹을 생성해 데이터를 수집하고 조회해요.
#!/bin/bash
# Amazon SageMaker Feature Store Tutorial Script - Version 3
# This script demonstrates how to use Amazon SageMaker Feature Store with AWS CLI
# Setup logging
LOG_FILE="sagemaker-featurestore-tutorial.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "Starting SageMaker Feature Store tutorial script at $(date)"
echo "All commands and outputs will be logged to $LOG_FILE"
echo ""
# Track created resources for cleanup
CREATED_RESOURCES=()
# Function to handle errors
handle_error() {
echo "ERROR: $1"
echo "Attempting to clean up resources..."
cleanup_resources
exit 1
}
# Function to check command status
check_status() {
if echo "$1" | grep -i "error" > /dev/null; then
handle_error "$1"
fi
}
# Function to wait for feature group to be created
wait_for_feature_group() {
local feature_group_name=$1
local status="Creating"
echo "Waiting for feature group ${feature_group_name} to be created..."
while [ "$status" = "Creating" ]; do
sleep 5
status=$(aws sagemaker describe-feature-group \
--feature-group-name "${feature_group_name}" \
--query 'FeatureGroupStatus' \
--output text)
echo "Current status: ${status}"
if [ "$status" = "Failed" ]; then
handle_error "Feature group ${feature_group_name} creation failed"
fi
done
echo "Feature group ${feature_group_name} is now ${status}"
}
# Function to clean up resources
cleanup_resources() {
echo "Cleaning up resources..."
# Clean up in reverse order
for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do
resource="${CREATED_RESOURCES[$i]}"
resource_type=$(echo "$resource" | cut -d: -f1)
resource_name=$(echo "$resource" | cut -d: -f2)
echo "Deleting $resource_type: $resource_name"
case "$resource_type" in
"FeatureGroup")
aws sagemaker delete-feature-group --feature-group-name "$resource_name"
;;
"S3Bucket")
echo "Emptying S3 bucket: $resource_name"
aws s3 rm "s3://$resource_name" --recursive 2>/dev/null
echo "Deleting S3 bucket: $resource_name"
aws s3api delete-bucket --bucket "$resource_name" 2>/dev/null
;;
"IAMRole")
echo "Detaching policies from role: $resource_name"
aws iam detach-role-policy --role-name "$resource_name" --policy-arn "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess" 2>/dev/null
aws iam detach-role-policy --role-name "$resource_name" --policy-arn "arn:aws:iam::aws:policy/AmazonS3FullAccess" 2>/dev/null
echo "Deleting IAM role: $resource_name"
aws iam delete-role --role-name "$resource_name" 2>/dev/null
;;
*)
echo "Unknown resource type: $resource_type"
;;
esac
done
}
# Function to create SageMaker execution role
create_sagemaker_role() {
local role_name="SageMakerFeatureStoreRole-$(openssl rand -hex 4)"
echo "Creating SageMaker execution role: $role_name" >&2
# Create trust policy document
local trust_policy='{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "sagemaker.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
# Create the role
local role_result=$(aws iam create-role \
--role-name "$role_name" \
--assume-role-policy-document "$trust_policy" \
--description "SageMaker execution role for Feature Store tutorial" 2>&1)
if echo "$role_result" | grep -i "error" > /dev/null; then
handle_error "Failed to create IAM role: $role_result"
fi
echo "Role created successfully" >&2
CREATED_RESOURCES+=("IAMRole:$role_name")
# Tag the role
echo "Tagging IAM role..." >&2
aws iam tag-role --role-name "$role_name" --tags Key=project,Value=doc-smith Key=tutorial,Value=sagemaker-featurestore 2>&1
# Attach necessary policies
echo "Attaching policies to role..." >&2
# SageMaker execution policy
local policy1_result=$(aws iam attach-role-policy \
--role-name "$role_name" \
--policy-arn "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess" 2>&1)
if echo "$policy1_result" | grep -i "error" > /dev/null; then
handle_error "Failed to attach SageMaker policy: $policy1_result"
fi
# S3 access policy
local policy2_result=$(aws iam attach-role-policy \
--role-name "$role_name" \
--policy-arn "arn:aws:iam::aws:policy/AmazonS3FullAccess" 2>&1)
if echo "$policy2_result" | grep -i "error" > /dev/null; then
handle_error "Failed to attach S3 policy: $policy2_result"
fi
# Get account ID for role ARN
local account_id=$(aws sts get-caller-identity --query Account --output text)
local role_arn="arn:aws:iam::${account_id}:role/${role_name}"
echo "Role ARN: $role_arn" >&2
echo "Waiting 10 seconds for role to propagate..." >&2
sleep 10
# Return only the role ARN to stdout
echo "$role_arn"
}
# Handle SageMaker execution role
ROLE_ARN=""
if [ -z "$1" ]; then
echo "Creating SageMaker execution role automatically..."
ROLE_ARN=$(create_sagemaker_role)
if [ -z "$ROLE_ARN" ]; then
handle_error "Failed to create SageMaker execution role"
fi
else
ROLE_ARN="$1"
# Validate the role ARN
ROLE_NAME=$(echo "$ROLE_ARN" | sed 's/.*role\///')
ROLE_CHECK=$(aws iam get-role --role-name "$ROLE_NAME" 2>&1)
if echo "$ROLE_CHECK" | grep -i "error" > /dev/null; then
echo "Creating a new role automatically..."
ROLE_ARN=$(create_sagemaker_role)
if [ -z "$ROLE_ARN" ]; then
handle_error "Failed to create SageMaker execution role"
fi
fi
fi
# Handle cleanup option
AUTO_CLEANUP=""
if [ -n "$2" ]; then
AUTO_CLEANUP="$2"
fi
# Generate a random identifier for resource names
RANDOM_ID=$(openssl rand -hex 4)
echo "Using random identifier: $RANDOM_ID"
# Set variables
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
check_status "$ACCOUNT_ID"
echo "Account ID: $ACCOUNT_ID"
# Get current region
REGION=$(aws configure get region)
if [ -z "$REGION" ]; then
REGION="us-east-1"
echo "No default region configured, using: $REGION"
else
echo "Using region: $REGION"
fi
# Check for shared prereq bucket
PREREQ_BUCKET=$(aws cloudformation describe-stacks --stack-name tutorial-prereqs-bucket \
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text 2>/dev/null)
if [ -n "$PREREQ_BUCKET" ] && [ "$PREREQ_BUCKET" != "None" ]; then
S3_BUCKET_NAME="$PREREQ_BUCKET"
BUCKET_IS_SHARED=true
echo "Using shared bucket: $S3_BUCKET_NAME"
else
BUCKET_IS_SHARED=false
S3_BUCKET_NAME="sagemaker-featurestore-${RANDOM_ID}-${ACCOUNT_ID}"
fi
PREFIX="featurestore-tutorial"
CURRENT_TIME=$(date +%s)
echo "Creating S3 bucket: $S3_BUCKET_NAME"
# Create bucket in current region (skip if using shared bucket)
if [ "$BUCKET_IS_SHARED" = "false" ]; then
if [ "$REGION" = "us-east-1" ]; then
BUCKET_RESULT=$(aws s3api create-bucket --bucket "$S3_BUCKET_NAME" \
--region "$REGION" 2>&1)
else
BUCKET_RESULT=$(aws s3api create-bucket --bucket "$S3_BUCKET_NAME" \
--region "$REGION" \
--create-bucket-configuration LocationConstraint="$REGION" 2>&1)
fi
if echo "$BUCKET_RESULT" | grep -i "error" > /dev/null; then
echo "Failed to create S3 bucket: $BUCKET_RESULT"
exit 1
fi
echo "$BUCKET_RESULT"
CREATED_RESOURCES+=("S3Bucket:$S3_BUCKET_NAME")
# Tag the S3 bucket
echo "Tagging S3 bucket: $S3_BUCKET_NAME"
aws s3api put-bucket-tagging --bucket "$S3_BUCKET_NAME" --tagging 'TagSet=[{Key=project,Value=doc-smith},{Key=tutorial,Value=sagemaker-featurestore}]' 2>&1
# Block public access to the bucket
BLOCK_RESULT=$(aws s3api put-public-access-block \
--bucket "$S3_BUCKET_NAME" \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" 2>&1)
if echo "$BLOCK_RESULT" | grep -i "error" > /dev/null; then
echo "Failed to block public access to S3 bucket: $BLOCK_RESULT"
cleanup_resources
exit 1
fi
else
echo "Using shared bucket (skipping creation)"
fi
# Create feature groups
echo "Creating feature groups..."
# Create customers feature group
CUSTOMERS_FEATURE_GROUP_NAME="customers-feature-group-${RANDOM_ID}"
echo "Creating customers feature group: $CUSTOMERS_FEATURE_GROUP_NAME"
CUSTOMERS_RESPONSE=$(aws sagemaker create-feature-group \
--feature-group-name "$CUSTOMERS_FEATURE_GROUP_NAME" \
--record-identifier-feature-name "customer_id" \
--event-time-feature-name "EventTime" \
--feature-definitions '[
{"FeatureName": "customer_id", "FeatureType": "Integral"},
{"FeatureName": "name", "FeatureType": "String"},
{"FeatureName": "age", "FeatureType": "Integral"},
{"FeatureName": "address", "FeatureType": "String"},
{"FeatureName": "membership_type", "FeatureType": "String"},
{"FeatureName": "EventTime", "FeatureType": "Fractional"}
]' \
--online-store-config '{"EnableOnlineStore": true}' \
--offline-store-config '{
"S3StorageConfig": {
"S3Uri": "s3://'${S3_BUCKET_NAME}'/'${PREFIX}'"
},
"DisableGlueTableCreation": false
}' \
--role-arn "$ROLE_ARN" \
--tags Key=project,Value=doc-smith Key=tutorial,Value=sagemaker-featurestore 2>&1)
if echo "$CUSTOMERS_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to create customers feature group: $CUSTOMERS_RESPONSE"
cleanup_resources
exit 1
fi
echo "$CUSTOMERS_RESPONSE"
CREATED_RESOURCES+=("FeatureGroup:$CUSTOMERS_FEATURE_GROUP_NAME")
# Create orders feature group
ORDERS_FEATURE_GROUP_NAME="orders-feature-group-${RANDOM_ID}"
echo "Creating orders feature group: $ORDERS_FEATURE_GROUP_NAME"
ORDERS_RESPONSE=$(aws sagemaker create-feature-group \
--feature-group-name "$ORDERS_FEATURE_GROUP_NAME" \
--record-identifier-feature-name "customer_id" \
--event-time-feature-name "EventTime" \
--feature-definitions '[
{"FeatureName": "customer_id", "FeatureType": "Integral"},
{"FeatureName": "order_id", "FeatureType": "String"},
{"FeatureName": "order_date", "FeatureType": "String"},
{"FeatureName": "product", "FeatureType": "String"},
{"FeatureName": "quantity", "FeatureType": "Integral"},
{"FeatureName": "amount", "FeatureType": "Fractional"},
{"FeatureName": "EventTime", "FeatureType": "Fractional"}
]' \
--online-store-config '{"EnableOnlineStore": true}' \
--offline-store-config '{
"S3StorageConfig": {
"S3Uri": "s3://'${S3_BUCKET_NAME}'/'${PREFIX}'"
},
"DisableGlueTableCreation": false
}' \
--role-arn "$ROLE_ARN" \
--tags Key=project,Value=doc-smith Key=tutorial,Value=sagemaker-featurestore 2>&1)
if echo "$ORDERS_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to create orders feature group: $ORDERS_RESPONSE"
cleanup_resources
exit 1
fi
echo "$ORDERS_RESPONSE"
CREATED_RESOURCES+=("FeatureGroup:$ORDERS_FEATURE_GROUP_NAME")
# Wait for feature groups to be created
wait_for_feature_group "$CUSTOMERS_FEATURE_GROUP_NAME"
wait_for_feature_group "$ORDERS_FEATURE_GROUP_NAME"
# Ingest data into feature groups
echo "Ingesting data into feature groups..."
# Ingest customer data
echo "Ingesting customer data..."
CUSTOMER1_RESPONSE=$(aws sagemaker-featurestore-runtime put-record \
--feature-group-name "$CUSTOMERS_FEATURE_GROUP_NAME" \
--record '[
{"FeatureName": "customer_id", "ValueAsString": "573291"},
{"FeatureName": "name", "ValueAsString": "John Doe"},
{"FeatureName": "age", "ValueAsString": "35"},
{"FeatureName": "address", "ValueAsString": "123 Main St"},
{"FeatureName": "membership_type", "ValueAsString": "premium"},
{"FeatureName": "EventTime", "ValueAsString": "'${CURRENT_TIME}'"}
]' 2>&1)
if echo "$CUSTOMER1_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to ingest customer 1 data: $CUSTOMER1_RESPONSE"
cleanup_resources
exit 1
fi
echo "$CUSTOMER1_RESPONSE"
CUSTOMER2_RESPONSE=$(aws sagemaker-featurestore-runtime put-record \
--feature-group-name "$CUSTOMERS_FEATURE_GROUP_NAME" \
--record '[
{"FeatureName": "customer_id", "ValueAsString": "109382"},
{"FeatureName": "name", "ValueAsString": "Jane Smith"},
{"FeatureName": "age", "ValueAsString": "28"},
{"FeatureName": "address", "ValueAsString": "456 Oak Ave"},
{"FeatureName": "membership_type", "ValueAsString": "standard"},
{"FeatureName": "EventTime", "ValueAsString": "'${CURRENT_TIME}'"}
]' 2>&1)
if echo "$CUSTOMER2_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to ingest customer 2 data: $CUSTOMER2_RESPONSE"
cleanup_resources
exit 1
fi
echo "$CUSTOMER2_RESPONSE"
# Ingest order data
echo "Ingesting order data..."
ORDER1_RESPONSE=$(aws sagemaker-featurestore-runtime put-record \
--feature-group-name "$ORDERS_FEATURE_GROUP_NAME" \
--record '[
{"FeatureName": "customer_id", "ValueAsString": "573291"},
{"FeatureName": "order_id", "ValueAsString": "ORD-001"},
{"FeatureName": "order_date", "ValueAsString": "2023-01-15"},
{"FeatureName": "product", "ValueAsString": "Laptop"},
{"FeatureName": "quantity", "ValueAsString": "1"},
{"FeatureName": "amount", "ValueAsString": "1299.99"},
{"FeatureName": "EventTime", "ValueAsString": "'${CURRENT_TIME}'"}
]' 2>&1)
if echo "$ORDER1_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to ingest order 1 data: $ORDER1_RESPONSE"
cleanup_resources
exit 1
fi
echo "$ORDER1_RESPONSE"
ORDER2_RESPONSE=$(aws sagemaker-featurestore-runtime put-record \
--feature-group-name "$ORDERS_FEATURE_GROUP_NAME" \
--record '[
{"FeatureName": "customer_id", "ValueAsString": "109382"},
{"FeatureName": "order_id", "ValueAsString": "ORD-002"},
{"FeatureName": "order_date", "ValueAsString": "2023-01-20"},
{"FeatureName": "product", "ValueAsString": "Smartphone"},
{"FeatureName": "quantity", "ValueAsString": "1"},
{"FeatureName": "amount", "ValueAsString": "899.99"},
{"FeatureName": "EventTime", "ValueAsString": "'${CURRENT_TIME}'"}
]' 2>&1)
if echo "$ORDER2_RESPONSE" | grep -i "error" > /dev/null; then
echo "Failed to ingest order 2 data: $ORDER2_RESPONSE"
cleanup_resources
exit 1
fi
echo "$ORDER2_RESPONSE"
# Retrieve records from feature groups
echo "Retrieving records from feature groups..."
# Get a single customer record
echo "Getting customer record with ID 573291:"
CUSTOMER_RECORD=$(aws sagemaker-featurestore-runtime get-record \
--feature-group-name "$CUSTOMERS_FEATURE_GROUP_NAME" \
--record-identifier-value-as-string "573291" 2>&1)
if echo "$CUSTOMER_RECORD" | grep -i "error" > /dev/null; then
echo "Failed to get customer record: $CUSTOMER_RECORD"
cleanup_resources
exit 1
fi
echo "$CUSTOMER_RECORD"
# Get multiple records using batch-get-record
echo "Getting multiple records using batch-get-record:"
BATCH_RECORDS=$(aws sagemaker-featurestore-runtime batch-get-record \
--identifiers '[
{
"FeatureGroupName": "'${CUSTOMERS_FEATURE_GROUP_NAME}'",
"RecordIdentifiersValueAsString": ["573291", "109382"]
},
{
"FeatureGroupName": "'${ORDERS_FEATURE_GROUP_NAME}'",
"RecordIdentifiersValueAsString": ["573291", "109382"]
}
]' 2>&1)
if echo "$BATCH_RECORDS" | grep -i "error" > /dev/null && ! echo "$BATCH_RECORDS" | grep -i "Records" > /dev/null; then
echo "Failed to get batch records: $BATCH_RECORDS"
cleanup_resources
exit 1
fi
echo "$BATCH_RECORDS"
# List feature groups
echo "Listing feature groups:"
FEATURE_GROUPS=$(aws sagemaker list-feature-groups 2>&1)
if echo "$FEATURE_GROUPS" | grep -i "error" > /dev/null; then
echo "Failed to list feature groups: $FEATURE_GROUPS"
cleanup_resources
exit 1
fi
echo "$FEATURE_GROUPS"
# Display summary of created resources
echo ""
echo "==========================================="
echo "TUTORIAL COMPLETED SUCCESSFULLY!"
echo "==========================================="
echo "Resources created:"
echo "- S3 Bucket: $S3_BUCKET_NAME"
echo "- Customers Feature Group: $CUSTOMERS_FEATURE_GROUP_NAME"
echo "- Orders Feature Group: $ORDERS_FEATURE_GROUP_NAME"
if [[ " ${CREATED_RESOURCES[@]} " =~ " IAMRole:" ]]; then
echo "- IAM Role: $(echo "${CREATED_RESOURCES[@]}" | grep -o 'IAMRole:[^[:space:]]*' | cut -d: -f2)"
fi
echo ""
echo "You can now:"
echo "1. View your feature groups in the SageMaker console"
echo "2. Query the offline store using Amazon Athena"
echo "3. Use the feature groups in your ML workflows"
echo "==========================================="
echo ""
# Handle cleanup
if [ "$AUTO_CLEANUP" = "y" ]; then
echo "Auto-cleanup enabled. Starting cleanup..."
cleanup_resources
echo "Cleanup completed."
elif [ "$AUTO_CLEANUP" = "n" ]; then
echo "Auto-cleanup disabled. Resources will remain in your account."
echo "To clean up later, run this script again with cleanup option 'y'"
else
echo "==========================================="
echo "CLEANUP CONFIRMATION"
echo "==========================================="
echo "Do you want to clean up all created resources? (y/n): "
read -r CLEANUP_CHOICE
if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then
echo "Starting cleanup..."
cleanup_resources
echo "Cleanup completed."
else
echo "Skipping cleanup. Resources will remain in your account."
echo "To clean up later, delete the following resources:"
echo "- Feature Groups: $CUSTOMERS_FEATURE_GROUP_NAME, $ORDERS_FEATURE_GROUP_NAME"
echo "- S3 Bucket: $S3_BUCKET_NAME"
if [[ " ${CREATED_RESOURCES[@]} " =~ " IAMRole:" ]]; then
echo "- IAM Role: $(echo "${CREATED_RESOURCES[@]}" | grep -o 'IAMRole:[^[:space:]]*' | cut -d: -f2)"
fi
echo ""
echo "Estimated ongoing cost: ~$0.01 per month for online store"
fi
fi
echo "Script completed at $(date)"
API 상세는 AWS CLI Command Reference의 다음 항목을 참고해요.
AttachRolePolicy,CreateBucket,CreateFeatureGroup,CreateRole,DeleteBucket,DeleteFeatureGroup,DeleteRole,DescribeFeatureGroup,DetachRolePolicy,GetCallerIdentity,GetRole,ListFeatureGroups,PutPublicAccessBlock,Rm