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

Allow Blockstore to open unknown columns #34174

Merged
merged 7 commits into from
Nov 30, 2023

Conversation

steviez
Copy link
Contributor

@steviez steviez commented Nov 20, 2023

Problem

As we develop new features / modifications, we occasionally need to introduce new columns to the Blockstore. Adding new columns create a compatibility break due to a requirement that all columns present in the database must be opened. That is, if a new version introduces a new column, older software that is unaware of the column is no longer able to open the blockstore. We have gotten around this in the past by backporting a "stub" PR for new columns to older versions. This is annoying, and only creates compatibility for the one or two versions that we decide to backport to.

Summary of Changes

In order to avoid the above problem, add logic to the rocksdb open code that detects all of the columns in the database. Then, compare this to the list of "known" columns, and create descriptors for the detected columns that were unknown.

Testing

I have a unit test that exercises this; however, I also tested manually with a blockstore modified with master (which recently had a new column from #33979) and v1.17 (which is unaware of the new column).

With v1.17, the blockstore can be opened if the access mode is NOT Primary (ie read-only). So, the read-only solana-ledger-tool operations would still work. However, trying to open the database with Primary (which is what solana-validator needs), an error is encountered:

...
[2023-11-20T06:33:03.531947483Z INFO  solana_ledger::blockstore] Opening database at "/home/sol/ledger/rocksdb"
Failed to open blockstore at "/home/sol/ledger/": Error { message: "Invalid argument: Column families not opened: merkle_root_meta" }

Then, with my change in place:

...
[2023-11-20T06:37:08.103438353Z INFO  solana_ledger::blockstore] Opening database at "/home/sol/ledger/rocksdb"
[2023-11-20T06:37:08.106858572Z INFO  solana_ledger::blockstore_db] Detected unknown column default, opening column with basic options
[2023-11-20T06:37:08.106870414Z INFO  solana_ledger::blockstore_db] Detected unknown column merkle_root_meta, opening column with basic options

Funny enough, it complains about us not having a descriptor for default, but that column must have special handling as we've never created a descriptor for that column. So, I added handling for that in a later commit

ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
ledger/src/blockstore_db.rs Show resolved Hide resolved
Copy link

codecov bot commented Nov 20, 2023

Codecov Report

Merging #34174 (b5e3797) into master (e832765) will increase coverage by 0.0%.
Report is 10 commits behind head on master.
The diff coverage is 100.0%.

Additional details and impacted files
@@           Coverage Diff            @@
##           master   #34174    +/-   ##
========================================
  Coverage    81.9%    81.9%            
========================================
  Files         819      819            
  Lines      219350   219417    +67     
========================================
+ Hits       179698   179804   +106     
+ Misses      39652    39613    -39     

@steviez steviez marked this pull request as ready for review November 20, 2023 17:09
@steviez
Copy link
Contributor Author

steviez commented Nov 27, 2023

To answer my own question, the rust-rocksdb crate auto handles opening the default column for us:
https://github.com/rust-rocksdb/rust-rocksdb/blob/1c6ea8f81a2921594f05b2aae4f7cbd40ac89655/src/db.rs#L618-L623

            // Always open the default column family.
            if !cfs_v.iter().any(|cf| cf.name == DEFAULT_COLUMN_FAMILY_NAME) {
                cfs_v.push(ColumnFamilyDescriptor {
                    name: String::from(DEFAULT_COLUMN_FAMILY_NAME),
                    options: Options::default(),
                });
            }

Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

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

Looks good! Just nits and minor question.

ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
ledger/src/blockstore_db.rs Show resolved Hide resolved
ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
Copy link
Contributor Author

@steviez steviez left a comment

Choose a reason for hiding this comment

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

Rebased to tip of master for good measure too

ledger/src/blockstore_db.rs Outdated Show resolved Hide resolved
ledger/src/blockstore_db.rs Show resolved Hide resolved
Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

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

lgtm!

Copy link
Contributor

@yhchiang-sol yhchiang-sol left a comment

Choose a reason for hiding this comment

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

Thank you for working on this. This is one of the compatibility issues in my mind that I haven't had a chance to work on.

This PR solves the missing CF case when we revert to an older version where the new CF created in the newer version doesn't exist in the older version. The PR looks good to me.

One thing that might be a bit tricky is the CF options change under the same CF.
From RocksDB's consistency, it's better to use the CF options stored in the DB. However, from the reversion's point of view, using the CF options crafted in the older code makes more sense, which is what this PR uses.

Both options have some use cases in different situations, but this would be something to keep in mind when we have CF options change.

@steviez
Copy link
Contributor Author

steviez commented Nov 30, 2023

One thing that might be a bit tricky is the CF options change under the same CF. From RocksDB's consistency, it's better to use the CF options stored in the DB. However, from the reversion's point of view, using the CF options crafted in the older code makes more sense, which is what this PR uses.

Both options have some use cases in different situations, but this would be something to keep in mind when we have CF options change.

Hmm, I see your point and that is an interesting dilemma. We haven't had change to settings in a while, but I could certainly see how this could be problematic. Something like changing memtable size is probably fine; however, changing the size (reducing) per level could be problematic, and I'm not sure how Rocks would handle that (massive compaction when opening?).

Even if we don't explicitly change the settings, our custom settings that we apply to every column certainly differ from the default that I used to open unknown columns with. I would hope that by disabling compaction and not reading/writing the unknown column, that columns are left as-is

In any, going to push this PR but a good item to think about for the future

@steviez steviez merged commit 71c1782 into solana-labs:master Nov 30, 2023
32 checks passed
@steviez steviez deleted the bstore_auto_detect_columns branch November 30, 2023 19:25
@steviez steviez added the v1.17 PRs that should be backported to v1.17 label Nov 30, 2023
Copy link
Contributor

mergify bot commented Nov 30, 2023

Backports to the beta branch are to be avoided unless absolutely necessary for fixing bugs, security issues, and perf regressions. Changes intended for backport should be structured such that a minimum effective diff can be committed separately from any refactoring, plumbing, cleanup, etc that are not strictly necessary to achieve the goal. Any of the latter should go only into master and ride the normal stabilization schedule. Exceptions include CI/metrics changes, CLI improvements and documentation updates on a case by case basis.

mergify bot pushed a commit that referenced this pull request Nov 30, 2023
As we develop new features or modifications, we occassionally need to
introduce new columns to the Blockstore. Adding a new column introduces
a compatibility break given that opening the database in Primary mode
(R/W access) requires opening all columns. Reverting to an old software
version that is unaware of the new column is obviously problematic.

In the past, we have addressed by backporting minimal "stub" PR's to
older versions. This is annoying, and only allow compatibility for the
single version or two that we backport to.

This PR adds a change to automatically detect all columns, and create
default column descriptors for columns we were unaware of. As a result,
older software versions can open a Blockstore that was modified by a
newer software version, even if that new version added columns that the
old version is unaware of.

(cherry picked from commit 71c1782)

# Conflicts:
#	ledger/src/blockstore_db.rs
steviez added a commit that referenced this pull request Nov 30, 2023
…34287)

* Allow Blockstore to open unknown columns (#34174)

As we develop new features or modifications, we occassionally need to
introduce new columns to the Blockstore. Adding a new column introduces
a compatibility break given that opening the database in Primary mode
(R/W access) requires opening all columns. Reverting to an old software
version that is unaware of the new column is obviously problematic.

In the past, we have addressed by backporting minimal "stub" PR's to
older versions. This is annoying, and only allow compatibility for the
single version or two that we backport to.

This PR adds a change to automatically detect all columns, and create
default column descriptors for columns we were unaware of. As a result,
older software versions can open a Blockstore that was modified by a
newer software version, even if that new version added columns that the
old version is unaware of.

(cherry picked from commit 71c1782)

# Conflicts:
#	ledger/src/blockstore_db.rs

* Merge conflicts

---------

Co-authored-by: steviez <steven@solana.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v1.17 PRs that should be backported to v1.17
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants