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

Xinfo command #2069

Merged
merged 16 commits into from Feb 12, 2020
Merged

Xinfo command #2069

merged 16 commits into from Feb 12, 2020

Conversation

MichalCho
Copy link
Contributor

Hi,

I've noticed that xinfo command is missing so I've started to implement it.
This is just to start a discussion about how this should be fixed.
Currently I've implemented a single xinfo command that returns a map of <String,Objects>.
This would work for any variant of xinfo (stream,groups, consumers) but it's a bit generic.

The other idea would be to create 3 xinfo commands, returning something like XInfoStream, XInfoConsumers etc. objects. That would give a better visibility of returned values.
I will be happy to work on it.

@MichalCho
Copy link
Contributor Author

MichalCho commented Oct 1, 2019

@sazzad16 @marcosnils @gkorland @xetorthio could you give me some input?
Thanks

@sazzad16
Copy link
Collaborator

sazzad16 commented Oct 1, 2019

My initial thought is that Params is a better fit for this.

@MichalCho
Copy link
Contributor Author

My initial thought is that Params is a better fit for this.

But this will not solve the problem of returning very generic object.
jedis.xinfo(myKey, STREAM) and jedis.xinfo(myKey, CONSUMERS, myGroup) would return the same type - just a map. Then user would to need to know the implementation to actually check the returned values example:

jedis.xinfo(myKey, STREAM).get("first-entry); - they would need to know that this is a StreamElement and do casting in their code.

What I had in mind is to have 3 functions returning different types which expose more clear getters

XStreamInfo streamInfo = jedis.xinfo(myKey, STREAM); //Type specific to information about a stream
streamInfo.getFirstEntry(); //returned type is StreamElement, no need for casting;

Other option would be to return something like XInfoElement that contains 3 fields:

XInfoStream
XinfoGroups
XInfoConsumers

exposing getXInfoStream, getXInfoGroups and XInfoConsumers.
They would be set when a specific info is requested. That would require if-not-null check.

@gkorland gkorland marked this pull request as ready for review October 15, 2019 21:36
@gkorland
Copy link
Contributor

Sorry my mistake... should be draft

@MichalCho
Copy link
Contributor Author

Sorry my mistake... should be draft

No problem, I'm working on the new implementation, I hope I can have some commits ready soon

@gkorland
Copy link
Contributor

@MichalCho sorry to nudge, but are still working on this PR?

@MichalCho
Copy link
Contributor Author

@gkorland no problem. Sorry I got quite busy at the office. I will try to use holiday break now to craft some code. Will you be willing to do some code review?

@gkorland
Copy link
Contributor

gkorland commented Dec 19, 2019

@MichalCho no pressure, I just wanted to check if it's still relevant for you.
Sure, once you finalize the PR I'll find some time to review it

@startjava
Copy link

@MichalCho sorry,,,i want know xinfo() method Progress?? :) :) :D

@MichalCho
Copy link
Contributor Author

@MichalCho sorry,,,i want know xinfo() method Progress?? :) :) :D

Hi @startjava
I will try to have some commits ready for review after New Year's Eve.
Are you waiting for this feature? :)

@startjava
Copy link

@MichalCho
not waiting this feature,But Always waiting this feature :):) :D

A lot of refactoring to be done. This commit is just for showing
the idea
@MichalCho
Copy link
Contributor Author

MichalCho commented Dec 29, 2019

Hi,
@gkorland I've just pushed my initial idea how to implement this. It require a lot more testing (I've just created one happy case test case). Do you think you could have a look just at it to give me feedback what do you think about usability? Maybe my approach is not the best, I will be happy to discuss it. Then I can continue to work on it after New Year and finalize PR according to your feedback
Thanks

src/main/java/redis/clients/jedis/BuilderFactory.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BuilderFactory.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BuilderFactory.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BuilderFactory.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BuilderFactory.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/Jedis.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/Jedis.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BinaryClient.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BinaryClient.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/BinaryClient.java Outdated Show resolved Hide resolved
@sazzad16
Copy link
Collaborator

sazzad16 commented Dec 30, 2019

@MichalCho Even though I've put some review comments, I'm liking* I'm not entirely liking the idea. Are we complicating it too much? As the Redis documentation for XINFO says

a well behaving client should fetch the whole list, and report it to the user, for example, as a dictionary data structure

Perhaps one method (per class) returning a Map is enough? @gkorland WDYT?

  • BTW, sorry about typo which reversed what I tried to mean.

@MichalCho
Copy link
Contributor Author

@sazzad16 thanks a lot for the review. Regarding formatting comments - I was aware of most of them, I just wanted to show something before leaving for holidays :)

@MichalCho Even though I've put some review comments, I'm liking the idea. Are we complicating it too much? As the Redis documentation for XINFO says

a well behaving client should fetch the whole list, and report it to the user, for example, as a dictionary data structure

Perhaps one method (per class) returning a Map is enough? @gkorland WDYT?

I was thinking about it. Then I had an idea to provide getters for all map members. Otherwise user will have to try to get on Object from the map and he will have to know what kind of object it is.

something like (if xinfo returns just a map):
Long streamLenght info = (Long)jedis.xinfo("xadd-stream1", StreamInfo.StreamInfoType.getStreamInfoType()).get("lenght");

Another issue that I had in mind is - when we decode the answer we can not be fully flexible as we need to know how to decode the answer (to know that "length" is a integer, "last-generated-id" is a string and "first-entry" is a stream entry object).
One solution would be to try to decode first element that is a name of a field (let's say "length") and then try to decode next element using known type (let's say first try to decode as a string, if we fail, then try to decode as stream object, then as a integer etc) until we succeed. If we fail we throw some kind of decoding exception.
I think that would be the only way to be fully transparent and able to react on changes done on redis side.
I will try to fix the stuff you have commented on and also craft my new approach for decoding an answer from redis. in few days. In the meantime if you have some thought about it, please let me know.

@startjava
Copy link

@MichalCho hi~ what time xinfo method final over? thank you job !

xinfo commands return now just a Map<String,Object>
In case when (in the future) redis includes additional fields in a reply
we will try to decode them using known decoders
@MichalCho
Copy link
Contributor Author

hi @sazzad16!

I've implemented your comments and changed the returned type just to be Map<String,Object>

During the weekend I will try to add more test cases and maybe make the code prettier.
Looking forward to any comments from you and @gkorland
Thanks

PS @startjava it's ongoing, you can check the latest commit

Some code clean up done together with more tests
@MichalCho
Copy link
Contributor Author

MichalCho commented Jan 13, 2020

Hi @sazzad16 and @gkorland

I've added more tests and worked on the review comments. I think that the PR is ready for a final review from you guys.

I have one thing in mind - how should we act if redis decide to add more fields in a reply and we are not able to decode them? Ignore them and decode what we can? Throw an exception? Log something?
So far I just silently ignore elements that are not possible to decode by any of known Builder used for that type of info (stream,group, consumer).

Please share your thoughts.
Thanks!

Some minor refactoring done together with restoring getters
for elements known to be returned by redis now.
In case new elements are added in the future the map can be used
to obtain them.
@MichalCho
Copy link
Contributor Author

MichalCho commented Jan 14, 2020

I've just committed changes according to review comments.

I've restored getters for map elements, that are known to be present in a reply as per today.
In case some more elements are added in the future they can still be accessed through the map.

But please check my reply to your comment - should we have those getters or just keep constants (not variables) that can make getting objects from the map easier.

Still the question is - how shall we try to decode those unknown elements. As for now we try use a set of decoding functions that we know are needed to decode specific type of answer. Should all decoding function should be added?
Anyway I feel we are getting closer to finalize that PR

Refactor xinfo to simplify usage.
Javadoc will be added in another commit
@MichalCho
Copy link
Contributor Author

hi @gkorland,

I've implemented your suggestion - splitting xinfo into 3 different methods. I've also added some simple javadocs to make it easier for a user to understand how to use xinfo with jedis.

I had some doubts regarding how I create info objects, for example:

if (map!= null) {
     streamInfo = map;
     length = (Long) map.get(LENGTH);  

This can fail if length is not present in the map( for example future version of redis will remove this filed). But according to redis documentation:

Note that you should not rely on the fields exact position, nor on the number of fields, new fields may be added in the future.

so I guess it's save to assume that fields that exist now will always be there. Nothing is mentioned about removing fields.

Comment on lines 360 to 364
byte[] xinfoStream (byte[] key);

List<byte[]> xinfoGroup (byte[] key);

List<byte[]> xinfoConsumers (byte[] key, byte[] group);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why byte[]? What about the Objects?

Copy link
Contributor Author

@MichalCho MichalCho Feb 4, 2020

Choose a reason for hiding this comment

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

I think we need some aligment here. I can see that some BinaryJedisCommands returns Objects (or Collections of Objects) - in some cases using builder factory directly like

  public List<GeoRadiusResponse> georadiusByMember(final byte[] key, final byte[] member, final double radius,
      final GeoUnit unit, final GeoRadiusParam param) {
    checkIsInMultiOrPipeline();
    client.georadiusByMember(key, member, radius, unit, param);
    return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
  }

some other returns byte arrays or collection of byte arrays etc.
Maybe I missunderstood the purpose of binary client - I thought it was supposed to use just binary data, without trying to decode them. Could you please help me to understand that?
I have seen in #2084 that you mention that returning List<byte[]> instead of List is a bug

If we should return objects then I would propose following:

  Object xinfoStream (byte[] key);

  List<Object> xinfoGroup (byte[] key);

  List<Object> xinfoConsumers (byte[] key, byte[] group);

Copy link
Collaborator

Choose a reason for hiding this comment

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

Following would be good enough:

StreamInfo xinfoStream (byte[] key);
List<StreamGroupInfo> xinfoGroup (byte[] key);
List<StreamConsumersInfo> xinfoConsumers (byte[] key, byte[] group);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored it that way and added a test for binary usage.

Comment on lines 83 to 85
public static final String STREAM = "STREAM";
public static final String GROUPS = "GROUPS";
public static final String CONSUMERS = "CONSUMERS";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Prefix these, perhaps with XINFO_. Also put these along with SENTINEL_..., CLUSTER_..., PUBSUB_... variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Also a test for binary xinfo commands has been added
@MichalCho
Copy link
Contributor Author

Pinging @sazzad16 @gkorland :)

List<StreamConsumersInfo> consumersInfo = jedis.xinfoConsumers(STREAM_NAME, G1);

//Stream info test
assertEquals(2L,streamInfo.getStreamInfo().get(LENGTH));
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing LAST_GENERATED_ID

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a way that we check if it's not null - it is difficult to know what last-generated should be

Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't it be id2?

Copy link
Contributor Author

@MichalCho MichalCho Feb 11, 2020

Choose a reason for hiding this comment

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

you are right. I've missed that we have that info already.

I have also changed returned object to be StreamEntryID

src/main/java/redis/clients/jedis/Protocol.java Outdated Show resolved Hide resolved
src/main/java/redis/clients/jedis/StreamInfo.java Outdated Show resolved Hide resolved
firstEntry = (StreamEntry) map.get(FIRST_ENTRY);
lastEntry = (StreamEntry) map.get(LAST_ENTRY);

} else throw new IllegalArgumentException("InfoMap can not be null");
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are throwing runtime exception anyway when map is null isn't it better to just let it throw NullPointerException?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. My initial thought was that it is not nice to see NullPointers

pending = (long) map.get(PENDING);
lastDeliveredId = (String) map.get(LAST_DELIVERED);

} else throw new IllegalArgumentException();
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are throwing runtime exception anyway when map is null isn't it better to just let it throw NullPointerException?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. My initial thought was that it is not nice to see NullPointers

pending = (long) map.get(PENDING);


} else throw new IllegalArgumentException();
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are throwing runtime exception anyway when map is null isn't it better to just let it throw NullPointerException?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. My initial thought was that it is not nice to see NullPointers

Added test for last-generated-id
Moved xinfo keywords to enum
Removed throwing runtime exception from xinfo classes
@gkorland
Copy link
Contributor

@MichalCho
Copy link
Contributor Author

MichalCho commented Feb 11, 2020

Tests are failing https://travis-ci.org/xetorthio/jedis/jobs/648852944

I'm on it

Update:
in test environment, if everything is executed fast consumersInfo.get(0).getIdle() returns sometimes 0 - as this is info returned by server.
In the test we create a consumer and then check for its details.
Easy fix would be just to introduce some delay between creating a consumer and reading it's state - like with simple Thread.sleep() - what do you think @gkorland?

@gkorland
Copy link
Contributor

@MichalCho sounds OK just sleep for 1ms

LastDeliveredId type has been changed to be StreamEntryId instead
of simple String.

Also unstable test for Idle Consumer time has been fixed.
@MichalCho
Copy link
Contributor Author

MichalCho commented Feb 12, 2020

I think we are done here. @gkorland is there a list of features/ issues to be implemented/fixed? I would like to contribute more :)

@sazzad16 sazzad16 merged commit 528debd into redis:master Feb 12, 2020
@sazzad16
Copy link
Collaborator

Merged into master. Excellent job @MichalCho

@MichalCho
Copy link
Contributor Author

Thanks,
@gkorland @sazzad16 do we have a list of features/ issues to be implemented/fixed? I would like to contribute more?

@gkorland
Copy link
Contributor

gkorland commented Feb 13, 2020

@MichalCho thanks for the great help!
You can see here the tasks marked as features https://github.com/xetorthio/jedis/labels/FEATURE

@MichalCho MichalCho deleted the xinfo_command branch February 13, 2020 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants