Search docs.molo17.com

    Migrate Gluesync 1.x data modeling configurations

    A Gluesync 1.x data modeling configuration maps onto a Query entity or a document model. A converted entity keeps the behavior 1.x had, so you can cut over without changing the data that your applications read. This page lists what maps to what, what to confirm in each draft, the behavior a converted entity keeps, and the cut-over runbooks.

    Available from Gluesync 2.3.2.

    How 1.x entities map

    Gluesync 1.x product Entity kind Becomes in Gluesync 2 Bucket in the report

    sql-to-nosql

    query: one statement

    A Query entity with one level

    FLAT

    sql-to-nosql

    queryDataModeling: a root statement and level-2 statements

    A Query entity with the root and one MANY child level for each level-2 entry

    MULTI_LEVEL

    nosql-to-sql

    dataModeling: levels 1 to 3

    A document model: the level-1 table is the root, level 2 and 3 are arrays

    DOCUMENT_SPLIT

    Either

    A table or mapping entity, a query data modeling entity with more than two levels, an entity listed in couchbase.entitiesToApplyTtl, an entitiesKeyMapping separator that Gluesync 2 cannot render, or a statement or key that Core Hub refuses

    No draft: the entity stays on Gluesync 1.x

    UNSUPPORTED

    Gluesync 1.x skipped levels deeper than 3 in nosql-to-sql, and a conversion reports them.

    Convert with Automator

    The conversion runs in Gluesync Automator (see also Use Gluesync Automator), not in Core Hub: Core Hub has no conversion endpoint and no conversion tool. Automator turns the entities of a 1.x configuration into drafts, asks Core Hub to check each draft, and saves the drafts you choose on an existing pipeline. Use it from the web interface, or from the command line with gs1_import.py.

    Requirements
    • Core Hub 2.3.2 or later. Automator calls the describe endpoint POST /pipelines/{pipelineId}/agents/{agentId}/discovery/data-model and the validate endpoint POST /pipelines/{pipelineId}/config/entities/validate, which older Core Hubs do not have.

    • A pipeline that already holds the source and target agents of the entities, for example the MSSQL CDC agent and the Couchbase agent for sql-to-nosql, or the Couchbase agent and the SQL Server agent for nosql-to-sql.

    • A user that can create entities, and the 1.x configuration as a JSON file.

    Both ways run the same three steps.

    1. Convert. No call to Core Hub. One draft for each 1.x entity that Gluesync 2 can reproduce, and a report of what became of every 1.x entity. Keys whose name looks like a secret are removed before the file is read, and listed by their JSON path.

    2. Check with Core Hub. A Query draft is described against the source agent, and every draft is validated as an entity. Nothing is written.

    3. Save. The drafts you choose are saved with PUT /pipelines/{pipelineId}/config/entities, after you confirm.

    Nothing is saved without your confirmation. A draft that Core Hub checked and found valid is chosen by default. A draft that Core Hub refused, could not reach, or did not validate is left out until you choose it.

    In the web interface

    For how to start Automator and sign in to Core Hub, see Gluesync Automator and the guide Use Gluesync Automator.

    1. Open the Import tab and find the card Import a GS1 (Gluesync 1.x) configuration.

    2. Choose the GS1 configuration (.json) file. A file larger than the upload limit (GS_IMPORT_MAX_UPLOAD_MB, 512 MB by default, the limit of backup imports too) or nested too deeply is refused with a message.

    3. Select the Pipeline and the GS1 product: sql-to-nosql, nosql-to-sql, or nosql-to-sql, Sybase build (whose fields read column to document field).

    4. Leave Source agent id and Target agent id empty to use the source and the target of the pipeline, or type the ids.

    5. Optionally fill Root counts, a JSON object of the number of roots of each 1.x entity, such as {"ORDERS": 54321}. A Query entity draft then sets maxDeletesPerWindow to 1 percent of the roots of its entity, and at least 100.

    6. Select Convert and check. To skip the check of Core Hub, tick Convert only first.

    7. Read the report. A line shows the secret keys that were removed unread. The table has one row for each 1.x entity, with the columns Save, GS1 entity, Bucket, Converter report (the issues of the conversion) and Core Hub checks. The checks column shows the describe verdict and the keys that describe proposes, whether validate found the draft valid or refused it, and their issues, among them the describe warnings of a Query draft. A row that is not chosen by default says why.

    8. Tick the drafts to save and select Save chosen entities. A confirmation asks Save N entities (names) to pipeline X?. Choose Cancel and nothing is saved.

    On the command line

    export CORE_HUB_URL=https://corehub.example.com:1717
    export GS1_IMPORT_TOKEN=<your-token>      # otherwise it logs in as admin with DEFAULT_PASSWORD
    echo '{"ORDERS": 54321}' > root-counts.json
    
    # convert and check: writes the report, the drafts and the checks of Core Hub
    python gs1_import.py convert --config gs1-config.json --pipeline <PIPELINE_ID> \
      --repository SQL_TO_NOSQL --root-counts root-counts.json --output gs1-report.json
    
    # save the drafts you choose: it asks first, and --yes confirms
    python gs1_import.py import --config gs1-config.json --pipeline <PIPELINE_ID> \
      --repository SQL_TO_NOSQL --root-counts root-counts.json --entities ORDERS,ORDER_LINES --yes

    For a nosql-to-sql configuration, name the product and the agents:

    python gs1_import.py convert --config gs1-config.json --pipeline <PIPELINE_ID> \
      --repository NOSQL_TO_SQL --source-agent <COUCHBASE_AGENT_ID> --target-agent <SQLSERVER_AGENT_ID> \
      --output gs1-report.json
    Option Meaning

    --config

    The 1.x configuration, a JSON file.

    --pipeline

    The pipeline the drafts are for.

    --repository

    SQL_TO_NOSQL (the default), NOSQL_TO_SQL or NOSQL_TO_SQL_SYBASE.

    --source-agent, --target-agent

    The agent ids. They default to the source and the target agent of the pipeline.

    --root-counts

    A JSON file: the 1.x entity name and its number of roots.

    --output

    Writes the report, the drafts and the checks of Core Hub to a file.

    convert --offline

    Does not call Core Hub. It needs --source-agent and --target-agent.

    import --entities

    The drafts to save, separated by commas. Without it, the drafts that Core Hub checked and found valid. The others are listed and can be named here.

    import --yes

    Confirms the save. Without it the command asks, and saves nothing when it cannot ask.

    What offline skips

    convert --offline and Convert only make no call to Core Hub, so neither describe nor validate runs:

    • No structure check runs. Every draft that the converter accepts is listed and chosen, and Core Hub refuses the faulty ones only when you save them.

    • A Query draft keeps the target of the converter, which has no column mapping. Core Hub refuses to save it (ROOT_KEY_NOT_MAPPED) until it is described. Use the offline report to read what became of each entity, and run the check to get drafts you can save.

    When Core Hub cannot be reached for one draft, the report says so for that entity and keeps the draft, but does not choose it.

    What the check adds to a Query draft

    Describe types the labels of the draft and proposes the keys. A Query entity without an entitiesKeyMapping was keyed by the primary key of the table in FROM: the check takes the key that describe proposes (GS1_ROOT_KEY_TO_CONFIRM), so look at it in the report. The target of the draft then maps the result view of the root, its labels and one array column for each child level, onto the collection, each column in the type of the target agent. A label named like a static field (type, scope) keeps the static value.

    Secrets

    Keys whose name looks like a secret (password, passphrase, token, API key, private key, access key, auth and the like) are removed unread, whatever their level, and listed by JSON path in the report. The value of such a key is never printed, and neither is the value of a setting that Gluesync 2 has no counterpart for: the report names the setting only.

    A draft follows the mapping on this page. The conversion reports what became of each 1.x entity with the issue codes named below.

    Read the conversion report

    An entity that stays on Gluesync 1.x

    An entity in the bucket UNSUPPORTED gets no draft. Fix its cause and convert again, or leave it on Gluesync 1.x. An error that makes an entity UNSUPPORTED comes from the structure checks of Core Hub, which parse the level statements: for a Query draft the errors describe answers before it asks the agent, for a document split the validation issues.

    Code What it means What to do

    GS1_ENTITY_KIND_NOT_SUPPORTED

    The entity is a table or a mapping entity, or a dataModeling entity with no single level-1 table.

    Keep it on 1.x, or rewrite it as a data modeling entity.

    GS1_SEPARATOR_NOT_SUPPORTED

    The entitiesKeyMapping separator of the entity is not one of ::, -, _, / or none, so Gluesync 2 cannot give the same ids.

    Keep it on 1.x, or change the separator and rewrite its documents.

    GS1_TTL_TOUCH_NOT_SUPPORTED

    The entity is listed in couchbase.entitiesToApplyTtl: 1.x refreshed the expiry of its documents after each sync, which Gluesync 2 does not do.

    Keep it on 1.x.

    NON_DETERMINISTIC_QUERY

    A statement depends on the moment it runs, for example it filters on the current date.

    Rewrite it with a flag or a date column that you maintain in the source, or apply the time filter in the application. Convert again.

    KEY_NOT_PLAIN_COLUMN

    A root key, item key or parent link is an expression, or a column of a table other than the one the root key comes from.

    Make it a plain column of that table. An array keyed on a table reached through a join moves into an entity of its own (the hint names the array), or the source exposes the root key on that table. A conversion never splits an entity by itself.

    KEY_NOT_PROJECTED

    A level does not project the labels of the root key. 1.x wrote its ids from them.

    Project them in the statement.

    Other structure errors of Query entities (see Query entities) are reported the same way.

    Warnings on a draft

    A draft with warnings is still a draft. Each warning asks you to confirm a choice. Gluesync raises ITEM_KEY_MISSING, CHILD_PK_WITHOUT_ROOT_KEY and ROOT_KEY_COLLISION_POSSIBLE itself, whenever you describe or validate the entity.

    Code What it means What to do

    GS1_ROOT_KEY_TO_CONFIRM

    A Query entity had no entitiesKeyMapping: 1.x keyed its documents on the primary key of the table in FROM.

    Describe the draft and confirm the root key that describe proposes.

    GS1_DOCUMENT_ID_TO_CONFIRM

    Document model: the draft takes the root key out of the document id with no prefix and the separator ::.

    Set the prefix and the separator of the keyMapping to the ones your ids use. For ids that do not carry the key, see [keys-from-the-body].

    GS1_FLATTENED_FIELD_TO_PLACE

    Document model: a FLATTENED table merged every sub-object of the item into its row, and the configuration does not say which field came from which one.

    The draft puts those fields under one MERGED level whose path is pathToConfirm. Set the path of the sub-object, split it per sub-object, and move the item’s own fields back to the level.

    GS1_SETTING_NOT_CARRIED

    1.x ignored or failed on the setting too, or it has no meaning in the draft. The message says which: a level deeper than 3, a primaryTable of a query data modeling entity, an entitiesKeyMapping entry of a query data modeling entity, a type that is not a 1.x data type, a CUSTOM_FORMAT without a dateFormat, a join relation 1.x could not parse, or collections switched off.

    Usually nothing. When collections are switched off, 1.x read the default collection and kept the documents whose type matches: set the Couchbase source agent to scopes and collections off, with the 1.x type field as its custom type field name.

    GS1_SETTING_ELSEWHERE

    A setting of the 1.x configuration that the draft does not carry. The message names the Gluesync 2 agent or entity setting it goes to, or why Gluesync 2 does not need it.

    Set that setting on the agent or the entity if the 1.x value mattered. See the table below.

    ITEM_KEY_MISSING

    Document model: the items of an array have no key of their own. The draft writes one row per parent, the last item’s, as 1.x did.

    Nothing, if one row per parent is what you had. Otherwise add the item key.

    CHILD_PK_WITHOUT_ROOT_KEY

    Document model: the primary key of a child table lacks the root key. The draft deletes through the parent chain (deleteScope CHAIN).

    Nothing for now: deletes are only reported. See [document-models-with-deletes-reported] for the caveat.

    ROOT_KEY_COLLISION_POSSIBLE

    The root key comes from document ids and lands in a case-insensitive or numeric column, where two ids can become one key and mix their rows.

    Use a case-sensitive collation on the key columns, or make sure ids never differ only by case or number format. Or store the id with a documentIdColumn.

    The settings that GS1_SETTING_ELSEWHERE names:

    1.x setting Where it goes in Gluesync 2

    sourceHost, sourcePort, sourceName, sourceUsername, and the target equivalents

    The connection is set on the source or target agent.

    sourceChangeRetention

    The Couchbase agent: Source change retention in hours.

    copySourceEntitiesAtStartup

    Gluesync 2 takes the snapshot when the pipeline starts. Start the pipeline without a snapshot to skip it.

    copySourceEntitiesChanges

    false makes the converted entity snapshot only. true is the default.

    maxMigrationItemsCountPerIteration

    The source entity’s maxSnapshotPageKbSize: Gluesync 2 pages the snapshot by size.

    maxItemsCountPerTransaction, maxTransactionCountPerIteration

    The source entity’s maxFetchItemsCountPerIteration and maxTransactionMessageKbSize.

    pollingIntervalMilliseconds

    The source entity’s pollingIntervalMilliseconds. The DCP source does not use it, because it is pushed the changes.

    ignoreDocumentWithNullKeys

    1.x skipped only the rows whose key was NULL and wrote the rest. Gluesync 2 fails a document that holds a NULL key (NULL_ITEM_KEY), and the insertConflictStrategy of the target entity decides: SKIP skips the document.

    couchbase.timeoutSeconds, queryTimeoutSeconds, kvTimeoutSeconds, kvDurableTimeoutSeconds

    The Couchbase agent: Connection Timeout, Query Timeout, Key-Value Timeout and Key-Value Durable Timeout, in seconds.

    couchbase.customTypeKey

    The Couchbase agent: Custom 'type' field name, which tells the entities of one bucket apart.

    couchbase.indexReplicaCount, indexesBuildTimeoutMilliseconds

    The Couchbase agent: Index Replica Count and Indexes Build Timeout Seconds (in seconds).

    couchbase.directReadDocumentMode

    Nothing to turn on: DCP delivers the whole document.

    couchbase.eventing, pollingMilliseconds, disableAutomaticMigrationIndexesCreation

    1.x captured changes with an Eventing function. Gluesync 2 reads them through DCP, with no Eventing function and no polling.

    mssql.loginTimeout

    The SQL Server agent has no login timeout setting: it connects with the driver’s default.

    Batch, thread, buffer, node and status settings of 1.x

    Internals of 1.x. Core Hub schedules and buffers the work, so Gluesync 2 does not need them.

    metricsLogDelay, machineStatsUpdateDelay

    Core Hub publishes metrics and machine statistics itself.

    A setting that no rule knows is named as one that Gluesync 2 has no counterpart for. Settings that the draft carries in the model get no note: entitiesKeys (the root key), entitiesDataTypes, convertDateAs with dateFormat, useCollections, ttlSeconds and skipExpiration, and entitiesKeyMapping (the document key).

    What a converted entity keeps from 1.x

    Relational to document

    A converted Query entity keeps the document ids of 1.x: the keyMapping renders them as GS1 (a Query entity without entitiesKeyMapping, and query data modeling entities, whose keys are sorted by label as 1.x sorted them) or GS1_DROP_NULLS (a Query entity with entitiesKeyMapping, in the order of its fields). It also omits empty arrays (emptyArray OMIT), writes the static fields type and scope that 1.x added to every document, and allows INSERT and UPDATE only: it deletes nothing until you add DELETE after sign-off.

    Couchbase to SQL Server

    A converted document model reproduces these behaviors of 1.x.

    1. Numeric and boolean type discriminators. A bucket without scopes and collections can hold its entities apart by a type field that holds a number or a boolean, for example Kind: 1 with "type": 1 in the 1.x configuration. The Couchbase source accepts it: the entity’s type is the text of the value, and the snapshot, the counts, the sampling and the DCP changes all match both "1" and 1. A value read as 1.0 counts as 1.

    2. Field types. couchbase.entitiesDataTypes becomes fieldValueTypes of the model, under the names 1.x used (STRING, INT, FLOAT, DOUBLE, LONG, BIG_DECIMAL, BOOLEAN, LOCAL_DATE, LOCAL_DATE_TIME, LOCAL_TIME, OFFSET_DATE_TIME, BINARY). A type applies to a field of that name in the document, in an object field and in the objects of an array field, as in 1.x. A type name that 1.x did not know made 1.x fail, so it is reported (GS1_SETTING_NOT_CARRIED) and not carried.

    3. Date format. convertDateAs CUSTOM_FORMAT with dateFormat becomes the dateFormat of the model, which applies to the fields typed as dates. With dd/MM/yyyy, 15/03/2021 is written as the date 2021-03-15, 01/01/0001 as 0001-01-01 and null as NULL. 1.x read the pattern only for CUSTOM_FORMAT, and kept the text when no pattern was set, and so does a converted entity (the conversion warns). Any other convertDateAs reads ISO-8601 dates.

    4. Arrays and maps as JSON text. The model sets structuredValueText to GS1_JSON. A list or a map in a text column is one JSON array or object, compact, in document order, with numbers as 1.x rendered them. An amount of 20500000.25 stays 20500000.25 and does not become 2.050000025E7, so CONVERT(decimal(18,6), JSON_VALUE(col, '$.Amount')) keeps working. See Document models for the example.

    5. Last item wins. The model sets duplicateItemKeys to LAST_WINS: of two items with one key, the last one is written, and the rest of the document too. A level keyed by the key of its parent alone writes one row per parent.

    6. An array used as a column and as a level. An array mapped to a column of the parent table and also split into a table of its own is written to both. Its column holds the array as JSON text, and each item in that text carries the parent link columns it lacked, after its own fields. 1.x added them when it joined the parent before splitting.

    7. Keys read from the document body. An entity that deletes nothing reads the key of a change from the body of the document and not from its id, so an id such as terminal::600000 with the numeric key TerminalNo works. The id-based key resolution applies only to an entity that writes deletes. Several such entities can share a bucket without collections, told apart by their type field. A DCP deletion carries only the id: it is dropped quietly when no id attributes it.

    8. Levels on arrays no document holds. A level on an array field that no sampled document holds is accepted, and its container column keeps the type of the model, so the entity does not fail.

    9. Fields that are always null. A field that is null in every sampled document is typed as a nullable string and writes NULL.

    10. Deletes only reported. 1.x never deleted SQL rows. Every level of the converted model has deletes REPORT_ONLY and absentArray KEEP_CHILDREN. A level whose key lacks the root key has deleteScope CHAIN: its warning is that an item that moves to another document can be deleted by a delayed event of the document it left. onExpiry is IGNORE when 1.x had a ttlSeconds above 0 or skipExpiration, so that rows outlive documents that expire.

    Two intentional differences

    • Carriage return and line feed. Gluesync 2 keeps CR LF inside a string value as the document holds it. 1.x stored LF only. A text such as "Line one\r\nLine two" is 0D 0A in the Gluesync 2 row and 0A in the 1.x row. Compare values with this in mind.

    • Key order in JSON text. The order of the keys inside an object written as JSON text follows the document as stored. In 1.x, a change could reorder the keys of an object after it was captured. The JSON values are the same, so compare such columns as parsed JSON.

    Cut over

    Order of work

    1. Let Gluesync 1.x finish its migration before Gluesync 2 starts its snapshot on the same bucket. Both read the bucket through an index, and two index builds at the same time collide on Couchbase. Gluesync 1.x does not retry. Gluesync 2 names its snapshot index gs2_migration_<entity>_type_index, so the two never share one: 1.x creates gluesync_migration_<entity>_type_index and drops it when its migration ends. An index with the 1.x name is left alone. Gluesync 2 also waits, up to the Indexes Build Timeout Seconds of the Couchbase agent, for an index whose build is in progress.

    2. Run the two systems side by side into different targets (another schema, or another collection) until you have compared the results.

    3. Cut over one entity at a time, in the order below.

    Relational to document: Query entities

    The cut-over adopts the documents 1.x wrote. Gluesync 2 starts on top of them, rewrites only the documents whose body differs, and deletes nothing until you sign off the number of deletes.

    1. Convert and describe. Convert the configuration with Automator, with the root counts. Check every draft against the real source, confirm the root key of the entities that reported GS1_ROOT_KEY_TO_CONFIRM, validate the draft and save it with its types. Keep what the draft set: the keyMapping rendering, emptyArray OMIT, the static type and scope fields and INSERT, UPDATE as the allowed operations.

    2. Compare. Compare at least 1,000 live documents of each collection with what Gluesync 2 would write.

      curl -X POST "https://your-gluesync.com/pipelines/$PIPELINE/agents/$SOURCE_AGENT/discovery/data-model/compare" \
        -H "Authorization: Bearer <your-token>" -H "Content-Type: application/json" \
        -d '{ "entityId": "<entity id>", "sample": 1000 }'

      The MCP tool is compare_documents. Give rootKeys instead of sample to compare chosen documents. The answer counts the documents compared and identical, and lists every difference with its kind and path: ID_MISMATCH, MISSING_IN_TARGET, EXTRA_IN_TARGET, FIELD_MISSING, FIELD_EXTRA, VALUE, ORDER, ABSENT_VS_EMPTY and TYPE. Values of personal data columns, and of fields the model does not know, come back hashed.

      ID_MISMATCH must be 0 before the cut-over: fix a mismatch in the rendering, never accept it. Review every other difference with the owner of the data. 1.x trimmed every string and wrote integral decimals as integers (TYPE, VALUE), kept array items in arrival order where Gluesync 2 orders them by item key (ORDER), and could lose items of an array when its streaming grouping split a root, so the array holds only some of them (FIELD_MISSING). Decimals and timestamps are compared as the Couchbase target writes them: a decimal as a JSON number without its scale (3000.00 is 3000.0) and a timestamp without the trailing zeros of its fraction. EXTRA_IN_TARGET is a document that no live root renders: 1.x never deleted it, and the sweep below removes it after sign-off.

    3. Back up the collection, for example with cbbackupmgr, with a scope and collection filter. The rollback restores it.

    4. Stop the 1.x entity. Note the time: the rollback window starts here.

    5. Start the Gluesync 2 entity with the saved draft. It has no DELETE, and the Couchbase target never truncates on a snapshot, so the snapshot rewrites only the documents that differ. CDC then continues from the snapshot.

    6. Dry run the sweep that removes stale documents.

      curl -X POST "https://your-gluesync.com/pipelines/$PIPELINE/commands/sync/reconcile?entityId=$ENTITY&mode=TARGET_IDS&dryRun=true" \
        -H "Authorization: Bearer <your-token>"
      curl "https://your-gluesync.com/pipelines/$PIPELINE/commands/sync/reconcile?entityId=$ENTITY" \
        -H "Authorization: Bearer <your-token>"

      The status shows deleted, the number of documents no live root renders. Nothing is touched. The collection needs an index on META().id: without one the sweep is refused with RECONCILE_INDEX_MISSING and the CREATE INDEX statement to run.

    7. Get written sign-off of that number.

    8. Add DELETE to the allowed operations of the target entity and save it.

    9. Run the sweep for real, with dryRun=false. A sweep whose deletes exceed maxDeletesPerWindow ends HELD and deletes nothing. Raise the limit only with a new sign-off.

    Rollback. It is possible while the source keeps the changes that 1.x needs to resume: the change retention of the SQL Server CDC of the source, minus a day, from the moment you stopped 1.x. Past that, SQL Server CDC silently moves to the oldest change still retained, and changes are lost. Stop the Gluesync 2 entity, restore the backup (1.x does not undo documents that Gluesync 2 rewrote with another shape, and does not bring back the ones the sweep deleted), start the 1.x entity so that it resumes from its own checkpoint, and compare a sample again.

    Document to relational: document models

    1. Convert the configuration with Automator, with the product nosql-to-sql. Every 1.x entity that gets no draft stays on 1.x. Walk through the warnings and the settings named by GS1_SETTING_ELSEWHERE.

    2. Prepare the tables. Use the tables 1.x wrote, or a copy of them in another schema. The draft types every target column as a placeholder VARCHAR: set the SQL Server type of each column to that of the table before you save. Check the document id of the root (GS1_DOCUMENT_ID_TO_CONFIRM).

    3. Validate and save the entity. Fix every error. Validation reads the real tables of the target.

    4. Back up the tables.

    5. Let 1.x finish and stop it, as described under Order of work.

    6. Adopt the rows or start clean. To keep the rows of 1.x and upsert over them, switch on Disable truncate on target table before snapshot inserts on the target agent before the snapshot. Otherwise the snapshot empties the level tables first.

    7. Start the entity with a snapshot, then let CDC run. Every level reports its deletes and deletes nothing.

    8. Compare. Compare the row counts and a sample of the rows of each level table with the ones 1.x wrote. The differences should be the two above, and nothing else.

    9. Review the reported deletes. They are in the table gluesync_docmodel_reported_deletes of the schema of the root table, and counted in gluesync_docmodel_children_deleted_total. Get sign-off.

    10. Switch the levels to ON (writePolicy.deletes) and save. The entity restarts and applies the recorded deletes first. Make sure the target entity allows DELETE.

    Rollback. Stop the Gluesync 2 entity, restore the backup of the tables, and start the 1.x entity again from its own checkpoint.