You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RemoteSandboxService has two related performance problems against the v1_remote_sandbox table, both untouched by OpenHands/OpenHands#14637 (which fixed the same class of issue for poll_agent_servers in OpenHands/OpenHands#14636). Filing as a follow-up so the sandbox request path is tracked on its own.
1. DB session held across runtime-api network I/O → idle in transaction
RemoteSandboxServiceInjector.inject opens get_db_session(...) for the entire service/request lifetime (openhands/app_server/sandbox/remote_sandbox_service.py:869-887), and the service methods then perform runtime-api network calls with that transaction still open:
Result: backends sit idle in transaction while waiting on network, holding open snapshots that block autovacuum on v1_remote_sandbox (dead tuples accumulate → scans get progressively more expensive). Observed in production: sessions idle-in-transaction for several minutes running the v1_remote_sandbox SELECT below.
_secure_select() (:214-219) only adds WHERE created_by_user_id = ... when a user id is present. In admin context (no user filter) it emits an unbounded query:
SELECT id, created_by_user_id, sandbox_spec_id, session_api_key_hash, created_at
FROM v1_remote_sandbox;
The plan is a full Seq Scan over the whole table — no predicate, so an index cannot help. Any method that executes _secure_select() without a LIMIT in admin context reads the entire table on every call, which is the dominant CPU cost when sandbox activity is high.
Bound the admin-context _secure_select() — require a filter, paginate, or cap with LIMIT; and/or cache + reduce call frequency for any reconcile-style caller that legitimately needs the full set. (An index is not the fix for the no-WHERE query.)
Summary
RemoteSandboxServicehas two related performance problems against thev1_remote_sandboxtable, both untouched by OpenHands/OpenHands#14637 (which fixed the same class of issue forpoll_agent_serversin OpenHands/OpenHands#14636). Filing as a follow-up so the sandbox request path is tracked on its own.1. DB session held across runtime-api network I/O →
idle in transactionRemoteSandboxServiceInjector.injectopensget_db_session(...)for the entire service/request lifetime (openhands/app_server/sandbox/remote_sandbox_service.py:869-887), and the service methods then perform runtime-api network calls with that transaction still open:search_sandboxes→_get_runtimes_batch()(network) —:314-329pause_old_sandboxes→/list+pause_sandbox()loop (network) —:574-606get_sandbox→_get_runtime()(network) —:341-347Result: backends sit
idle in transactionwhile waiting on network, holding open snapshots that block autovacuum onv1_remote_sandbox(dead tuples accumulate → scans get progressively more expensive). Observed in production: sessions idle-in-transaction for several minutes running thev1_remote_sandboxSELECT below.Same root cause as OpenHands/OpenHands#14636. OpenHands/OpenHands#14637 established the fix pattern (
fetch → release → network → re-acquire → write) but only for the polling loop.2. Unbounded full-table scan in admin context
_secure_select()(:214-219) only addsWHERE created_by_user_id = ...when a user id is present. In admin context (no user filter) it emits an unbounded query:The plan is a full
Seq Scanover the whole table — no predicate, so an index cannot help. Any method that executes_secure_select()without aLIMITin admin context reads the entire table on every call, which is the dominant CPU cost when sandbox activity is high.Proposed fix
RemoteSandboxService: release the DB session before runtime-api network calls and re-acquire a short-lived session only for writes. Don't span_send_runtime_api_request/_get_runtimes_batchwith an opendb_session._secure_select()— require a filter, paginate, or cap withLIMIT; and/or cache + reduce call frequency for any reconcile-style caller that legitimately needs the full set. (An index is not the fix for the no-WHEREquery.)Related
poll_agent_serversmemory paging