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

Initial support for client-side caching #3658

Merged
merged 1 commit into from
Dec 28, 2023
Merged

Conversation

sazzad16
Copy link
Collaborator

No description provided.

@chayim chayim changed the title Client-side caching support Initial support for client side caching Dec 26, 2023
public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config,
ClientSideCache cache) {
super(hostPort, config);
if (config.getRedisProtocol() != RedisProtocol.RESP3) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@vladvildanov @dvora-h IMHO we should do this in other clients if not already there.

Choose a reason for hiding this comment

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

@chayim It's already there

But, we had a discussion with you and @uglide and we agreed that we don't throw an exception in case if user do not explicitly set RESP3, but instead we're forcing RESP3 connection.

@@ -43,6 +43,11 @@ public RedisInputStream(InputStream in) {
this(in, INPUT_BUFFER_SIZE);
}

public Byte peekByte() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we start adding javadocs here

@@ -252,4 +257,18 @@ private void ensureFill() throws JedisConnectionException {
}
}
}

private void ensureFillSafe() {
Copy link
Contributor

Choose a reason for hiding this comment

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

and here

if (count >= limit) {
try {
limit = in.read(buf);
count = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

count doesn't need to be within the try, can be outside

@@ -252,4 +257,18 @@ private void ensureFill() throws JedisConnectionException {
}
}
}

private void ensureFillSafe() {
if (count >= limit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

possibly silly but if count >= limit && limit > 0?


InOrder inOrder = Mockito.inOrder(cache);
inOrder.verify(cache).invalidateKeys(Mockito.notNull());
inOrder.verify(cache).getValue("foo");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: store the key name in a variable and reuse

if (key instanceof byte[]) {
cache.remove(convertKey((byte[]) key));
} else {
throw new JedisException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));

Choose a reason for hiding this comment

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

Why in this case function signature doesn't looks like invalidateKey(byte[] key)?

protected ClientSideCache() {
}

protected void invalidateKeys(List list) {

Choose a reason for hiding this comment

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

Design discussion:

As I understood ClientSideCache object represents a DAO/persistence layer, so it's actually just a storage and you can perform CRUD operations against it. From this perspective this method should be rather named as delete() or batchDelete().

Invalidation is a more complex process built around cache that involves business logic and belong to another layer, something like this:

  1. Get invalid key (keys).
  2. Find all command responses associated with this key (keys).
  3. Delete them from cache.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have used Redis terms for easier recognition. Things could be changed later but not now.

import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.SafeEncoder;

public class ClientSideCache {

Choose a reason for hiding this comment

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

Do you really need to implement your own cache storage? Don't you have something ready to use, maybe some lightweight library that provides a cache storage API?

I'm pretty much sure it will be quite time consuming to implement search mechanism in cache to search for all the data associated with key, as well as provide metadata for telemetry (cache hits, cache misses). Also, you have to think about TTL mechanism.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We should define our cache APIs. I don't think we'll find anything that perfectly matches all our use cases. With a defined set of APIs, users may choose to provide their own implementation based on their preferred library.

At some point, this will be based on a library. But we have started simple.

Copy link
Contributor

Choose a reason for hiding this comment

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

Internal separation of concern is a good idea

public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config,
ClientSideCache cache) {
super(hostPort, config);
if (config.getRedisProtocol() != RedisProtocol.RESP3) {

Choose a reason for hiding this comment

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

@chayim It's already there

But, we had a discussion with you and @uglide and we agreed that we don't throw an exception in case if user do not explicitly set RESP3, but instead we're forcing RESP3 connection.


private void clientTrackingOn() {
String reply = connection.executeCommand(new CommandObject<>(
new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON").add("BCAST"),

Choose a reason for hiding this comment

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

@chayim @uglide As I remember BCAST is not an option here

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct. @sazzad16


private final ClientSideCache cache;

public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config) {

Choose a reason for hiding this comment

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

How about to use Proxy pattern here? Instead of introducing new connection type you can introduce a proxy object that decides whether you need to read against cache or delegate it to another "real" connection against Redis.

Aggregation is always preferable against inheritance :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've found it easier to start.

@codecov-commenter
Copy link

Codecov Report

Attention: 19 lines in your changes are missing coverage. Please review.

Comparison is base (77d52ab) 75.43% compared to head (a84421b) 75.35%.

❗ Current head a84421b differs from pull request most recent head dfe8f7c. Consider uploading reports for the commit dfe8f7c to get more accurate results

Files Patch % Lines
src/main/java/redis/clients/jedis/Connection.java 54.54% 4 Missing and 1 partial ⚠️
...java/redis/clients/jedis/JedisClientSideCache.java 75.00% 2 Missing and 3 partials ⚠️
...main/java/redis/clients/jedis/ClientSideCache.java 80.00% 2 Missing and 2 partials ⚠️
...ava/redis/clients/jedis/util/RedisInputStream.java 70.00% 1 Missing and 2 partials ⚠️
src/main/java/redis/clients/jedis/Protocol.java 81.81% 0 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3658      +/-   ##
============================================
- Coverage     75.43%   75.35%   -0.09%     
- Complexity     4897     4915      +18     
============================================
  Files           297      299       +2     
  Lines         14965    15037      +72     
  Branches       1129     1137       +8     
============================================
+ Hits          11289    11331      +42     
- Misses         3182     3199      +17     
- Partials        494      507      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@chayim chayim marked this pull request as ready for review December 28, 2023 10:25
chayim
chayim previously approved these changes Dec 28, 2023
Copy link
Contributor

@chayim chayim left a comment

Choose a reason for hiding this comment

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

Alpha ready

@sazzad16 sazzad16 changed the base branch from master to 5.2.0 December 28, 2023 10:45
@sazzad16 sazzad16 dismissed chayim’s stale review December 28, 2023 10:45

The base branch was changed.

@sazzad16 sazzad16 changed the title Initial support for client side caching Initial support for client-side caching Dec 28, 2023
@sazzad16 sazzad16 merged commit da9c463 into redis:5.2.0 Dec 28, 2023
4 checks passed
@sazzad16 sazzad16 deleted the csc-sync-01 branch December 28, 2023 12:52
uglide added a commit that referenced this pull request Sep 27, 2024
* Initial support for client-side caching (#3658)

* Support for client-side caching - phase 2 (#3673)

* Code re-use?

* Stop forcing to read push notifications before checking cache and remove BCAST

* Rename variable

* Remove ensureFillSafe()

* Refactor peeking and reading push notifications

* Cleanup comments

* Fix transaction failure tests using mock (#3683)

Now we have to mock Protocol#read(RedisInputStream, ClientSideCache) instead of Protocol#read(RedisInputStream).

* Support client-side caching from UnifiedJedis (#3691)

* Support client side caching from UnifiedJedis

* Support client side caching as a separate parameter

* format imports

* Support CSC in sentinel mode

* undo change

* Client-side caching by hashing command arguments (#3700)

* Support TTL in client side caching (using Caffeine library)

* Also Guava cache

* format pom.xml

* Client-side caching by command arguments

TODO: Compute hash code.

* send keys

* todo comment for clean-up

* rename method to invalidate

* Client-side caching by hashing command arguments

* Hash command arguments for CaffeineCSC using OpenHFT hashing

* Clean-up keyHashes map

* added javadoc

* rename method

* remove lock

* descriptive name

* descriptive names and fix

* common default values in base class

* Cover Redis commands for client side caching (#3702)

* Support Client-side caching through URI/URL (#3703)

* Support Client-side caching through URI/URL

* check idx of '=' sign

* nicer exception

* edit/fix condition

* rename param

* Throw IllegalArgumentException at all such cases

* Test GuavaCSC and CaffeineCSC (#3742)

* Support white-list and black-list commands and keys (#3755)

* Create csc package

* Create csc.util package

* Create a config interface for client-side caching

* Default isCacheable

* Config to WhiteList/BlackList commands and String keys

* Create csc test package(s)

* Test white-list/black-list commands and keys

* Merge fix

* Remove csc.util package

* Fix javadoc links

* Added ClientSideCacheable interface and removed ClientSideCacheConfig interface

* Format imports

* Re-create csc.util package

* Rename to allow/deny instead of white/black

* Introduce interface(s) for hashing CommandObject (#3743)

* Client-side cache related naming changes (#3758)

Changes:
1. CommandLongHashing is renamed to CommandLongHasher.
2. Expanded the names of GuavaCSC (GuavaClientSideCache) and CaffeineCSC (CaffeineClientSideCache).

* Reformat clientSideCache variable names (#3761)

* Format tabs in pom.xml

* Use Experimental annotation

* Fix client side cache tests (#3799)

Due to redis/redis#13167 

* Fix JedisClusterClientSideCacheTest

* Fix JedisSentineledClientSideCacheTest

* Remove openhft hashing from source dependency (#3800)

* Test different functionalities of client side cache (#3828)

* Test JedisURIHelper#getClientSideCache(URI) (#3835)

* Merge fix: after introducing EndpointConfig in #3836

* Tweak maximumSize test in CaffeineClientSideCacheTest

* Little more tweak maximumSize test in CaffeineClientSideCacheTest

* Fix incompatibilities with the latest RedisStack (#3855)

* Fix tests

- Skip Graph tests
- Fix JSON RESP3 test

* JSON.GET behaves identically on RESP2 and RESP3

* Revert "Fix incompatibilities with the latest RedisStack (#3855)"

This reverts commit 6b9d338.

* [TEMPORARY] [TEST] Use redis-stack-server:7.4.0-rc1 image for testing

* Support RediSearch DIALECT 5 (#3831)

- [x] Avoid escaping at query time
- [ ] Alias for tag fields (EXACT)
- [x] Avoid repeating for numeral equality
- [x] New dialect (5)

* Support FLOAT16 and BFLOAT16 VecSim storage types (#3849)

* Test: INTERSECTS and DIJOINT conditions support in GeoSearch (#3862)

* Support IGNORE and other optional arguments for timeseries commands (#3860)

* Re-implement TS.ADD command with optional arguments
* Implement TS.INCRBY and TS.DECRBY commands with optional arguments
* Support IGNORE argument for TS.[ CREATE | ALTER | ADD | INCRBY | DECRBY] commands

---

* Cover optional arguments for timeseries commands
   - Re-implement TS.ADD command with optional arguments
   - Implement TS.INCRBY and TS.DECRBY commands with optional arguments

* Introduce EncodingFormat enum for <COMPRESSED|UNCOMPRESSED>

* Support IGNORE option
   and rename to TSIncrOrDecrByParams

* Polish #3860: Separate params for TS.INCRBY and TS.DECRBY (#3863)

* Support indexing of MISSING and EMPTY values (#3866)

* Little tweak maximumSize test in CaffeineClientSideCacheTest

* Inject ClientSideCacheable via set method (#3882)

* Use CommandObject(s) as cache-key (#3875)

and remove hashing of CommandObject(s).

* #3886 merge fix

* Revert "[TEMPORARY] [TEST] Use redis-stack-server:7.4.0-rc1 image for testing"

This reverts commit 92c09f3.

* More tweak maximumSize test in CaffeineClientSideCacheTest

This reverts and modifies commit 3534996.

* Remove client side cache support through uri/url (#3892)

This partially reverts #3703 and #3835

* Bump com.google.guava:guava from 33.0.0-jre to 33.2.1-jre (#3893)

* Prepare client side caching - design 2 (#3889)

* Separate CacheConnection

* Introduce CacheKey and CacheEntry

* Little tweak maximumSize test in CaffeineClientSideCacheTest

* Remove resetting timeout; we'll PING instead

* Refactor Client-Side Caching implementation (#3900)

* adding a DataProvider to access connection from cache

* resolve keys from commandarguments

* clean up in unifiiedjedis and add csc test with ssl

* - fix readtimeout exception with sockets for consuming invalidations pending in buffer
- apply a default list of cacheable commands to DefaultClientSideCacheable
- fix failing unit tests with cacheable / non-cacheable keys
- remove formatting changes

* - add serialization for cache instances
- add unit test with UnifiedJedis
- add benchmark for CSC execution
- clean unused imports

* - added 'Cache' interface and 'DefaultCache' implementation in regard to design doc
- added 'EvictionPolicy' interface and LRU implementation
- move cache object validation and cache control stuf from 'ClientSideCache' into 'CacheConnection'
- make guava and caffeine caches experimental

* - added SSLSocketWrapper and plug it to use 'available'
- handle exceptions properly
- fix some issues with unit tests

* implementing thread safety

* - fix eviction issue and add related test
- fix consuming invalidation messages on a response read
- introduce cachestats
- fix potential issue with cacheKeysRelatedtoRedisKey cleanup
- tests for sequential access, concurrent acces and maxsize

* - renmae abstract cache class
- add test case for returning new instance of cache object

* - change order of execution in sequential acces test

* -  flush the cache on any disconnect
- replace LRU policy references with EvictionPolicy interface
-  add some constructor overloads to enable custom eviction policies on cache

* fix testcache

* fix javadoc issue

* - fix multithreaded eviction policy issue
- update guava and caffeine implementations according to abstract cache

* Jedis test plan coverage for CSC (#3918)

* initial changes

* cover tests for JedisPooled and functionality

* fix javadoc

* cover new tests for JedisCluster and JedisSentineled

* Fix CSC allow-and-deny-list and rename Cacheable interface

* Tag CommandArguments#getKeys() as Internal

* cover lruEvictionTest

* Address code reviews and more updates

* fix format and more minor changes

* format Connection

* modify WeakReference usage

* Use ExecutorService.shutdownNow() in tests (#3922)

* Use ExecutorService.shutdownNow()

* More ExecutorService.shutdownNow() and other changes

* [minor change] Avoid creating same CacheKey twice

* Support caching null values (#3939)

* caching null results

* add more assertion

* Adding CacheConfig (#3919)

* add cacheconfig

* remove empty file

* -modify constructors with cache as public
- trim guava caffeine

* remove cachetype

* - add getCache to UnifiedJedis
- add builder method to CacheConfig

* add evictionpolicy to cacheconfig

* - unifiedjedis constructor with cacheconfig
-  wrap IOException on protocol read error

* fix merge issue

---------

Co-authored-by: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>

* Polish "Adding CacheConfig"

Polish #3919 - address some pending change requests

- Swap contructor placements
- Fix grammar in exception message

* Adding Cache class to CacheConfig (#3942)

* adding cacheclass to cacheconfig

* - add cachefactory test

* - revert connection ctors to public
- udpate some tests with UnifiedJedis.getCache
- add ping to flaky tests

* remove unnecessary anonymous types

* change ctor access modifiers

* fix test name

* make cachefactory methods static

* removing pings  due to still flaky with inv messages

* - drop CustomCache in tests and use TestCache
- check null cacheable issue with defaultcache
- support both ctors in custom cache classes regarding to value of cacheconfig.cacheable

* remove unncessary maxsize

* - remove inline anonymious

* Server version check for CSC activation  (#3954)

* checking server version for CSC

* fix format change

* fix noauth hello exception in integration tests

* fix version check

* remove redundant check

* remove unused imports

* 'toString' for Version

* rename to RedisVersion

* moving RedisVersion package

---------

Co-authored-by: Igor Malinovskiy <u.glide@gmail.com>
Co-authored-by: atakavci <58048133+atakavci@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants