Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] Add Web Cache Example to docs #10588

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/examples/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Ray Examples
testing-tips.rst
progress_bar.rst
plot_streaming.rst
web_cache.rst
placement-group.rst

.. customgalleryitem::
Expand All @@ -37,6 +38,11 @@ Ray Examples
:tooltip: Implement a simple streaming application using Ray’s actors.
:description: :doc:`/auto_examples/plot_streaming`


.. customgalleryitem::
:tooltip: Implement a simple Web cache.
:description: :doc:`/auto_examples/web_cache`

.. customgalleryitem::
:tooltip: Learn placement group use cases with examples.
:description: :doc:`/auto_examples/placement-group`
Expand Down
141 changes: 141 additions & 0 deletions doc/examples/web_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""
Web Cache
=========

This program illustrates how to use a Ray actor to cache data coming
from an external API. It starts up a Flask server and
the actor, makes a couple fake API calls to fetch the same data,
and prints out the run time of each
call. The second call is substantially faster
because the data exists in the cache.

To run the example, you need to install the dependencies

.. code-block:: bash

pip install flask
pip install requests

and then execute this script as follows:

.. code-block:: bash

python web_cache.py


Setting up imports
------------------

Let's first import the necessary dependencies and start the Ray runtime.

"""
from typing import Dict
import time
import requests
import ray
import flask

ray.init()
app = flask.Flask(__name__)

#####################################################################
# The following code block defines a WebCache Ray Actor. This
# allows the class to be run in a multi-process environment.
# Ray handles serialization of all remote method calls to this
# actor so that you do not need to worry about race conditions.
# We will use this to store and retrieve
# the results of expensive API calls across our Flask handlers.


@ray.remote
class WebCache:
def __init__(self):
# A dict will serve as our cache
self.users: Dict[int, any] = {}

def get_user(self, uid: int):
return self.users.get(uid)

def put_user(self, uid: int, user: any):
self.users[uid] = user


####################################################################
# Now, instantiate a WebCache actor.
#
# Rather than saving a handle to the actor, we use a "detached actor".
#
# This is an actor that can be fetched by name, as opposed to having
# to pass its handle around. You can instantiate
# an actor once at app initialization time and use it from all your
# handlers without passing a new argument through to them.

WebCache.options(name="web_cache", lifetime="detached").remote()

#############################################################################
# Finally, this is the handler that we use to illustrate the concept.
# The code first fetches the WebCache actor by name. It then
# checks whether the necessary data exists in the cache. If the
# data exists, the code returns it from the cache, otherwise
# it makes an expensive API call and populates the cache with the
# data.


def fetch_user_from_api(uid: int):
# Imagine this function is an expensive call to an external API
time.sleep(5)
return {"name": "max", "id": uid}


@app.route("/get_user/<uid>")
def fetch_external_user_handler(uid):
"""This handler retrieves data from an external API if the
data isn't cached, otherwise it returns the cached data."""
uid = int(uid)
# Check whether the data we want is already cached.
cache = ray.get_actor(
"web_cache") # This is used for fetching detached actors
if not cache:
print("not cache!")
user = ray.get(cache.get_user.remote(uid))
if user:
return user

# Otherwise fetch from API and update the cache
user = fetch_user_from_api(uid)
cache.put_user.remote(uid, user)
return user


##################################################################
# To test this web cache, we define the following function.
# This function is being run as a Ray task because the flask
# server is blocking, so we need to run this code from another process.
# Keeping these in the same file is for example purposes only.


@ray.remote
def test_web_cache():
# sleep for a moment to make sure the server has started up.
time.sleep(5)
# Request the same user id twice. We expect the first call to take
# substantially longer because the value isn't cached yet.
timer_st = time.monotonic()
requests.get("http://localhost:3000/get_user/4")
timer_end = time.monotonic()
first_req_runtime_s = timer_end - timer_st

timer_st = time.monotonic()
requests.get("http://localhost:3000/get_user/4")
timer_end = time.monotonic()
second_req_runtime_s = timer_end - timer_st

print(f"The first request took {first_req_runtime_s} seconds.")
print(f"The second request took {second_req_runtime_s} seconds.")


###################################################################
# Below, we instantiate the application and test the cache.

test_web_cache.remote()
app.run(host="localhost", port=3000)