Skip to content

Improve ray actors resilience - #195

Merged
Ahmath-Gadji merged 3 commits into
devfrom
improve_ray_actors_resilience
Jan 16, 2026
Merged

Improve ray actors resilience#195
Ahmath-Gadji merged 3 commits into
devfrom
improve_ray_actors_resilience

Conversation

@Ahmath-Gadji

@Ahmath-Gadji Ahmath-Gadji commented Jan 9, 2026

Copy link
Copy Markdown
Collaborator

This PR improves the reliability and configurability of the Ray-based indexing system by strengthening concurrency control, worker resilience, and error handling.

Key changes:

  • Introduced a dedicated index concurrency group for the Ray indexer, enabling finer-grained control over indexing workloads. Updated the Indexer.add_file flow to use the new concurrency group and safely skip indexing when document serialization fails, with clearer logging.
  • Increased fault tolerance for remote workers by allowing unlimited restarts and limited task retries where appropriate.
  • Improved exception logging in document serialization to aid debugging.
  • Added and documented RAY_memory_monitor_refresh_ms to allow control over Ray’s memory monitoring behavior.
  • Refactored actor creation logic for more robust and graceful failure handling.

Overall, these updates make indexing more robust and better suited for concurrent and distributed workloads.

Summary by CodeRabbit

  • New Features

    • Added new concurrency controls for indexing (including serialization and insert concurrency) and an env var to control memory-monitoring frequency.
  • Documentation

    • Documented three new env vars for memory monitor and indexer concurrency.
  • Improvements

    • Workers now auto-restart on failure for greater resilience.
    • Serialization errors are logged more clearly.
  • Bug Fixes

    • Skips chunking when serialization produces no document to avoid extra processing.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new Indexer "serialize" concurrency group and a serialize_file Ray method, enables Ray actor restarts (max_restarts=5) for marker and serializer actors, adds related environment variables to docs and .env.example, and updates serializer error logging.

Changes

Cohort / File(s) Summary
Env example
/.env.example
Added a commented block and example # RAY_memory_monitor_refresh_ms=0 (no runtime effect unless uncommented).
Hydra config
/.hydra_config/config.yaml
Added serialize under ray.indexer.concurrency_groups (default from INDEXER_SERIALIZE_CONCURRENCY) and increased insert default under ray.indexer.concurrency_groups (was 10, now 100).
Documentation
docs/content/docs/documentation/env_vars.md
Added entries for RAY_memory_monitor_refresh_ms, INDEXER_SERIALIZE_CONCURRENCY, and INDEXER_INSERT_CONCURRENCY.
Indexer actor
openrag/components/indexer/indexer.py
Added "serialize" concurrency group to the Indexer actor config; introduced serialize_file method decorated with @ray.method(concurrency_group="serialize"); add_file now calls self.serialize_file and skips chunking with a warning if serialization returns no document.
Marker workers
openrag/components/indexer/loaders/pdf_loaders/marker.py
Added max_restarts=5 to @ray.remote decorators for MarkerWorker and MarkerPool.
Serializer actor
openrag/components/indexer/loaders/serializer.py
Changed @ray.remote to @ray.remote(max_restarts=5); improved exception logging in serialize_document (captures exception as e, logs error, then re-raises).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I nibble at configs, tidy up the lair,
Actors wake and restart with extra care.
Serialize hops in its own little lane,
Docs and envs whisper the new config name.
☕📜

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve ray actors resilience' accurately summarizes the main changes in the PR, which focus on enhancing Ray actor fault tolerance and concurrency control across multiple components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ahmath-Gadji

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.hydra_config/config.yaml:
- Line 156: The env var name used for the index concurrency is inconsistent:
change the occurrence of INDEXER_index_CONCURRENCY to use the uppercase
convention (INDEXER_INDEX_CONCURRENCY) so it matches the other keys (e.g.,
INDEXER_UPDATE_CONCURRENCY); update the line that reads index:
${oc.decode:${oc.env:INDEXER_index_CONCURRENCY, 50}} to reference
${oc.env:INDEXER_INDEX_CONCURRENCY, 50} instead.

In @docs/content/docs/documentation/env_vars.md:
- Line 315: Add a table row documenting the missing environment variable
INDEXER_index_CONCURRENCY in the "Indexer Concurrency Groups" section: include
the name `INDEXER_index_CONCURRENCY`, type `int`, default `50`, and a short
description such as "Maximum concurrent indexing operations" so the docs match
the definition in .hydra_config/config.yaml.
🧹 Nitpick comments (2)
openrag/components/indexer/loaders/serializer.py (1)

23-26: Verify unlimited restarts strategy aligns with operational requirements.

Setting max_restarts=-1 enables unlimited actor restarts, which increases fault tolerance but could mask recurring failures or lead to resource exhaustion if an actor repeatedly crashes. This is a significant operational decision.

Ensure:

  • Monitoring/alerting is in place to detect restart loops
  • The root causes of actor failures are investigated rather than indefinitely restarted
  • This aligns with your operational requirements for the Ray-based indexing system

The max_task_retries=2 limit is appropriate and prevents indefinite task retry loops.

openrag/components/indexer/loaders/pdf_loaders/marker.py (1)

27-27: Add max_task_retries to MarkerWorker and MarkerPool for consistency and task failure handling.

While max_restarts=-1 improves actor resilience, both MarkerWorker (line 27) and MarkerPool (line 149) lack task retry limits. The DocSerializer in serializer.py follows a better pattern with both max_restarts=-1 and max_task_retries=2, limiting individual task retries while allowing actor restarts.

Without max_task_retries, tasks that consistently fail will retry indefinitely on these actors, potentially wasting resources. Consider adding max_task_retries=2 (or an appropriate value from config) to both decorators to align with the existing DocSerializer pattern and prevent unbounded resource consumption on failed tasks.

Also applies to: 149-149

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df02bc8 and 6884b73.

📒 Files selected for processing (7)
  • .env.example
  • .hydra_config/config.yaml
  • docs/content/docs/documentation/env_vars.md
  • openrag/components/indexer/indexer.py
  • openrag/components/indexer/loaders/pdf_loaders/marker.py
  • openrag/components/indexer/loaders/serializer.py
  • openrag/utils/dependencies.py
🔇 Additional comments (6)
.env.example (1)

44-45: LGTM! Clear documentation for optional configuration.

The commented-out example with an explanatory comment is helpful for users who need to disable memory-based task killing. This aligns well with the documentation added in env_vars.md.

openrag/components/indexer/loaders/serializer.py (1)

90-92: LGTM! Enhanced exception logging improves debuggability.

The improved exception handling now captures and logs the error string before re-raising, which will help with troubleshooting serialization failures. This aligns well with the PR's goal of improving error handling.

openrag/utils/dependencies.py (1)

11-15: Verify that broad exception handling doesn't mask unexpected errors.

The change from except ValueError: to except Exception: makes actor creation more resilient by catching all exceptions and falling back to creating a new actor. However, this broader scope could mask unexpected errors beyond the "actor not found" case that ValueError specifically handles.

Consider:

  • Logging unexpected exceptions (non-ValueError) before creating a new actor
  • This helps distinguish between normal actor-not-found scenarios vs. actual errors (network issues, permission problems, etc.)
🔍 Suggested enhancement for better observability
 def get_or_create_actor(name, cls, namespace="openrag", **options):
+    from utils.logger import get_logger
+    logger = get_logger()
     try:
         return ray.get_actor(name, namespace=namespace)
-    except Exception:
+    except ValueError:
+        # Actor not found - create it
         return cls.options(name=name, namespace=namespace, **options).remote()
+    except Exception as e:
+        # Unexpected error - log and attempt recovery
+        logger.warning(f"Unexpected error getting actor {name}: {e}, creating new actor")
+        return cls.options(name=name, namespace=namespace, **options).remote()

This approach provides better observability while maintaining the resilience benefits.

openrag/components/indexer/indexer.py (3)

24-35: LGTM! Concurrency group properly configured.

The new "index" concurrency group is correctly added following the existing pattern for other groups. The configuration is properly wired to the config file with a sensible default of 50 concurrent operations.


59-59: LGTM! Appropriate concurrency group separation.

Moving add_file to the "index" concurrency group is a good design choice. This method orchestrates the entire indexing flow (serialization, chunking, insertion) and should have separate concurrency control from the chunking operations it invokes.


94-101: LGTM! Excellent defensive error handling.

The conditional check gracefully handles serialization failures by skipping chunking when no document is produced. The warning log provides visibility into the skip, which aids debugging. This prevents potential downstream errors and aligns well with the PR's resilience goals.

Comment thread .hydra_config/config.yaml Outdated
Comment thread docs/content/docs/documentation/env_vars.md
@Ahmath-Gadji
Ahmath-Gadji force-pushed the improve_ray_actors_resilience branch from 6884b73 to b4d1f70 Compare January 9, 2026 13:57
@Ahmath-Gadji
Ahmath-Gadji marked this pull request as ready for review January 9, 2026 13:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @openrag/components/indexer/loaders/pdf_loaders/marker.py:
- Line 27: The actor is configured with unlimited restarts via @ray.remote(...,
max_restarts=-1) which can cause tight restart loops on persistent failures;
change the MarkerWorker actor’s Ray options to use a bounded restart count
(e.g., max_restarts=3 or another reasonable finite number) instead of -1 and/or
add simple backoff logic inside the MarkerWorker constructor/startup path (use
try/except around initialization and sleep with exponential backoff before
re-raising to trigger a delayed restart) while keeping MARKER_NUM_GPUS
unchanged; update any tests or docs that assume unlimited restarts accordingly.
🧹 Nitpick comments (2)
openrag/utils/dependencies.py (1)

11-15: Consider logging the exception for better observability.

Broadening the exception handling from ValueError to Exception improves resilience by catching more failure modes when looking up Ray actors. However, silently catching all exceptions might hide unexpected errors during debugging.

♻️ Optional: Add logging for better observability
+from utils.logger import get_logger
+
+logger = get_logger()
+
 def get_or_create_actor(name, cls, namespace="openrag", **options):
     try:
         return ray.get_actor(name, namespace=namespace)
-    except Exception:
+    except Exception as e:
+        logger.debug(f"Actor {name} not found or unavailable, creating new: {e}")
         return cls.options(name=name, namespace=namespace, **options).remote()
openrag/components/indexer/loaders/pdf_loaders/marker.py (1)

149-149: Consider the cascade effect of unlimited pool restarts.

Setting max_restarts=-1 for MarkerPool means each restart will reinitialize all MarkerWorker actors (line 160), potentially causing expensive resource allocation and disruption. If the pool encounters a persistent issue, unlimited restarts could lead to repeated expensive reinitialization cycles.

Consider whether a bounded restart limit with monitoring and alerting would be more appropriate for the pool coordinator.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6884b73 and b4d1f70.

📒 Files selected for processing (6)
  • .hydra_config/config.yaml
  • docs/content/docs/documentation/env_vars.md
  • openrag/components/indexer/indexer.py
  • openrag/components/indexer/loaders/pdf_loaders/marker.py
  • openrag/components/indexer/loaders/serializer.py
  • openrag/utils/dependencies.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • .hydra_config/config.yaml
  • docs/content/docs/documentation/env_vars.md
  • openrag/components/indexer/loaders/serializer.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: index-backup-restore
🔇 Additional comments (5)
openrag/utils/dependencies.py (2)

41-42: LGTM!

The new get_indexer() function follows the established pattern for actor creation and properly exposes the Indexer actor with a detached lifetime.


53-53: LGTM!

Module-level initialization of the indexer actor follows the existing pattern and provides convenient access to the Indexer actor throughout the codebase.

openrag/components/indexer/indexer.py (3)

33-33: LGTM!

Adding the "index" concurrency group enables dedicated concurrency control for indexing operations, consistent with the existing pattern for other operation types.


59-59: LGTM!

Assigning add_file to the "index" concurrency group enables independent concurrency control for indexing operations, improving resource management and aligning with the PR's goal of strengthening concurrency control.


94-101: Good defensive programming for serialization failures.

The conditional check properly handles cases where serialization returns no document, preventing downstream errors during chunking. The warning message clearly indicates the skip condition.

Comment thread openrag/components/indexer/loaders/pdf_loaders/marker.py Outdated
@Ahmath-Gadji
Ahmath-Gadji force-pushed the improve_ray_actors_resilience branch from a2309b6 to e604ad6 Compare January 15, 2026 14:58
| `RAY_ENABLE_RECORD_ACTOR_TASK_LOGGING` | `number` | `1` | Enables logs at task level in the Ray dashboard for better debugging and monitoring. |
| `RAY_task_retry_delay_ms` | `number` | `3000` | Delay (in milliseconds) before retrying a failed task. Controls the wait time between retry attempts. |
| `RAY_ENABLE_UV_RUN_RUNTIME_ENV` | `number` | `0` | Controls UV runtime environment integration. **Critical**: Must be set to `0` when using the newest version of UV to avoid compatibility issues. |
|`RAY_memory_monitor_refresh_ms`| `number` | 250 ms | To control the frequency of memory usage checks and task or actor termination if needed. If you set this value to 0, task killing is disabled. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make it in all capitalized letter? So be consistant with the other config variables (except for RAY_task_retry_delay_ms , that should be eventually changed as well)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don't see how this env var is used?

@Ahmath-Gadji Ahmath-Gadji Jan 16, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make it in all capitalized letter? So be consistant with the other config variables (except for RAY_task_retry_delay_ms , that should be eventually changed as well)

It's an innate RAY variable so we don't have control over the naming. I didn't used anywhere in my code; i documented it here cause according to the RAY documentation it can help with memory issues for example disable actor killing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a bit weird to add vars from external lib just to document it exists, but I don't have a strong opposition

|----------|------|---------|-------------|
| `INDEXER_DEFAULT_CONCURRENCY` | int | 1000 | Default concurrency limit for general operations |
| `INDEXER_UPDATE_CONCURRENCY` | int | 100 | Maximum concurrent document update operations |
| `INDEXER_INDEX_CONCURRENCY` | int | 50 | Maximum concurrent indexing operations |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the naming a bit confusing, since insert/update/delete are also indexing operations. In this case, this is more add or insert, but I see that we already have INDEXER_INSERT_CONCURRENCY (surprisingly set to a small default of 10). But from what I see, insert_documents is only used by add_file, so in the index concurrency group. Maybe we can:

  • rename INDEXER_INDEX_CONCURRENCY into INDEXER_ADD_CONCURRENCY
  • remove INDEXER_INDEX_INSERT_CONCURRENCY and the associated concurrency group?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • INDEXER_INSERT_CONCURRENCY controls vdb insertion concurrency. The value is indeed small; i set it up to 100

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here’s what’s happening: whenever a file is uploaded, add_file is triggered, which then calls serialize_file. This function relies on the DocSerializer Ray actor.

async def add_file(
self,
path: Union[str, List[str]],
metadata: Optional[Dict] = {},
partition: Optional[str] = None,
user: Optional[Dict] = None,
):
task_state_manager = ray.get_actor("TaskStateManager", namespace="openrag")
task_id = ray.get_runtime_context().get_task_id()
file_id = metadata.get("file_id", None)
log = self.logger.bind(file_id=file_id, partition=partition, task_id=task_id)
log.info("Queued file for indexing.")
try:
# Set task details
user_metadata = {
k: v for k, v in metadata.items() if k not in {"file_id", "source"}
}
await task_state_manager.set_details.remote(
task_id,
file_id=metadata.get("file_id"),
partition=partition,
metadata=user_metadata,
user_id=user.get("id"),
)
# Check/normalize partition
partition = self._check_partition_str(partition)
metadata = {**metadata, "partition": partition}
# Serialize
doc = await serialize_file(task_id, path, metadata=metadata)

Under heavy load, the DocSerializer tends to crash. My intuition is that this is due to a large number of calls to serializer.serialize_document, combined with the fact that these calls are not executed concurrently.

async def serialize_file(task_id: str, path: str, metadata: Optional[Dict] = {}):
import ray
from components.ray_utils import call_ray_actor_with_timeout
serializer = ray.get_actor("DocSerializer", namespace="openrag")
future = serializer.serialize_document.remote(task_id, path, metadata=metadata)

Introducing INDEXER_INDEX_CONCURRENCY (which has since been refactored and renamed to INDEXER_SERIALIZE_CONCURRENCY) helped mitigate these errors.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes more sense now, thank you

@Ahmath-Gadji
Ahmath-Gadji force-pushed the improve_ray_actors_resilience branch from e604ad6 to 897e487 Compare January 16, 2026 13:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@openrag/components/indexer/indexer.py`:
- Around line 59-65: The serialize_file signature uses a mutable default for
metadata and an incorrect type hint for task_id; change the signature in
serialize_file to accept metadata: Optional[Dict] = None and task_id:
Optional[str] = None, and inside the function normalize metadata to a fresh dict
(e.g., metadata = dict(metadata or {}) or metadata = {} if None) before any
.update() or assignment so callers don't share state; update any references in
serialize_file and callers (e.g., eml_loader.py) to rely on the normalized
metadata variable.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e604ad6 and 897e487.

📒 Files selected for processing (5)
  • .hydra_config/config.yaml
  • docs/content/docs/documentation/env_vars.md
  • openrag/components/indexer/indexer.py
  • openrag/components/indexer/loaders/pdf_loaders/marker.py
  • openrag/components/indexer/loaders/serializer.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/content/docs/documentation/env_vars.md
  • .hydra_config/config.yaml
🧰 Additional context used
🪛 Ruff (0.14.11)
openrag/components/indexer/indexer.py

63-63: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)


64-64: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: api-tests
  • GitHub Check: index-backup-restore
🔇 Additional comments (6)
openrag/components/indexer/loaders/serializer.py (2)

23-23: Confirm Ray actor restart policy is intentional.
Bounded restarts are a good resilience improvement; please verify that max_restarts=5 matches the desired failure-recovery behavior for this actor in Ray 2.47.1.


87-89: Improved exception logging context.
Including the exception detail in structured logs is helpful for debugging while preserving failure propagation.

openrag/components/indexer/loaders/pdf_loaders/marker.py (2)

27-27: Bounded restart policy looks reasonable—please verify count.
Confirm that max_restarts=5 is the intended resilience level for MarkerWorker under your failure scenarios.


159-159: MarkerPool restart policy aligned—verify desired behavior.
Please double-check that max_restarts=5 is sufficient for pool availability targets in Ray 2.47.1.

openrag/components/indexer/indexer.py (2)

100-113: Guarding chunking on serialization failure is a solid resilience win.
Skipping chunking when no document is produced prevents cascading errors.


33-33: No action required. The serialize concurrency group is already defined in .hydra_config/config.yaml:162 with a default value of 50 (INDEXER_SERIALIZE_CONCURRENCY), so no KeyError will occur at runtime.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread openrag/components/indexer/indexer.py
@Ahmath-Gadji
Ahmath-Gadji merged commit e188493 into dev Jan 16, 2026
3 of 4 checks passed
@Ahmath-Gadji
Ahmath-Gadji deleted the improve_ray_actors_resilience branch January 22, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants