Skip to content

Commit ef9b664

Browse files
NicolappsConvex, Inc.
authored andcommitted
Add a new /deploy2/evaluate_push/ endpoint (#42524)
This PR adds a new endpoint, `/deploy2/evaluate_push/`, which allows the user to push modules and determine what are the indexes that are being added/deleted. Unlike `/deploy2/start_push`, this doesn’t start index backfill or schema validation. GitOrigin-RevId: 39774de5f9d73490bb3cbd189ab9ac813d7fbefc
1 parent 0069b74 commit ef9b664

File tree

5 files changed

+149
-31
lines changed

5 files changed

+149
-31
lines changed

crates/application/src/deploy_config.rs

Lines changed: 100 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,63 @@ pub struct PushMetrics {
150150
pub occ_stats: OccRetryStats,
151151
}
152152

153+
struct EvaluatedPushContents {
154+
app: CheckedComponent,
155+
auth_info: Vec<AuthInfo>,
156+
component_definition_packages: BTreeMap<ComponentDefinitionPath, SourcePackage>,
157+
evaluated_components: BTreeMap<ComponentDefinitionPath, EvaluatedComponentDefinition>,
158+
external_deps_id: Option<ExternalDepsPackageId>,
159+
user_environment_variables: BTreeMap<EnvVarName, EnvVarValue>,
160+
}
161+
153162
impl<RT: Runtime> Application<RT> {
154163
#[fastrace::trace]
155164
pub async fn start_push(&self, config: &ProjectConfig) -> anyhow::Result<StartPushResponse> {
165+
let EvaluatedPushContents {
166+
app,
167+
auth_info,
168+
component_definition_packages,
169+
mut evaluated_components,
170+
external_deps_id,
171+
user_environment_variables,
172+
} = self.evaluate_push_contents(config).await?;
173+
174+
let schema_change = self
175+
.handle_schema_change_in_start_push(&app, &evaluated_components)
176+
.await?;
177+
self.database
178+
.load_indexes_into_memory(btreeset! { SCHEMAS_TABLE.clone() })
179+
.await?;
180+
181+
// TODO(ENG-7533): Clean up exports from the start push response when we've
182+
// updated clients to use `functions` directly.
183+
for (path, definition) in evaluated_components.iter_mut() {
184+
// We don't need to include exports for the root since we don't use codegen
185+
// for the app's `api` object.
186+
if path.is_root() {
187+
continue;
188+
}
189+
anyhow::ensure!(definition.definition.exports.is_empty());
190+
definition.definition.exports = file_based_exports(&definition.functions)?;
191+
}
192+
193+
let resp = StartPushResponse {
194+
environment_variables: user_environment_variables,
195+
external_deps_id,
196+
component_definition_packages,
197+
app_auth: auth_info,
198+
analysis: evaluated_components,
199+
app,
200+
schema_change,
201+
};
202+
Ok(resp)
203+
}
204+
205+
#[fastrace::trace]
206+
async fn evaluate_push_contents(
207+
&self,
208+
config: &ProjectConfig,
209+
) -> anyhow::Result<EvaluatedPushContents> {
156210
let unix_timestamp = self.runtime.unix_timestamp();
157211
let (external_deps_id, component_definition_packages) =
158212
self.upload_packages(config).await?;
@@ -206,7 +260,7 @@ impl<RT: Runtime> Application<RT> {
206260
)
207261
.await?;
208262

209-
let mut evaluated_components = self
263+
let evaluated_components = self
210264
.evaluate_components(
211265
config,
212266
&component_definition_packages,
@@ -231,39 +285,18 @@ impl<RT: Runtime> Application<RT> {
231285
let ctx = TypecheckContext::new(&evaluated_components, &initializer_evaluator);
232286
let app = ctx.instantiate_root().await?;
233287

234-
let schema_change = self
235-
._handle_schema_change_in_start_push(&app, &evaluated_components)
236-
.await?;
237-
self.database
238-
.load_indexes_into_memory(btreeset! { SCHEMAS_TABLE.clone() })
239-
.await?;
240-
241-
// TODO(ENG-7533): Clean up exports from the start push response when we've
242-
// updated clients to use `functions` directly.
243-
for (path, definition) in evaluated_components.iter_mut() {
244-
// We don't need to include exports for the root since we don't use codegen
245-
// for the app's `api` object.
246-
if path.is_root() {
247-
continue;
248-
}
249-
anyhow::ensure!(definition.definition.exports.is_empty());
250-
definition.definition.exports = file_based_exports(&definition.functions)?;
251-
}
252-
253-
let resp = StartPushResponse {
254-
environment_variables: user_environment_variables,
255-
external_deps_id,
256-
component_definition_packages,
257-
app_auth: auth_info,
258-
analysis: evaluated_components,
288+
Ok(EvaluatedPushContents {
259289
app,
260-
schema_change,
261-
};
262-
Ok(resp)
290+
auth_info,
291+
component_definition_packages,
292+
evaluated_components,
293+
external_deps_id,
294+
user_environment_variables,
295+
})
263296
}
264297

265298
#[fastrace::trace]
266-
async fn _handle_schema_change_in_start_push(
299+
async fn handle_schema_change_in_start_push(
267300
&self,
268301
app: &CheckedComponent,
269302
evaluated_components: &BTreeMap<ComponentDefinitionPath, EvaluatedComponentDefinition>,
@@ -289,6 +322,20 @@ impl<RT: Runtime> Application<RT> {
289322
Ok(schema_change)
290323
}
291324

325+
#[fastrace::trace]
326+
async fn handle_schema_change_in_evaluate_push(
327+
&self,
328+
app: &CheckedComponent,
329+
evaluated_components: &BTreeMap<ComponentDefinitionPath, EvaluatedComponentDefinition>,
330+
) -> anyhow::Result<SchemaChange> {
331+
let mut tx = self.begin(Identity::system()).await?;
332+
let schema_change = ComponentConfigModel::new(&mut tx)
333+
.start_component_schema_changes(app, evaluated_components)
334+
.await?;
335+
drop(tx);
336+
Ok(schema_change)
337+
}
338+
292339
#[fastrace::trace]
293340
async fn evaluate_components(
294341
&self,
@@ -451,6 +498,24 @@ impl<RT: Runtime> Application<RT> {
451498
.await
452499
}
453500

501+
#[fastrace::trace]
502+
pub async fn evaluate_push(
503+
&self,
504+
config: &ProjectConfig,
505+
) -> anyhow::Result<EvaluatePushResponse> {
506+
let EvaluatedPushContents {
507+
app,
508+
evaluated_components,
509+
..
510+
} = self.evaluate_push_contents(config).await?;
511+
512+
let schema_change = self
513+
.handle_schema_change_in_evaluate_push(&app, &evaluated_components)
514+
.await?;
515+
516+
Ok(EvaluatePushResponse { schema_change })
517+
}
518+
454519
#[fastrace::trace]
455520
pub async fn wait_for_schema(
456521
&self,
@@ -860,6 +925,11 @@ pub struct StartPushResponse {
860925
pub schema_change: SchemaChange,
861926
}
862927

928+
#[derive(Debug)]
929+
pub struct EvaluatePushResponse {
930+
pub schema_change: SchemaChange,
931+
}
932+
863933
impl From<NodeDependencyJson> for NodeDependency {
864934
fn from(value: NodeDependencyJson) -> Self {
865935
Self {

crates/common/src/knobs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ pub static APPLICATION_MAX_CONCURRENT_HTTP_ACTIONS: LazyLock<usize> = LazyLock::
821821
});
822822

823823
/// The maximum number of concurrent package uploads during
824-
/// `/api/deploy2/start_push`.
824+
/// `/api/deploy2/start_push` + `/api/deploy2/evaluate_push`.
825825
pub static APPLICATION_MAX_CONCURRENT_UPLOADS: LazyLock<usize> =
826826
LazyLock::new(|| env_config("APPLICATION_MAX_CONCURRENT_UPLOADS", 4));
827827

crates/local_backend/src/deploy_config2.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
};
55

66
use application::deploy_config::{
7+
EvaluatePushResponse,
78
FinishPushDiff,
89
SchemaStatusJson,
910
StartPushRequest,
@@ -169,6 +170,22 @@ pub struct SerializedStartPushResponse {
169170
schema_change: SerializedSchemaChange,
170171
}
171172

173+
impl TryFrom<EvaluatePushResponse> for SerializedEvaluatePushResponse {
174+
type Error = anyhow::Error;
175+
176+
fn try_from(value: EvaluatePushResponse) -> Result<Self, Self::Error> {
177+
Ok(Self {
178+
schema_change: value.schema_change.try_into()?,
179+
})
180+
}
181+
}
182+
183+
#[derive(Deserialize, Serialize)]
184+
#[serde(rename_all = "camelCase")]
185+
pub struct SerializedEvaluatePushResponse {
186+
schema_change: SerializedSchemaChange,
187+
}
188+
172189
#[derive(Deserialize, Serialize)]
173190
#[serde(rename_all = "camelCase")]
174191
struct SerializedIndexDiff {
@@ -205,6 +222,32 @@ pub async fn start_push(
205222
Ok(Json(SerializedStartPushResponse::try_from(resp)?))
206223
}
207224

225+
// This endpoint is similar to `start_push`, but it doesn’t save the schema (so
226+
// it won’t start schema validation/index backfill). It can be used to determine
227+
// what will be the effects of a large push without starting work that can take
228+
// a long time on large instances.
229+
#[debug_handler]
230+
pub async fn evaluate_push(
231+
State(st): State<LocalAppState>,
232+
Json(req): Json<StartPushRequest>,
233+
) -> Result<impl IntoResponse, HttpResponseError> {
234+
let _identity = must_be_admin_from_key_with_write_access(
235+
st.application.app_auth(),
236+
st.instance_name.clone(),
237+
req.admin_key.clone(),
238+
)
239+
.await?;
240+
let config = req.into_project_config().map_err(|e| {
241+
anyhow::Error::new(ErrorMetadata::bad_request("InvalidConfig", e.to_string()))
242+
})?;
243+
let resp =
244+
st.application.evaluate_push(&config).await.map_err(|e| {
245+
e.wrap_error_message(|msg| format!("Hit an error while pushing:\n{msg}"))
246+
})?;
247+
248+
Ok(Json(SerializedEvaluatePushResponse::try_from(resp)?))
249+
}
250+
208251
const DEFAULT_SCHEMA_TIMEOUT_MS: u32 = 10_000;
209252

210253
#[derive(Deserialize)]

crates/local_backend/src/router.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ pub fn router(st: LocalAppState) -> Router {
320320
.route("/push_config", post(push_config))
321321
.route("/prepare_schema", post(prepare_schema))
322322
.route("/deploy2/start_push", post(deploy_config2::start_push))
323+
.route(
324+
"/deploy2/evaluate_push",
325+
post(deploy_config2::evaluate_push),
326+
)
323327
.route("/run_test_function", post(run_test_function))
324328
.route(
325329
"/deploy2/wait_for_schema",

npm-packages/convex/src/cli/lib/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ export function diffConfig(
11031103
* legacy push path:
11041104
* - /api/push_config
11051105
* modern push paths:
1106+
* - /api/deploy2/evaluate_push
11061107
* - /api/deploy2/start_push
11071108
* - /api/deploy2/finish_push
11081109
*

0 commit comments

Comments
 (0)