lemmy icon indicating copy to clipboard operation
lemmy copied to clipboard

Move SQL triggers from migrations into reusable sql file

Open dullbananas opened this issue 2 years ago • 7 comments

  • Fix some aggregates fields being set in update triggers but not in insert triggers, which can be a problem when something is both created and updated before being sent to another instance (fixes #3544)

  • Check if the post is removed or deleted in the comment trigger (previously only done in the post update trigger)

  • Remove handling of deletion that's already done by ON DELETE CASCADE in foreign keys

  • Use published field instead of calling now again, which is more correct for federation

  • No more total recount of community_aggregates.comments

  • Optimize all triggers for bulk operations (except for the trigger that deletes a post's comments) (very important for the tool introduced in #4285)

  • End the painful era of trigger migrations

Here's the report triggers that used to be in this PR, in case they need to be added again in the future:

        -- When a thing is removed, resolve its reports
        CREATE FUNCTION r.resolve_reports_when_thing_removed ( )
            RETURNS TRIGGER
            LANGUAGE plpgsql
            AS $$
            BEGIN
                UPDATE
                    thing_report
                SET
                    resolved = TRUE, resolver_id = first_removal.mod_person_id, updated = first_removal.when_ FROM ( SELECT DISTINCT
                            thing_id
                        FROM new_removal
                        INNER JOIN thing ON thing.id = new_removal.thing_id) AS removal_group, LATERAL (
                    SELECT
                        *
                    FROM new_removal
                    WHERE
                        new_removal.thing_id = removal_group.thing_id ORDER BY when_ ASC LIMIT 1) AS first_removal
            WHERE
                thing_report.thing_id = first_removal.thing_id
                    AND NOT thing_report.resolved
                    AND COALESCE(thing_report.updated < first_removal.when_, TRUE);
                RETURN NULL;
END;
    $$;
    CREATE TRIGGER resolve_reports
        AFTER INSERT ON mod_remove_thing REFERENCING NEW TABLE AS new_removal
        FOR EACH STATEMENT
        EXECUTE FUNCTION r.resolve_reports_when_thing_removed ( );

dullbananas avatar Dec 27 '23 01:12 dullbananas

And yet you triggered me :/

I was adding subscribers_local field to community_aggregates table and community_aggregates_subscriber_count trigger and this PR is not compatible with it right? https://github.com/LemmyNet/lemmy/pull/4166

ismailkarsli avatar Dec 28 '23 08:12 ismailkarsli

@ismailkarsli I will make this PR work with it by changing the migration date and the trigger definitions in down.sql and replaceable_schema.sql

dullbananas avatar Dec 28 '23 15:12 dullbananas

@ismailkarsli I will make this PR work with it by changing the migration date and the trigger definitions in down.sql and replaceable_schema.sql

Oh so you're not replacing current triggers? Alright let me know if anything to be done on my side.

ismailkarsli avatar Dec 29 '23 13:12 ismailkarsli

Current triggers are being replaced but there's nothing you need to do in your PR

dullbananas avatar Dec 29 '23 14:12 dullbananas

This seems ready to merge now. It's kinda messy but not as much as triggers being in migrations.

dullbananas avatar Feb 06 '24 15:02 dullbananas

The one downside I can think of tho: sometimes we need to recalculate historical values based on new SQL function changes, and those will need to be done in migrations. And that means the functions need to be generated before the historical update is run.

Assuming you are talking about the rank calculations, this can be solved by copying the full calculation expression into the migration instead of calling a function. I will change the functions to single-expression SQL language functions to make this easier.

Edit: polymorphic functions still need to be plpgsql, but that's okay if their body is just a return statement

dullbananas avatar Feb 12 '24 23:02 dullbananas

This test randomly failed then passed

https://woodpecker.join-lemmy.org/repos/129/pipeline/5610/20

dullbananas avatar Feb 17 '24 00:02 dullbananas

Some tests are failing.

dessalines avatar Feb 21 '24 15:02 dessalines

I tried to fix it by making the triggers less dependent on the existence of referenced rows (by removing inner joins or replacing them with left joins), and instead using deferred constraints to prevent constraint violation errors. This makes the triggers more likely to have consistent behavior. It also makes them more simple, therefore more easy to debug.

Before the change, I repeatedly ran tests for only lemmy_db_schema, and it passed each time. Then I ran tests once for all crates, and it failed. After the change described above, the tests for all crates kept on passing.

I also fixed a mistake in down.sql and verified that there aren't any other mistakes in the migration by comparing the schema dump with and without diesel migration redo, so there shouldn't be any issues caused by the migration. I added that check to CI.

dullbananas avatar Feb 24 '24 05:02 dullbananas

It works on my machine. Pray for me. 😭

dullbananas avatar Feb 25 '24 02:02 dullbananas

I fixed the problem by changing the CI step order so lemmy_server doesn't initialize the local site in the main database before the rust tests.

But there was another problem, and I don't know if it's caused by this PR. The federation tests failed, and all 5 servers printed the error "Failed to deleted old denied users: 0". I tried to get more details by replacing {e} with {e:?}, but then the tests passed.

dullbananas avatar Feb 25 '24 18:02 dullbananas

I will probably get the new review comments addressed in a week or less. This weekend I couldn't work on this because I was mostly either enjoying proprietary stuff at disneyland or desperately trying to sleep in a shaky bus full of typical high school boys.

dullbananas avatar Mar 04 '24 01:03 dullbananas

Haha no probs. BTW what's your matrix id? We'd like to get you into our dev chat.

dessalines avatar Mar 04 '24 13:03 dessalines

@dullbananas:matrix.org

dullbananas avatar Mar 04 '24 13:03 dullbananas

I did it

dullbananas avatar Mar 08 '24 01:03 dullbananas

Nice! I'd really like if @phiresky could also take a look at this.

dessalines avatar Mar 11 '24 20:03 dessalines

One more comment: I think this PR needs a general motivation section: It's not obvious why it is would be a good idea to circumvent the migration method for triggers - the migration system ensures a consistent state and a consistent application order for the migration, and it allows for downgrades (though this is not used in Lemmy currently it could be consider important to allow downgrading lemmy versions on issues).

I see the file looks cleaner, but it's the kind of thing where after some months there's an issue with it due to the "weird" way this new sql is applied, and the question will be raised why this was done at all. So I think we should have an answer to that question before we merge this.

phiresky avatar Mar 17 '24 22:03 phiresky

Downgrading is part of the docs now, eg in case of accidental upgrade to dev version. So it would be good if thats still possible. Maybe with changes to the docs.

https://join-lemmy.org/docs/administration/troubleshooting.html#downgrading

Nutomic avatar Mar 18 '24 10:03 Nutomic

Downgrades are still possible, and a migration would not pass CI if its successful execution (including down.sql) depends on whether or not the sql code in replaceable_schema was applied. These inconsistencies can always be fixed, since starting a migration with DROP SCHEMA IF EXISTS r CASCADE always works if another way can't be found.

dullbananas avatar Mar 18 '24 15:03 dullbananas

The huge benefit that's not obvious is it's now easier to see inconsistencies between trigger functions, which is why I found and fixed bugs in this PR

dullbananas avatar Mar 19 '24 02:03 dullbananas

Other reasons to not put triggers in migrations:

  • Creating a migration would involve more steps than just editing SQL code, which new contributors might struggle to learn and do correctly
  • When more things are in migrations, running all migrations becomes slower, which is a problem for CI and setting up a new server
  • Schema dump is less organized than the new SQL files in replaceable_schema, and it requires discovering and running a script in order to see it, so it's far worse for learning about Lemmy's triggers for the first time
  • Needing to do all changes in migrations can discourage small code improvements

dullbananas avatar Mar 22 '24 23:03 dullbananas

Tests now pass, and I also made replaceable_schema code run in the same transaction as the last migration, so if it replaceable_schema doesn't successfully run, then it will be run again when the server is restarted, which would otherwise be prevented by the lack of pending migrations

dullbananas avatar Mar 25 '24 02:03 dullbananas

cc @phiresky

dessalines avatar Mar 27 '24 16:03 dessalines

In a future PR, I think I will add a custom implentation of migration commands (e.g. lemmy_server migration revert) that always runs replaceable_schema after the last migration and runs DROP SCHEMA IF EXISTS r when reverting it. Usage of diesel migration will be prevented with a migration at the beginning that throws an error and is skipped by the custom migration runner. This will make things more simple.

Edit: This will also make it possible to store the previously run code in a single-row table and use that instead of checking if any pending migrations were run to determine whether or not to run replaceable_schema again, since it will be guaranteed that the r schema is not partially destroyed by cascading drops.

dullbananas avatar Apr 01 '24 14:04 dullbananas

I'm good to have this go into 0.19.4 if everyone else is.

dessalines avatar Apr 16 '24 03:04 dessalines

Looks good as far as I can tell, but you need to resolve a conflict.

Nutomic avatar Apr 16 '24 09:04 Nutomic

Failed to run 2024-02-24-034523_replaceable-schema with: constraint "person_aggregates_person_id_fkey" of relation "person_aggregates" does not exist,

-beta3 -> -beta.4

ticoombs avatar Apr 19 '24 06:04 ticoombs

This migration cannot be performed at my instance. When upgrading from 0.19.3 to 0.19.4 I get:

Error: LemmyError { message: Unknown("Couldn't run DB Migrations: Failed to run 2024-02-24-034523_replaceable-schema with: function name \"hot_rank\" is not unique"), inner: Couldn't run DB Migrations: Failed to run 2024-02-24-034523_replaceable-schema with: function name "hot_rank" is not unique, context: SpanTrace [] }

kroese avatar Apr 22 '24 19:04 kroese

You've created multiple hot_rank functions then, you'll need to remove one of them. This would fail CI and upgrades on our dev machines if this migration was broken.

I'll also test this locally with a production DB.

dessalines avatar Apr 22 '24 19:04 dessalines

@dessalines I am not really sure what you mean by you created multiple hot_rank functions? Do you mean that I manually executed some SQL commands? Because I never did that.

I started with an empty database around version 0.17 and no command was ever executed against my database, except for the automatic migrations when upgrading to new releases. Except for one time that I was asked by @dullbananas to ran a query to gather performance statistics from the pg_stat table. But that query did not alter any schema's or functions.

kroese avatar Apr 22 '24 19:04 kroese