-
Notifications
You must be signed in to change notification settings - Fork 876
Filter query: has-agent connecting, connected, disconnected, timeout #5145
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
Conversation
latest_build.last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout :: bigint < NOW() | ||
) | ||
WHEN @has_agent = 'connected' THEN | ||
latest_build.last_connected_at IS NOT NULL |
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.
Should this case also verify that the state isn't actually disconnected
either via timeout or disonnected_at
?
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 extended the condition.
Frankly speaking,, I'm thinking about refactoring that part of the code and creating an extra "runtime" column for agent status
. I'm not quite sure if it's possible to use SQL clauses to reflect this logic. I really wouldn't like to create functions...
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 guess that you can take a look at the CASE logic implementation one more time as I've rewritten it to look similar to the Go code.
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.
@mtojek I think the case logic is exactly what you want. But you would have to add that case logic to all reads. So either remove all GetWorkspaceAgentsByXXX
and make 1 with a filter (like workspaces/template/etc). Or you could also make a view
. I looked into this before with sqlc in an issue here:
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.
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.
The only reason I mentioned a view
is because sqlc creates our model types from the sql.
So you have GetWorkspaceAgentByAuthToken
, GetWorkspaceAgentByID
, GetWorkspaceAgentByInstanceID
, GetWorkspaceAgentsByResourceIDs
, GetWorkspaceAgentsCreatedAfter
. If you update 1 to select this extra "status" column dynamically, sqlc creates a new model type for this. It will not be type WorkspaceAgent
. I think it has the name of the function, eg GetWorkspaceAgentByXXX
(iirc?). So you will need to add this new column to all the calls, and I think each call gets a unique model type. A way around this is to consolidate all these calls too.
A "view" is a way to repackage the extra dynamic column in a way sqlc still sees it as a single type. So all the above queries go from ... FROM workspace_agents ...
to ... FROM workspace_agents_view ...
. The sqlc type will be type WorkspaceAgentsView
.
So tl;dr the view is just a way to keep the sqlc clean. You'll see when you run a make gen
with a dynamic column (SELECT *, "unknown" as status FROM workspace_agents
).
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.
So either remove all GetWorkspaceAgentsByXXX and make 1 with a filter (like workspaces/template/etc)
Fortunately, at moment we only need it to search workspaces using filtering. I guess that we don't need to hurry up and inject the case logic everywhere.
The only reason I mentioned a view is because sqlc creates our model types from the sql.
In terms of clean code and the benefits of sqlc, I totally agree with you. I admit that I wouldn't like to get stuck in this pull request with too many changes and I'm wondering if I can split it into multiple PRs.
Follow-up ideas:
- Replace the
WITH
expression with aview
. Adjust the rest of the codebase to use it. - Make agent
status
dynamically set in SQL. Remove the agent status logic from Go code - we don't need it in two places, except fordatabasefake
.
Frankly speaking, if there aren't big performance or clean code concerns around this PR, I would leave it as is, and focus on improvements once the requested feature is delivered.
Let me know what you think.
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.
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.
That's a good point @mtojek, it definitely hampers the use case. Ideally this value would come from either the terraform provider or be stored in the database by some other means, at which point it can be part of the query.
I don't find it unthinkable that we would serialize certain coder server runtime properties in the database, either as a temp table or one that is (re)written on startup. The inactivity seconds could then be stored there and joined.
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.
A few more thoughts on the query structure, but really liking how it turned out so far. 👍🏻
@@ -38,12 +38,55 @@ WHERE | |||
); | |||
|
|||
-- name: GetWorkspaces :many | |||
WITH workspace_builds_agents AS ( |
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.
If you also CTE'd the latest_build
query, like this:
WITH latest_build AS (
SELECT ...
), workspace_builds_agents AS (
SELECT ...
)
You could join latest_builds
in workspace_builds_agents
to reduce the agent results to the latest build only. This should make the query more performant (reduced rows).
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.
This is a good suggestion, but won't it be a problem that latest_build
goes over all workspaces?
SELECT
workspaces.*, COUNT(*) OVER () as count
FROM
workspaces
LEFT JOIN LATERAL (
SELECT
...
WHERE
workspace_builds.workspace_id = workspaces.id
...
) latest_build ON TRUE
I presume that we will need latest_builds
(plural) for every workspace, but I'm not sure if it isn't the same complexity.
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.
Ok, I investigated a few concepts we talked about offline with @mafredri:
- Replace
latest_build
withworkspace_latest_builds
CTE.
Unfortunately, I wasn't able to select only the last build per workspace.
- Replace
workspace_build_agent
CTE withJOIN LATERAL
.
Workspaces with multiple agents will return multiple records, which is unexpected.
- Place the CASE logic in the WHERE clause
It looks like the conditional logic will fire only if has-agent
is specified and it shouldn't affect other GetWorkspaces
queries.
Let me know your thoughts.
FROM | ||
workspace_builds | ||
JOIN | ||
provisioner_jobs | ||
ON | ||
provisioner_jobs.id = workspace_builds.job_id | ||
JOIN | ||
workspace_resources | ||
ON | ||
workspace_resources.job_id = provisioner_jobs.id |
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 haven't tested if this works, but I would suggest this final change:
FROM | |
workspace_builds | |
JOIN | |
provisioner_jobs | |
ON | |
provisioner_jobs.id = workspace_builds.job_id | |
JOIN | |
workspace_resources | |
ON | |
workspace_resources.job_id = provisioner_jobs.id | |
FROM | |
latest_build | |
JOIN | |
workspace_resources | |
ON | |
workspace_resources.job_id = latest_build.provisioner_job_id |
You'll need to add provisioner_jobs.id AS provisioner_job_id
to the select for latest_build
though (renaming via AS would be optional, doesn't collide with any other names).
This should be more performant than re-joining these tables here. (It also eliminates the need for the build_number
check in WHERE
.)
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'm afraid that it would work if latest_build
is CTE. Otherwise, it's: relation "latest_build" does not exist
.
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.
Looks great, nice job on the query! 👍🏻
Fixes: #4679
This PR transforms the logic from workspace agent into a database query. It isn't straightforward as it needs to project data from workspace resources and workspace agents, then map
has-agent
conditions (connecting, timeout, disconnected, connected) onto db fields.Notice:
has-agent:
filter only works for workspaces in thestart
transition.An alternative approach, which isn't a spaghetti db query, would assume a query filter/mapping applied in the application layer, on received workspaces query results from DB.