Skip to content
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

Generate triggers #17156

Merged
merged 9 commits into from
May 29, 2024
Merged

Generate triggers #17156

merged 9 commits into from
May 29, 2024

Conversation

SimonRichardson
Copy link
Member

@SimonRichardson SimonRichardson commented Apr 5, 2024

Triggers were written whereby any update to the database would cause a new insert into the change log, even if the value was identical. This causes a lot of churn in the change log. We can generate the triggers using the columns from the actual database schema. The triggers will always be correct and always up to date.

Part of the change set includes fixing watchers and watcher tests:

Cloud watcher

The watch cloud test wasn't working how it was expected. The upsert was just triggering a new change in the old code, prior to this patch. The cloud-config is currently immutable, I'll open a bug to fix this. As the config is not updated and the trigger prevents a noop change then the test fails. The solution is to then update a field on the cloud that will cause a change. In this case, the endpoint was updated.

Bug about cloud-config: https://bugs.launchpad.net/juju/+bug/2066410

Model config watcher

The model config watch wasn't correct once the generated triggers were added. Firstly, we deleted all of the keys and then re-added them again even if they were the same. This caused a lot of churn in the changestream. The fix was just to ensure that we inserted, updated, and deleted rows correctly.

The second problem was that we would see the initial changes from bootstrap and then see them again when the change stream was processing requests. This might be ok for some watchers (I highly doubt that), but the concept of watchers are that, watch changes and then react. For most things that take information from the API server, the initial
event is thrown away, so it's pointless trying to get that. It's better to just read the state of the world and then respond to changes.

The model_config watcher will now emit an empty initial event and then subsequent events will tell you what has changed.

Checklist

  • Code style: imports ordered, good names, simple structure, etc
  • Comments saying why design decisions were made
  • Go unit tests, with comments saying what you're testing

QA steps

$ make rebuild-triggers
$ juju bootstrap lxd test
$ juju add-model default
$ juju deploy ubuntu

Links

Jira card: JUJU-6055

manadart

This comment was marked as outdated.

@SimonRichardson
Copy link
Member Author

@manadart there is a slight wart with this, as it's compiled into non-test code, the lack of the file or even a typo means you can't create the file again without commenting out the code that depends on it.

@manadart
Copy link
Member

manadart commented Apr 8, 2024

I was thinking about this over the weekend. The inequality checks don't work if either of the values are NULL, and we can't use arbitrary values for IFNULL on both sides, because we don't know the valid values for every field.

It looks like we will have might have to do the filtering in the specific watchers.

@SimonRichardson
Copy link
Member Author

I was thinking about this over the weekend. The inequality checks don't work if either of the values are NULL, and we can't use arbitrary values for IFNULL on both sides, because we don't know the valid values for every field.

We can check for that in the DB schema and work through that.

@SimonRichardson
Copy link
Member Author

For example PRAGMA table_info(<table-name>); will tell you if it's expecting NULL or not. Then we can apply the following rules:

  1. If NOT NULL - a != b
  2. If NULL - (IFNULL(a, b) OR a != b)

Copy link
Member

@manadart manadart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a better option than the esoteric trigger functions. We'll need to ensure it's understood and accepted as an alternative.

@@ -177,24 +177,7 @@ func (st *State) updateBlockDevices(ctx context.Context, tx *sqlair.TX, machineU
fsTypeByName[fsType.Name] = fsType.ID
}

insertQuery := `
INSERT INTO block_device (uuid, machine_uuid, name, label, device_uuid, hardware_id, wwn, bus_address, serial_id, mount_point, size_mib, filesystem_type_id, in_use)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have gone in its own commit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to rewind this, I was trying to test the null change.

generate/triggergen/main.go Outdated Show resolved Hide resolved
domain/schema/controller.go Outdated Show resolved Hide resolved
@SimonRichardson
Copy link
Member Author

This is on hold until after the beta 4.0 release. Then we can bake these changes into the codebase.

@SimonRichardson
Copy link
Member Author

/build

@SimonRichardson
Copy link
Member Author

Bug about missing cloud config update https://bugs.launchpad.net/juju/+bug/2066410

@SimonRichardson SimonRichardson force-pushed the generate-triggers branch 3 times, most recently from 36ff90a to bbb17e5 Compare May 23, 2024 14:12
"github.com/juju/juju/core/database/schema"
)

type tableNamespaceID = int
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should rethink this at some point. It doesn't provide strong guarantees between schema and change log watchers.

@@ -29,3 +33,24 @@ func (s *SchemaApplier) Apply(c *gc.C, ctx context.Context, runner database.TxnR
c.Assert(err, gc.IsNil)
c.Check(changeSet.Post, gc.Equals, s.Schema.Len())
}

// DumpChangeLog dumps the change log to the test log.
func DumpChangeLog(ctx context.Context, c *gc.C, runner database.TxnRunner) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is VERY useful for debugging the change log.

Copy link
Member

@manadart manadart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've already got a conflict, but everything looks good.

Probably worth an email to crew about working with it.

domain/schema/testing/schema.go Outdated Show resolved Hide resolved
generate/triggergen/main.go Show resolved Hide resolved
Triggers were written whereby any update to the database would
cause a new insert into the change log, even if the value was
identical. This causes a lot of churn in the change log.
Instead we can generate the triggers using the columns from the
actual database schema. The triggers will always be correct and
always up to date.
To make it easier to build and see, we split each trigger into
individual trigger documents.
The watch cloud test wasn't working how it was expected. The upsert
was just triggering a new change in the old code, prior to this patch.
The cloud config is currently immutable, I'll open a bug to fix this.
As the config is not updated and the trigger prevents a noop change
then the test fails. The solution is to then update a field on the
cloud that will actually cause a change. In this case the endpoint
being updated.
The model config watch wasn't correct once the generated triggers
were added. Firstly, we deleted all of the keys and then re-added
them again even if they where the same. This caused a lot of churn
in the changestream. The fix was just ensure that we inserted,
updated and deleted rows correctly.

The second problem was that we would see the initial changes from
bootstrap and then see them again when the change stream was processing
requests. This might be ok for some watchers (I highly doubt that),
but the concept of watchers are, watch changes and then react. For
most things that take information from the API server, the initial
event is thrown away, so it's pointless trying to get that. It's
better to just read the state of the world then respond to changes.
The model_config watcher will now emit an empty initial event and
then subsequent events will tell you what has changed.
We need to ensure that the change stream is actually idle. We
can do this by checking what the current upper bound is vs what is
the MAX change log id. If those match we're done!

This is only ever run in test code as we don't care for production.
The trigger gen script was put together to be a prove out that it
was possible. The prototype should reuse functions and modularize
the content. This change does that by splitting it into different
concerns.
We no longer render out the subnet association any more.
@SimonRichardson
Copy link
Member Author

/build

We don't need to wait for the change stream to become idle every
time. It just needs to be used after an update to the state, not
after every change.
@SimonRichardson
Copy link
Member Author

/merge

@jujubot jujubot merged commit f179ef9 into juju:main May 29, 2024
14 of 17 checks passed
@SimonRichardson SimonRichardson deleted the generate-triggers branch May 29, 2024 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants