Migration configuration
Read time: 12 minutes
Last edited: Oct 21, 2024
Overview
This topic explains how to configure LaunchDarkly SDKs to manage migrations or modernizations. You might use this feature if you are optimizing queries, upgrading to new tech stacks, migrating from one database to another, or other similar technology changes. This feature is available for server-side and edge SDKs only.
Prerequisites
Before you configure your SDK to manage a migration, you must complete the following prerequisites:
- Create a migration feature flag. This is a temporary flag used to migrate data or systems while keeping your application available and disruption free. Migration flags break up the switch from an old to a new implementation into a series of recommended stages where movement from one stage to the next is done in incremental steps.
- Determine how many stages your migration will have. You can select from the following options as part of creating a migration feature flag:
- Two stages: For migrations where you cannot run the new system and old system at the same time
- Four stages: For migrations that can run both the new and old systems at the same time
- Six stages: For migrations where you need to migrate
READS
andWRITES
separately
To learn more, read Migration flags.
Migration configuration options
There are two categories of migration options that you can configure for each LaunchDarkly SDK:
- Options for reading and writing data: You can define how to read from and write to both the old system and the new system. You can also define a method to check whether the two reads are a match, and whether the migration should execute serially or concurrently. To learn how these options apply to each migration stage, read Use SDKs to manage a migration.
- Options for tracking metrics: You can configure whether the SDK should track latency and errors, so that you can monitor the performance of your application during the migration.
Details about each SDK's configuration are available in the SDK-specific sections below:
Server-side SDKs
This feature is available in the following server-side SDKs:
.NET (server-side)
Expand .NET (server-side) code sample
To define how to read from the old and new systems, define the Read
function. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the Write
function.
To configure the metrics for your migration, set the TrackLatency
and TrackErrors
options.
Here's how:
// define how to compare the two read valuesbool Checker(string a, string b) => a.Equals(b);var migration = new MigrationBuilder<string, string, string, string>(_client).Read((payload) => MigrationMethod.Success("read old"),(payload) => MigrationMethod.Success("read new"),Checker).Write((payload) => MigrationMethod.Success("write old"),(payload) => MigrationMethod.Success("write new")).ReadExecution(MigrationExecution.Parallel()) // or MigrationExecution.Serial(MigrationSerialOrder.Fixed).TrackErrors(true) // true by default.TrackLatency(true) // true by default.Build();
To learn more, read MigrationBuilder
.
Go
Expand Go code sample
You can use the Migration
configuration to define the options for your migration.
To define how to read from the old and new systems, define the Read
function. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the Write
function.
To configure the metrics for your migration, set the TrackLatency
and TrackErrors
options.
Here's how:
client, _ := ld.MakeClient("sdk-key-123abc", 5*time.Second)var comparison ld.MigrationComparisonFncomparison = func(interface{}, interface{}) bool {// compare the two read valuesreturn true}migrator, err := ld.Migration(client).Read(func(interface{}) (interface{}, error) {return "old read", nil},func(interface{}) (interface{}, error) {return "new read", nil},&comparison,).ReadExecutionOrder(ldmigration.Random).Write(func(interface{}) (interface{}, error) {return "old write result", nil},func(interface{}) (interface{}, error) {return "new write result", nil},).TrackLatency(true).TrackErrors(true).Build()
To learn more, read Migration
.
Java
Expand Java code sample
You can use the MigrationBuilder
to define the options for your migration.
To define how to read from the old and new systems, define the read
function, including how to check whether the two reads are a match. You can also define whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the write
function.
To configure the metrics for your migration, set the trackLatency
and trackErrors
options.
Here's how:
LDClient client = new LDClient("sdk-key-123abc");MigrationBuilder<String, String, String, String> migrationBuilder = new MigrationBuilder<>(client).read((payload) -> MigrationMethodResult.Success("read old"),(payload) -> MigrationMethodResult.Success("read new"),(a, b) -> a.equals(b)).readExecution(MigrationExecution.Serial(MigrationSerialOrder.RANDOM)) // default is .Parallel.write((payload) -> MigrationMethodResult.Success("write old"),(payload) -> MigrationMethodResult.Success("write new")).trackLatency(true).trackErrors(true).build();Migration<String, String, String, String> migration = migrationBuilder.build();
To learn more, read MigrationBuilder
.
Node.js (server-side)
Expand Node.js (server-side) code sample
To define how to read from the old and new systems, define the readOld
and readNew
functions. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the writeOld
and writeNew
functions.
Each of the readOld
, readNew
, writeOld
, and writeNew
functions accept an optional argument, which is typically used to define what to read or write. They should return LDMigrationSuccess
or LDMigrationError
, or can throw an exception. The code sample below uses a mix to illustrate these possibilities.
To configure the metrics for your migration, set the latencyTracking
and errorTracking
options.
Here's how to configure each of these options:
import * as ld from '@launchdarkly/node-server-sdk';const options: ld.LDMigrationOptions = {readNew: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},readOld: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},writeNew: async(params?: {key: string, value: string}) => {console.log("Writing to new: ", params);// if failurethrow new Exception("example exception")},writeOld: async(params?: {key: string, value: string}) => {console.log("Writing to old: ", params);// if failurereturn LDMigrationError(new Error('example error'));},check: (old, new) => {// Define your consistency check for read operations// and return a boolean. Depending on your migration,// this may be as simple as 'return a === b;'},execution: new LDConcurrentExecution(),// or new LDSerialExecution(LDExecutionOrdering.Random),// or new LDSerialExecution(LDExecutionOrdering.Fixed),latencyTracking: true, // defaults to trueerrorTracking: true, // defaults to true}const client = ld.init('sdk-key-123abc');const migration = ld.createMigration(client, options);
To learn more, read LDMigrationOptions
.
PHP
Expand PHP code sample
To define how to read from the old and new systems, call the read
method. You can also define how to check whether the two reads are a match, and whether the reads should take place serially or in a randomized execution order.
To define how to write to the old and new systems, call the write
method.
Each of the read
and write
method accept new
and old
methods for how to read from or write to both the new and old systems. These new
and old
methods accept an optional payload parameter, which is typically used to define what to read or write. They should return an LaunchDarkly\Types\Result
instance.
To configure the metrics for your migration, set the trackLatency
and trackErrors
options.
Here's how to configure each of these options:
use LaunchDarkly\Migrations;use LaunchDarkly\Types;$builder = new Migrations\MigratorBuilder($client);$builder->read(fn (?string $payload) => Types\Result::success("old read"),fn (?string $payload) => Types\Result::success("new read"),fn(string $old, string $new) => $old == $new,);$builder->write(fn (?string $payload) => Types\Result::success("old write"),fn (?string $payload) => Types\Result::success("new write"));$builder->readExecutionOrder(Migrations\ExecutionOrder::SERIAL);// could also use ExecutionOrder::RANDOM$builder->trackLatency(true); // defaults to true$builder->trackErrors(true); // defaults to true$result = $builder->build();
To learn more, read MigratorBuilder
.
Python
Expand Python code sample
To define how to read from the old and new systems, call the read
method. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, call the write
method.
Each of the read
and write
method accept new
and old
methods for how to read from or write to both the new and old systems. These new
and old
methods accept an optional payload parameter, which is typically used to define what to read or write. They should return an ldclient.Result
instance.
To configure the metrics for your migration, set the track_latency
and track_errors
options.
Here's how to configure each of these options:
from ldclient import Result, MigratorBuilder, ExecutionOrderbuilder = MigratorBuilder(ldclient.get())builder.read(lambda _: Result.success("read old"), lambda _: Result.success("read new"), lambda lhs, rhs: lhs == rhs)builder.write(lambda _: Result.success("write old"), lambda _: Result.success("write new"))builder.read_execution_order(ExecutionOrder.PARALLEL)# could also use ExecutionOrder.SERIAL, ExecutionOrder.RANDOMbuilder.track_latency(True) # defaults to Truebuilder.track_errors(True) # defaults to Trueresult = builder.build()
To learn more, read ldclient.migrations
.
Ruby
Expand Ruby code sample
To define how to read from the old and new systems, define the read
function. You can also define how to check whether the two reads are a match, and whether the reads should take place in serial or in parallel.
To define how to write to the old and new systems, define the write
function.
To configure the metrics for your migration, set the track_latency
and track_errors
options.
Here's how:
builder = LaunchDarkly::Migrations::MigratorBuilder.new(@client)builder.read(->(_payload) { LaunchDarkly::Result.success('old value') },->(_payload) { LaunchDarkly::Result.success('new value') },->(lhs, rhs) { lhs == rhs })builder.write(->(_payload) { LaunchDarkly::Result.success('old value') },->(_payload) { LaunchDarkly::Result.success('new value') })builder.read_execution_order(builder.EXECUTION_PARALLEL) # or .EXECUTION_SERIAL, or .EXECUTION_RANDOMbuilder.track_latency(true) # defaults to truebuilder.track_errors(true) # defaults to truemigrator = builder.build
To learn more, read MigratorBuilder
.
Rust
Expand Rust code sample
To define how to read from the old and new systems, define the read
function. You can also define how to check whether the two reads are a match, and whether the reads should take place in serial or concurrently.
To define how to write to the old and new systems, define the write
function.
To configure the metrics for your migration, set the track_latency
and track_errors
options.
Here's how:
let client = Arc::new(client);let mut builder = MigratorBuilder::new(client.clone()).read(|_payload: &String| async move { Ok(()) }.boxed(),|_payload: &String| async move { Ok(()) }.boxed(),Some(|lhs, rhs| lhs == rhs),).write(|_payload: &String| async move { Ok(()) }.boxed(),|_payload: &String| async move { Ok(()) }.boxed(),);builder = builder.read_execution_order(ExecutionOrder::Concurrent) // Or ExecutionOrder::Serial or ExecutionOrder::Random.track_latency(true) // defaults to true.track_errors(true); // defaults to truelet migrator = builder.build().expect("build migrator");
To learn more, read MigratorBuilder
.
Edge SDKs
This feature is available in the following edge SDKs:
Akamai
Expand Akamai code sample
To define how to read from the old and new systems, define the readOld
and readNew
functions. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the writeOld
and writeNew
functions.
Each of the readOld
, readNew
, writeOld
, and writeNew
functions accept an optional argument, which is typically used to define what to read or write. They should return LDMigrationSuccess
or LDMigrationError
, or can throw an exception. The code sample below uses a mix to illustrate these possibilities.
Although the migration options latencyTracking
and errorTracking
default to true
, the Akamai SDK does not track metrics for migrations, because the Akamai SDK does not send events back to LaunchDarkly.
Here's how to configure each of these options:
import {createMigration,init,LDConcurrentExecution,LDMigrationError,LDMigrationOptions,LDMigrationSuccess,} from '@launchdarkly/akamai-server-edgekv-sdk';const options: LDMigrationOptions = {readNew: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},readOld: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},writeNew: async(params?: {key: string, value: string}) => {console.log("Writing to new: ", params);// if failure - can throw an exceptionthrow new Error("example exception")},writeOld: async(params?: {key: string, value: string}) => {console.log("Writing to old: ", params);// if failure - can return the errorreturn LDMigrationError(new Error('example error'));},check: (old, new) => {// Define your consistency check for read operations// and return a boolean. Depending on your migration,// this may be as simple as 'return a === b;'},execution: new LDConcurrentExecution(),// or new LDSerialExecution(LDExecutionOrdering.Random),// or new LDSerialExecution(LDExecutionOrdering.Fixed),}const client = init({sdkKey: 'client-side-id-123abc',namespace: 'your-edgekv-namespace',group: 'your-edgekv-group-id'});const migration = createMigration(client, options);
To learn more, read LDMigrationOptions
.
Cloudflare
Expand Cloudflare code sample
To define how to read from the old and new systems, define the readOld
and readNew
functions. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the writeOld
and writeNew
functions.
Each of the readOld
, readNew
, writeOld
, and writeNew
functions accept an optional argument, which is typically used to define what to read or write. They should return LDMigrationSuccess
or LDMigrationError
, or can throw an exception. The code sample below uses a mix to illustrate these possibilities.
To configure the metrics for your migration, set the latencyTracking
and errorTracking
options.
Here's how to configure each of these options:
import {createMigration,init,LDConcurrentExecution,LDMigrationError,LDMigrationOptions,LDMigrationSuccess,} from '@launchdarkly/cloudflare-server-sdk';const options: LDMigrationOptions = {readNew: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},readOld: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},writeNew: async(params?: {key: string, value: string}) => {console.log("Writing to new: ", params);// if failure - can throw an exceptionthrow new Error('example exception')},writeOld: async(params?: {key: string, value: string}) => {console.log("Writing to old: ", params);// if failure - can return the failurereturn LDMigrationError(new Error('example error'));},check: (old, new) => {// Define your consistency check for read operations// and return a boolean. Depending on your migration,// this may be as simple as 'return a === b;'},execution: new LDConcurrentExecution(),// or new LDSerialExecution(LDExecutionOrdering.Random),// or new LDSerialExecution(LDExecutionOrdering.Fixed),latencyTracking: true, // defaults to trueerrorTracking: true, // defaults to true}const client = init('sdk-key-123abc', env.LD_KV, { sendEvents: true });const migration = createMigration(client, options);
To learn more, read LDMigrationOptions
.
Vercel
Expand Vercel code sample
To define how to read from the old and new systems, define the readOld
and readNew
functions. You can also define how to check whether the two reads are a match, and whether the reads should take place concurrently or serially.
To define how to write to the old and new systems, define the writeOld
and writeNew
functions.
Each of the readOld
, readNew
, writeOld
, and writeNew
functions accept an optional argument, which is typically used to define what to read or write. They should return LDMigrationSuccess
or LDMigrationError
, or can throw an exception. The code sample below uses a mix to illustrate these possibilities.
To configure the metrics for your migration, set the latencyTracking
and errorTracking
options.
Here's how to configure each of these options:
import {createMigration,init,LDConcurrentExecution,LDMigrationError,LDMigrationOptions,LDMigrationSuccess,} from '@launchdarkly/vercel-server-sdk';const options: LDMigrationOptions = {readNew: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},readOld: async(key?: string) => {console.log("Reading from new: ", key);return LDMigrationSuccess(true);},writeNew: async(params?: {key: string, value: string}) => {console.log("Writing to new: ", params);// if failure - can throw an exceptionthrow new Error("example exception")},writeOld: async(params?: {key: string, value: string}) => {console.log("Writing to old: ", params);// if failure - can return the failurereturn LDMigrationError(new Error('example error'));},check: (old, new) => {// Define your consistency check for read operations// and return a boolean. Depending on your migration,// this may be as simple as 'return a === b;'},execution: new LDConcurrentExecution(),// or new LDSerialExecution(LDExecutionOrdering.Random),// or new LDSerialExecution(LDExecutionOrdering.Fixed),latencyTracking: true, // defaults to trueerrorTracking: true, // defaults to true}const client = init('<client-side-id-123abc>', edgeConfigClient, { sendEvents: true });const migration = createMigration(client, options);
To learn more, read LDMigrationOptions
.