diff --git a/admin/jobs/river/reconcile_deployment.go b/admin/jobs/river/reconcile_deployment.go index 0428bfcdeb8..bf7a3369e06 100644 --- a/admin/jobs/river/reconcile_deployment.go +++ b/admin/jobs/river/reconcile_deployment.go @@ -124,11 +124,20 @@ func (w *ReconcileDeploymentWorker) Work(ctx context.Context, job *river.Job[Rec return nil } - // Update the deployment status - depl, err = w.admin.DB.UpdateDeploymentStatus(ctx, depl.ID, newStatus, "") + // Update the deployment status. + // We skip the update when the status is unchanged (as on the periodic reconcile of a running deployment) because it bumps updated_on, + // which the runtime treats as a change to the deployment: it forces a repo pull and a full re-parse of the project (see runtime.configReloader). + // We re-fetch the deployment first since the status may have changed while we were working (e.g. UpdateDeploymentInner sets it to updating). + depl, err = w.admin.DB.FindDeployment(ctx, depl.ID) if err != nil { return err } + if depl.Status != newStatus || depl.StatusMessage != "" { + depl, err = w.admin.DB.UpdateDeploymentStatus(ctx, depl.ID, newStatus, "") + if err != nil { + return err + } + } // If current depl.DesiredStatusUpdatedOn != desiredStatusUpdatedOn when job started, then the deployment changed while we were working and we should reschedule another job. if !depl.DesiredStatusUpdatedOn.Equal(desiredStatusUpdatedOn) { diff --git a/cli/cmd/project/deployment/deployments_test.go b/cli/cmd/project/deployment/deployments_test.go index dbe61be3694..b1bf62dd326 100644 --- a/cli/cmd/project/deployment/deployments_test.go +++ b/cli/cmd/project/deployment/deployments_test.go @@ -11,6 +11,7 @@ import ( "github.com/rilldata/rill/admin/testadmin" "github.com/rilldata/rill/cli/testcli" adminv1 "github.com/rilldata/rill/proto/gen/rill/admin/v1" + "github.com/rilldata/rill/runtime" "github.com/rilldata/rill/runtime/drivers" "github.com/rilldata/rill/runtime/pkg/gitutil" "github.com/rilldata/rill/runtime/testruntime/testmode" @@ -72,6 +73,32 @@ vars: return modelOutputFn == 1 }, 10*time.Second, 100*time.Millisecond, "unexpected model output") + // reconcile the unchanged deployment twice: neither should bump updated_on, + // and the second should not make the runtime re-parse the project + // (the first may, since the runtime has not seen the deployment's updated_on before) + ctrl, err := adm.Runtime.Controller(t.Context(), depl.RuntimeInstanceID) + require.NoError(t, err) + parserStateVersion := func() int64 { + require.NoError(t, ctrl.WaitUntilIdle(t.Context(), false)) + pp, err := ctrl.Get(t.Context(), runtime.GlobalProjectParserName, false) + require.NoError(t, err) + return pp.Meta.StateVersion + } + depl2 := adm.TriggerDeployment(t, "reload-configs-test", "rill-mgd-deploy") + require.True(t, depl.UpdatedOn.Equal(depl2.UpdatedOn), "updated_on changed from %v to %v", depl.UpdatedOn, depl2.UpdatedOn) + stateVersion := parserStateVersion() + depl2 = adm.TriggerDeployment(t, "reload-configs-test", "rill-mgd-deploy") + require.True(t, depl.UpdatedOn.Equal(depl2.UpdatedOn), "updated_on changed from %v to %v", depl.UpdatedOn, depl2.UpdatedOn) + require.Equal(t, stateVersion, parserStateVersion(), "the project was re-parsed") + + // changing the slots reprovisions the runtime and marks the deployment as updating while that happens; + // the reconcile should still leave it running + result = u1.Run(t, "project", "edit", "--prod-slots=3", "--org=reload-configs-test", "--project=rill-mgd-deploy") + require.Equal(t, 0, result.ExitCode, result.Output) + depl = adm.TriggerDeployment(t, "reload-configs-test", "rill-mgd-deploy") + require.Equal(t, database.DeploymentStatusRunning, depl.Status) + require.Empty(t, depl.StatusMessage) + // set env via `rill env set limit 10` result = u1.Run(t, "env", "set", "limit", "10", "--org=reload-configs-test", "--project=rill-mgd-deploy") require.Equal(t, 0, result.ExitCode, result.Output)