Migrating from Oracle Autonomous Database to YugabyteDB with YB-Voyager
Tags: yugabytedb, postgres, oracle, migration
Published: 2023-01-04 16:38:10


Migrating to YugabyteDB is easy: because it is PostgreSQL-compatible, many tools already exists. For migrating from Oracle, [ora2pg](https://ora2pg.darold.net/) is widely used. This is what [YugabyteDB Voyager](https://docs.yugabyte.com/preview/migrate/) runs under the hood when migrating from Oracle.

In this post I'll show a simple case easy to reproduce: running YugabyteDB Voyager in a Docker container to migrate from The Oracle Database managed service (called "Autonomous Database")  to a YugabyteDB cluster. I'll use the demo schema coming with the free Autonomous Database so that it is easy to reproduce without the need to pay for Oracle. For the target, no problem, YugabyteDB is Open Source.

## Start Docker

I start a temporary container from the YugabyteDB docker image. I'll not run the database here, just `sleep infinity` and start a `bash` shell:

```sh
docker run -it --rm -v "C:\Downloads\mig":/mig yugabytedb/yugabyte bash

```

## no sudo 

I'm following the documentation to install YugabyteDB Voyager. The only difference is that I've no `sudo` in this image but I'm already `root`:

```sh
cd /mig
yum install -y git java-11-openjdk
update-alternatives --set java java-11-openjdk.x86_64
git clone https://github.com/yugabyte/yb-voyager.git
cd yb-voyager/installer_scripts
type sudo || { sudo() { "$@"; } ; export -f sudo ; }
yes Y | ./install-yb-voyager
cd -
. ~/.bashrc
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3jx73w121u31p59dnjeu.png)

## Download Oracle wallet

To connect to my Oracle Autonomous Database, I need a wallet that contains the certificate and listener addresses. I download it to my laptop:

![Download Wallet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1vw4226ywzmkxu9syuu.png)

I copy it into the Docker container:
```sh
docker cp Wallet_o21c.zip 9cfb744ee47f:/var/tmp
```

I unzip it:
```sh
yum install -y unzip
unzip -d . /var/tmp/Wallet_o21c.zip
sed -e '/WALLET_LOCATION/s/"[^"]*"/"$TNS_ADMIN"/' -i sqlnet.ora
export TNS_ADMIN=$PWD
```
Note that the `sqlnet.ora` in it supposes that the wallet location is under the `$ORACLE_HOME/network/admin` but with the Oracle InstantClient (installed with YugabyteDB Managed) there's no ORACLE_HOME. I've defined the TNS_ADMIN environment variable to the directory where I unzipped the wallet and that's why I use this in `sqlnet.ora`

I've put it in my local directory, `/home/yugabyte`, for this simple demo. This is not a best practice but just a quick way to learn how it works.

I test the connection with `sqlplus`:

```
grep -E "^o21c_tp *=" $TNS_ADMIN/tnsnames.ora &&
sqlplus demo/",,P455w0rd,,"@o21c_tp <<'SQL'
column "Size" format a30
select segment_type, count(*)
, dbms_xplan.format_size2(sum(bytes)) "Size"
from dba_segments 
where owner='SH'
group by segment_type
order by sum(bytes);
SQL
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/436f3gckpek1t0s6yio4.png)

## Export schema

Here is the export of the schema:

```sh
yes Y | yb-voyager export schema --export-dir $PWD schema \
--source-db-type oracle --oracle-tns-alias o21c_tp \
--source-db-user demo --source-db-password ",,P455w0rd,," \
--source-db-name=_this_is_ignored_ --source-db-schema=SH \
--start-clean
```

Note that the `--source-db-name` is mandatory but not used here where I use a `--oracle-tns-alias`.

## Analyze schema

I exported the `SH` sample schema which is really simple with only 9 tables:
```sh
yb-voyager analyze-schema --export-dir $PWD 
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gd30w7cunkbjmokzp13u.png)

## Export Data

The following exports all table rows ready to be imported. The source must be read only

```sh
yb-voyager export data --export-dir $PWD \
--source-db-type oracle --oracle-tns-alias o21c_tp \
--source-db-user demo --source-db-password ",,P455w0rd,," \
--source-db-name=_this_is_ignored_ --source-db-schema=SH
```

You can follow the performance on the source from the Oracle Performance Hub:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gcw27mil3tlmn3grcs8.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u18odzhg1q5tjyc50azq.png)

The files are in the `data` subdirectory as COPY statements ready to import into YugabyteDB.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy2k7ri4xtq7qphsn507.png)

## Import into YugabyteDB

I create a database in my target cluster, import the schema and then data, then create the indexes and trigger:

```sh
psql -h yb0.pachot.net -p 5433 -U yugabyte -c "create database sh"

yes Y | yb-voyager import schema --export-dir $PWD \
        --target-db-host yb0.pachot.net \
        --target-db-user yugabyte\
        --target-db-password yugabyte \
        --target-db-name sh \
        --target-db-schema public \
        --start-clean

yes Y | yb-voyager import data --export-dir $PWD \
        --target-db-host yb0.pachot.net \
        --target-db-user yugabyte\
        --target-db-password yugabyte \
        --target-db-name sh \
        --target-db-schema public 

yes Y | yb-voyager import schema --export-dir $PWD \
        --target-db-host yb0.pachot.net \
        --target-db-user yugabyte\
        --target-db-password yugabyte \
        --target-db-name sh \
        --target-db-schema public \
        --post-import-data

```

The big advantage of using YugabyteDB Voyager is that it makes it adds some optimization to import in parallel, to benefit from the distributed YugabyteDB. It can also resume when interrupted.

## Customize the Schema

If you have run the above, you got an error when importing the schema:
```
ALTER TABLE costs ADD CONSTRAINT costs_channel_fk FOREIGN KEY (channel_id) REFER ...

    ERROR: there is no unique constraint matching given keys for referenced table "channels" (SQLSTATE 42830)
    STATEMENT: ALTER TABLE costs ADD CONSTRAINT costs_channel_fk FOREIGN KEY (channel_id) REFERENCES channels(channel_id) ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE NOT VALID;
```

The Oracle SH demo schema has its primary keys disabled. This is not a normal situation because it means that there's no index on the primary key. The consequence is that no primary key have been created when exporting the schema.

To fix it I quickly generated, from the Oracle source, the commands to create the primary key in YugabyteDB:
```sql
select 'alter table '||table_name
||' add primary key ('
||listagg(column_name || ' hash') within group (order by position)
||');'
from all_cons_columns where owner='SH' and (owner,constraint_name) in (
select owner,constraint_name from all_constraints where constraint_type='P'
) group by owner, table_name
;
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5610lsh7lpj9r0mkrl0.png)

I've added the output into `./schema/tables/table.sql` before the creation of Foreign Keys

I've got another error:
```
sh=# ALTER TABLE costs ADD CONSTRAINT costs_product_fk FOREIGN KEY (prod_id) REFERENCES products(prod_id) ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE NOT VALID;
ERROR:  foreign key constraint "costs_product_fk" cannot be implemented
DETAIL:  Key columns "prod_id" and "prod_id" are of incompatible types: numeric and integer.
```

I've no idea why some NUMBER have been translated to NUMERIC and others to INTEGER. Anyway, it makes no sense to use NUMERIC for an ID in PostgreSQL or YugabyteDB. And INTEGER is not better as it can reach its maximum and be hard to change. I've modified all NUMBER id's with the following regexp:

```sh
sed -E 's/_id (integer|numeric) /_id bigint /' -i /home/yugabyte/schema/tables/table.sql

```

Even if there are tools to ease the migration, it is always a good idea check the schema. Some decisions are harder to change later. The default schema may be correct to import quickly and test your application- However, before migrating production, the DDL should be carefully validated so that datatypes and primary keys are efficient in the long term.