|
20 | 20 | #include "iceberg/update/fast_append.h" |
21 | 21 |
|
22 | 22 | #include <format> |
| 23 | +#include <limits> |
23 | 24 | #include <optional> |
24 | 25 | #include <string> |
25 | 26 | #include <thread> |
|
32 | 33 | #include "iceberg/avro/avro_register.h" |
33 | 34 | #include "iceberg/constants.h" |
34 | 35 | #include "iceberg/manifest/manifest_entry.h" |
| 36 | +#include "iceberg/manifest/manifest_reader.h" |
35 | 37 | #include "iceberg/manifest/manifest_writer.h" |
36 | 38 | #include "iceberg/partition_spec.h" |
37 | 39 | #include "iceberg/schema.h" |
38 | 40 | #include "iceberg/snapshot.h" |
39 | 41 | #include "iceberg/table_metadata.h" |
| 42 | +#include "iceberg/table_properties.h" |
| 43 | +#include "iceberg/test/executor.h" |
40 | 44 | #include "iceberg/test/matchers.h" |
41 | 45 | #include "iceberg/test/update_test_base.h" |
42 | 46 | #include "iceberg/transaction.h" |
| 47 | +#include "iceberg/update/update_properties.h" // IWYU pragma: keep |
43 | 48 |
|
44 | 49 | namespace iceberg { |
45 | 50 |
|
@@ -117,6 +122,29 @@ class FastAppendTest : public UpdateTestBase { |
117 | 122 | return writer->ToManifestFile(); |
118 | 123 | } |
119 | 124 |
|
| 125 | + void SetManifestTargetSizeBytes(int64_t size_bytes) { |
| 126 | + ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties()); |
| 127 | + props->Set(std::string(TableProperties::kManifestTargetSizeBytes.key()), |
| 128 | + std::to_string(size_bytes)); |
| 129 | + EXPECT_THAT(props->Commit(), IsOk()); |
| 130 | + EXPECT_THAT(table_->Refresh(), IsOk()); |
| 131 | + } |
| 132 | + |
| 133 | + Result<std::vector<ManifestFile>> CurrentDataManifests() { |
| 134 | + ICEBERG_ASSIGN_OR_RAISE(auto snapshot, table_->current_snapshot()); |
| 135 | + SnapshotCache snapshot_cache(snapshot.get()); |
| 136 | + ICEBERG_ASSIGN_OR_RAISE(auto manifests, snapshot_cache.DataManifests(file_io_)); |
| 137 | + return std::vector<ManifestFile>(manifests.begin(), manifests.end()); |
| 138 | + } |
| 139 | + |
| 140 | + Result<std::vector<ManifestEntry>> ReadEntries(const ManifestFile& manifest) { |
| 141 | + ICEBERG_ASSIGN_OR_RAISE( |
| 142 | + auto spec, table_->metadata()->PartitionSpecById(manifest.partition_spec_id)); |
| 143 | + ICEBERG_ASSIGN_OR_RAISE(auto reader, |
| 144 | + ManifestReader::Make(manifest, file_io_, schema_, spec)); |
| 145 | + return reader->Entries(); |
| 146 | + } |
| 147 | + |
120 | 148 | std::shared_ptr<PartitionSpec> spec_; |
121 | 149 | std::shared_ptr<Schema> schema_; |
122 | 150 | std::shared_ptr<DataFile> file_a_; |
@@ -183,6 +211,63 @@ TEST_F(FastAppendTest, AppendManyFiles) { |
183 | 211 | EXPECT_EQ(snapshot->summary.at("added-files-size"), std::to_string(total_size)); |
184 | 212 | } |
185 | 213 |
|
| 214 | +TEST_F(FastAppendTest, WriteManifestGroups) { |
| 215 | + SetManifestTargetSizeBytes(std::numeric_limits<int64_t>::max()); |
| 216 | + |
| 217 | + test::ThreadExecutor executor; |
| 218 | + std::shared_ptr<FastAppend> fast_append; |
| 219 | + ICEBERG_UNWRAP_OR_FAIL(fast_append, table_->NewFastAppend()); |
| 220 | + fast_append->WriteManifestsWith(executor, 3); |
| 221 | + |
| 222 | + constexpr size_t kFileCount = 15'000; |
| 223 | + constexpr size_t kGroupSize = 7'500; |
| 224 | + std::vector<std::shared_ptr<DataFile>> files; |
| 225 | + files.reserve(kFileCount); |
| 226 | + for (size_t index = 0; index < kFileCount; ++index) { |
| 227 | + auto data_file = |
| 228 | + CreateDataFile(std::format("/data/group_{}.parquet", index), |
| 229 | + /*record_count=*/1, /*size=*/1, static_cast<int64_t>(index % 2)); |
| 230 | + fast_append->AppendFile(data_file); |
| 231 | + files.push_back(std::move(data_file)); |
| 232 | + } |
| 233 | + |
| 234 | + EXPECT_THAT(fast_append->Commit(), IsOk()); |
| 235 | + |
| 236 | + EXPECT_THAT(table_->Refresh(), IsOk()); |
| 237 | + EXPECT_EQ(executor.submit_count(), 2); |
| 238 | + ICEBERG_UNWRAP_OR_FAIL(auto manifests, CurrentDataManifests()); |
| 239 | + ASSERT_EQ(manifests.size(), 2U); |
| 240 | + |
| 241 | + for (size_t group_index = 0; group_index < manifests.size(); ++group_index) { |
| 242 | + ASSERT_TRUE(manifests[group_index].added_files_count.has_value()); |
| 243 | + EXPECT_EQ(manifests[group_index].added_files_count.value(), kGroupSize); |
| 244 | + |
| 245 | + ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadEntries(manifests[group_index])); |
| 246 | + ASSERT_EQ(entries.size(), kGroupSize); |
| 247 | + const size_t offset = group_index * kGroupSize; |
| 248 | + for (size_t entry_index = 0; entry_index < entries.size(); ++entry_index) { |
| 249 | + ASSERT_NE(entries[entry_index].data_file, nullptr); |
| 250 | + EXPECT_EQ(entries[entry_index].data_file->file_path, |
| 251 | + files[offset + entry_index]->file_path); |
| 252 | + } |
| 253 | + } |
| 254 | +} |
| 255 | + |
| 256 | +TEST_F(FastAppendTest, InvalidManifestParallelism) { |
| 257 | + test::ThreadExecutor executor; |
| 258 | + std::shared_ptr<FastAppend> fast_append; |
| 259 | + ICEBERG_UNWRAP_OR_FAIL(fast_append, table_->NewFastAppend()); |
| 260 | + fast_append->WriteManifestsWith(executor, 0); |
| 261 | + fast_append->AppendFile(file_a_); |
| 262 | + |
| 263 | + auto result = fast_append->Commit(); |
| 264 | + EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); |
| 265 | + EXPECT_THAT( |
| 266 | + result, |
| 267 | + HasErrorMessage("Manifest write parallelism must be greater than 0, but was: 0")); |
| 268 | + EXPECT_EQ(executor.submit_count(), 0); |
| 269 | +} |
| 270 | + |
186 | 271 | TEST_F(FastAppendTest, EmptyTableAppendUpdatesSequenceNumbers) { |
187 | 272 | EXPECT_THAT(table_->current_snapshot(), HasErrorMessage("No current snapshot")); |
188 | 273 | const int64_t base_sequence_number = table_->metadata()->last_sequence_number; |
|
0 commit comments