I'll try. Actually it's just what we did on my job this week.

For a long time we've had a single Cassandra database for write-performant storage in our project. Let's say it was a storage for Foo objects, which we have to write like thousands per second and read back aggregated stats like total numbers, number per parent object, writes per second/minute/hour etc.

With time it's read performance degraded. Also from time to time the database was diving into some internal service state (like index compacting or something like that), which further affected performance. At the end Cassandra cluster consisted of 30 hosts, which costs us about US$1000/day. So we decided to cut it off and change it for MongoDB, where we have much more expertise on the inside.

In our code we had a service class, let's call it FooService. All operations with Foo objects (write, read, stats, delete) was made via that service object, like FooService::addFoo(Foo, Bar, Baz). Any business logic class wanting to deal with Foo objects just declares FooService as is dependency, which is injected in runtime on calling business logic class constructor. So when certain code path doesn't need Foo objects - system don't have to initialise FooService at all.

FooService, in turn, dependes on FooRepository, which is a descendant of abstract CassandraRepository, which has code to connect to cluster, send requests, handle errors and so on. CassandraRepository is a descendant of AbstractRepository with methods like getItemById, getItemsByIds and so on.

Overall LOC count in our project is about 400k, it is not very complex, but complex enough to stuck with task like that if your architecture is not clean enough. But in our case the task was super easy:

1.Spin up MongoDB cluster with just 7 hosts (master, 3 sharded replicas and 3 shadow replicas)

2.Inherit FooNewRepository from MongoRepository, point it on new cluster.

3.Inject FooNewRepository into FooService and start performing mutable operations (create, update, delete) into both Mongo and Cassandra bound repos

4.Migrate historical data with script, which is using the same common codebase (it took about 3 weeks actually)

5.Switch reads to FooNewRepository

6.Check that everyting is okay

7.Drop all Cassandra-related code from codebase and rename FooNewRepository to just FooRepository

Because FooService declares it's repository dependencies as AbstractRepository, ALL the new code except FooRepository wass looking EXACTLY the same as before. The team noticed changes only due to our weekly all-hands, where we discussing all such matters. All the coding took about 2 days net, most of it was a migrating script, so with about US$750/day cost cut we adjusted that work in a matter of days.

And now the cherry on a cake. When we tried to shutdown a Cassandra cluster, we've got errors on another instance of our service, which used the same database, but was forked about 6 months ago to temporarily serve another country/language. We turned Cassandra back on, I checked out that fork, cherry-picked only Cassandra->Mongo commits from master branch, run the migration script (there were much less data for that country), and voila - all tests passed like a charm, new code working now (we will migrate it to full master branch later).

edit: formatting and grammar