VOOZH about

URL: https://dzone.com/articles/migrating-feature-toggles-with-unleash-and-cockroa

โ‡ฑ Migrating Feature Toggles With Unleash and CockroachDB


Related

  1. DZone
  2. Data Engineering
  3. Databases
  4. Migrating Feature Toggles With Unleash and CockroachDB

Migrating Feature Toggles With Unleash and CockroachDB

This tutorial will demonstrate a real-world migration story. Migrations are not easy and challenges exist no matter the source database, including PostgreSQL.

By Jan. 16, 22 ยท Tutorial
Likes
Comment
Save
3.0K Views

Join the DZone community and get the full member experience.

Join For Free

Motivation

I'd like to illustrate a typical migration from an application backed by Postgresql. Considering the operational database market with a variety of database choices, we find common issues moving from a monolithic database to a distributed SQL flavor. Considerations must be taken in approaching each migration, whether application code needs a rewrite or application logic must be changed to accommodate the new paradigms. I won't touch on every point but I will do my best to emphasize some of the salient points.

All my articles start with a customer problem. My client is considering CockroachDB for their database fleet and being a net new platform in the organization, the choice was made to evaluate CockroachDB as a drop-in replacement for Postgresql as a backing store for Unleash feature management platform. Should be easy right? Not so much.

High-Level Steps

  • Problem
  • Workaround
  • Solution
  • Next steps
  • Clean up

Problem

We're going to use a local instance of Unleash, the problem is reproducible irrelevant of the deployment model.

First, we need to check out the Unleash repo, the same problem can be reproduced whether you used Unleash Docker image or the pre-built node application.

git clone https://github.com/Unleash/unleash.git


We also are going to use a single node instance of CockroachDB to keep it simple.

cockroach start-single-node --insecure --background


We need to create a target database

ockroach sql --insecure -e "CREATE DATABASE unleash;"


Find the connection configuration server-dev.ts in your checked-out repo and fill out the details.

 db: {
 user: 'root',
 password: '',
 host: 'localhost',
 port: 26257,
 database: 'unleash',
 ssl: false,
 },


Start the application - yarn install.

yarn install v1.22.17
[1/5] Validating package.json...
[2/5] Resolving packages...
success Already up-to-date.
$ yarn run build
yarn run v1.22.17
$ yarn run clean
$ del-cli --force dist
$ yarn run copy-templates && tsc --pretty
$ copyfiles -u 1 src/mailtemplates/**/*.mustache dist/
Done in 8.99s.
Done in 9.79s.
yarn start:dev
9:22:19 AM - Starting compilation in watch mode...


9:22:26 AM - Found 0 errors. Watching for file changes.
[2022-01-14T09:22:28.664] [DEBUG] server-impl.js - DB migration: start
[2022-01-14T09:22:33.542] [ERROR] server-impl.js - Failed to migrate db error: column "strategies" does not exist
 at addChunk (node:internal/streams/readable:324:12)
 at readableAddChunk (node:internal/streams/readable:297:9)
 at Socket.Readable.push (node:internal/streams/readable:234:10)
 at TCP.onStreamRead (node:internal/stream_base_commons:190:23)
From previous event:
 at processImmediate (node:internal/timers:473:21) {
 length: 105,
 severity: 'ERROR',
 code: '42703',
 detail: undefined,
 hint: undefined,
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'column_resolver.go',
 line: '196',
 routine: 'NewUndefinedColumnError'
}
error: column "strategies" does not exist
 length: 105,
 severity: 'ERROR',
 code: '42703',
 detail: undefined,
 hint: undefined,
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'column_resolver.go',
 line: '196',
 routine: 'NewUndefinedColumnError'
}


This error appears as part of the initial Unleash migration attempt. This step works out of the box with Postgresql. Let's see what we have in our database.

root@:26257/unleash> show tables;
 schema_name | table_name | type | owner | estimated_row_count | locality
--------------+------------+-------+-------+---------------------+-----------
 public | events | table | root | 1 | NULL
 public | features | table | root | 0 | NULL
 public | migrations | table | root | 7 | NULL
 public | strategies | table | root | 1 | NULL
(4 rows)


There is some data but it is not what we expect to see in the stable version. Let's inspect the migrations table.

root@:26257/unleash> select * from migrations;
 id | name | run_on
---------------------+-------------------------------------------------------+--------------------------
 727776459344379905 | /20141020151056-initial-schema | 2022-01-14 09:22:29.554
 727776459921555457 | /20141110144153-add-description-to-features | 2022-01-14 09:22:29.731
 727776463439036417 | /20141117200435-add-parameters-template-to-strategies | 2022-01-14 09:22:30.804
 727776466625527809 | /20141117202209-insert-default-strategy | 2022-01-14 09:22:31.777
 727776467290947585 | /20141118071458-default-strategy-event | 2022-01-14 09:22:31.98
 727776467763429377 | /20141215210141-005-archived-flag-to-features | 2022-01-14 09:22:32.124
 727776472216240129 | /20150210152531-006-rename-eventtype | 2022-01-14 09:22:33.483
(7 rows)


This information tells me the migration step failed after 20150210152531-006-rename-eventtype step. Let's inspect the migration directory in our repo.

cd src/migrations && ls
20141020151056-initial-schema.js
20141110144153-add-description-to-features.js
20141117200435-add-parameters-template-to-strategies.js
20141117202209-insert-default-strategy.js
20141118071458-default-strategy-event.js
20141215210141-005-archived-flag-to-features.js
20150210152531-006-rename-eventtype.js
20160618193924-add-strategies-to-features.js
...


The migrations execute in order and our problem appears after 20150210152531-006-rename-eventtype.js so it must be 20160618193924-add-strategies-to-features.js file. Our overarching goal is to execute all of the migrations in this directory and there are currently 90+ migrations in this directory. Recall we were able to only execute 7 so far! We will look at the why of the problem in a later step. Let's see how we can bypass the migrations and actually get the application running atop CockroachDB.

Workaround

The workaround is rather simple and I have the amazing Unleash team to thank for that. We're going to perform the initialization of Unleash on Postgresql, take a backup and restore it to CockroachDB, then attempt to start the application again.

Change the connection configuration to reflect your Postgresql instance and run the application again. The application will start shortly after the successful migration.

Let's inspect the database.

psql "postgresql://localhost:5432/unleash?sslmode=disable" -U unleash_user


Let's list out all of the tables, recall we only had 4 tables in CockroachDB.

psql (14.1)
Type "help" for help.

unleash=# \dt
 List of relations
 Schema | Name | Type | Owner 
--------+----------------------+-------+--------------
 public | addons | table | unleash_user
 public | api_tokens | table | unleash_user
 public | client_applications | table | unleash_user
 public | client_instances | table | unleash_user
 public | client_metrics_env | table | unleash_user
 public | context_fields | table | unleash_user
 public | environments | table | unleash_user
 public | events | table | unleash_user
 public | feature_environments | table | unleash_user
 public | feature_strategies | table | unleash_user
 public | feature_tag | table | unleash_user
 public | feature_types | table | unleash_user
 public | features | table | unleash_user
 public | migrations | table | unleash_user
 public | project_environments | table | unleash_user
 public | projects | table | unleash_user
 public | reset_tokens | table | unleash_user
 public | role_permission | table | unleash_user
 public | role_user | table | unleash_user
 public | roles | table | unleash_user
 public | settings | table | unleash_user
 public | strategies | table | unleash_user
 public | tag_types | table | unleash_user
 public | tags | table | unleash_user
 public | unleash_session | table | unleash_user
 public | user_feedback | table | unleash_user
 public | user_splash | table | unleash_user
 public | users | table | unleash_user


We have a lot more tables in PostgreSQL.

Let's take a backup we're going to use in CockroachDB.

pg_dump unleash > /tmp/unleash.sql -U unleash_user


Upload the file to the CockroachDB user file filesystem.

cockroach userfile upload /tmp/unleash.sql --insecure --host=localhost
successfully uploaded to userfile://defaultdb.public.userfiles_root/unleash.sql


Import the Database

cockroach sql --insecure -e "import pgdump 'userfile://defaultdb.public.userfiles_root/unleash.sql' WITH ignore_unsupported_statements;" --database unleash;


The last step will produce an error.

ERROR: referenced table "public.events_id_seq" not found in tables being imported (public.feature_types,public.projects)


It is due to the following syntax with SEQUENCE

CREATE SEQUENCE public.events_id_seq
 AS integer
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;
invalid syntax: statement ignored: at or near "start": syntax error: unimplemented: this syntax
SQLSTATE: 0A000
DETAIL: source SQL:
CREATE SEQUENCE public.events_id_seq
 AS integer
 START WITH 1
 ^
HINT: You have attempted to use a feature that is not yet implemented.
See: https://go.crdb.dev/issue-v/25110/v21.2


The problem line is AS integer, luckily it's the only issue common to this pgdump and there is a total of 5 instances of this error. Find and replace all of the instances and repeat the last steps to import the pgdump into CockroachDB. By the way, AS integer is an accepted syntax in CockroachDB 22.1 which is coming in the first half of 2022!

CREATE SEQUENCE public.events_id_seq
 --AS integer
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;


Every instance of the syntax needs to be addressed.

CREATE SEQUENCE public.addons_id_seq
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;

CREATE SEQUENCE public.events_id_seq
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;

CREATE SEQUENCE public.migrations_id_seq
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;

CREATE SEQUENCE public.roles_id_seq
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;
 
CREATE SEQUENCE public.users_id_seq
 START WITH 1
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;


Attempt to Import Again, Optionally Re-Create the Database

cockroach sql --insecure -e "DROP DATABASE unleash CASCADE;"
cockroach sql --insecure -e "CREATE DATABASE unleash;"


Execute Import

cockroach sql -e "import pgdump 'userfile://defaultdb.public.userfiles_root/unleash.sql' WITH ignore_unsupported_statements" --insecure --host=localhost --database=unleash
 job_id | status | fraction_completed | rows | index_entries | bytes
---------------------+-----------+--------------------+------+---------------+--------
 726931514777108481 | succeeded | 1 | 166 | 59 | 16566
(1 row)


Let's inspect the database, now in CockroachDB.

root@:26257/unleash> show tables;
 schema_name | table_name | type | owner | estimated_row_count | locality
--------------+----------------------+----------+-------+---------------------+-----------
 public | addons | table | root | 0 | NULL
 public | addons_id_seq | sequence | root | 0 | NULL
 public | api_tokens | table | root | 0 | NULL
 public | client_applications | table | root | 0 | NULL
 public | client_instances | table | root | 0 | NULL
 public | client_metrics_env | table | root | 0 | NULL
 public | context_fields | table | root | 0 | NULL
 public | environments | table | root | 0 | NULL
 public | events | table | root | 0 | NULL
 public | events_id_seq | sequence | root | 0 | NULL
 public | feature_environments | table | root | 0 | NULL
 public | feature_strategies | table | root | 0 | NULL
 public | feature_tag | table | root | 0 | NULL
 public | feature_types | table | root | 0 | NULL
 public | features | table | root | 0 | NULL
 public | migrations | table | root | 0 | NULL
 public | migrations_id_seq | sequence | root | 0 | NULL
 public | project_environments | table | root | 0 | NULL
 public | projects | table | root | 0 | NULL
 public | reset_tokens | table | root | 0 | NULL
 public | role_permission | table | root | 0 | NULL
 public | role_user | table | root | 0 | NULL
 public | roles | table | root | 0 | NULL
 public | roles_id_seq | sequence | root | 0 | NULL
 public | settings | table | root | 0 | NULL
 public | strategies | table | root | 0 | NULL
 public | tag_types | table | root | 0 | NULL
 public | tags | table | root | 0 | NULL
 public | unleash_session | table | root | 0 | NULL
 public | user_feedback | table | root | 0 | NULL
 public | user_splash | table | root | 0 | NULL
 public | users | table | root | 0 | NULL
 public | users_id_seq | sequence | root | 0 | NULL
(33 rows)


Let's inspect the migrations table.

root@:26257/unleash> select * from migrations;
 id | name | run_on
-----+------------------------------------------------------------------------+--------------------------
 1 | /20141020151056-initial-schema | 2022-01-10 19:42:19.111
 2 | /20141110144153-add-description-to-features | 2022-01-10 19:42:19.121
 3 | /20141117200435-add-parameters-template-to-strategies | 2022-01-10 19:42:19.123
 4 | /20141117202209-insert-default-strategy | 2022-01-10 19:42:19.126
 5 | /20141118071458-default-strategy-event | 2022-01-10 19:42:19.129
 6 | /20141215210141-005-archived-flag-to-features | 2022-01-10 19:42:19.132
 7 | /20150210152531-006-rename-eventtype | 2022-01-10 19:42:19.135
 8 | /20160618193924-add-strategies-to-features | 2022-01-10 19:42:19.139
 9 | /20161027134128-create-metrics | 2022-01-10 19:42:19.145
 10 | /20161104074441-create-client-instances | 2022-01-10 19:42:19.149
 11 | /20161205203516-create-client-applications | 2022-01-10 19:42:19.154
 12 | /20161212101749-better-strategy-parameter-definitions | 2022-01-10 19:42:19.165
 13 | /20170211085502-built-in-strategies | 2022-01-10 19:42:19.168
 14 | /20170211090541-add-default-strategies | 2022-01-10 19:42:19.179
 15 | /20170306233934-timestamp-with-tz | 2022-01-10 19:42:19.183
 16 | /20170628205541-add-sdk-version-to-client-instances | 2022-01-10 19:42:19.186
 17 | /20190123204125-add-variants-to-features | 2022-01-10 19:42:19.188
 18 | /20191023184858-flexible-rollout-strategy | 2022-01-10 19:42:19.192
 19 | /20200102184820-create-context-fields | 2022-01-10 19:42:19.198
 20 | /20200227202711-settings | 2022-01-10 19:42:19.203
 21 | /20200329191251-settings-secret | 2022-01-10 19:42:19.206
 22 | /20200416201319-create-users | 2022-01-10 19:42:19.214
 23 | /20200429175747-users-settings | 2022-01-10 19:42:19.216
 24 | /20200805091409-add-feature-toggle-type | 2022-01-10 19:42:19.222
 25 | /20200805094311-add-feature-type-to-features | 2022-01-10 19:42:19.224
 26 | /20200806091734-add-stale-flag-to-features | 2022-01-10 19:42:19.226
 27 | /20200810200901-add-created-at-to-feature-types | 2022-01-10 19:42:19.229
 28 | /20200928194947-add-projects | 2022-01-10 19:42:19.234
 29 | /20200928195238-add-project-id-to-features | 2022-01-10 19:42:19.237
 30 | /20201216140726-add-last-seen-to-features | 2022-01-10 19:42:19.239
 31 | /20210105083014-add-tag-and-tag-types | 2022-01-10 19:42:19.25
 32 | /20210119084617-add-addon-table | 2022-01-10 19:42:19.255
 33 | /20210121115438-add-deprecated-column-to-strategies | 2022-01-10 19:42:19.257
 34 | /20210127094440-add-tags-column-to-events | 2022-01-10 19:42:19.259
 35 | /20210208203708-add-stickiness-to-context | 2022-01-10 19:42:19.262
 36 | /20210212114759-add-session-table | 2022-01-10 19:42:19.268
 37 | /20210217195834-rbac-tables | 2022-01-10 19:42:19.276
 38 | /20210218090213-generate-server-identifier | 2022-01-10 19:42:19.28
 39 | /20210302080040-add-pk-to-client-instances | 2022-01-10 19:42:19.284
 40 | /20210304115810-change-default-timestamp-to-now | 2022-01-10 19:42:19.287
 41 | /20210304141005-add-announce-field-to-application | 2022-01-10 19:42:19.29
 42 | /20210304150739-add-created-by-to-application | 2022-01-10 19:42:19.293
 43 | /20210322104356-api-tokens-table | 2022-01-10 19:42:19.299
 44 | /20210322104357-api-tokens-convert-enterprise | 2022-01-10 19:42:19.303
 45 | /20210323073508-reset-application-announcements | 2022-01-10 19:42:19.306
 46 | /20210409120136-create-reset-token-table | 2022-01-10 19:42:19.314
 47 | /20210414141220-fix-misspellings-in-role-descriptions | 2022-01-10 19:42:19.318
 48 | /20210415173116-rbac-rename-roles | 2022-01-10 19:42:19.322
 49 | /20210421133845-add-sort-order-to-strategies | 2022-01-10 19:42:19.328
 50 | /20210421135405-add-display-name-and-update-description-for-strategies | 2022-01-10 19:42:19.333
 51 | /20210423103647-lowercase-all-emails | 2022-01-10 19:42:19.339
 52 | /20210428062103-user-permission-to-rbac | 2022-01-10 19:42:19.344
 53 | /20210428103923-onboard-projects-to-rbac | 2022-01-10 19:42:19.349
 54 | /20210504101429-deprecate-strategies | 2022-01-10 19:42:19.353
 55 | /20210520171325-update-role-descriptions | 2022-01-10 19:42:19.357
 56 | /20210602115555-create-feedback-table | 2022-01-10 19:42:19.368
 57 | /20210610085817-features-strategies-table | 2022-01-10 19:42:19.382
 58 | /20210615115226-migrate-strategies-to-feature-strategies | 2022-01-10 19:42:19.385
 59 | /20210618091331-project-environments-table | 2022-01-10 19:42:19.391
 60 | /20210618100913-add-cascade-for-user-feedback | 2022-01-10 19:42:19.395
 61 | /20210624114602-change-type-of-feature-archived | 2022-01-10 19:42:19.401
 62 | /20210624114855-drop-strategies-column-from-features | 2022-01-10 19:42:19.405
 63 | /20210624115109-drop-enabled-column-from-features | 2022-01-10 19:42:19.408
 64 | /20210625102126-connect-default-project-to-global-environment | 2022-01-10 19:42:19.412
 65 | /20210629130734-add-health-rating-to-project | 2022-01-10 19:42:19.416
 66 | /20210830113948-connect-projects-to-global-envrionments | 2022-01-10 19:42:19.42
 67 | /20210831072631-add-sort-order-and-type-to-env | 2022-01-10 19:42:19.426
 68 | /20210907124058-add-dbcritic-indices | 2022-01-10 19:42:19.442
 69 | /20210907124850-add-dbcritic-primary-keys | 2022-01-10 19:42:19.449
 70 | /20210908100701-add-enabled-to-environments | 2022-01-10 19:42:19.456
 71 | /20210909085651-add-protected-field-to-environments | 2022-01-10 19:42:19.462
 72 | /20210913103159-api-keys-scoping | 2022-01-10 19:42:19.467
 73 | /20210915122001-add-project-and-environment-columns-to-events | 2022-01-10 19:42:19.477
 74 | /20210920104218-rename-global-env-to-default-env | 2022-01-10 19:42:19.483
 75 | /20210921105032-client-api-tokens-default | 2022-01-10 19:42:19.487
 76 | /20210922084509-add-non-null-constraint-to-environment-type | 2022-01-10 19:42:19.491
 77 | /20210922120521-add-tag-type-permission | 2022-01-10 19:42:19.495
 78 | /20210928065411-remove-displayname-from-environments | 2022-01-10 19:42:19.499
 79 | /20210928080601-add-development-and-production-environments | 2022-01-10 19:42:19.503
 80 | /20210928082228-connect-default-environment-to-all-existing-projects | 2022-01-10 19:42:19.507
 81 | /20211004104917-client-metrics-env | 2022-01-10 19:42:19.52
 82 | /20211011094226-add-environment-to-client-instances | 2022-01-10 19:42:19.526
 83 | /20211013093114-feature-strategies-parameters-not-null | 2022-01-10 19:42:19.528
 84 | /20211029094324-set-sort-order-env | 2022-01-10 19:42:19.531
 85 | /20211105104316-add-feature-name-column-to-events | 2022-01-10 19:42:19.534
 86 | /20211105105509-add-predata-column-to-events | 2022-01-10 19:42:19.537
 87 | /20211108130333-create-user-splash-table | 2022-01-10 19:42:19.543
 88 | /20211109103930-add-splash-entry-for-users | 2022-01-10 19:42:19.546
 89 | /20211126112551-disable-default-environment | 2022-01-10 19:42:19.549
 90 | /20211130142314-add-updated-at-to-projects | 2022-01-10 19:42:19.552
 91 | /20211209205201-drop-client-metrics | 2022-01-10 19:42:19.555
(91 rows)


So far so good, let's update the Unleash connection parameters in server-dev.ts file back to CockroachDB as above. Unleash uses knex for database connection management. CockroachDB has its own dialect when it comes to knex. We have to change the client before we can proceed. Find the db-pool.ts file and edit it.

Change the client line from pg to cockroachdb

- client: 'pg',
+ client: 'cockroachdb',
export function createDb({
 db,
 getLogger,
}: Pick<IUnleashConfig, 'db' | 'getLogger'>): Knex {
 const logger = getLogger('db-pool.js');
 return knex({
 client: 'cockroachdb',
 version: db.version,
 connection: db,
 pool: db.pool,
 searchPath: db.schema,
 asyncStackTraces: true,
 log: {
 debug: (msg) => logger.debug(msg),
 warn: (msg) => logger.warn(msg),
 error: (msg) => logger.error(msg),
 },
 });
}


The last step is to start the application again.

yarn install
yarn start:dev
10:28:48 AM - Starting compilation in watch mode...


10:28:56 AM - Found 0 errors. Watching for file changes.
[2022-01-14T10:28:57.646] [DEBUG] server-impl.js - DB migration: start
[2022-01-14T10:28:57.778] [DEBUG] server-impl.js - DB migration: end
[2022-01-14T10:28:57.833] [DEBUG] /middleware/api-token.ts - Enabling api-token middleware
[2022-01-14T10:28:57.834] [DEBUG] /middleware/rbac-middleware.ts - Enabling RBAC middleware
[2022-01-14T10:28:57.849] [INFO] server-impl.js - Unleash has started. { address: '::', family: 'IPv6', port: 4242 }
[2022-01-14T10:28:57.850] [DEBUG] server-impl.js - Registering graceful shutdown


We can now navigate to the Unleash UI and authenticate with the default credentials.

And we're in!


I am not going to cover further than that as I've done some superficial testing of Unleash with CockroachDB and it worked most of the time. A full end-to-end test of the integration is still required. I'm merely demonstrating the migration story in this article.

The real solution is to make CockroachDB a first-class citizen as we've covered the running of Unleash atop CockroachDB but the elephant in the room is migrations, as they still don't work for initial deployment and likely continuous operation of Unleash. The longer-term solution is below:

Solution

Considering the original problem was with a migration, let's take a look at the 20160618193924-add-strategies-to-features.js file, we see the following code block.

exports.up = function (db, callback) {
 db.runSql(
 `
--create new strategies-column
ALTER TABLE features ADD "strategies" json;

--populate the strategies column
UPDATE features
SET strategies = ('[{"name":"'||f.strategy_name||'","parameters":'||f.parameters||'}]')::json
FROM features as f
WHERE f.name = features.name;

--delete old strategy-columns
ALTER TABLE features DROP COLUMN "strategy_name";
ALTER TABLE features DROP COLUMN "parameters";
 `,
 callback,
 );
};


Recall the original error message.

[2022-01-14T09:22:33.542] [ERROR] server-impl.js - Failed to migrate db error: column "strategies" does not exist


If you know something about CockroachDB schema changes currently in 21.2 and below, schema change jobs are executed asynchronously and the current recommendation is to separate DML and DDL statements into their own migrations.

This coincides with what Unleash team was seeing in the initial investigation of the issue. The solution is to split up the migration into 3 different steps. The quickest solution is to make 2 more copies of the migration file and name it in the order of execution

20160618193924-add-strategies-to-features1.js 20160618193924-add-strategies-to-features3.js
20160618193924-add-strategies-to-features2.js


The first file will include the first ALTER statement.

'use strict';

exports.up = function (db, callback) {
 db.runSql(
 `
--create new strategies-column
ALTER TABLE features ADD "strategies" json;
 `,
 callback,
 );
};

exports.down = function (db, callback) {
 db.runSql(
 `
--create old columns
ALTER TABLE features ADD "parameters" json;
ALTER TABLE features ADD "strategy_name" varchar(255);
 `,
 callback,
 );
};


The second file will include the UPDATE statement.

'use strict';

exports.up = function (db, callback) {
 db.runSql(
 `
--populate the strategies column
UPDATE features
SET strategies = ('[{"name":"'||f.strategy_name||'","parameters":'||f.parameters||'}]')::json
FROM features as f
WHERE f.name = features.name;
 `,
 callback,
 );
};

exports.down = function (db, callback) {
 db.runSql(
 `
--populate old columns
UPDATE features
SET strategy_name = f.strategies->0->>'name',
 parameters = f.strategies->0->'parameters'
FROM features as f
WHERE f.name = features.name;
 `,
 callback,
 );
};


The third file will include the last ALTER statement.

'use strict';

exports.up = function (db, callback) {
 db.runSql(
 `
--delete old strategy-columns
ALTER TABLE features DROP COLUMN "strategy_name";
ALTER TABLE features DROP COLUMN "parameters";
 `,
 callback,
 );
};

exports.down = function (db, callback) {
 db.runSql(
 `
--drop new column
ALTER TABLE features DROP COLUMN "strategies";
 `,
 callback,
 );
};


CockroachDB supports multiple statements in schema change but we don't support mixing DML and DDL together today. Work is ongoing to address this gap in the near future!

Drop the existing database and re-create it.

DROP DATABASE unleash CASCADE;
CREATE DATABASE unleash;


Start the application again.

yarn install
yarn start:dev
10:50:20 AM - Starting compilation in watch mode...


10:50:27 AM - Found 0 errors. Watching for file changes.
[2022-01-14T10:50:29.054] [DEBUG] server-impl.js - DB migration: start
[2022-01-14T10:50:36.076] [ERROR] server-impl.js - Failed to migrate db error: column "parameters" does not exist
 length: 105,
 severity: 'ERROR',
 code: '42703',
 detail: undefined,
 hint: undefined,
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'column_resolver.go',
 line: '196',
 routine: 'NewUndefinedColumnError'
}
error: column "parameters" does not exist
 length: 105,
 severity: 'ERROR',
 code: '42703',
 detail: undefined,
 hint: undefined,
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'column_resolver.go',
 line: '196',
 routine: 'NewUndefinedColumnError'
}


Notice, it's a different error now, which means we either broke something else or we successfully advanced to the next failing migration!

Let's look at the tables, recall we had 4 tables in the original migration.

root@:26257/unleash> show tables;
 schema_name | table_name | type | owner | estimated_row_count | locality
--------------+---------------------+-------+-------+---------------------+-----------
 public | client_applications | table | root | 0 | NULL
 public | client_instances | table | root | 0 | NULL
 public | client_metrics | table | root | 0 | NULL
 public | events | table | root | 1 | NULL
 public | features | table | root | 0 | NULL
 public | migrations | table | root | 6 | NULL
 public | strategies | table | root | 1 | NULL
(7 rows)


We were able to advance! Let's look at the migrations table.

root@:26257/unleash> select * from migrations;
 id | name | run_on
---------------------+-------------------------------------------------------+--------------------------
 727793761311883265 | /20141020151056-initial-schema | 2022-01-14 10:50:29.696
 727793761921007617 | /20141110144153-add-description-to-features | 2022-01-14 10:50:29.882
 727793765553864705 | /20141117200435-add-parameters-template-to-strategies | 2022-01-14 10:50:30.991
 727793768073625601 | /20141117202209-insert-default-strategy | 2022-01-14 10:50:31.76
 727793768659845121 | /20141118071458-default-strategy-event | 2022-01-14 10:50:31.939
 727793768965963777 | /20141215210141-005-archived-flag-to-features | 2022-01-14 10:50:32.032
 727793772693061633 | /20150210152531-006-rename-eventtype | 2022-01-14 10:50:33.169
 727793772928466945 | /20160618193924-add-strategies-to-features1 | 2022-01-14 10:50:33.241
 727793776370712577 | /20160618193924-add-strategies-to-features2 | 2022-01-14 10:50:34.292
 727793776680828929 | /20160618193924-add-strategies-to-features3 | 2022-01-14 10:50:34.387
 727793780668694529 | /20161027134128-create-metrics | 2022-01-14 10:50:35.603
 727793781111259137 | /20161104074441-create-client-instances | 2022-01-14 10:50:35.738
 727793781682929665 | /20161205203516-create-client-applications | 2022-01-14 10:50:35.913
(13 rows)


We were able to advance through 20161027134128-create-metrics, 20161104074441-create-client-instances and 20161205203516-create-client-applications migrations!

I'm going to wrap it up here and spoil the surprise by saying that I've attempted to execute all subsequent failing migrations. Eventually, there's a happy path but this article is getting lengthy already.

Next Steps

The next step with Unleash and CockroachDB is to collaborate and introduce a CockroachDB specific version of the features management platform. There's work and architectural changes necessary to make some of the migrations work in the way CockroachDB expects, for example, one of the migrations is using the DELETE USING syntax:

Failed to migrate db error: at or near "where": syntax error: unimplemented: this syntax
 length: 460,
 severity: 'ERROR',
 code: '0A000',
 detail: 'source SQL:\n' +
 'DELETE FROM client_instances a USING client_instances b WHERE a.app_name = b.app_name AND a.instance_id = b.instance_id AND a.created_at < b.created_at\n' +
 ' ^',
 hint: 'You have attempted to use a feature that is not yet implemented.\n' +
 'See: https://go.crdb.dev/issue-v/40963/v21.2',
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'lexer.go',
 line: '217',
 routine: 'UnimplementedWithIssueDetail'
}
error: at or near "where": syntax error: unimplemented: this syntax length: 460,
 severity: 'ERROR',
 code: '0A000',
 detail: 'source SQL:\n' +
 'DELETE FROM client_instances a USING client_instances b WHERE a.app_name = b.app_name AND a.instance_id = b.instance_id AND a.created_at < b.created_at\n' +
 ' ^',
 hint: 'You have attempted to use a feature that is not yet implemented.\n' +
 'See: https://go.crdb.dev/issue-v/40963/v21.2',
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: undefined,
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'lexer.go',
 line: '217',
 routine: 'UnimplementedWithIssueDetail'


We do not support the syntax today, so the workaround is to rewrite the statement with something like this:

DELETE FROM client_instances a
WHERE a.instance_id IN
(
SELECT a.app_name
FROM client_instances a
INNER JOIN client_instances b ON a.app_name = b.app_name
WHERE a.app_name = b.app_name AND a.instance_id = b.instance_id AND a.created_at < b.created_at
);


And that's just some of the failing errors along with the original where DML and DDL are intermixed. We're going to continue our wonderful collaboration with the Unleash team, whom I thank wholly for their incredible support throughout this experiment. Stay tuned!

CockroachDB Database connection application

Published at DZone with permission of Artem Ervits. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Java EE 6 Pet Catalog with GlassFish and MySQL
  • Create a Multi-Tenancy Application in Nest.js, Part 4: Authentication and Authorization Setup
  • Getting Started With JPA/Hibernate
  • Application Self-Healing: Common Failures and How to Avoid Them

Partner Resources

ร—

Comments

The likes didn't load as expected. Please refresh the page and try again.

Let's be friends: