-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incremental Configuration Updates #191
Conversation
d6f826b
to
6fc5530
Compare
5643bae
to
c16adef
Compare
c16adef
to
433760f
Compare
After talking with @sukhwinder33445 about this change as he implements the web counterpart, I have removed the For historical reasons and if it might become relevant again, I have attached the diff below. incr_cfg_delete diffdiff --git a/schema/pgsql/schema.sql b/schema/pgsql/schema.sql
index a5abfea..08f5406 100644
--- a/schema/pgsql/schema.sql
+++ b/schema/pgsql/schema.sql
@@ -88,6 +88,23 @@ CREATE FUNCTION incr_cfg_bump_changed_at_relation_2nd() -- join_tbl text, foreig
END;
$$;
+-- incr_cfg_delete is a BEFORE TRIGGER for DELETE, which sets changed_at to the current timestamp and raises the deleted
+-- column instead of actually deleting the row.
+CREATE FUNCTION incr_cfg_delete()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+ BEGIN
+ -- Cannot change OLD's attributes, as this would require returning OLD which implies continuing with deletion.
+ EXECUTE format('
+ UPDATE %s
+ SET changed_at = EXTRACT(EPOCH FROM NOW()) * 1000, deleted = ''y''
+ WHERE id = %s',
+ TG_TABLE_NAME, OLD.id);
+ RETURN NULL;
+ END;
+ $$;
+
CREATE TABLE available_channel_type (
type text NOT NULL,
name text NOT NULL,
@@ -119,6 +136,11 @@ CREATE TRIGGER trg_channel_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_channel_incr_cfg_delete
+ BEFORE DELETE ON channel
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TABLE contact (
id bigserial,
full_name text NOT NULL,
@@ -140,6 +162,11 @@ CREATE TRIGGER trg_contact_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contact_incr_cfg_delete
+ BEFORE DELETE ON contact
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TABLE contact_address (
id bigserial,
contact_id bigint NOT NULL REFERENCES contact(id),
@@ -160,6 +187,11 @@ CREATE TRIGGER trg_contact_address_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contact_address_incr_cfg_delete
+ BEFORE DELETE ON contact_address
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TABLE contactgroup (
id bigserial,
name text NOT NULL,
@@ -178,6 +210,11 @@ CREATE TRIGGER trg_contactgroup_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contactgroup_incr_cfg_delete
+ BEFORE DELETE ON contactgroup
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
-- Changes to contactgroup_member should be notified by an updated contactgroup.changed_at.
CREATE TABLE contactgroup_member (
contactgroup_id bigint NOT NULL REFERENCES contactgroup(id),
@@ -208,6 +245,11 @@ CREATE TRIGGER trg_schedule_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_schedule_incr_cfg_delete
+ BEFORE DELETE ON schedule
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TABLE timeperiod (
id bigserial,
owned_by_schedule_id bigint REFERENCES schedule(id), -- nullable for future standalone timeperiods
@@ -225,6 +267,11 @@ CREATE TRIGGER trg_timeperiod_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_timeperiod_incr_cfg_delete
+ BEFORE DELETE ON timeperiod
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
-- Changes to timeperiod_entry should be notified by an updated timeperiod.changed_at.
CREATE TABLE timeperiod_entry (
id bigserial,
@@ -315,6 +362,11 @@ CREATE TRIGGER trg_source_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_source_incr_cfg_delete
+ BEFORE DELETE ON source
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TABLE object (
id bytea NOT NULL, -- SHA256 of identifying tags and the source.id
source_id bigint NOT NULL REFERENCES source(id),
@@ -377,6 +429,11 @@ CREATE TRIGGER trg_rule_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_rule_incr_cfg_delete
+ BEFORE DELETE ON rule
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
-- Changes to rule_escalation should be notified by an updated rule.changed_at.
CREATE TABLE rule_escalation (
id bigserial,
diff --git a/schema/pgsql/upgrades/025.sql b/schema/pgsql/upgrades/025.sql
index ee5f595..3c36de4 100644
--- a/schema/pgsql/upgrades/025.sql
+++ b/schema/pgsql/upgrades/025.sql
@@ -52,6 +52,21 @@ CREATE FUNCTION incr_cfg_bump_changed_at_relation_2nd() -- join_tbl text, foreig
END;
$$;
+CREATE FUNCTION incr_cfg_delete()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+ BEGIN
+ -- Cannot change OLD's attributes, as this would require returning OLD which implies continuing with deletion.
+ EXECUTE format('
+ UPDATE %s
+ SET changed_at = EXTRACT(EPOCH FROM NOW()) * 1000, deleted = ''y''
+ WHERE id = %s',
+ TG_TABLE_NAME, OLD.id);
+ RETURN NULL;
+ END;
+ $$;
+
ALTER TABLE channel
ADD COLUMN changed_at bigint NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) * 1000,
ADD COLUMN deleted boolenum NOT NULL DEFAULT 'n';
@@ -98,21 +113,41 @@ CREATE TRIGGER trg_channel_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_channel_incr_cfg_delete
+ BEFORE DELETE ON channel
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_contact_incr_cfg_update
BEFORE INSERT OR UPDATE ON contact
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contact_incr_cfg_delete
+ BEFORE DELETE ON contact
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_contact_address_incr_cfg_update
BEFORE INSERT OR UPDATE ON contact_address
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contact_address_incr_cfg_delete
+ BEFORE DELETE ON contact_address
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_contactgroup_incr_cfg_update
BEFORE INSERT OR UPDATE ON contactgroup
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_contactgroup_incr_cfg_delete
+ BEFORE DELETE ON contactgroup
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_contactgroup_member_changed_at_relation
AFTER INSERT OR UPDATE OR DELETE ON contactgroup_member
FOR EACH ROW
@@ -123,11 +158,21 @@ CREATE TRIGGER trg_schedule_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_schedule_incr_cfg_delete
+ BEFORE DELETE ON schedule
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_timeperiod_incr_cfg_update
BEFORE INSERT OR UPDATE ON timeperiod
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_timeperiod_incr_cfg_delete
+ BEFORE DELETE ON timeperiod
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_timeperiod_entry_changed_at_relation
AFTER INSERT OR UPDATE OR DELETE ON timeperiod_entry
FOR EACH ROW
@@ -143,11 +188,21 @@ CREATE TRIGGER trg_source_incr_cfg_update
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_source_incr_cfg_delete
+ BEFORE DELETE ON source
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_rule_incr_cfg_update
BEFORE INSERT OR UPDATE ON rule
FOR EACH ROW
EXECUTE FUNCTION incr_cfg_bump_changed_at();
+CREATE TRIGGER trg_rule_incr_cfg_delete
+ BEFORE DELETE ON rule
+ FOR EACH ROW
+ EXECUTE FUNCTION incr_cfg_delete();
+
CREATE TRIGGER trg_rule_escalation_changed_at_relation
AFTER INSERT OR UPDATE OR DELETE ON rule_escalation
FOR EACH ROW |
My thinking when suggesting to not have Now this turned in to quite a bit of complex trigger mechanics instead. I would definitely consider adding these two columns to all tables instead of requiring three variants of the trigger functions. Is everything you implemented in PostgreSQL triggers also possible with MySQL and MariaDB?
Edit: this part became irrelevant by itself, I wrote this comment before seeing the previous comment. |
I would say that if for example the |
Why do you want to make a distinction here? Would that make anything easier for web?
Does this even work here? Reads like this is a magic feature just for |
Yes. For example, if the rule name is changed, the form returns new values that we can write directly to
But Update: Web should do all that manually, and the triggers can be removed. |
433760f
to
b39adec
Compare
7c983b0
to
fe367d4
Compare
While the triggers allowed me to offload a lot of complexity to the database, it now went back to the notification daemon. I hope that I have found most of the obvious bugs that can occur, especially during partial updates. This version now works "trigger-free" and has the two columns in all tables, including relationship tables. Please give it a try and report back. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments that caught my eyes!
fe367d4
to
ede286f
Compare
Small note, mostly for future me: I have just rebased against the current |
TODO: Fetch each table by its own. This "flattening" should ease the synchronization and might even allow more code reusage. |
ede286f
to
90b26f8
Compare
90b26f8
to
39ba43b
Compare
Are the |
39ba43b
to
845faae
Compare
853992e
to
63d2508
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tested (hopefully 🤞 all use cases) and works perfectly for me!
I can't reproduce the error from yesterday, but even then I don't think it's a bug in the GO code, but rather in the web part.
I was only able to get the error again by manually editing some deleted rotation members in the database.
2024-07-10T11:00:42.886+0200 ERROR runtime-updates Creating configuration element failed {"table": "rotation_member", "id": 30, "new": {"id": 30, "rotation_id": 16, "contact_group_id": 1}, "error": "creation callback error, schedule rotation member refers unknown rotation 16"}
Did editing involve setting |
63d2508
to
f3916e3
Compare
The actual error from the "creation callback [states, that the] schedule rotation member refers unknown rotation 16". The creation callback cannot1 be called upon an Footnotes
|
f3916e3
to
252db66
Compare
That's exactly what I mean when I wrote "by manually editing". Btw. just updated my local branch for the web part and now it is allowing me to delete event rules and noticed this noise. 2024-07-10T15:34:15.325+0200 WARN incident Incident refers unknown rule {"object": "dummy-7!onesecond-0", "incident": "#125", "rule_id": 2} If you don't drop that faulty rule from |
Good catch that I was able to reproduce. There are a few relationship relations between the
Furthermore, there is a link between the On the other side, there is also I am currently taking a look how to reflect deletions from the configuration tables there. |
Mariadb ≥10.1 does not support the default (UNIX_TIMESTAMP() * 1000) More info: Icinga/icinga-notifications#191 (comment)
09ad259
to
fb36770
Compare
Following new fancy ✨ changes were introduced:
|
fb36770
to
beef754
Compare
Previously, the entire configuration stored in the database was synchronized every second. With the growth of configurations in live environments on the horizon, this would simply not scale well. This brings us to incremental updates. By introducing two new columns - "changed_at" as a Unix millisecond timestamp and "deleted" as a boolean - for all tables referenced in the ConfigSet structure, SQL queries can be modified to retrieve only those rows with a more recent timestamp. The "deleted" column became necessary to detect disappearances, since the synchronization now only takes newer items into account. Some additional fields needed to be added to the ConfigSet to track relationships. Even though the codebase served well at the time, there was some code that did almost the same thing as other code, just in different ways. So a huge refactoring was done. This resulted in an internal generic function that handles all synchronization with custom callbacks. The web counterpart is being developed in <Icinga/icinga-notifications-web#187>. Closes #5.
beef754
to
2d2048e
Compare
Previously, the entire configuration stored in the database was synchronized every second. With the growth of configurations in live environments on the horizon, this would simply not scale well. This brings us to incremental updates.
By introducing two new columns - "changed_at" as a Unix millisecond timestamp and "deleted" as a boolean - for all tables referenced in the ConfigSet structure, SQL queries can be modified to retrieve only those rows with a more recent timestamp. The "deleted" column became necessary to detect disappearances, since the synchronization now only takes newer items into account. Some additional fields needed to be added to the ConfigSet to track relationships.
Even though the codebase served well at the time, there was some code that did almost the same thing as other code, just in different ways. So a huge refactoring was done. This resulted in an internal generic function that handles all synchronization with custom callbacks.
The web counterpart is being developed in Icinga/icinga-notifications-web#187.
Closes #5.