Skip to content

Commit 6be386a

Browse files
emmaling27Convex, Inc.
authored andcommitted
Make local node executor run a server instead of a one-shot process (#34778)
This PR makes node actions in self-hosted backend way faster. 100ms -> 2ms for a simple action that logs a line. Instead of starting a one-shot node process to execute every single node action, which requires downloading the source and any external packages specified, this change starts a node server in `LocalNodeExecutor` that caches source and external packages similar to how the AWS lambda implementation works. GitOrigin-RevId: 0421a6182e811cd879c69738ad23222ebb32c824
1 parent 956737d commit 6be386a

File tree

17 files changed

+468
-203
lines changed

17 files changed

+468
-203
lines changed

Cargo.lock

Lines changed: 43 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ tokio = { version = "1", features = [ "full" ] }
150150
tokio-metrics = { version = "0.3.1" }
151151
tokio-metrics-collector = { version = "0.2.1" }
152152
tokio-postgres = { version = "0.7.10", features = [ "with-serde_json-1" ] }
153-
tokio-process-stream = { version = "0.4.0" }
154153
tokio-stream = { version = "0.1", features = [ "io-util", "sync", "signal" ] }
155154
tokio-tungstenite = { version = "0.26.2", features = [ "native-tls-vendored" ] }
156155
tokio-util = { version = "0.7.13", features = [ "io", "rt", "io-util" ] }

crates/application/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ model = { path = "../model", features = ["testing"] }
9999
must-let = { workspace = true }
100100
node_executor = { path = "../../crates/node_executor", features = ["testing"] }
101101
openidconnect = { workspace = true }
102+
portpicker = { workspace = true }
102103
pretty_assertions = { workspace = true }
103104
proptest = { workspace = true }
104105
proptest-derive = { workspace = true }

crates/application/src/test_helpers.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ use model::{
8787
virtual_system_mapping,
8888
};
8989
use node_executor::{
90-
local::LocalNodeExecutor,
90+
noop::NoopNodeExecutor,
9191
Actions,
92+
NodeExecutor,
9293
};
9394
use storage::Storage;
9495
use value::{
@@ -117,6 +118,7 @@ pub static OBJECTS_TABLE_COMPONENT: ComponentId = ComponentId::test_user();
117118
pub struct ApplicationFixtureArgs {
118119
pub tp: Option<TestPersistence>,
119120
pub event_logger: Option<Arc<dyn UsageEventLogger>>,
121+
pub node_executor: Option<Arc<dyn NodeExecutor>>,
120122
}
121123

122124
impl ApplicationFixtureArgs {
@@ -126,6 +128,13 @@ impl ApplicationFixtureArgs {
126128
..Default::default()
127129
}
128130
}
131+
132+
pub fn with_node_executor(node_executor: Arc<dyn NodeExecutor>) -> Self {
133+
Self {
134+
node_executor: Some(node_executor),
135+
..Default::default()
136+
}
137+
}
129138
}
130139

131140
#[async_trait]
@@ -227,8 +236,9 @@ impl<RT: Runtime> ApplicationTestExt<RT> for Application<RT> {
227236
database: database.clone(),
228237
};
229238

230-
let node_process_timeout = *ACTION_USER_TIMEOUT + Duration::from_secs(5);
231-
let node_executor = Arc::new(LocalNodeExecutor::new(node_process_timeout)?);
239+
let node_executor = args
240+
.node_executor
241+
.unwrap_or_else(|| Arc::new(NoopNodeExecutor::new()));
232242
let actions = Actions::new(
233243
node_executor,
234244
convex_origin.clone(),

crates/application/src/tests/analyze.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::collections::BTreeMap;
1+
use std::{
2+
collections::BTreeMap,
3+
sync::Arc,
4+
time::Duration,
5+
};
26

37
use common::types::{
48
ModuleEnvironment,
@@ -8,10 +12,14 @@ use model::{
812
config::types::ModuleConfig,
913
udf_config::types::UdfConfig,
1014
};
15+
use node_executor::local::LocalNodeExecutor;
1116
use runtime::prod::ProdRuntime;
1217

1318
use crate::{
14-
test_helpers::ApplicationTestExt,
19+
test_helpers::{
20+
ApplicationFixtureArgs,
21+
ApplicationTestExt,
22+
},
1523
tests::NODE_SOURCE,
1624
Application,
1725
};
@@ -56,7 +64,13 @@ export default {
5664
// This test requires prod runtime since it analyzes node modules.
5765
#[convex_macro::prod_rt_test]
5866
async fn test_analyze(rt: ProdRuntime) -> anyhow::Result<()> {
59-
let application = Application::new_for_tests(&rt).await?;
67+
let application = Application::new_for_tests_with_args(
68+
&rt,
69+
ApplicationFixtureArgs::with_node_executor(Arc::new(
70+
LocalNodeExecutor::new(Duration::from_secs(10)).await?,
71+
)),
72+
)
73+
.await?;
6074
let modules = vec![
6175
ModuleConfig {
6276
path: "a.js".parse()?,
@@ -162,7 +176,13 @@ export { hello, internalHello };
162176
}
163177
"#;
164178

165-
let application = Application::new_for_tests(&rt).await?;
179+
let application = Application::new_for_tests_with_args(
180+
&rt,
181+
ApplicationFixtureArgs::with_node_executor(Arc::new(
182+
LocalNodeExecutor::new(Duration::from_secs(10)).await?,
183+
)),
184+
)
185+
.await?;
166186
let modules = vec![
167187
ModuleConfig {
168188
path: "isolate_source.js".parse()?,
@@ -218,7 +238,13 @@ export { hello, internalHello };
218238
// This test requires prod runtime since it analyzes node modules.
219239
#[convex_macro::prod_rt_test]
220240
async fn test_analyze_crons(rt: ProdRuntime) -> anyhow::Result<()> {
221-
let application = Application::new_for_tests(&rt).await?;
241+
let application = Application::new_for_tests_with_args(
242+
&rt,
243+
ApplicationFixtureArgs::with_node_executor(Arc::new(
244+
LocalNodeExecutor::new(Duration::from_secs(10)).await?,
245+
)),
246+
)
247+
.await?;
222248
let modules = vec![
223249
ModuleConfig {
224250
path: "a.js".parse()?,

crates/local_backend/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pub async fn make_app(
173173
};
174174

175175
let node_process_timeout = *ACTION_USER_TIMEOUT + Duration::from_secs(5);
176-
let node_executor = Arc::new(LocalNodeExecutor::new(node_process_timeout)?);
176+
let node_executor = Arc::new(LocalNodeExecutor::new(node_process_timeout).await?);
177177
let actions = Actions::new(
178178
node_executor,
179179
config.convex_origin_url()?,

crates/node_executor/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,23 @@ common = { path = "../common" }
1818
errors = { path = "../errors" }
1919
fastrace = { workspace = true }
2020
futures = { workspace = true }
21+
futures-async-stream = { workspace = true }
2122
home = { workspace = true }
2223
http = { workspace = true }
2324
isolate = { path = "../isolate" }
2425
keybroker = { path = "../keybroker" }
2526
maplit = { workspace = true }
2627
metrics = { path = "../metrics" }
2728
model = { path = "../model" }
29+
portpicker = { workspace = true }
30+
reqwest = { version = "0.11", features = ["json"] }
2831
serde = { workspace = true }
2932
serde_json = { workspace = true }
3033
sourcemap = { workspace = true }
3134
storage = { path = "../storage" }
3235
sync_types = { package = "convex_sync_types", path = "../convex/sync_types" }
3336
tempfile = { workspace = true }
3437
tokio = { workspace = true }
35-
tokio-process-stream = { workspace = true }
3638
tracing = { workspace = true }
3739
udf = { path = "../udf" }
3840
value = { path = "../value" }

0 commit comments

Comments
 (0)