Skip to content

Commit 90dbde5

Browse files
LaunchDarklyReleaseBoteli-darklyLaunchDarklyCIbwoskow-ldSam Stokes
authored
prepare 3.1.0 release (#28)
## [3.1.0] - 2023-10-11 ### Added: - Introduced ldmigration and ldsampling utility packages to support technology migration use cases. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Eli Bishop <eli@launchdarkly.com> Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Sam Stokes <sstokes@launchdarkly.com> Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com> Co-authored-by: Ember Stevens <ember.stevens@launchdarkly.com> Co-authored-by: ember-stevens <79482775+ember-stevens@users.noreply.github.com> Co-authored-by: Casey Waldren <cwaldren@launchdarkly.com> Co-authored-by: Matthew Wagner <rengawm@gmail.com> Co-authored-by: ld-repository-standards[bot] <113625520+ld-repository-standards[bot]@users.noreply.github.com> Co-authored-by: Kane Parkinson <93555788+kparkinson-ld@users.noreply.github.com> Co-authored-by: Matthew M. Keeler <mkeeler@launchdarkly.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Louis Chan <91093020+louis-launchdarkly@users.noreply.github.com>
1 parent 065e537 commit 90dbde5

File tree

8 files changed

+205
-2
lines changed

8 files changed

+205
-2
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Repository Maintainers
2+
* @launchdarkly/team-sdk-go

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ require (
1616
github.com/kr/text v0.2.0 // indirect
1717
github.com/pmezard/go-difflib v1.0.0 // indirect
1818
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
19-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
19+
gopkg.in/yaml.v3 v3.0.0 // indirect
2020
)

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p
2626
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2727
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
2828
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
29-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
3029
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
30+
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
31+
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

ldmigration/ldmigration_info.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package ldmigration contains general types used by LaunchDarkly SDK components to support technology migrations.
2+
package ldmigration

ldmigration/migration.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package ldmigration
2+
3+
import "fmt"
4+
5+
// ExecutionOrder represents the various execution modes this SDK can operate
6+
// under while performing migration-assisted reads.
7+
type ExecutionOrder string
8+
9+
const (
10+
// Serial execution ensures the authoritative read will always complete execution before executing the
11+
// non-authoritative read.
12+
Serial ExecutionOrder = "serial"
13+
// Random execution randomly decides if the authoritative read should execute first or second.
14+
Random ExecutionOrder = "random"
15+
// Concurrent executes both reads in separate go routines, and waits until both calls have finished before
16+
// proceeding.
17+
Concurrent ExecutionOrder = "concurrent"
18+
)
19+
20+
// Operation represents a type of migration operation; namely, read or write.
21+
type Operation string
22+
23+
const (
24+
// Read denotes a read-related migration operation.
25+
Read Operation = "read"
26+
// Write denotes a write-related migration operation.
27+
Write Operation = "write"
28+
)
29+
30+
// ConsistencyCheck records the results of a consistency check and the ratio at
31+
// which the check was sampled.
32+
//
33+
// For example, a sampling ratio of 10 indicts this consistency check was
34+
// sampled approximately once every ten operations.
35+
type ConsistencyCheck struct {
36+
consistent bool
37+
samplingRatio int
38+
}
39+
40+
// NewConsistencyCheck creates a new consistency check reflecting the provided values.
41+
func NewConsistencyCheck(wasConsistent bool, samplingRatio int) *ConsistencyCheck {
42+
return &ConsistencyCheck{
43+
consistent: wasConsistent,
44+
samplingRatio: samplingRatio,
45+
}
46+
}
47+
48+
// Consistent returns whether or not the check returned a consistent result.
49+
func (c ConsistencyCheck) Consistent() bool {
50+
return c.consistent
51+
}
52+
53+
// SamplingRatio returns the 1 in x sampling ratio used to determine if the consistency check should be run.
54+
func (c ConsistencyCheck) SamplingRatio() int {
55+
return c.samplingRatio
56+
}
57+
58+
// Origin represents the source of origin for a migration-related operation.
59+
type Origin string
60+
61+
const (
62+
// Old represents the technology source we are migrating away from.
63+
Old Origin = "old"
64+
// New represents the technology source we are migrating towards.
65+
New Origin = "new"
66+
)
67+
68+
// Stage denotes one of six possible stages a technology migration could be a
69+
// part of, progressing through the following order.
70+
//
71+
// Off -> DualWrite -> Shadow -> Live -> RampDown -> Complete
72+
type Stage string
73+
74+
const (
75+
// Off - migration hasn't started, "old" is authoritative for reads and writes
76+
Off Stage = "off"
77+
78+
// DualWrite - write to both "old" and "new", "old" is authoritative for reads
79+
DualWrite Stage = "dualwrite"
80+
81+
// Shadow - both "new" and "old" versions run with a preference for "old"
82+
Shadow Stage = "shadow"
83+
84+
// Live - both "new" and "old" versions run with a preference for "new"
85+
Live Stage = "live"
86+
87+
// RampDown - only read from "new", write to "old" and "new"
88+
RampDown Stage = "rampdown"
89+
90+
// Complete - migration is done
91+
Complete Stage = "complete"
92+
)
93+
94+
// ParseStage parses a MigrationStage from a string, or returns an error if the stage is unrecognized.
95+
func ParseStage(val string) (Stage, error) {
96+
switch val {
97+
case "off":
98+
return Off, nil
99+
case "dualwrite":
100+
return DualWrite, nil
101+
case "shadow":
102+
return Shadow, nil
103+
case "live":
104+
return Live, nil
105+
case "rampdown":
106+
return RampDown, nil
107+
case "complete":
108+
return Complete, nil
109+
default:
110+
return Off, fmt.Errorf("invalid stage %s provided", val)
111+
}
112+
}

ldsampling/package_info.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package ldsampling provides functionality related to sampling.
2+
package ldsampling

ldsampling/sampler.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package ldsampling
2+
3+
import (
4+
"math/rand"
5+
"time"
6+
)
7+
8+
// NewSampler creates a *RatioSampler instance that can be used to
9+
// determine sampling selections.
10+
//
11+
// The random number generator used is seeded with the current system time.
12+
func NewSampler() *RatioSampler {
13+
return NewSamplerFromSource(rand.NewSource(time.Now().UnixNano()))
14+
}
15+
16+
// NewSamplerFromSource creates a *RatioSampler instance similar to
17+
// NewSampler with the additional benefit of providing the random number
18+
// source.
19+
func NewSamplerFromSource(source rand.Source) *RatioSampler {
20+
return &RatioSampler{rng: rand.New(source)} //nolint:gosec // doesn't need cryptographic security
21+
}
22+
23+
// RatioSampler provides a simple interface for determining sample selections.
24+
//
25+
// The distribution calculation relies on random number generation, it does not
26+
// perform any tracking to ensure a strict 1 in x outcome.
27+
//
28+
// A non-positive ratio effectively disables sampling, resulting in Sample always
29+
// returning false. A ratio of 1 will result in every call being true. Any other
30+
// ratio results in a 1 in x chance of being sampled.
31+
//
32+
// As a RatioSampler relies on rand.Source, the sampler is not safe for
33+
// concurrent use.
34+
type RatioSampler struct {
35+
rng *rand.Rand
36+
}
37+
38+
// Sample returns a boolean to determine whether or not something should be
39+
// sampled. It should not be called concurrently.
40+
func (r *RatioSampler) Sample(ratio int) bool {
41+
if ratio <= 0 {
42+
return false
43+
}
44+
45+
if ratio == 1 {
46+
return true
47+
}
48+
49+
return r.rng.Float64() < 1/float64(ratio)
50+
}

ldsampling/sampler_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package ldsampling
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestRatioSampler(t *testing.T) {
11+
t.Run("simple sampling", func(t *testing.T) {
12+
sampler := NewSampler()
13+
14+
assert.False(t, sampler.Sample(-1), "negative ratio should sample false")
15+
assert.False(t, sampler.Sample(0), "zero ratio should sample false")
16+
assert.True(t, sampler.Sample(1), "one ratio should sample true")
17+
})
18+
19+
t.Run("random sampling", func(t *testing.T) {
20+
sampler := NewSamplerFromSource(rand.NewSource(1))
21+
22+
picks := 0
23+
for i := 0; i < 1_000; i++ {
24+
if sampler.Sample(2) {
25+
picks += 1
26+
}
27+
}
28+
29+
// This isn't a perfect 1 in 2 ratio, but the sampling is
30+
// probabilistic. Since we control the seed, this should be safe for
31+
// testing.
32+
assert.Equal(t, 508, picks)
33+
})
34+
}

0 commit comments

Comments
 (0)