InterviewKata
Software architecture · Case study 16 · Legacy modernization

TSB: when a big-bang migration fails

Over one weekend in April 2018, the UK bank TSB moved most of its operations and customer data from Lloyds Banking Group's systems to a newly built platform. When it went live, internet, mobile, telephone and branch banking all ran into serious problems. There was no way back to the old platform. The bank didn't return to business as usual until December. The regulators' findings make it one of the best-documented migration failures, and almost every lesson in this set shows up in it.

A 3-minute conversation: Sam asks, Priya explains
0:00 / 3:05
Real caseAnalysed from the FCA's Final Notice to TSB (December 2022), the FCA's press release, the PRA's notice on TSB's former CIO (April 2023) and the independent Slaughter and May review (November 2019). This page states only what those documents state.

1. The situation

TSB was created in June 2014 when Lloyds Banking Group (LBG) divested it. It kept running on the LBG IT platform under an outsourcing agreement that allowed it to stay until July 2024. In July 2015, the Spanish bank Sabadell bought TSB; the rationale included the returns from moving TSB onto Sabadell's own platform, Proteo. The aim was to migrate by the end of 2017.

The FCA calls the scale "unprecedented". The target was a newly built UK version, Proteo4UK, "not yet proven", needing "significant customisation and a large number of external suppliers". Sabadell's IT subsidiary, SABIS, would design, build, test and then run it, relying on 85 third parties of its own. TSB had about 5.2 million customers.

2. What mattered

3. The options

OptionWhat it meant
StayKeep using the LBG IT platform, as the agreement allowed, until July 2024
Carve-outCreate a copy of the LBG platform, run by a third party
Migrate, in stagesMove customers to Proteo4UK in groups, by product or sort code
Migrate, predominantly in one eventChosen: move some functions early, then most operations and customer data in one weekend

Some functions did move earlier, in "Governed Transition Events" from 2017: the Faster Payments system, the mobile app and ATMs. Sabadell's usual method was a "big bang" migration in a single weekend, and TSB "opted for a predominantly single event data migration".

4. The decision, and what it left out

Decision: Migrate most of TSB's operations and data to Proteo4UK
  in one Main Migration Event, 20-22 April 2018

Context: TSB on LBG's platform; a newly built Proteo4UK, delivered and
  run by SABIS with 85 third parties; some functions already moved.
Consequences recorded by the regulators:
  - No roll-back: after the event it was "essentially impossible" to
    revert to the LBG platform
  - Non-functional testing reduced: around the end of February 2018, a
    decision "outside of the appropriate governance forum" cut testing in
    Active-Active configuration across TSB's data centres
  - Supplier readiness assumed: TSB did not sufficiently understand
    SABIS's capability to operate the platform
  - Incident plans sized for days: playbooks covered the first 48 hours

The risk was known. Board minutes from December 2015 record that the board "could not be put in a position where they gave up the carve-out option, migration 'stalled' or was found to be unworkable leaving the bank in a position without an IT infrastructure."

5. How it works

The programme from 2015 to the weekend of the migration, and what followed.

6. The technique in code

What TSB couldn't do: move customers in waves, stop at a readiness gate, and move a wave back. The program compares four plans when a platform defect appears early. The waves and the staff pilot are invented; the 5.2 million is TSB's customer count. It was run in all three languages; each prints the output below it.

CUSTOMERS = 5_200_000


def migrate(name, waves, pilot=0, gate=True, roll_back=True, defect_in_wave=1):
    """Move customers in waves (wave 0 = an optional pilot). A platform defect shows up in the given wave.
    With a readiness gate, migration stops there; everyone moved so far is hit by the defect."""
    sizes = ([pilot] if pilot else []) + [CUSTOMERS // waves + (1 if i < CUSTOMERS % waves else 0) for i in range(waves)]
    moved = 0
    for wave, size in enumerate(sizes, start=0 if pilot else 1):
        moved += size
        if gate and wave >= defect_in_wave:
            break                         # stop and fix before the next wave
    after = "can be moved back while it's fixed" if roll_back else "stuck on the new platform until it's fixed"
    print(f"{name:<30} {moved:>9,} customers hit; {after}")


migrate("single event, no way back", waves=1, gate=False, roll_back=False)
migrate("10 waves, no readiness gate", waves=10, gate=False)
migrate("10 waves, gate, roll-back", waves=10)
migrate("staff pilot, then 10 waves", waves=10, pilot=5_000, defect_in_wave=0)
const CUSTOMERS = 5_200_000;

function migrate(name: string, waves: number, { pilot = 0, gate = true, rollBack = true, defectInWave = 1 } = {}) {
  // Move customers in waves (wave 0 = an optional pilot). A platform defect shows up in the given wave.
  // With a readiness gate, migration stops there; everyone moved so far is hit by the defect.
  const sizes = [...(pilot ? [pilot] : []), ...Array.from({ length: waves }, (_, i) => Math.floor(CUSTOMERS / waves) + (i < CUSTOMERS % waves ? 1 : 0))];
  let moved = 0;
  for (let i = 0; i < sizes.length; i++) {
    const wave = pilot ? i : i + 1;
    moved += sizes[i];
    if (gate && wave >= defectInWave) break;   // stop and fix before the next wave
  }
  const after = rollBack ? "can be moved back while it's fixed" : "stuck on the new platform until it's fixed";
  console.log(`${name.padEnd(30)} ${moved.toLocaleString("en-US").padStart(9)} customers hit; ${after}`);
}

migrate("single event, no way back", 1, { gate: false, rollBack: false });
migrate("10 waves, no readiness gate", 10, { gate: false });
migrate("10 waves, gate, roll-back", 10);
migrate("staff pilot, then 10 waves", 10, { pilot: 5_000, defectInWave: 0 });
import java.util.*;

public class Main {
    static final int CUSTOMERS = 5_200_000;

    static void migrate(String name, int waves, int pilot, boolean gate, boolean rollBack, int defectInWave) {
        // Move customers in waves (wave 0 = an optional pilot). A platform defect shows up in the given wave.
        // With a readiness gate, migration stops there; everyone moved so far is hit by the defect.
        List<Integer> sizes = new ArrayList<>();
        if (pilot > 0) sizes.add(pilot);
        for (int i = 0; i < waves; i++) sizes.add(CUSTOMERS / waves + (i < CUSTOMERS % waves ? 1 : 0));
        int moved = 0;
        for (int i = 0; i < sizes.size(); i++) {
            int wave = pilot > 0 ? i : i + 1;
            moved += sizes.get(i);
            if (gate && wave >= defectInWave) break;   // stop and fix before the next wave
        }
        String after = rollBack ? "can be moved back while it's fixed" : "stuck on the new platform until it's fixed";
        System.out.println(String.format(Locale.US, "%-30s %,9d customers hit; %s", name, moved, after));
    }

    public static void main(String[] args) {
        migrate("single event, no way back", 1, 0, false, false, 1);
        migrate("10 waves, no readiness gate", 10, 0, false, true, 1);
        migrate("10 waves, gate, roll-back", 10, 0, true, true, 1);
        migrate("staff pilot, then 10 waves", 10, 5_000, true, true, 0);
    }
}
single event, no way back      5,200,000 customers hit; stuck on the new platform until it's fixed
10 waves, no readiness gate    5,200,000 customers hit; can be moved back while it's fixed
10 waves, gate, roll-back        520,000 customers hit; can be moved back while it's fixed
staff pilot, then 10 waves         5,000 customers hit; can be moved back while it's fixed

Waves have real costs, which the FCA records as TSB's reasons: running two platforms, a staged move between two providers, and customers seeing their products split across systems. The point isn't that waves are free. It's that a plan with no gate and no way back puts every customer behind one decision.

7. The numbers

Try it: plan the cutover

A platform defect shows up early. How many customers does it reach under your plan? Waves and pilot sizes are illustrative.

8. What happened

From early after go-live, customers met data breaches, failures in internet and mobile banking, failures in telephone banking, branch technology failures and problems with card payments. The FCA describes a cascade: digital failures pushed customers to the phones, which were overloaded, which pushed them into branches, where systems were running slowly. The direct causes "substantially related to issues with IT configuration, capacity and coding". The FCA notes that had the reduced Active-Active testing been done, a configuration problem in the data centres that took internet and mobile banking down "would have been found".

The FCA found TSB in breach of Principle 3: it failed to organise and control the programme with adequate risk management, including its outsourcing to SABIS. TSB itself told the regulators it would not have gone ahead had it thought an incident of that scale might happen.

9. Lessons

10. Your turn

  1. Find your roll-back (10 min). For the riskiest change on your roadmap: after go-live, how would you go back, and until when can you?
  2. List the non-functional tests (10 min). Load, failover, data-centre configuration: which ones are planned, and who could cut them?
  3. Design a wave plan (10 min). Who would go first (staff, a region, a product), and what would have to be true before the next wave?
Say it in the interview
  • "Tell me about a migration that went wrong." TSB in 2018 moved most of 5.2 million customers to a new platform in one weekend, with no way back. Reduced non-functional testing missed a data-centre configuration problem, and the bank took until December to recover. I'd plan waves with a pilot, readiness gates and a roll-back, and treat cutting load testing as a risk decision.

11. Check yourself

Why couldn't TSB undo the migration once problems appeared?

A predominantly single event with no roll-back put every customer behind one decision.

What does the FCA say reduced testing would likely have found?

Testing in Active-Active configuration was cut around the end of February 2018.

Which reason did TSB give for a predominantly single event?

Its reasons also included cost, the difficulty of a staged move and LBG's expected resistance.

12. Sources and further reading

SourceWhy
FCA Final Notice: TSB Bank plc (December 2022)The full findings: the programme, the decision, testing, outsourcing and the incident.
TSB fined £48.65m for operational resilience failings (FCA)The fines and the summary.
PRA fines the former CIO of TSB (Bank of England)Supplier readiness and senior managers' responsibility.
Slaughter and May independent review (TSB)The review the TSB board commissioned, published November 2019.