No results for ""
EXPAND ALL
  • Home
  • API docs

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 and WRITES 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 values
bool 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.MigrationComparisonFn
comparison = func(interface{}, interface{}) bool {
// compare the two read values
return 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 failure
throw new Exception("example exception")
},
writeOld: async(params?: {key: string, value: string}) => {
console.log("Writing to old: ", params);
// if failure
return 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 true
errorTracking: 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, ExecutionOrder
builder = 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.RANDOM
builder.track_latency(True) # defaults to True
builder.track_errors(True) # defaults to True
result = 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_RANDOM
builder.track_latency(true) # defaults to true
builder.track_errors(true) # defaults to true
migrator = 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 true
let 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 exception
throw new Error("example exception")
},
writeOld: async(params?: {key: string, value: string}) => {
console.log("Writing to old: ", params);
// if failure - can return the error
return 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 exception
throw new Error('example exception')
},
writeOld: async(params?: {key: string, value: string}) => {
console.log("Writing to old: ", params);
// if failure - can return the failure
return 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 true
errorTracking: 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 exception
throw new Error("example exception")
},
writeOld: async(params?: {key: string, value: string}) => {
console.log("Writing to old: ", params);
// if failure - can return the failure
return 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 true
errorTracking: true, // defaults to true
}
const client = init('<client-side-id-123abc>', edgeConfigClient, { sendEvents: true });
const migration = createMigration(client, options);

To learn more, read LDMigrationOptions.