Skip to content

Commit f2c257c

Browse files
committed
Introduce a new FormatVersion enum, and document it
The intent is to replace the constants in FDBRecordStore with this, but I wanted to introduce it on it's own, primarily to show that FormatVersionTest.testOrderingAlignment works with the constants. This is part of #710, but in order for that be complete, the constants will have to be updated too.
1 parent 683d109 commit f2c257c

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* FormatVersion.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.provider.foundationdb;
22+
23+
/**
24+
* This version is recorded for each store, and controls certain aspects of the on-disk behavior.
25+
* <p>
26+
* The primary reason for this version is to ensure that if two different versions of code interact with the same
27+
* store, the old version won't misinterpret other data in the store. Some of these version changes are relatively
28+
* minor such as {@link #CACHEABLE_STATE}, where, at worst, an old version of the code would unset the fact that
29+
* the store state is cacheable. Others are more major, such as {@link #SAVE_UNSPLIT_WITH_SUFFIX}, where two
30+
* versions of the code could be reading/writing record versions to different locations.
31+
* </p>
32+
* <p>
33+
* When the store is opened, the format version on disk will be upgraded to the one provided by
34+
* {@link FDBRecordStore.Builder#setFormatVersion}, or {@link #getDefaultFormatVersion()}. There is not currently
35+
* a defined policy for increasing the default version, so it is best to set the format version explicitly; see
36+
* <a href="https://github.com/FoundationDB/fdb-record-layer/issues/709">issue #709</a> for more information.
37+
* </p>
38+
* <p>
39+
* Generally, if all running instances support a given format version, it is ok to start using it, however upgrading
40+
* some format versions may be expensive, especially older versions on larger stores.
41+
* </p>
42+
*/
43+
public enum FormatVersion implements Comparable<FormatVersion> {
44+
/**
45+
* Initial FormatVersion.
46+
*/
47+
INFO_ADDED(1),
48+
/**
49+
* This FormatVersion introduces support for tracking record conuts as defined by:
50+
* {@link com.apple.foundationdb.record.RecordMetaData#getRecordCountKey()}.
51+
*/
52+
RECORD_COUNT_ADDED(2),
53+
/**
54+
* This FormatVersion causes the key as defined in {@link com.apple.foundationdb.record.RecordMetaData#getRecordCountKey()} to be stored in the
55+
* StoreHeader, ensuring that if the key is changed, the record count will be updated.
56+
* <p>
57+
* Unlike indexes, the RecordCountKey does not have a {@code lastModifiedVersion}, and thus the store detects
58+
* that the counts need to be rebuilt by checking the key in the StoreHeader.
59+
* </p>
60+
* <p>
61+
* Warning: There is no way to rebuild the record count key across transactions, and it does not check the
62+
* {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase.UserVersionChecker}, so changing the record count key will cause the store to attempt to rebuild the
63+
* counts when opening the store. If you have not strated using this version (or
64+
* {@link #RECORD_COUNT_ADDED}), you may want to consider replacing the RecordCountKey with a
65+
* {@link com.apple.foundationdb.record.metadata.IndexTypes#COUNT} index first.
66+
* </p>
67+
*/
68+
RECORD_COUNT_KEY_ADDED(3),
69+
/**
70+
* This FormatVersion was introduced to support testing of upgrading the format version past
71+
* {@link #RECORD_COUNT_KEY_ADDED}, but does not change the behavior of the store.
72+
*/
73+
FORMAT_CONTROL(4),
74+
/**
75+
* This FormatVersion causes all stores to store the split suffix, even if
76+
* {@link com.apple.foundationdb.record.RecordMetaData#isSplitLongRecords()} is {@code false}, unless
77+
* {@link com.apple.foundationdb.record.RecordMetaDataProto.DataStoreInfo#getOmitUnsplitRecordSuffix()} is {@code true} on the StoreHeader.
78+
* <p>
79+
* In order to maintain backwards compatiblity, and not require rewriting all the records, if upgrading from an
80+
* earlier FormatVersion to this one, if the metadata does not allow splitting long records,
81+
* {@linkplain com.apple.foundationdb.record.RecordMetaDataProto.DataStoreInfo#getOmitUnsplitRecordSuffix() getOmitUnsplitRecordSuffix()}
82+
* will be set to {@code true} on the StoreHeader.
83+
* </p>
84+
* <p>
85+
* By always including the suffix, it allows a couple benefits:
86+
* <ul>
87+
* <li>The metadata will be able to change to support splitting long records in the future</li>
88+
* <li>When upgrading to {@link #SAVE_UNSPLIT_WITH_SUFFIX} it can store the versions adjacent
89+
* to the record, rather than a separate sub-range.</li>
90+
* </ul>
91+
* </p>
92+
*/
93+
SAVE_UNSPLIT_WITH_SUFFIX(5),
94+
/**
95+
* This FormatVersion causes the record versions (if enabled via {@link com.apple.foundationdb.record.RecordMetaData#isStoreRecordVersions()})
96+
* to be stored adjacent to the record itself, rather than in a separate sub-range.
97+
* <p>
98+
* This most notably improves the performance or load of reading records, particularly a range of records, as it
99+
* doesn't have to do a separate read to get the versions.
100+
* </p>
101+
* <p>
102+
* Note: If the store is omitting the unsplit record suffix due to
103+
* {@link com.apple.foundationdb.record.RecordMetaDataProto.DataStoreInfo#getOmitUnsplitRecordSuffix()}, this will continue to store the
104+
* record versions in the separate space.
105+
* </p>
106+
* <p>
107+
* Warning: If {@link com.apple.foundationdb.record.RecordMetaData#isStoreRecordVersions()} is enabled when upgrading to this version,
108+
* the code will try to move the versions transactionally when opening the store.
109+
* </p>
110+
*/
111+
SAVE_VERSION_WITH_RECORD(6),
112+
/**
113+
* This FormatVersion allows the record state to be cached and invalidated with the meta-data version key.
114+
* @see com.apple.foundationdb.record.provider.foundationdb.storestate.MetaDataVersionStampStoreStateCache
115+
* @see FDBRecordStore#setStateCacheability
116+
*/
117+
CACHEABLE_STATE(7),
118+
/**
119+
* This FormatVersion allows the user to store additional fields in the StoreHeader.
120+
* These fields aren't used by the record store itself, but allow the user to set and read additional information.
121+
* @see FDBRecordStore#setHeaderUserField
122+
*
123+
*/
124+
HEADER_USER_FIELDS(8),
125+
/**
126+
* This FormatVersion allows the store to mark indexes as {@link com.apple.foundationdb.record.IndexState#READABLE_UNIQUE_PENDING} if
127+
* appropriate.
128+
*/
129+
READABLE_UNIQUE_PENDING(9),
130+
/**
131+
* This FormatVersion allows building non-idempotent indexes (e.g. COUNT) from a source index.
132+
*/
133+
CHECK_INDEX_BUILD_TYPE_DURING_UPDATE(10);
134+
135+
private final int value;
136+
137+
FormatVersion(final int value) {
138+
this.value = value;
139+
}
140+
141+
/**
142+
* The minimum {@code FormatVersion}.
143+
* @return the minimum {@code FormatVersion}
144+
*/
145+
static FormatVersion getMinimumVersion() {
146+
return INFO_ADDED;
147+
}
148+
149+
/**
150+
* The maximum {@code FormatVersion} that this version of the Record Layer can support.
151+
* @return the maximum supported version
152+
*/
153+
public static FormatVersion getMaximumSupportedVersion() {
154+
return CHECK_INDEX_BUILD_TYPE_DURING_UPDATE;
155+
}
156+
157+
/**
158+
* The default FormatVersion that this code will set when opening a record store, if the user does not call
159+
* {@link FDBRecordStore.Builder#setFormatVersion}.
160+
* <p>
161+
* Note: We don't currently have a well-defined policy for updating this, see
162+
* <a href="https://github.com/FoundationDB/fdb-record-layer/issues/709">Issue #709</a>.
163+
* </p>
164+
*/
165+
public static FormatVersion getDefaultFormatVersion() {
166+
return CACHEABLE_STATE;
167+
}
168+
169+
int getValueForSerialization() {
170+
return value;
171+
}
172+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* FormatVersionTest.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.provider.foundationdb;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import java.util.Arrays;
26+
import java.util.Comparator;
27+
import java.util.List;
28+
import java.util.stream.Collectors;
29+
import java.util.stream.IntStream;
30+
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
33+
class FormatVersionTest {
34+
35+
/**
36+
* The format versions were just constants, this test was written before the introduction to ensure that the numbers
37+
* didn't change.
38+
*/
39+
@Test
40+
void testOrderingAlignment() {
41+
final List<Integer> versions = List.of(FDBRecordStore.INFO_ADDED_FORMAT_VERSION,
42+
FDBRecordStore.RECORD_COUNT_ADDED_FORMAT_VERSION,
43+
FDBRecordStore.RECORD_COUNT_KEY_ADDED_FORMAT_VERSION,
44+
FDBRecordStore.FORMAT_CONTROL_FORMAT_VERSION,
45+
FDBRecordStore.SAVE_UNSPLIT_WITH_SUFFIX_FORMAT_VERSION,
46+
FDBRecordStore.SAVE_VERSION_WITH_RECORD_FORMAT_VERSION,
47+
FDBRecordStore.CACHEABLE_STATE_FORMAT_VERSION,
48+
FDBRecordStore.HEADER_USER_FIELDS_FORMAT_VERSION,
49+
FDBRecordStore.READABLE_UNIQUE_PENDING_FORMAT_VERSION,
50+
FDBRecordStore.CHECK_INDEX_BUILD_TYPE_DURING_UPDATE_FORMAT_VERSION);
51+
52+
assertEquals(IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()),
53+
versions);
54+
assertEquals(FDBRecordStore.MAX_SUPPORTED_FORMAT_VERSION, versions.get(versions.size() - 1));
55+
}
56+
57+
@Test
58+
void testCompleteness() {
59+
assertEquals(
60+
IntStream.rangeClosed(FormatVersion.getMinimumVersion().getValueForSerialization(),
61+
FormatVersion.getMaximumSupportedVersion().getValueForSerialization())
62+
.boxed().collect(Collectors.toList()),
63+
Arrays.stream(FormatVersion.values())
64+
.map(FormatVersion::getValueForSerialization)
65+
.sorted()
66+
.collect(Collectors.toList()));
67+
}
68+
69+
@Test
70+
void testMinimumVersion() {
71+
assertEquals(Arrays.stream(FormatVersion.values()).min(Comparator.naturalOrder()).orElseThrow(),
72+
FormatVersion.getMinimumVersion());
73+
}
74+
75+
@Test
76+
void testMaximumSupportedVersion() {
77+
assertEquals(Arrays.stream(FormatVersion.values()).max(Comparator.naturalOrder()).orElseThrow(),
78+
FormatVersion.getMaximumSupportedVersion());
79+
}
80+
81+
/**
82+
* Assert ordering by serialization value aligns with ordering by enum comparison.
83+
*/
84+
@Test
85+
void testOrdering() {
86+
assertEquals(Arrays.stream(FormatVersion.values())
87+
.sorted(Comparator.comparing(FormatVersion::getValueForSerialization))
88+
.collect(Collectors.toList()),
89+
Arrays.stream(FormatVersion.values())
90+
.sorted()
91+
.collect(Collectors.toList()));
92+
}
93+
}

0 commit comments

Comments
 (0)