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

JNI Reader for Table Iterator #12511

Open
wants to merge 37 commits into
base: main
Choose a base branch
from

Conversation

swamirishi
Copy link

Adding a Jni layer for table Iterator. Also changing the table iterator's interface preparse internal key so that SSTFileReaderIterator can be reused.

@swamirishi
Copy link
Author

swamirishi commented Apr 5, 2024

@cbi42 @jowlyzhang Can you please review this? In ozone we depend on jni for rocksdb apis. I have just added JNI layer for the table_iterator written in your PR.

@swamirishi swamirishi force-pushed the JniReaderForTableIterator branch from 52a2255 to 6b83480 Compare April 5, 2024 01:03
@swamirishi swamirishi force-pushed the JniReaderForTableIterator branch from 6b83480 to dcd5135 Compare April 5, 2024 01:04
@adamretter adamretter self-requested a review April 5, 2024 07:03
Copy link
Collaborator

@adamretter adamretter left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I have left some feedback on the Java changes which need some amendment - also a Java test needs to be added please for this new API. I will leave @ajkr @pdillinger and the RocksDB team to comment on the internal C++ changes to RocksDB table... as I cannot myself say if they are desirable or not for the RocksDB team.

*/
class RawSstFileReaderIterator extends SstFileReaderIterator {

protected RawSstFileReaderIterator(SstFileReader reader, long nativeHandle) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please set all arguments and variables to final where possible.

Copy link
Author

Choose a reason for hiding this comment

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

done

*/
class RawSstFileReaderIterator extends SstFileReaderIterator {

protected RawSstFileReaderIterator(SstFileReader reader, long nativeHandle) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This class needs to implement some distinct form of disposeInternal to avoid leaking memory when it is finished with.

Copy link
Author

Choose a reason for hiding this comment

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

It extends SstFileReaderIterator which clears off the required handle.

@@ -113,7 +113,7 @@ std::unique_ptr<Iterator> SstFileReader::NewTableIterator() {
// InternalIterator.
return nullptr;
}
return std::make_unique<TableIterator>(internal_iter);
return std::make_unique<TableIterator>(internal_iter, &rep_->options);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ajkr @pdillinger Can you comment on this change in the internals for RocksDB please?

@@ -24,7 +25,7 @@ class TableIterator : public Iterator {
}

public:
explicit TableIterator(InternalIterator* iter) : iter_(iter) {}
explicit TableIterator(InternalIterator* iter, Options* options) : iter_(iter), options_(options) {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ajkr @pdillinger Can you comment on this change in the internals for RocksDB please?

@@ -48,22 +49,65 @@ class TableIterator : public Iterator {
bool Valid() const override { return iter_->Valid(); }
void SeekToFirst() override { return iter_->SeekToFirst(); }
void SeekToLast() override { return iter_->SeekToLast(); }
void Seek(const Slice& target) override { return iter_->Seek(target); }
void Seek(const Slice& target) override {
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ajkr @pdillinger Can you comment on this change in the internals for RocksDB please?

Copy link
Contributor

Choose a reason for hiding this comment

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

I will review this. Thanks!

void SeekForPrev(const Slice& target) override {
return iter_->SeekForPrev(target);
std::string seek_key_buf;
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ajkr @pdillinger Can you comment on this change in the internals for RocksDB please?

@@ -48,22 +49,65 @@ class TableIterator : public Iterator {
bool Valid() const override { return iter_->Valid(); }
void SeekToFirst() override { return iter_->SeekToFirst(); }
void SeekToLast() override { return iter_->SeekToLast(); }
void Seek(const Slice& target) override { return iter_->Seek(target); }
void Seek(const Slice& target) override {
Copy link
Contributor

Choose a reason for hiding this comment

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

I will review this. Thanks!

@@ -48,22 +49,65 @@ class TableIterator : public Iterator {
bool Valid() const override { return iter_->Valid(); }
void SeekToFirst() override { return iter_->SeekToFirst(); }
void SeekToLast() override { return iter_->SeekToLast(); }
void Seek(const Slice& target) override { return iter_->Seek(target); }
void Seek(const Slice& target) override {
Copy link
Contributor

Choose a reason for hiding this comment

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

The type of key taken as input for Seek and SeekForPrev should be consistent with the type of key produced by Iterator::key. Can you add jni APIs for the util methods GetInternalKeyForSeek etc instead?

Copy link
Author

Choose a reason for hiding this comment

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

Can't we change the definition of the TableIterator to seek and get only on the user_key instead? Isn't this a better way to define this class?

Copy link
Contributor

Choose a reason for hiding this comment

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

The name TableIterator indicates it's iterating the raw table, and that makes internal key a better format of key for these APIs.

Status GetProperty(std::string /*prop_name*/,
std::string* /*prop*/) override {
assert(false);
return Status::NotSupported("TableIterator does not support GetProperty.");
}

uint64_t SequenceNumber() {
Copy link
Contributor

Choose a reason for hiding this comment

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

The public API is Iterator in rocksdb/includes/iterator.h, the TableIterator is an internal class that is not supposed to be publicly accessible. These SequenceNumber, and type APIs are not defined for Iterator.

We have added ParseEntry util methods to parse sequence number and type out of an internal key. You can have you own wrapper iterator class wrapping a RocksDB Iterator, and add the parsing logic in to the wrapper iterator.

Copy link
Author

Choose a reason for hiding this comment

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

We need all of these infos separately and storing the parsed entry would be much more optimal. Otherwise for each and every call we would have to parse the key over and over again. All I am doing here is changing the definition of the table iter which would seek over the same user key and return user key when getKey() is called. I have added 2 more functions on top of iterator, I believe that should be fine, since JNI would be called only when TableIterator is initialized.

Copy link
Author

Choose a reason for hiding this comment

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

@jowlyzhang TableIterator itself is a wrapper for InternalIterator why have 2 wrappers? Should we consider making this public?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's worth having 2 wrappers because it will be your own wrapper iterator that you don't need to check into RocksDB codebase, you own those logic and you can modify it however you want. Parsing an entry every time over and over again isn't computationally heavy or performance wise concerning, you can just wrap it in your own wrapper iterator. It's beset for the RocksDB TableIterator to just do the most vanilla things of iterating raw entries. Others wishing to add their own iterating constraints can just add on top of it.

Copy link
Author

Choose a reason for hiding this comment

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

@jowlyzhang I have updated the code to add another jni functionality to convert internal Key to user key and vice versa

@swamirishi
Copy link
Author

@jowlyzhang Can you check this PR?

@jowlyzhang
Copy link
Contributor

@jowlyzhang Can you check this PR?

Thanks for making the changes. Since this PR now mostly have java changes, I'll leave it to @adamretter to make the call here.

Copy link
Collaborator

@adamretter adamretter left a comment

Choose a reason for hiding this comment

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

The issues I think I have identified repeat throughout and should be addressed please

kEntryWideColumnEntity((byte)0x7),
kEntryOther((byte)0x8);
private final byte value;
private static Map<Byte, EntryType> reverseLookupMap = new HashMap<>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need a map here? The collection is small enough to iterate I would have thought?

Copy link
Author

Choose a reason for hiding this comment

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

removed the map and falling back to iterating through the values.

private final byte value;
private static Map<Byte, EntryType> reverseLookupMap = new HashMap<>();

EntryType(byte value) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be final param.

Copy link
Author

Choose a reason for hiding this comment

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

done

jint len) {
const std::unique_ptr<char[]> target(new char[len]);
if (target == nullptr) {
jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this has been tested as the class name is incorrect. Also there are util methods in portal.h for throwing errors that could be used.

Copy link
Author

Choose a reason for hiding this comment

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

Added util method to also do operations on indirect byte array buffers

"Memory allocation failed in RocksDB JNI function");
return;
}
env->GetByteArrayRegion(jtarget, off, len,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking

Copy link
Author

Choose a reason for hiding this comment

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

done

auto slice_size = key_slice.size();
jsize copy_size = std::min(static_cast<uint32_t>(slice_size),
static_cast<uint32_t>(jlen));
env->SetByteArrayRegion(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking

Copy link
Author

Choose a reason for hiding this comment

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

done

auto slice_size = key_slice.size();
jsize copy_size = std::min(static_cast<uint32_t>(slice_size),
static_cast<uint32_t>(int_key_len));
env->SetByteArrayRegion(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking.

Copy link
Author

Choose a reason for hiding this comment

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

done

reinterpret_cast<const ROCKSDB_NAMESPACE::Options *>(options_handle);
const std::unique_ptr<char[]> target(new char[user_key_len]);
if (target == nullptr) {
jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Class name and testing?

Copy link
Author

Choose a reason for hiding this comment

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

done

"Memory allocation failed in RocksDB JNI function");
return static_cast<jsize>(0);
}
env->GetByteArrayRegion(user_key, user_key_off, user_key_len,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking.

Copy link
Author

Choose a reason for hiding this comment

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

done

auto slice_size = key_slice.size();
jsize copy_size = std::min(static_cast<uint32_t>(slice_size),
static_cast<uint32_t>(int_key_len));
env->SetByteArrayRegion(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking.

Copy link
Author

Choose a reason for hiding this comment

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

done

// exception thrown: OutOfMemoryError
return nullptr;
}
env->SetByteArrayRegion(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs error checking.

Copy link
Author

Choose a reason for hiding this comment

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

done

@swamirishi
Copy link
Author

@adamretter Apologies for the late response and not following through addressing review comments on time. I have addressed all the review comments. Can you please take a look at it? I have added a Java test for the same as well and also added required util functions as well in portal.h. FYI these util functions can be used in other JNI classes as well.

@swamirishi swamirishi requested a review from adamretter May 28, 2024 16:34
@swamirishi swamirishi requested a review from adamretter June 12, 2024 02:09
@swamirishi
Copy link
Author

@adamretter I have addressed all the review comments. Can you please take a look at the patch again?

@swamirishi
Copy link
Author

@adamretter Can you take a look at this?

@adamretter
Copy link
Collaborator

@swamirishi Not all of my comments have been addressed.

@swamirishi swamirishi force-pushed the JniReaderForTableIterator branch 2 times, most recently from 08d06eb to 8a82a9e Compare June 22, 2024 02:44
@swamirishi
Copy link
Author

@swamirishi Not all of my comments have been addressed.

@adamretter Yeah sorry I missed one of the comments. I have changed the method name and set the parameters to final. Can you check if this is fine now?

@adamretter adamretter changed the title Jni reader for table iterator JNI Reader for Table Iterator Jul 2, 2024
@swamirishi
Copy link
Author

@adamretter can you please look into this I have addressed all the review comments here.

@swamirishi
Copy link
Author

@adamretter @jowlyzhang Can you please take a look at this patch? I have addressed all the review comments and this has been pending for a while.

@adamretter
Copy link
Collaborator

@swamirishi Apologies, I have been on holiday. I will try and review this shortly...

@swamirishi
Copy link
Author

@adamretter thanks a lot

@swamirishi
Copy link
Author

@adamretter Any updates on this?

@swamirishi
Copy link
Author

@adamretter Can you review this PR or is there anyone else who can review this PR if you are loaded with other stuff

Copy link
Collaborator

@adamretter adamretter left a comment

Choose a reason for hiding this comment

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

Still some missing error handling that needs to be implemented.

}
env->GetByteArrayRegion(jkey, jkey_off, jkey_len,
reinterpret_cast<jbyte*>(target.get()));
ROCKSDB_NAMESPACE::Slice target_slice(target.get(), jkey_len);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing error check for the preceding JNI call, e.g. if (env->ExceptionOccurred())

reinterpret_cast<const ROCKSDB_NAMESPACE::Options *>(options_handle);
auto *parsed_entry_info =
reinterpret_cast<ROCKSDB_NAMESPACE::ParsedEntryInfo *>(handle);
jbyte *target = env->GetByteArrayElements(jtarget, nullptr);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You need to consider the lifetime of target... should there be a corresponding call to ReleaseByteArrayElements?

Copy link
Author

@swamirishi swamirishi Jan 7, 2025

Choose a reason for hiding this comment

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

Yeah you are right thanks for pointing out the issue. I have added a boolean in ParsedEntryInfo struct to clear up on every parseEntry function or close

/* Helper for copying value in source into a byte array.
*/
template <class T>
static jint copyToByteArray(JNIEnv* env, T& source, jbyteArray jtarget,
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are already some copyBytes functions in portal.h that are very similar apart from they allocate the jbyteArray. Could you create an overload of copyBytes that takes the jtarget, jtarget_off, jtarget_len, and then rename this copyBytes and refactor to remove code duplication?

Copy link
Author

Choose a reason for hiding this comment

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

done, renamed the function copyBytes. I don't think we can really reuse any of the existing function to reuse. I have called the existing copyBytes to create a new byte array instead of calling the copyToByteArray function

case ROCKSDB_NAMESPACE::EntryType::kEntryOther:
return 0x8;
default:
return 0x9;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel uncomfortable with this approach that raises an exception for an invalid enum value. Is there a better approach perhaps?

};
ROCKSDB_NAMESPACE::JniUtil::k_op_indirect(getInternalKeySeek, env, user_key,
user_key_off, user_key_len);
ROCKSDB_NAMESPACE::Slice key_slice = seek_key_buf;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing an error check to see if k_op_indirect raised a Java exception.

Copy link
Author

Choose a reason for hiding this comment

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

fixed in k_op_indirect function.

};
ROCKSDB_NAMESPACE::JniUtil::k_op_indirect(getInternalKeySeek, env, user_key,
user_key_off, user_key_len);
ROCKSDB_NAMESPACE::Slice key_slice = seek_key_buf;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing an error check to see if k_op_indirect raised a Java exception.

Copy link
Author

Choose a reason for hiding this comment

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

fixed in k_op_indirect function.

}
ROCKSDB_NAMESPACE::JniUtil::copyToByteArray(
env, key_slice, jkey, 0, static_cast<jint>(key_slice.size()));
return jkey;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing an error check to see if copyToByteArray raised a Java exception.

Copy link
Author

Choose a reason for hiding this comment

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

added a check in copyBytes function

}
ROCKSDB_NAMESPACE::JniUtil::copyToByteArray(
env, key_slice, jkey, 0, static_cast<jint>(key_slice.size()));
return jkey;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing an error check to see if copyToByteArray raised a Java exception.

}
ROCKSDB_NAMESPACE::JniUtil::copyToByteArray(
env, key_slice, jkey, 0, static_cast<jint>(key_slice.size()));
return jkey;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing an error check to see if copyToByteArray raised a Java exception.

@adamretter
Copy link
Collaborator

@swamirishi Are you still working on this?

@swamirishi
Copy link
Author

@swamirishi Are you still working on this?
@adamretter Apologies for not addressing the issues. I have been busy working on other stuff. But I am working on it now and would be able to address all the review comments by EOD.

@swamirishi
Copy link
Author

@adamretter I have addressed most of the review comments to the best of my knowledge. I had a couple of doubts for few review comments and have left comments inline.

@swamirishi
Copy link
Author

@adamretter Can this be merged?

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