Go의 DynamoDB UpdateItem (AWS SDK v2)
Go에서 UpdateItem 자체는 단순합니다. 어색한 부분은 map[string]types.AttributeValue가 인터페이스 의 맵이라는 점이며, 그래서 보내는 값도 받는 값도 아홉 개 멤버 구조체 중 하나를 가리키는 포인터입니다. 이 설계 선택 하나가 아래의 마찰 대부분을 설명하며, 그 시작은 이 코드의 fmt.Println이 여러분의 항목을 출력하지 않는다는 사실입니다.
코드
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
out, err := client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
TableName: aws.String("Music"),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
"SongTitle": &types.AttributeValueMemberS{Value: "Cubano Chant"},
},
UpdateExpression: aws.String("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2"),
ExpressionAttributeNames: map[string]string{
"#upd0": "Genre",
"#upd1": "Year",
"#upd2": "Awards",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":updValue0": &types.AttributeValueMemberS{Value: "Latin Jazz"},
":updValue1": &types.AttributeValueMemberN{Value: "1994"},
":updValue2": &types.AttributeValueMemberN{Value: "1"},
},
ReturnValues: types.ReturnValueAllNew,
})
if err != nil {
log.Fatalf("update item: %v", err)
}
fmt.Println(out.Attributes) // the item after the update
}설명
AttributeValueMemberN.Value는 숫자 타입이 아니라string이며,attributevalue.Marshal도 그대로 유지합니다.int64(9007199254740993)은 정확히"9007199254740993"으로 마셜링됩니다. DynamoDB 숫자는 어떤 Go 부동소수점보다 큰 38자리 정밀도를 담으므로 SDK는 결코 변환하지 않습니다. 파싱은 경계에서 여러분이 의도적으로 하는 것입니다.ReturnValues: types.ReturnValueAllNew— 이 상수의 값은 리터럴"ALL_NEW"입니다. 문서 대신 타입에서 직접 읽고 싶다면types.ReturnValueAllNew.Values()가 다섯 가지(NONE,ALL_OLD,UPDATED_OLD,ALL_NEW,UPDATED_NEW)를 모두 나열해 줍니다.UpdateExpression은 여러분이 직접 만드는*string이거나, 아래의expression패키지에 넘기는 것입니다. 어느 쪽이든 파서는 DynamoDB뿐입니다.ADD #upd2 :updValue2가 원자적 증가를 하고,ConditionExpression의attribute_exists(Artist)가 이 호출을 업서트가 아니라 업데이트 전용으로 만들며, 절 문법은 업데이트 표현식에 있습니다.ValidationException에는 Go 타입이 없습니다.types/errors.go는ConditionalCheckFailedException,TransactionCanceledException,ProvisionedThroughputExceededException,TransactionConflictException을 포함해 35개의 오류 구조체를 정의합니다. 검증 실패는 그중에 없으므로, 잘못된 표현식은 문자열로만 알아볼 수 있는 일반적인smithy.APIError로 드러납니다:operation error DynamoDB: UpdateItem, https response error StatusCode: 400, RequestID: 702d67f1-4f15-43dc-b3e9-cea691878801, api error ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Yearapi error접두사에 주목하세요. 아래의 모델링된ConditionalCheckFailedException에는 붙지 않습니다. 이 접두사의 존재는 여기서 구체 타입에 대한errors.As가 도움이 되지 않으리라는 꽤 좋은 신호입니다.반면 조건 실패는 진짜 타입입니다.
var ccf *types.ConditionalCheckFailedException; errors.As(err, &ccf)가 일치하며, 입력에ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld를 붙이면ccf.Item이 실제 그 시점의 항목으로 채워져 도착합니다.ccf.ErrorMessage()는err.Error()가 앞에 붙이는 전송 계층 서두 없이 순수한The conditional request failed입니다.
결과 다시 읽기
인터페이스 맵에 대한 fmt.Println(out.Attributes)는 주소를 출력합니다:
map[Artist:0x1c8cf589e060 Awards:0x1c8cf589e078 Genre:0x1c8cf589e090 SongTitle:0x1c8cf589e0c0 Year:0x1c8cf589e0a8]빠져나갈 길은 두 가지입니다. 대부분의 코드가 택해야 할 방식인 구조체로의 언마셜링:
var song struct {
Artist string
Genre string
Year int
Awards int
}
err = attributevalue.UnmarshalMap(out.Attributes, &song)
// {Artist:Arturo Sandoval Genre:Latin Jazz Year:1994 Awards:1}또는 관심 있는 멤버 하나만 타입 단언하기. .Value가 문자열이라는 것을 잊지 마세요:
n, ok := out.Attributes["Awards"].(*types.AttributeValueMemberN)
if ok {
awards, _ := strconv.Atoi(n.Value) // "1" -> 1
fmt.Println(awards)
}expression 패키지에 맡기기
이 사이트에서 UpdateExpression을 대신 생성해 주는 SDK는 Go뿐입니다. feature/dynamodb/expression이 절과 두 맵을 함께 조립합니다:
upd := expression.Set(expression.Name("Genre"), expression.Value("Latin Jazz")).
Set(expression.Name("Year"), expression.Value(1994)).
Add(expression.Name("Awards"), expression.Value(1))
expr, _ := expression.NewBuilder().WithUpdate(upd).Build()나오는 결과는 여러분이 쓴 것과 다릅니다:
UpdateExpression: ADD #0 :0
SET #1 = :1, #2 = :2
Names: map[#0:Awards #1:Genre #2:Year]빌더는 절의 순서를 바꾸고, 줄바꿈으로 구분하고, 플레이스홀더 번호를 스스로 매겼습니다. 그래서 #0은 여러분이 처음 언급한 이름이 아니라 Awards입니다. 하위 단계에서는 아무도 신경 쓰지 않지만, 이 문자열들은 편집을 거치며 안정적이지 않으므로 테스트에서 단언 대상으로 삼기에는 좋지 않습니다. expr.Update(), expr.Names(), expr.Values()를 곧바로 UpdateItemInput에 넘기고 다시는 들여다보지 마세요.
좋은 점은 모든 이름에 별칭을 붙여 주므로 예약어가 배포될 수 있는 버그 부류에서 빠진다는 것입니다. 표현식을 직접 손으로 쓴다면 속성 이름을 먼저 DynamoDB 예약어 검사기에 통과시키세요. AWS 목록은 573개 항목이고 Year, Name, Status가 모두 거기 있습니다. 그리고 항목을 인터페이스 포인터의 맵이 아니라 데이터로 보고 싶다면 DynoTable을 다운로드하세요.
관련 예제
- Java의 DynamoDB UpdateItem — AWS SDK for Java 2.x로 하는 동일한 업데이트.
- Go의 DynamoDB PutItem — 대신 항목 전체를 대체합니다.
- DynamoDB 업데이트 표현식 —
SET,ADD,REMOVE,DELETE와 관용구. - ReturnValues 이해하기 — 각
ReturnValues옵션이 주는 것. - "Attribute name is a reserved keyword" — 여기서 별칭 맵이 선택 사항이 아닌 이유.
- "Invalid UpdateExpression" 문법 오류 — 흔한 SET/ADD 문법 실수 해독.
참고 자료
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- dynamodb/types package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Update expressions — Amazon DynamoDB Developer Guide
위에 링크된 공식 AWS 문서를 기준으로 2026-07-28에 마지막으로 검증했습니다.