Slide 1

Slide 1 text

Implementing Feature Flags in Serverless Environments - GDG Go & Cloud User Group Hamburg -

Slide 2

Slide 2 text

Hello I'm Mathias Lafeldt Freelance Solutions Architect @mlafeldt

Slide 3

Slide 3 text

Feature Flags 101 The Ultimate Feature Flag Guide ! https://rollout.io/blog/ultimate-feature-flag-guide

Slide 4

Slide 4 text

"A feature flag is a way to change your software's functionality without changing and re-deploying your code."

Slide 5

Slide 5 text

A Very Contrived Example if (feature.isActive("holiday-greeting")) { print("Happy holidays," + user.name + "!"); }

Slide 6

Slide 6 text

Use Cases of Feature Flags • Separate deployment from feature rollout • Canary releases • Production testing • Turning things off with a kill switch • A/B testing, Chaos Engineering, and other experiments • Migrations • Trunk-based development

Slide 7

Slide 7 text

Open Source and SaaS Solutions • Unleash - Enterprise ready feature toggles service (OSS) • Rollout - A secure feature management system for the enterprise • Split - Feature experimentation platform for engineering and product teams • LaunchDarkly - Feature management platform for modern development

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

LaunchDarkly Go SDK Example import ld "gopkg.in/launchdarkly/go-client.v4" ldClient, err := ld.MakeClient("some-sdk-key", 5*time.Second) if err != nil { ... } defer ldClient.Close() ldUser := ld.NewUser("some-user") flagA, _ := ldClient.BoolVariation("some.bool.flag", ldUser, false) if flagA { ... } flagB, _ := ldClient.IntVariation("some.int.flag", ldUser, 2018) doSomething(flagB)

Slide 12

Slide 12 text

Go Serverless, not Flagless: Implementing Feature Flags in Serverless Environments https://blog.launchdarkly.com/go-serveless-not-flagless- implementing-feature-flags-in-serverless-environments (April 14, 2017)

Slide 13

Slide 13 text

"[Lambda functions] will have poor cold start performance because we need to call out to LaunchDarkly whenever a new Execution Context is created."

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Using the Redis Feature Store import ld "gopkg.in/launchdarkly/go-client.v4" import redis "gopkg.in/launchdarkly/go-client.v4/redis" config := ld.DefaultConfig config.FeatureStore = redis.NewRedisFeatureStoreFromUrl( "redis://localhost:6379", "launchdarkly", 30*time.Second, nil) // Enable daemon mode to only read flags from Redis config.UseLdd = true ldClient, err := ld.MakeCustomClient("some-sdk-key", config, 5*time.Second) if err != nil { ... }

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

"From various reports around the web, [Lambda] cold starts within VPCs could add up to 10 seconds of latency!" https://medium.freecodecamp.org/lambda-vpc-cold-starts-a-latency-killer-5408323278dd

Slide 19

Slide 19 text

If only there were some serverless key-value store that we can easily access from AWS Lambda... !

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Let's Write a DynamoDB Feature Store! type FeatureStore interface { Get(kind VersionedDataKind, key string) (VersionedData, error) All(kind VersionedDataKind) (map[string]VersionedData, error) Init(map[VersionedDataKind]map[string]VersionedData) error Delete(kind VersionedDataKind, key string, version int) error Upsert(kind VersionedDataKind, item VersionedData) error Initialized() bool } var VersionedDataKinds = [...]VersionedDataKind{ Features, Segments, }

Slide 22

Slide 22 text

Diving into the DynamoDB API and AWS SDK • GetItem • PutItem • Scan • Query • BatchWriteItem • ...

Slide 23

Slide 23 text

$ curl -s -H 'Authorization: some-sdk-key' https://app.launchdarkly.com/sdk/latest-flags | jq . { "some.bool.flag": { "key": "some.bool.flag", "version": 2, "on": true, "prerequisites": [], "salt": "57c59e2cfb724c749ff438332035ccea", "sel": "cedb458484144ead94864dfff58cee51", "targets": [], "rules": [], "fallthrough": { "variation": 0 }, "offVariation": 1, "variations": [ true, false ], "trackEvents": true, "debugEventsUntilDate": null, "deleted": false }, "some.int.flag": ... }

Slide 24

Slide 24 text

dynamodbattribute.MarshalMap/UnmarshalMap type FeatureFlag struct { Key string `json:"key" bson:"key"` Version int `json:"version" bson:"version"` On bool `json:"on" bson:"on"` Prerequisites []Prerequisite `json:"prerequisites" bson:"prerequisites"` Salt string `json:"salt" bson:"salt"` Sel string `json:"sel" bson:"sel"` Targets []Target `json:"targets" bson:"targets"` Rules []Rule `json:"rules" bson:"rules"` Fallthrough VariationOrRollout `json:"fallthrough" bson:"fallthrough"` OffVariation *int `json:"offVariation" bson:"offVariation"` Variations []interface{} `json:"variations" bson:"variations"` TrackEvents bool `json:"trackEvents" bson:"trackEvents"` DebugEventsUntilDate *uint64 `json:"debugEventsUntilDate" bson:"debugEventsUntilDate"` Deleted bool `json:"deleted" bson:"deleted"` }

Slide 25

Slide 25 text

How to Store Everything in the Same DynamoDB Table? • Use the item's kind as the primary partition key, e.g. features or segments • Use the item's key as the primary sort key, e.g. feature.foo • Figure out how to do this without modifying LaunchDarkly's structs • ! Add a namespace attribute after using MarshalMap

Slide 26

Slide 26 text

Making the Test Suite Happy import ldtest "gopkg.in/launchdarkly/go-client.v4/shared_test" func TestDynamoDBFeatureStore(t *testing.T) { table := os.Getenv("LAUNCHDARKLY_DYNAMODB_TABLE") if table == "" { t.Skip("LAUNCHDARKLY_DYNAMODB_TABLE not set in environment") } ldtest.RunFeatureStoreTests(t, func() ld.FeatureStore { store, err := NewDynamoDBFeatureStore(table, nil) if err != nil { t.Fatal(err) } return store }) }

Slide 27

Slide 27 text

Making LaunchDarkly and AWS users happy (and this little puppy)

Slide 28

Slide 28 text

Now Open Source: DynamoDB Store for LaunchDarkly's Go SDK github.com/mlafeldt/launchdarkly-dynamo-store

Slide 29

Slide 29 text

Using the DynamoDB Feature Store import ld "gopkg.in/launchdarkly/go-client.v4" import "github.com/mlafeldt/launchdarkly-dynamo-store/dynamodb" store, err := dynamodb.NewDynamoDBFeatureStore("some-table", nil) if err != nil { ... } config := ld.DefaultConfig config.FeatureStore = store config.UseLdd = true ldClient, err := ld.MakeCustomClient("some-sdk-key", config, 5*time.Second) if err != nil { ... }

Slide 30

Slide 30 text

Wait - we are not done yet. ! How to propagate flag updates?

Slide 31

Slide 31 text

Serverless Flag Storage Pipeline

Slide 32

Slide 32 text

! Demo !

Slide 33

Slide 33 text

Takeaways • Use feature flags! • Take advantage of existing feature flag tooling • Cache flags in environments sensitive to cold starts • Choose the right key-value store for the job • Use a (serverless) flag storage pipeline to propagate changes

Slide 34

Slide 34 text

Thank you. @mlafeldt [email protected] github.com/mlafeldt/launchdarkly-dynamo-store