Skip to content

CASSANDRA-13606 Improve handling of 2i initialization failures - #570

Closed
bereng wants to merge 1 commit into
apache:trunkfrom
bereng:CASSANDRA-13606-4.0
Closed

CASSANDRA-13606 Improve handling of 2i initialization failures#570
bereng wants to merge 1 commit into
apache:trunkfrom
bereng:CASSANDRA-13606-4.0

Conversation

@bereng

@bereng bereng commented Apr 28, 2020

Copy link
Copy Markdown
Contributor

No description provided.

Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/internal/CassandraIndex.java Outdated
@bereng

bereng commented Apr 28, 2020

Copy link
Copy Markdown
Contributor Author

Comment thread src/java/org/apache/cassandra/index/internal/CassandraIndex.java Outdated
Comment thread test/unit/org/apache/cassandra/index/internal/CustomCassandraIndex.java Outdated
Comment thread src/java/org/apache/cassandra/index/Index.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
@bereng

bereng commented May 6, 2020

Copy link
Copy Markdown
Contributor Author

^are these runs good enough or do you recommend I run any other dtests? Thx in advance

Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated

@adelapena adelapena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is looking good. My major concern is that indexes seem to be missing writes done during initialization. This happens because they are not included into SIM#writableIndexes until SIM#markIndexBuilt is called. I think we should add indexes to writableIndexes as soon as they are registered, and remove them in markIndexFailed. Conversely, recovering an index would optimistically mark it as writable as soon as the task starts, setting it as not writable only if it fails, so we don't miss writes done while the recovery task is running.

That way, the meaning of "unavailable for writes" would be that we skip writes as an optimization when we know that we are going to need to do them again during recovery.

Also, the implementation of Index#supportsLoad is based on an internal state that index implementations manage with try/catch blocks in their initialization task. I think that most of that can be done in the SIM, replacing Index#supportsLoad(LoadType) by a simpler Index#supportedLoadWhenFailed getter, returning the LoadType that is supported when the index is failed. That way, SIM#markIndexBuilt would take care of re-adding the index to writable/queryableIndexes in recoveries, and SIM#markIndexFailed would be the only caller of Index#supportedLoadWhenFailed. If we wanted more flexibility in what operations are supported during the initial build, we could always have an Index#supportedLoadWhenCreating.

What do you think? Does it make any sense?

Comment thread src/java/org/apache/cassandra/index/Index.java Outdated
Comment thread src/java/org/apache/cassandra/index/internal/CassandraIndex.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/index/internal/CustomCassandraIndex.java Outdated
Comment thread test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java Outdated
@bereng

bereng commented May 11, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena 2 things:

  • I modified the enum logic
  • I didn't quite get the supportedLoadWhenFailed suggestion, neither I suspect would bring much value? but I am probably missing sthg. Please feel free to push or rephrase if you feel strongly about it.

Also, reminder to myself to rebase, squash and run a full dtest suite once we're happy.

@adelapena adelapena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't quite get the supportedLoadWhenFailed suggestion, neither I suspect would bring much value? but I am probably missing sthg. Please feel free to push or rephrase if you feel strongly about it.

I'll try to explain myself better. Currently we are managing the writability/queryability status of each index in two separate places:

  • In the SIM, with the writableIndexes/queryableIndexes collections.
  • In the Index instances, that hold an instance of LoadType, manipulated in the build tasks and exposed through the Index#supportsLoad method.

I think that this duplicate status management can be simplified, so the SIM would be the only responsible for the writability/queryability status management, and the indexes would merely express their capabilities/preferences when the build has failed. To do so:

  • For Index, instead of holding a LoadType instance updated with a try-catch during its build, we would just have an Index#supportedLoadTypeWhenNotBuilt() (or similar name) getter method that would return the LoadType that the index supports in case of not being properly build. That method would always return the same value, independently of the sate of the index, because it would only describe a capability of the index implementation.
  • SecondaryIndexManager would have all the information regarding index status, so it wouldn't need to query the indexes by their internal status. Instead, it would just ask the indexes: "what kind of loads do you support in case you are not completely built?". This way, the SIM status management would more or less be:
    • On starting index initial build or recovery, always add the index to writableIndexes and perhaps remove it from queryableIndexes if the index LoadType-when-unbuilt policy says so.
    • On markIndexBuilt, always add the index to both writableIndexes and queryableIndexes.
    • On markIndexFailed, remove the index from writableIndexes if its LoadType-when-unbuilt policy says so.

The main benefit of this would be simplifying writability/queryability status management, while minimizing the index implementation responsibilities.

We of course would lose some flexibility in terms of implementations that decide to be always read-only or write-only, or that can accept different loads depending on the type of build failure, etc. But I don't think we currently have a use case for them, and I'd prefer to focus in what we strictly need. In any case, I think it wouldn't be hard for those hypothetical implementations to do all these things without specific SIM support.

What do you think?

Comment thread src/java/org/apache/cassandra/index/Index.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
@bereng

bereng commented May 12, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena thanks for taking the time to rephrase that bit about the indexes and the load type.

You're mainly replacing the supported load type variable in Index with a method call and removing that state. Notice CassandraIndex defaults to ALL for the state and the Index#supportsLoad() method defaults to always true except for NONE. I would argue that makes any new index implementation concerns for that a zero overload. Like you can ignore it as defaults are already reflecting the std behavior.

So given the above it boils down to me seeing this as a feature and you probably as an unnecessary optimization. I'd obviously like to leave it as it is (but I am the father of the baby lol) but you know the codebase an it's uses better. So let me know if you'd like me to change it please?

@adelapena adelapena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're mainly replacing the supported load type variable in Index with a method call and removing that state. Notice CassandraIndex defaults to ALL for the state and the Index#supportsLoad() method defaults to always true except for NONE. I would argue that makes any new index implementation concerns for that a zero overload. Like you can ignore it as defaults are already reflecting the std behavior.

Indeed new index implementations don't need to do anything if the default behaviour is always accepting both reads and writes, independently of whether the index is properly built or not. I'm not sure that's the ideal default behaviour, especially when we are shipping a different behaviour for the standard indexes. Now, if we wanted to change the default behaviour, we would need to manage the their internal state in the index implementations, as we do in CassandraIndex.

In contrast, using a simple getter to retrieve the LoadType would make it easy for implementations to define their behaviour, independently of which default behaviour we have chosen. It will of course come at the cost of losing the feature of allowing indexes to be completely read-only of write-only. As I said, I'm not sure how useful is that feature, and it seems to me that nevertheless it would quite trivial for indexes to implement such behaviour without any specific API support.

I have put here a draft of what the alternative API would look like, in case you want to take a look.

Said that, and although it would not appear to be the case, I'm not super strong on this, and I'll be willing to keep the current method I you think that there might be cases where it can be useful.

I'm running dtests here. Particularly, secondary_indexes_test contains tests about building status and queryability after build failure, node restart, etc. We should check that those still work, and see if we want to extend them in some way.

Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
Comment thread src/java/org/apache/cassandra/index/SecondaryIndexManager.java Outdated
@bereng

bereng commented May 13, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena again, thanks a lot for looking into this. Given you've put the time into implementing your approach I have patched and adopted it. At the end of the day you're more familiar with the code and it's uses than me :-)

The dtests thing... mmmm we should try to have as much utest and as little dtest as possible imo. Let me go through them, as they're new to me, and get back to you.

Btw your dtest run looks good. I guess we didn't break much stuff :-)

Edit: On the recent 4.0 status email do you have any strong opinions on this ticket? I don't expect dtests will need much and we're close to merging it seems. I would vote to leave this in 4.0 being so close. My 2cts.

@adelapena

Copy link
Copy Markdown
Contributor

@adelapena again, thanks a lot for looking into this. Given you've put the time into implementing your approach I have patched and adopted it. At the end of the day you're more familiar with the code and it's uses than me :-)

Great, have you pushed the merge?

The dtests thing... mmmm we should try to have as much utest and as little dtest as possible imo. Let me go through them, as they're new to me, and get back to you

Right, I was thinking of testing the log messages informing about when an index becomes queryable/writable. The dtest test_failing_manual_rebuild_index looks ideal for this purpose, we could easily add node.grep_log checks after each index (re)build to check that it behaves at is supposed. I don't know if we have tools to verify logs and simulate node restarts in utest.

Btw your dtest run looks good. I guess we didn't break much stuff :-)

This is good news, it seems that at least we didn't make it worse than it was 😃

Edit: On the recent 4.0 status email do you have any strong opinions on this ticket? I don't expect dtests will need much and we're close to merging it seems. I would vote to leave this in 4.0 being so close. My 2cts.

I think we are close to merge. Extending dtests should be a quite small task, probably just the few log checks mentioned above. It's true that the ticket doesn't solve any serious problem that we have with current implementations, but it's not very invasive and it's almost done, so I would also be in favour of keeping it in 4.0

@bereng bereng May 14, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@adelapena please notice I changed sthg from your patch here. Specifically the queryable removal upon failure as it would fail this which looks legit.

@adelapena adelapena May 15, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps the problem was assigning LoadType.NOOP as the default behaviour. Indeed, removing that line leaves LoadType#supportsReads() without usages. I think that, to preserve the behaviour of the default indexes, and to have more flexibility, we should keep that line and make a distinction between failures in the initial build task and failures in rebuilds. As I mention on the dtest review, the classic behaviour would be:

/**
 * Returns the type of operations supported by the index in case its building has failed and it's needing recovery.
 * 
 * @param isInitialBuild {@code true} if the failure is for the initial build task on index creation, {@code false}
 * if the failure is for a rebuild or recovery.
 */
default LoadType getSupportedLoadTypeOnFailure(boolean isInitialBuild)
{
    return isInitialBuild ? LoadType.WRITE : LoadType.ALL;
}

I think that each behaviour has advantages and disadvantages depending on the use case and, while I'm quite happy with the changes in the index API, I'm a bit afraid of changing the behaviour of the standard index implementation. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @adelapena thanks again for looking into this. You're raising the point I wanted to discuss with you once this ticket was merged: Is it reasonable a 'not ready' index should be serving reads or writes?

I think we're finding ourselves battling that, but that's a bigger discussion. Maybe in some other ticket we could recover my approach which iirc would defer to the index implementation what to do 🤷

@adelapena adelapena May 18, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it reasonable a 'not ready' index should be serving reads or writes?

For initial build it should't serve reads, but for rebuilds it's more complicated because the index could still be in good condition for most reads. IMO if we were creating the index API from scratch or adding a new implementation probably the best option would be to set not properly (re)built indexes as unable to serve reads and writes. But, given that we have had the cf-based implementation around for a while, I think we should by now preserve their original behaviour and provide the API mechanisms to change that in new implementations, and perhaps also in the standard indexes in the future. That and the recovery task seem a pair of nice improvements to ship with this ticket.

Accepting reads and writes when the rebuild has failed makes some sense in the particular case of cf-based regular indexes because they don't have an initialization task and they are based on idempotent operations on the underlaying column family. I'd say they focus in availability over consistency because even failed rebuilds always leave the index is same or better condition than before. In contrast, it's easy to imagine other implementations where a failed rebuild leaves the index completely corrupt and unusable. I think there could easily be use cases out there relying on this behaviour, and I'm afraid of arbitrarily changing that behaviour without coming back. If we still want to move to the new approach, either in this ticket or in a dedicated one, we should probably open a discussion on the mail list to see if there are deployments relaying on the classic behaviour.

Another tempting option to ease the migration of cf-based indexes to the new consistency-over-availability approach would be making the enum returned by getSupportedLoadTypeOnFailure configurable through index options, for example:

CREATE INDEX my_idx ON t WITH OPTIONS = {'supported_load_on_failed_init': 'WRITE', 'supported_load_on_failed_rebuild': 'ALL'}

This is something that perhaps we could do in the followup ticket to change the default behaviour of regular cf-based indexes, while keeping this ticket focused on the API changes and the recovery task.

WDYT? Maybe I'm wrong and there isn't anyone out there relying on the old 2i behaviour and we should go straight to the the new failed-rebuild-means-broken behaviour.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @adelapena, regarding return isInitialBuild ? LoadType.WRITE : LoadType.ALL;

isInitialBuild is a new variable we'd have to create and track in SIM. And then Index would have to render which load is supported depending on 'when' the failure happened?. Sounds weird to me and like we are mixing concerns. It's easier for my brain to pick the idea of having the supported load state in the Index, SIM to track that instead of trying to infer it, set the defaults we'd like and be done. But that's for another discussion as you rightfully point.

I did try that change in SIM and this sends SIMTest into the most amazing failure scenarios with indexes blocking when they shouldn't, jstaks on all threads blocked and all kind of funnies. I did spend some time on it without success until I realized it should be:

return isInitialBuild ? LoadType.WRITE : LoadType.READ;

Correct?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @bereng. I understand that the original behaviour was:

return isInitialBuild ? LoadType.WRITE : LoadType.ALL;

That's because the indexes were always writable independently of any kind of build failures, there wasn't even a notion of writability. And, once they were queryable once, they were queryable forever.

Just took a quick look at the SIMTest failures, I think they are happening because TestingIndex.shouldFailCreate is wrongly missed from TestingIndex.clear(). Once it's fixed to reset it, and we change a couple of assertions about writability in initializingIndexNotQueryableButWritableAfterPartialRebuild, all the tests seem to pass, at least locally.

I'll take a closer look tomorrow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah good catch! you're right that is being missed. Amazing this didn't came up until today.

In any case I'd like your feedback on the extra flush also. Feel free to shoot a half-backed push if you fancy and when I get the rest of the feedback I'll adjusts tests, dtests and that should be it hopefully.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think that the flush should happed right after we have ensured that the indexes are marked as writable. Otherwise, we might have new untracked writes populating the new memtable while the indexes are not writable. In the same vein, the sstable selection should happen after we have marked the indexes as writable. I have almost ready a commit with this and a few minor suggestions, I'll push it tomorrow.

We're almost there!

@bereng

bereng commented May 14, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena I have pushed the latest + you can find the dtests PR on the jira ticket. The 2i dtest passes locally also. If you like what you see I'll run the full dtest suite. So far utests look good:

@adelapena

Copy link
Copy Markdown
Contributor

I have left a few suggested changes here. Changes for the dtest are here.

As mentioned in my previous comment, the sequence of operations in SIM#rebuildIndexesBlocking is modified to:

  1. Set indexes as writable to not miss incoming writes.
  2. Flush the base table if some index wasn't writable before, so the potentially unindexed memtable contents are moved to the set of sstables to be indexed.
  3. Acquire sstable refs and rebuild.

I have also done some modifications to the way we determine whether we should use Index#getBuildTaskSupport or Index#getRecoveryTaskSupport. SIM#buildIndexesBlocking is only called by either SIM#rebuildIndexesBlocking or SIM#handleNotification. I understand that if the caller is rebuildIndexesBlocking we are always doing a full rebuild, and probably the only reason to do that is that we are attempting to recover a damaged index. Conversely, if the caller is handleNotification we are doing a partial build of new data that is unable to recover previous errors. This is why I think that the isFullRebuild argument is enough to determine whether we are recovering or indexing new data. This makes full rebuild independent of the status of the index, so any call to nodetool rebuild_index will involve getRecoveryTaskSupport, which is intended to do any initialization work that might be needed prior to a full index build. Do you think this assumption is correct, or are we interested in treating separately full rebuilds without a previous failure?

Unit tests are also slightly modified to reflect the changes. SecondaryIndexManagerTest is extended with queryability/writability checks over a write-only-on-failure index.

Summarizing, I think that at this point the patch achieves the following:

  • Full index rebuilds give indexes the opportunity to run specific rebuild logic, including initialization. This logic can be different from the logic that is used when regularly indexing new sstables.
  • If any full rebuild or a partial build task fails, the index can start to reject queries and ignore writes until a full rebuild is successfully run. Whether it rejects queries or ignores writes is defined by the index implementation.
  • Rebuilt indexes don't miss data after having been ignoring writes.
  • Existing indexes preserve their original behaviour. They always accept writes and only reject reads if the initial build has failed.
  • The index API is backwards compatible.

Do you think it makes any sense?

@bereng

bereng commented May 21, 2020

Copy link
Copy Markdown
Contributor Author

Hi @adelapena. Thx for the commit. I patched it and apply it here. Feel free to push here directly next time so it's authored by you rather than me so I don't steal your thunder :-)

I think what you do here makes perfect sense from the pov you have. Basically you try to infer as much as possible and rely on reasonable assumptions. I still think, call me stubborn :-) , I'd like my approach better. But as you mention this patch brings goodness and if in the future we need to move closer to my pov it's an easy change. So I am +1 on merging this. Having catched the missing flush, the extra testing around things, etc it's all good value.

Let me run CI and I'll post results here when done.

@bereng

bereng commented May 21, 2020

Copy link
Copy Markdown
Contributor Author

CI runs:

I checked failures: SlowQueryLog is a known one, the SimpleStragey cast one is one I fixed in another ticket, the Cqlsh one on empty values passes locally, the other few look completely unrelated, etc. I think we're good to merge.

@adelapena

Copy link
Copy Markdown
Contributor

@bereng I can't see the links to CI. I'm running ci-cassandra too. Anyway, it seems that we are mostly ready to commit.

I think what you do here makes perfect sense from the pov you have. Basically you try to infer as much as possible and rely on reasonable assumptions. I still think, call me stubborn :-) , I'd like my approach better. But as you mention this patch brings goodness and if in the future we need to move closer to my pov it's an easy change. So I am +1 on merging this. Having catched the missing flush, the extra testing around things, etc it's all good value.

Now that we have the general mechanism to define queryability and writability on failure, we can open a followup ticket to more aggressively change the default behaviour of Cassandra indexes, possibly with some discussion in the mail list so we don't interfere with existing use cases. If necessary, it wouldn't be difficult to pass the LoadType to the CQL index options, so users can decide how their indexes will behave, choosing between availability and consistency. As for failing writes on badly built indexes instead of ignoring them, we should also have some discussion because it will quite aggressively affect the base table/keyspace availability, messing with repairs, compactions, etc. It will also might make sense to have some mechanism to make coordinators aware of which nodes are not available for reads and writes so they can avoid them.

@bereng

bereng commented May 25, 2020

Copy link
Copy Markdown
Contributor Author

Great, so you can't access them. I can't tell why as they seem to work for me and your ci-cassandra run failed on trying to pull my dtests branch. The joys of CI :-) What error do you get on my links or do you want to rerun yours?

@adelapena

Copy link
Copy Markdown
Contributor

I see a message telling Workflows >> null >> null >> null >>. However I can see the list of your workflows on https://circleci.com/gh/bereng/workflows/cassandra , but I can't see the detail of any of them 🤷

Currently I'm having trouble to login at ci-cassandra and re-run, I'll try later.

@adelapena

Copy link
Copy Markdown
Contributor

I have managed to see your CI run. It seems that CircleCI was punishing me for using the old UI, and at the same time it wasn't showing me the option to upgrade to the new UI 🤦

The results look good to me to. SlowQueryLog is failing because dtests include CASSANDRA-15503 but this PR doesn't. Perhaps we should squash+rebase and run a final CI round, and we'll be ready to merge, WDYT?

@bereng
bereng force-pushed the CASSANDRA-13606-4.0 branch from 56c873c to 26908a3 Compare May 27, 2020 08:16
@bereng

bereng commented May 27, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena apologies for the late reply. I was going down a rabbit hole debugging a failing test yesterday and I didn't get around to this. Rebased and squashed. We'll have to wait for the CI runs to complete now:

@bereng

bereng commented May 27, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena The CI runs are complete. The failures I see I think are just flaky tests because I ran some locally and they passed.

The scare comes from the j8/j11UnitTests that fail on SIMT (yikes). So I ran that test locally compiled in j8 under j8 and it passes. It passes also compiled on j8 and ran under j11. I checked the logs for the test and it's full of socket and 'too many open files' exceptions. I did run ant test under j11 locally and all passed.

Also I managed to rerun that j8 run here and you can see now a j8 utest failed but the j11 utests all passed 🤷 (pick your poison lol)

I think this is all CI headaches/noise. It would be great if you could run j8/j11 utests yourself on cassandra ci before merging to be on the safe side. Wdyt?

@adelapena

Copy link
Copy Markdown
Contributor

I think this is all CI headaches/noise. It would be great if you could run j8/j11 utests yourself on cassandra ci before merging to be on the safe side. Wdyt?

@bereng Most probably it's just CI noise. I'm running cassandra-ci, now that I'm able to login into it again:

utest dtest
108 126

Unfortunately I don't know if there is a way to specify the Java version in cassandra-ci.

One of the errors seems to occur in SecondaryIndexManagerTest. We might have made it flaky, so I'm running our classic private multiplexer here and here 🤞

@adelapena

Copy link
Copy Markdown
Contributor

@bereng It seems that SIMT#initializingIndexNotQueryableButMaybeNotWritableAfterPartialRebuild and indexWithFailedInitializationDoesNotChangeQueryabilityNorWritabilityAfterPartialRebuild are definitively flaky. I haven't dug into it, but it seems that somehow the SIM calls logAndMarkIndexesFailed with an unexpected null exception. The failure can be reproduced just running any of the problematic tests multiple times in IntelliJ, or you can see them in the multiplexer runs above.

@bereng
bereng force-pushed the CASSANDRA-13606-4.0 branch from 26908a3 to dd0a598 Compare May 28, 2020 10:48
@bereng

bereng commented May 28, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena ok that took a while to pin down. There were concurrency issues with the sets keeping track of the indices, which lead to wrong failedIndexes lists triggering the NPE. The fix is here. It was a legit bug :-)

I did run the test 1K times and it passed. Now waiting on CI.

Edit: Started CI again upon a GH connection error that failed them...

@adelapena

Copy link
Copy Markdown
Contributor

Nice detective work. So it seems that by introducing multiple indexes in the test we have found and fixed and unrelated bug in the SIM, that's a good thing.

If CI looks we'll finally be ready to merge :shipit:

@bereng

bereng commented May 28, 2020

Copy link
Copy Markdown
Contributor Author

@adelapena circle CI is on a partial outage and it fails on dtests env set up etc. If you fancy firing one in cassandra ci good, otherwise I'll try to get CI runs again tomorrow #justfyi

@adelapena

Copy link
Copy Markdown
Contributor

Cassandra CI re-running utests and dtests.

@bereng

bereng commented May 29, 2020

Copy link
Copy Markdown
Contributor Author

Lgtm. Wdyt?

@adelapena

Copy link
Copy Markdown
Contributor

Looks good to me too, merging.

@adelapena

adelapena commented May 29, 2020

Copy link
Copy Markdown
Contributor

Committed to master as 595dc61290a9fda15b6765711141039ec9609bb3.
We can close this PR and the one for dtest.

@bereng bereng closed this May 29, 2020
@bereng
bereng deleted the CASSANDRA-13606-4.0 branch May 29, 2020 12:55
blambov pushed a commit to blambov/cassandra that referenced this pull request Nov 24, 2022
adelapena pushed a commit to adelapena/cassandra that referenced this pull request Sep 26, 2023
…e#570)

Needed by CNDB tests.

(cherry picked from commit a3322ce)
(cherry picked from commit 60151be)
ekaterinadimitrova2 pushed a commit to ekaterinadimitrova2/cassandra that referenced this pull request Jun 3, 2024
…e#570)

Needed by CNDB tests.

(cherry picked from commit a3322ce)
(cherry picked from commit 60151be)
(cherry picked from commit badce52)
michaelsembwever pushed a commit to thelastpickle/cassandra that referenced this pull request Jan 7, 2026
…e#570)

Needed by CNDB tests.

(cherry picked from commit a3322ce)
(cherry picked from commit 60151be)
(cherry picked from commit badce52)
michaeljmarshall added a commit to michaeljmarshall/cassandra that referenced this pull request Feb 20, 2026
…pache#2042)

### What is the issue

Fixes: https://github.com/riptano/cndb/issues/15527
CNDB test PR: https://github.com/riptano/cndb/pull/16797

### What does this PR fix and why was it fixed

This PR upgrades jvector, which brings several improvements. Here are
the git commits brought in:

```
8b3e93cf (tag: 4.0.0-rc.8) chore: update changelog for 4.0.0-rc.8 (apache#627)
9d0488e5 release 4.0.0-rc.8 (apache#626)
570bd118 Refactor parallel writer (apache#608)
20c348ec Move buffer position in ByteBufferIndexWriter#writeFloats (apache#607)
d9ddce51 Ensure extractTrainingVectors return a list of at most MAX_PQ_TRAINING_SET_SIZE (apache#610)
d663b4f7 add config options for regression testing (apache#609)
7e493eee On-disk index cache for the Grid benchmark harness (apache#612)
e263cc80 Improved dataset loading; fixes, safeties, diagnostics, and better feedback (apache#613)
6b235ce7 bump to next SNAPSHOT (apache#605)
84bf5708 (tag: 4.0.0-rc.7) chore: update changelog for 4.0.0-rc.7 (apache#604)
fceeb885 release 4.0.0-rc.7 (apache#603)
51807cba add protection against bad ordinal mappings (apache#602)
6ca3b5e2 adding memory and disk usage stats to bench tests (apache#591)
a66fd914 Fix OnDiskGraphIndex#ramBytesUsed NPE (apache#588)
0ca5a392 Move float bulk-write into IndexWriter to enforce endianness (apache#577)
a6c6c09b Add diversityScoreFunctionFor to avoid creation of wrapper object (apache#592)
977c21d4 Relax the threshold of a flaky test related to an experimental feature (apache#598)
fa808d69 adding average nodes visited to benchmark tests (apache#552)
3bd15e70 Virtualize and Modularize DataSetLoader logic (apache#593)
42259e9f Speed up ivec reads by buffering (apache#584)
f967f1c9 virtualize DataSet (apache#589)
55f902f4 turn off parallel writes in grid (apache#582)
019a241d Parallelize graph writes (apache#542)
02fea879 Save allocation of a large array in PQVectors.encodeAndBuild (apache#574)
32a51821 javadoc for base [graph] (apache#548)
4eb607f8 javadoc for base [disk,exceptions] (apache#547)
30e8932c Enable the fused graph index  (apache#561)
d8848fc6 Start development on 4.0.0-rc.7-SNAPSHOT (apache#573)
c57f3a62 (tag: 4.0.0-rc.6) chore: update changelog for 4.0.0-rc.6 (apache#572)
214b7c20 release 4.0.0-rc.6 (apache#571)
e3686999 fix javadoc error (apache#570)
88669887 Ignoring testIncrementalInsertionFromOnDiskIndex_withNonIdentityOrdinalMapping and adding a TODO in buildAndMergeNewNodes (apache#569)
29a943e1 Computation of reconstruction errors for vector compressors (apache#567)
d8e9cb16 Add NVQ paper in README (apache#560)
d5cbe658 Add ImmutableGraphIndex.isHierarchical (apache#563)
b484dae2 Harden tests for heap graph reconstruction (apache#543)
9471c57d Make the thresholds in TestLowCardinalityFiltering tighter (apache#559)
21e4a226 Begin development on 4.0.0-rc.6 (apache#558)
4f661d99 Revert "Start development on 4.0.0-rc.6-SNAPSHOT"
fdee5779 Start development on 4.0.0-rc.6-SNAPSHOT
```

### SAI Version Bump

Adds a new sai on disk version: `fa`

### Fused PQ

With this version, we are adding a new, experimental feature to write PQ
vectors fused into the graph. In doing so, we are able to skip writing
the PQ vectors to the PQ file, which results in significant memory
savings since the PQ vectors in the `CassandraDiskAnn` graph searcher
consumers `O(n)` memory based on the number of vectors and their
quantized size. The fused pq vectors mostly fit within the page cache as
we read the node and its neighbors from disk, so we see minimal latency
reduction due to this change, though further testing is required to see
the real impact.

In order to enable fused pq, the runtime needs
`cassandra.sai.latest.version=fa` or greater and
`cassandra.sai.vector.enable_fused=true`. Note that because this feature
is still experimental, `cassandra.sai.vector.enable_fused` defaults to
`false`.

Another experimental feature introduced in this commit via the jvector
upgrade is parallel graph encoding and writing to disk. Writing the
fused graph requires increased CPU time to encode the graph node and we
write more bytes to disk, so this parallelism is likely necessary to
keep vector index creation/compaction times down. The key configurations
available with their associated defaults:

```java
    // When building a compaction graph, encode layer 0 nodes in parallel and subsequently use async io for writes.
    // This feature is experimental, so defaults to false.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_ENABLED("cassandra.sai.vector.encode_and_write_graph_in_parallel.enabled", "false"),
    // When parallel graph encoding is enabled, the number of threads to use for encoding. Defaults to 0, meaning
    // use all available processors as reported by the JVM.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_NUM_THREADS("cassandra.sai.vector.encode_and_write_graph_in_parallel.num_threads", "0"),
    // When parallel graph encoding is enabled, whether to use director buffers. Defaults to false, meaning heap
    // buffers are used. A buffer will be allocated per encoding thread. The size of each buffer is the size
    // of the encoded graph node at layer 0, which varies based on graph feature settings.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_USE_DIRECT_BUFFERS("cassandra.sai.vector.encode_and_write_graph_in_parallel.use_direct_buffers", "false"),
```

### OnDiskVectorValues and OnDiskVectorValuesWriter

`OnDiskVectorValues` is now in its own file and is now thread safe in
order to account for some necessary implementation details within
jvector. Added `OnDiskVectorValuesWriter` to improve test coverage and
to abstract away the flush issues associated with
`BufferedRandomAccessWriter` as described in
datastax/jvector#562.

### Verification

This PR also introduces new benchmarks as well as improved unit testing.
The new benchmarks verify the performance of the `OnDiskVectorValues`
and `OnDiskVectorValuesWriter` to confirm (at least directionally) the
time associated with read and write operations.

New tests have been added to verify that when we iterate over an
sstable's rows, we are able to assert that the sstable's vector value's
similarity to the one stored in the vector graph is ~1. This testing is
valuable in that it confirms the row id to ordinal mapping is correct at
every node. Previously, we relied on recall results to verify this for
us. This new pattern allows us to confirm _every_ node, which is more
thorough and removes most edge cases that might have led to partially
correct graphs that may have achieved acceptable recall.
lesnik2u pushed a commit to lesnik2u/cassandra that referenced this pull request May 26, 2026
…e#570)

Needed by CNDB tests.

(cherry picked from commit a3322ce)
(cherry picked from commit 60151be)
(cherry picked from commit badce52)

 (Rebase of commit a175a83)
lesnik2u pushed a commit to lesnik2u/cassandra that referenced this pull request Jul 21, 2026
…pache#2042)

Fixes: riptano/cndb#15527
CNDB test PR: riptano/cndb#16797

This PR upgrades jvector, which brings several improvements. Here are
the git commits brought in:

```
8b3e93cf (tag: 4.0.0-rc.8) chore: update changelog for 4.0.0-rc.8 (apache#627)
9d0488e5 release 4.0.0-rc.8 (apache#626)
570bd118 Refactor parallel writer (apache#608)
20c348ec Move buffer position in ByteBufferIndexWriter#writeFloats (apache#607)
d9ddce51 Ensure extractTrainingVectors return a list of at most MAX_PQ_TRAINING_SET_SIZE (apache#610)
d663b4f7 add config options for regression testing (apache#609)
7e493eee On-disk index cache for the Grid benchmark harness (apache#612)
e263cc80 Improved dataset loading; fixes, safeties, diagnostics, and better feedback (apache#613)
6b235ce7 bump to next SNAPSHOT (apache#605)
84bf5708 (tag: 4.0.0-rc.7) chore: update changelog for 4.0.0-rc.7 (apache#604)
fceeb885 release 4.0.0-rc.7 (apache#603)
51807cba add protection against bad ordinal mappings (apache#602)
6ca3b5e2 adding memory and disk usage stats to bench tests (apache#591)
a66fd914 Fix OnDiskGraphIndex#ramBytesUsed NPE (apache#588)
0ca5a392 Move float bulk-write into IndexWriter to enforce endianness (apache#577)
a6c6c09b Add diversityScoreFunctionFor to avoid creation of wrapper object (apache#592)
977c21d4 Relax the threshold of a flaky test related to an experimental feature (apache#598)
fa808d69 adding average nodes visited to benchmark tests (apache#552)
3bd15e70 Virtualize and Modularize DataSetLoader logic (apache#593)
42259e9f Speed up ivec reads by buffering (apache#584)
f967f1c9 virtualize DataSet (apache#589)
55f902f4 turn off parallel writes in grid (apache#582)
019a241d Parallelize graph writes (apache#542)
02fea879 Save allocation of a large array in PQVectors.encodeAndBuild (apache#574)
32a51821 javadoc for base [graph] (apache#548)
4eb607f8 javadoc for base [disk,exceptions] (apache#547)
30e8932c Enable the fused graph index  (apache#561)
d8848fc6 Start development on 4.0.0-rc.7-SNAPSHOT (apache#573)
c57f3a62 (tag: 4.0.0-rc.6) chore: update changelog for 4.0.0-rc.6 (apache#572)
214b7c20 release 4.0.0-rc.6 (apache#571)
e3686999 fix javadoc error (apache#570)
88669887 Ignoring testIncrementalInsertionFromOnDiskIndex_withNonIdentityOrdinalMapping and adding a TODO in buildAndMergeNewNodes (apache#569)
29a943e1 Computation of reconstruction errors for vector compressors (apache#567)
d8e9cb16 Add NVQ paper in README (apache#560)
d5cbe658 Add ImmutableGraphIndex.isHierarchical (apache#563)
b484dae2 Harden tests for heap graph reconstruction (apache#543)
9471c57d Make the thresholds in TestLowCardinalityFiltering tighter (apache#559)
21e4a226 Begin development on 4.0.0-rc.6 (apache#558)
4f661d99 Revert "Start development on 4.0.0-rc.6-SNAPSHOT"
fdee5779 Start development on 4.0.0-rc.6-SNAPSHOT
```

Adds a new sai on disk version: `fa`

With this version, we are adding a new, experimental feature to write PQ
vectors fused into the graph. In doing so, we are able to skip writing
the PQ vectors to the PQ file, which results in significant memory
savings since the PQ vectors in the `CassandraDiskAnn` graph searcher
consumers `O(n)` memory based on the number of vectors and their
quantized size. The fused pq vectors mostly fit within the page cache as
we read the node and its neighbors from disk, so we see minimal latency
reduction due to this change, though further testing is required to see
the real impact.

In order to enable fused pq, the runtime needs
`cassandra.sai.latest.version=fa` or greater and
`cassandra.sai.vector.enable_fused=true`. Note that because this feature
is still experimental, `cassandra.sai.vector.enable_fused` defaults to
`false`.

Another experimental feature introduced in this commit via the jvector
upgrade is parallel graph encoding and writing to disk. Writing the
fused graph requires increased CPU time to encode the graph node and we
write more bytes to disk, so this parallelism is likely necessary to
keep vector index creation/compaction times down. The key configurations
available with their associated defaults:

```java
    // When building a compaction graph, encode layer 0 nodes in parallel and subsequently use async io for writes.
    // This feature is experimental, so defaults to false.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_ENABLED("cassandra.sai.vector.encode_and_write_graph_in_parallel.enabled", "false"),
    // When parallel graph encoding is enabled, the number of threads to use for encoding. Defaults to 0, meaning
    // use all available processors as reported by the JVM.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_NUM_THREADS("cassandra.sai.vector.encode_and_write_graph_in_parallel.num_threads", "0"),
    // When parallel graph encoding is enabled, whether to use director buffers. Defaults to false, meaning heap
    // buffers are used. A buffer will be allocated per encoding thread. The size of each buffer is the size
    // of the encoded graph node at layer 0, which varies based on graph feature settings.
    SAI_ENCODE_AND_WRITE_VECTOR_GRAPH_IN_PARALLEL_USE_DIRECT_BUFFERS("cassandra.sai.vector.encode_and_write_graph_in_parallel.use_direct_buffers", "false"),
```

`OnDiskVectorValues` is now in its own file and is now thread safe in
order to account for some necessary implementation details within
jvector. Added `OnDiskVectorValuesWriter` to improve test coverage and
to abstract away the flush issues associated with
`BufferedRandomAccessWriter` as described in
datastax/jvector#562.

This PR also introduces new benchmarks as well as improved unit testing.
The new benchmarks verify the performance of the `OnDiskVectorValues`
and `OnDiskVectorValuesWriter` to confirm (at least directionally) the
time associated with read and write operations.

New tests have been added to verify that when we iterate over an
sstable's rows, we are able to assert that the sstable's vector value's
similarity to the one stored in the vector graph is ~1. This testing is
valuable in that it confirms the row id to ordinal mapping is correct at
every node. Previously, we relied on recall results to verify this for
us. This new pattern allows us to confirm _every_ node, which is more
thorough and removes most edge cases that might have led to partially
correct graphs that may have achieved acceptable recall.
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.

2 participants