Exemple #1
0
def attempt_migration_rollback(migration_instance: AsyncMigration):
    """
    Cycle through the operations in reverse order starting from the last completed op and run
    the specified rollback statements.
    """
    migration_instance.refresh_from_db()
    ops = get_async_migration_definition(migration_instance.name).operations
    # if the migration was completed the index is set 1 after, normally we should try rollback for current op
    current_index = min(migration_instance.current_operation_index,
                        len(ops) - 1)
    for op_index in range(current_index, -1, -1):
        try:
            op = ops[op_index]
            execute_op(op, str(UUIDT()), rollback=True)
        except Exception as e:
            error = f"At operation {op_index} rollback failed with error:{str(e)}"
            process_error(
                migration_instance=migration_instance,
                error=error,
                rollback=False,
                alert=True,
                current_operation_index=op_index,
            )

            return

    update_async_migration(migration_instance=migration_instance,
                           status=MigrationStatus.RolledBack,
                           progress=0)
Exemple #2
0
    def setUp(self):
        from posthog.client import sync_execute

        self.migration = get_async_migration_definition(MIGRATION_NAME)
        self.timestamp = 0
        sync_execute("TRUNCATE TABLE person_distinct_id")
        sync_execute("TRUNCATE TABLE person_distinct_id2")
        sync_execute(
            "ALTER TABLE person_distinct_id COMMENT COLUMN distinct_id 'dont_skip_0003'"
        )
    def test_is_required(self):
        from ee.clickhouse.client import sync_execute

        migration = get_async_migration_definition(MIGRATION_NAME)

        self.assertTrue(migration.is_required())

        settings.CLICKHOUSE_REPLICATION = True
        sync_execute("DROP TABLE events SYNC")
        sync_execute(DISTRIBUTED_EVENTS_TABLE_SQL())
        self.assertFalse(migration.is_required())
Exemple #4
0
def run_async_migration_next_op(
        migration_name: str,
        migration_instance: Optional[AsyncMigration] = None):
    """
    Runs the next operation specified by the currently running migration
    We run the next operation of the migration which needs attention

    Returns (run_next, success)
    Terminology:
    - migration_instance: The migration object as stored in the DB
    - migration_definition: The actual migration class outlining the operations (e.g. async_migrations/examples/example.py)
    """

    if not migration_instance:
        try:
            migration_instance = AsyncMigration.objects.get(
                name=migration_name, status=MigrationStatus.Running)
        except AsyncMigration.DoesNotExist:
            return (False, False)
    else:
        migration_instance.refresh_from_db()

    assert migration_instance is not None

    migration_definition = get_async_migration_definition(migration_name)
    if migration_instance.current_operation_index > len(
            migration_definition.operations) - 1:
        complete_migration(migration_instance)
        return (False, True)

    error = None
    current_query_id = str(UUIDT())

    try:
        op = migration_definition.operations[
            migration_instance.current_operation_index]

        execute_op(op, current_query_id)
        update_async_migration(
            migration_instance=migration_instance,
            current_query_id=current_query_id,
            current_operation_index=migration_instance.current_operation_index
            + 1,
        )

    except Exception as e:
        error = f"Exception was thrown while running operation {migration_instance.current_operation_index} : {str(e)}"
        process_error(migration_instance, error, alert=True)

    if error:
        return (False, False)

    update_migration_progress(migration_instance)
    return (True, False)
Exemple #5
0
def update_migration_progress(migration_instance: AsyncMigration):
    """
    We don't want to interrupt a migration if the progress check fails, hence try without handling exceptions
    Progress is a nice-to-have bit of feedback about how the migration is doing, but not essential
    """

    migration_instance.refresh_from_db()
    try:
        progress = get_async_migration_definition(migration_instance.name).progress(migration_instance)  # type: ignore
        update_async_migration(migration_instance=migration_instance, progress=progress)
    except:
        pass
Exemple #6
0
    def test_rollback(self):
        # :TRICKY: Relies on tables being migrated as unreplicated before.

        _create_event(team=self.team, distinct_id="test", event="$pageview")
        _create_event(team=self.team, distinct_id="test2", event="$pageview")

        settings.CLICKHOUSE_REPLICATION = True

        setup_async_migrations()
        migration = get_async_migration_definition(MIGRATION_NAME)

        self.assertEqual(len(migration.operations), 53)
        migration.operations[30].sql = "THIS WILL FAIL!"  # type: ignore

        migration_successful = start_async_migration(MIGRATION_NAME)
        self.assertFalse(migration_successful)
        self.assertEqual(AsyncMigration.objects.get(name=MIGRATION_NAME).status, MigrationStatus.RolledBack)

        self.verify_table_engines_correct(expected_engine_types=("ReplacingMergeTree", "CollapsingMergeTree", "Kafka"))
Exemple #7
0
def start_async_migration(migration_name: str,
                          ignore_posthog_version=False) -> bool:
    """
    Performs some basic checks to ensure the migration can indeed run, and then kickstarts the chain of operations

    Returns whether migration was successful
    Checks:
    1. We're not over the concurrent migrations limit
    2. The migration can be run with the current PostHog version
    3. The migration is not already running
    4. The migration is required given the instance configuration
    5. The service version requirements are met (e.g. X < ClickHouse version < Y)
    6. The migration's healthcheck passes
    7. The migration's dependency has been completed
    """

    migration_instance = AsyncMigration.objects.get(name=migration_name)
    over_concurrent_migrations_limit = len(
        get_all_running_async_migrations()) >= MAX_CONCURRENT_ASYNC_MIGRATIONS
    posthog_version_valid = ignore_posthog_version or is_posthog_version_compatible(
        migration_instance.posthog_min_version,
        migration_instance.posthog_max_version)

    if (not migration_instance or over_concurrent_migrations_limit
            or not posthog_version_valid
            or migration_instance.status == MigrationStatus.Running):
        return False

    migration_definition = get_async_migration_definition(migration_name)

    if not migration_definition.is_required():
        complete_migration(migration_instance, email=False)
        return True

    ok, error = check_service_version_requirements(
        migration_definition.service_version_requirements)
    if not ok:
        process_error(migration_instance,
                      error,
                      status=MigrationStatus.FailedAtStartup)
        return False

    ok, error = is_migration_dependency_fulfilled(migration_instance.name)
    if not ok:
        process_error(migration_instance,
                      error,
                      status=MigrationStatus.FailedAtStartup)
        return False

    ok, error = run_migration_precheck(migration_instance)
    if not ok:
        process_error(migration_instance,
                      f"Migration precheck failed with error:{error}",
                      status=MigrationStatus.FailedAtStartup)
        return False

    ok, error = run_migration_healthcheck(migration_instance)
    if not ok:
        process_error(
            migration_instance,
            f"Migration healthcheck failed with error:{error}",
            status=MigrationStatus.FailedAtStartup,
        )
        return False

    mark_async_migration_as_running(migration_instance)

    return run_async_migration_operations(migration_name, migration_instance)
Exemple #8
0
def run_migration_precheck(migration_instance: AsyncMigration):
    return get_async_migration_definition(migration_instance.name).precheck()