Change post slug but keep old one

This’ll probably have a few quirks, but in practice it’ll do what you’re after. Note that it’s set to only work for posts (not pages or custom post types).

All we’re doing is using the wp_unique_post_slug filter, which runs after WordPress has generated it’s own unique slug, and additionally checking it against all _wp_old_slug meta values. You’ll see in my custom query I still check it against slugs in the main posts table – without it, we could potentially “reset” the suffix count and create a collision with an existing post.

function wpse_183975_unique_post_slug( $slug, $post_id, $post_status, $post_type ) {
    if ( $post_status === 'publish' && $post_type === 'post' ) {
        global $wpdb;

        if ( preg_match( '/-([0-9]+)$/', $slug, $match ) ) { // Already has a number suffix
            $slug_base = substr( $slug, 0, -strlen( $match[0] ) ); // Strip suffix from slug
            $i = ( int ) $match[1];
        } else { // No suffix, start at 1
            $slug_base = $slug;
            $i = 1;
        }

        while ( // For as long as there is an existing slug, increment the suffix
            $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT m.post_id FROM $wpdb->postmeta AS m INNER JOIN $wpdb->posts AS p ON m.post_id = p.ID " .
                    "WHERE " .
                        "p.ID != %d AND " .
                        "p.post_type="post" AND " .
                        "( p.post_status="publish" AND p.post_name = %s ) OR " .
                        "( m.meta_key = '_wp_old_slug' AND m.meta_value = %s ) " .
                    "LIMIT 1",
                    $post_id,
                    $slug,
                    $slug
                )
            )
        ) {
            $slug = "$slug_base-" . ++$i;
        }
    }

    return $slug;
}

add_filter( 'wp_unique_post_slug', 'wpse_183975_unique_post_slug', 10, 4 );