-
Notifications
You must be signed in to change notification settings - Fork 31
feat: Add Redis caching for navigation pagination #488
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
base: main
Are you sure you want to change the base?
Conversation
5c8d201
to
8991cfc
Compare
b16cc88
to
8991cfc
Compare
|
||
if redis_enable: | ||
try: | ||
redis = await connect_redis() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how does this work with connect_redis_sentinel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is connect_redis and connect_redis_sentinel. I think maybe there is logic missing which should determine which one to use? Maybe you can explain how it works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should also log the redis connection whether its successful or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
If a user wants to use standalone Redis (the most common case), they should use
connect_redis()
. If they want to use Redis Sentinel (used by Cloudferro),connect_redis_sentinel()
should be used. The connection is configured differently in each case, which is why we have two separate functions. The user needs to configure either theREDIS
orREDIS_SENTINEL
environment variables. -
The selection of which function to use should be done in redis_utils.py:
- For standalone Redis: redis_settings: BaseSettings = RedisSettings()
- For Redis Sentinel: redis_settings: BaseSettings = RedisSentinelSettings()
-
Logging has been added for users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks for explaining - here you only use connect_redis but should you use connect_redis_sentinel as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user configures Redis Sentinel
, the connect_redis_sentinel
function should be used instead of connect_redis
. I could try to combine these two functions into one, which would choose the appropriate approach depending on whether the user wants standalone or Sentinel depending on config selected. This way, no one would need to manually replace function, only connect_redis
would be called. Both approaches are fine with me. Just to note, Cloudferro will probably be the only users of Sentinel, which is why I added this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, maybe even a helper function that calls the right function. If there are env options for sentinel in the codebase then we have to make it useable. Never say never.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonhealy1 this has been implemented. Moving forward, depending on the configuration specified via environment variables, it will be handled by connect_redis
. The developer is not expected to select the function.
"href": prev_link, | ||
}, | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this code - the redis_enabled block - be put into a function? It is used with all_collections too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code block was replaced here with func as well. Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's change the function name for handle_pagination_links - also, mentioned below - and let's check the REDIS IS ENABLED here before we go to the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thank you!
"href": prev_link, | ||
}, | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code into a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonhealy1 I have moved this code into handle_pagination_links
func that can be reused where needed.
8991cfc
to
e3e84cd
Compare
- Configure Redis image for tests of caching navigation - Update Make file Redis with test targets for ES and OS - Integrate Redis/Redis Sentinel client to cache navigation - Add Redis funcs Sentinel for navigation caching
Add tests for Redis pagination caching in search and collections endpoints, plus utility function tests.
f8a4ad9
to
ff38a98
Compare
3a0854f
to
34a842f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thanks @YuriZmytrakov
@YuriZmytrakov Let us know when this has been approved by QA |
c6a9d09
to
98c0550
Compare
98c0550
to
dbfe1b2
Compare
This PR has passed QA and is ready to be merged. |
I have added the installation steps for navigation via the Redis cache. The steps are straightforward and can be expanded once Redis is used for caching additional navigation links. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice feature addition! Just be careful with potential cache conflicts, as highlighted in the code comment.
async def save_self_link( | ||
redis: aioredis.Redis, token: Optional[str], self_href: str | ||
) -> None: | ||
"""Save the self link for the current token.""" | ||
if token: | ||
if sentinel_settings.REDIS_SENTINEL_HOSTS: | ||
ttl_seconds = sentinel_settings.REDIS_SELF_LINK_TTL | ||
elif standalone_settings.REDIS_HOST: | ||
ttl_seconds = standalone_settings.REDIS_SELF_LINK_TTL | ||
await redis.setex(f"nav:self:{token}", ttl_seconds, self_href) | ||
|
||
|
||
async def get_prev_link(redis: aioredis.Redis, token: Optional[str]) -> Optional[str]: | ||
"""Get the previous page link for the current token.""" | ||
if not token: | ||
return None | ||
return await redis.get(f"nav:self:{token}") | ||
|
||
|
||
async def redis_pagination_links( | ||
current_url: str, token: str, next_token: str, links: list | ||
) -> None: | ||
"""Handle Redis pagination.""" | ||
redis = await connect_redis() | ||
if not redis: | ||
logger.warning("Redis connection failed.") | ||
return | ||
|
||
try: | ||
if next_token: | ||
await save_self_link(redis, next_token, current_url) | ||
|
||
prev_link = await get_prev_link(redis, token) | ||
if prev_link: | ||
links.insert( | ||
0, | ||
{ | ||
"rel": "previous", | ||
"type": "application/json", | ||
"method": "GET", | ||
"href": prev_link, | ||
}, | ||
) | ||
except Exception as e: | ||
logger.warning(f"Redis pagination operation failed: {e}") | ||
finally: | ||
await redis.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By only storing the token
and not the full URL in the key, we might hit unwanted cache conflicts. A pagination token might occur in different search queries. For example, these 2 searches with different queries return the same pagination token.
❯ https stac.terrascope.be/search limit==1 collection==sentinel-2-l1c | jq '.links[] | select(.rel=="next") | .href'
"https://stac.terrascope.be/search?limit=1&collection=sentinel-2-l1c&token=WzE3NjEwMzAxMjkwMjQsIlMyQl9NU0lMMUNfMjAyNTEwMjFUMDcwMjA5X04wNTExX1IxMjBfVDM4S1FEXzIwMjUxMDIxVDA5NTkyOSIsInNlbnRpbmVsLTItbDFjIl0%3D"
❯ https stac.terrascope.be/search limit==1 | jq '.links[] | select(.rel=="next") | .href'
"https://stac.terrascope.be/search?limit=1&token=WzE3NjEwMzAxMjkwMjQsIlMyQl9NU0lMMUNfMjAyNTEwMjFUMDcwMjA5X04wNTExX1IxMjBfVDM4S1FEXzIwMjUxMDIxVDA5NTkyOSIsInNlbnRpbmVsLTItbDFjIl0%3D"
Retrieving the previous link from the cache would potentially change the search query in this case. The links will also only work for GET
searches, so best to explicitly limit this and do not try to retrieve the link for POST
searches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the heads up. This has been tested, no collisions have occurred. I don’t see any issues with replacing the token with the full URL as the key to add or retrieve the previous page URL from the Redis cache for navigation. However, please note that we are using the proxy, and if the full URL is used, the keys will become irrelevant. As a solution, I have added additional parameters from the URL endpoint + token. This prevents accidentally retrieving the wrong previous page URL, as different requests might share the same token. In this case, the following Redis keys will be created:
nav:search?limit=1&collection=sentinel-2-l1c&token=WzE3NjEwMzAxMjkwMjQsIlMyQl9NU0lMMUNfMjAyNTEwMjFUMDcwMjA5X04wNTExX1IxMjBfVDM4S1FEXzIwMjUxMDIxVDA5NTkyOSIsInNlbnRpbmVsLTItbDFjIl0%3D"
nav:/search?limit=1&token=WzE3NjEwMzAxMjkwMjQsIlMyQl9NU0lMMUNfMjAyNTEwMjFUMDcwMjA5X04wNTExX1IxMjBfVDM4S1FEXzIwMjUxMDIxVDA5NTkyOSIsInNlbnRpbmVsLTItbDFjIl0%3D"
Also, the POST
requests are excluded from retrieving the navigation links.
d409c95
to
1e492b6
Compare
1e492b6
to
fe0efb2
Compare
Related Issue(s):
Description:
Add Redis caching support for navigation pagination to enable proper
prev
/next
links in STAC API responses.PR Checklist:
pre-commit run --all-files
)make test
)