GoでDynamoDBにdatabase/sqlを使ってアクセスする
こちらの記事で一部紹介したbtnguyen2k/godynamo
の掘り下げ記事です
PartiQL
PartiQLとはAmazonがメンテナンスするOSSプロジェクトでNoSQLのためのSQL互換のクエリ言語です
PartiQL は、構造化データ、半構造化データ、ネストされたデータを含む複数のデータストア間で、SQL 互換のクエリアクセスを提供します。PartiQL は、Amazon 内で広く使用されており、現在、DynamoDB を含む多くの AWS のサービスの一部として利用できます。
DynamoDBで使用する場合にはJOIN
句やサブクエリが使えなかったり、
SELECT
と(INSERT
|UPDATE
|DELETE
)が同一のトランザクションが使えなかったり、
DynamoDB由来の制約はあるものの、個人的にはもっと注目されるべきソリューションだと思ってます
btnguyen2k/godynamo
btnguyen2k/godynamo
はPartiQLステートメントを使用してDynamoDBへアクセスするためのSQLドライバです
PartiQLステートメントの実行自体はaws-sdk-go-v2
で提供されているAPIを使用しているため、database/sql
のIFと互換を生むためのラッパーと捉えてもらってよいかと思います
CRUD
SELECT
INSERT
UPDATE
DELETE
をサポートしています
DDL
PartiQL for DynamoDB自体はCREATE TABLE
等のDDLはサポートしていないものの、btnguyen2k/godynamo
では独自の拡張PartiQL構文として
CREATE TABLE
ALTER TABLE
CREATE GSI
DROP TABLE
DROP GSI
が用意されています
接続文字列
下記が接続文字列のフォーマットです
Region=<aws-region>;AkId=<aws-access-key-id>;Secret_Key=<aws-secret-key>[;Endpoint=<aws-dynamodb-endpoint>][;TimeoutMs=<timeout-in-milliseconds>]
Data Source Name (DSN) format for AWS DynamoDB
読んで字のごとくですが各パラメータの意味合いはこんな感じです
パラメータ | 設定項目 |
---|---|
Region | AWSリージョン |
AkId | アクセスキーID |
Secret_Key | シークレットキー |
Endpoint | DynamoDBエンドポイント |
TimeoutMs | タイムアウト時間(ミリ秒) |
TimeoutMs
が設定されていない場合、タイムアウト時間はデフォルト値の10000msが適用されます
またRegion
、AkId
、Secret_Key
が設定されていない場合は、それぞれ以下の環境変数から取得して解決されます
リージョン | アクセスキーID | シークレットキー |
---|---|---|
AWS_REGION | AWS_ACCESS_KEY_ID | AWS_SECRET_ACCESS_KEY |
実行環境がAWS Lambdaであれば、上記3つはどれもランタイム環境変数の為、VPCエンドポイント経由でアクセスしたい、もしくはタイムアウト時間を独自に設定したい場合を除けば接続文字列は空文字で渡してしまっても問題ないです
package main
import (
"context"
"log"
"database/sql"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
_ "github.com/btnguyen2k/godynamo"
)
const driver string = "godynamo"
var (
dynamodb *sql.DB
)
func Handler(ctx context.Context) error {
// DynamoDBを利用したなにがしかの処理
}
func main() {
dynamodb = func() *sql.DB {
db, err := sql.Open(driver, "")
if err != nil {
log.Fatal(err)
}
return db
}()
defer dynamodb.Close()
lambda.Start(Handler)
}
1MB問題
DynamoDBで開発経験のある方なら馴染み深いであろう1MB問題は内部で自動的にフェッチしてくれるので、よりRDBに近い書き味でDynamoDBにアクセスできます
O11y
DataDog、New Relic、AWS X-Ray共にaws.Config
にミドルウェアを設定することで各AWSリソースのトレースを実現しています
btnguyen2k/godynamo
ではaws.Config
を基にDynamoDBとのコネクションを確立するためのRegisterAWSConfig
という関数が用意されているため、この関数にミドルウェアを設定したaws.Config
を渡すだけで簡単に自動計装をすることができます
btnguyen2k/godynamo
ではaws.Config
の更新/参照をsync.RWMutex
で排他制御していますが、RegisterAWSConfig
の呼び出しは極力コールドスタート時の一度だけとなるのが望ましいかと思います
DataDog
package main
import (
"context"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/btnguyen2k/godynamo"
awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go-v2/aws"
)
const driver string = "godynamo"
var (
dynamodb *sql.DB
)
func Handler(ctx context.Context) error {
// DynamoDBを利用したなにがしかの処理
}
func main() {
ctx := context.Background()
// aws.Configを取得
awsConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}
// 計装用のミドルウェアを設定
awstrace.AppendMiddleware(&awsConfig)
// aws.ConfigをSQLドライバに登録
godynamo.RegisterAWSConfig(awsConfig)
dynamodb = func() *sql.DB {
db, err := sql.Open(driver, "")
if err != nil {
log.Fatal(err)
}
return db
}()
defer dynamodb.Close()
lambda.Start(Handler)
}
New Relic
package main
import (
"context"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/btnguyen2k/godynamo"
nraws "github.com/newrelic/go-agent/v3/integrations/nrawssdk-v2"
"github.com/newrelic/go-agent/v3/newrelic"
)
const driver string = "godynamo"
var (
dynamodb *sql.DB
nrapp *newrelic.Application
)
func Handler(ctx context.Context) error {
txn := app.StartTransaction("Foo")
defer txn.End()
ctx = newrelic.NewContext(ctx, txn)
// DynamoDBを利用したなにがしかの処理
}
func main() {
ctx := context.Background()
// aws.Configを取得
awsConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}
nrapp = func() *newrelic.Application {
app, err := newrelic.NewApplication(
newrelic.ConfigAppName("test-app"),
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
newrelic.ConfigDistributedTracerEnabled(true),
)
if err != nil {
log.Fatal(err)
}
return app
}
// 計装用のミドルウェアを設定
nraws.AppendMiddlewares(&awsConfig.APIOptions, nil)
// aws.ConfigをSQLドライバに登録
godynamo.RegisterAWSConfig(awsConfig)
dynamodb = func() *sql.DB {
db, err := sql.Open(driver, "")
if err != nil {
log.Fatal(err)
}
return db
}()
defer dynamodb.Close()
lambda.Start(Handler)
}
Discussion