Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RPageStorage.cxx
Go to the documentation of this file.
1/// \file RPageStorage.cxx
2/// \author Jakob Blomer <jblomer@cern.ch>
3/// \date 2018-10-04
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
13#include <ROOT/RPageStorage.hxx>
15#include <ROOT/RColumn.hxx>
16#include <ROOT/RFieldBase.hxx>
20#include <ROOT/RNTupleModel.hxx>
22#include <ROOT/RNTupleUtils.hxx>
23#include <ROOT/RNTupleZip.hxx>
25#include <ROOT/RPageSinkBuf.hxx>
26#include <ROOT/StringUtils.hxx>
27#ifdef R__ENABLE_DAOS
29#endif
30#ifdef R__ENABLE_S3
32#endif
33
34#include <Compression.h>
35#include <TError.h>
36
37#include <algorithm>
38#include <atomic>
39#include <cassert>
40#include <cstring>
41#include <functional>
42#include <memory>
43#include <string_view>
44#include <unordered_map>
45#include <utility>
46
53
55
59
65
67 : fMetrics(""), fPageAllocator(std::make_unique<ROOT::Internal::RPageAllocatorHeap>()), fNTupleName(name)
68{
69}
70
72
74{
75 if (!fHasChecksum)
76 return;
77
78 auto charBuf = reinterpret_cast<const unsigned char *>(fBuffer);
79 auto checksumBuf = const_cast<unsigned char *>(charBuf) + GetDataSize();
80 std::uint64_t xxhash3;
81 RNTupleSerializer::SerializeXxHash3(charBuf, GetDataSize(), xxhash3, checksumBuf);
82}
83
85{
86 if (!fHasChecksum)
88
89 auto success = RNTupleSerializer::VerifyXxHash3(reinterpret_cast<const unsigned char *>(fBuffer), GetDataSize());
90 if (!success)
91 return R__FAIL("page checksum verification failed, data corruption detected");
93}
94
96{
97 if (!fHasChecksum)
98 return R__FAIL("invalid attempt to extract non-existing page checksum");
99
100 assert(fBufferSize >= kNBytesPageChecksum);
101 std::uint64_t checksum;
103 reinterpret_cast<const unsigned char *>(fBuffer) + fBufferSize - kNBytesPageChecksum, checksum);
104 return checksum;
105}
106
107//------------------------------------------------------------------------------
108
111{
112 auto [itr, _] = fColumnInfos.emplace(physicalColumnId, std::vector<RColumnInfo>());
113 for (auto &columnInfo : itr->second) {
114 if (columnInfo.fElementId == elementId) {
115 columnInfo.fRefCounter++;
116 return;
117 }
118 }
119 itr->second.emplace_back(RColumnInfo{elementId, 1});
120}
121
124{
125 auto itr = fColumnInfos.find(physicalColumnId);
126 R__ASSERT(itr != fColumnInfos.end());
127 for (std::size_t i = 0; i < itr->second.size(); ++i) {
128 if (itr->second[i].fElementId != elementId)
129 continue;
130
131 itr->second[i].fRefCounter--;
132 if (itr->second[i].fRefCounter == 0) {
133 itr->second.erase(itr->second.begin() + i);
134 if (itr->second.empty()) {
135 fColumnInfos.erase(itr);
136 }
137 }
138 break;
139 }
140}
141
143{
145 for (const auto &[physicalColumnId, _] : fColumnInfos)
146 result.insert(physicalColumnId);
147 return result;
148}
149
151{
152 if (fFirstEntry == ROOT::kInvalidNTupleIndex) {
153 /// Entry range unset, we assume that the entry range covers the complete source
154 return true;
155 }
156
157 if (clusterDesc.GetNEntries() == 0)
158 return true;
159 if ((clusterDesc.GetFirstEntryIndex() + clusterDesc.GetNEntries()) <= fFirstEntry)
160 return false;
161 if (clusterDesc.GetFirstEntryIndex() >= (fFirstEntry + fNEntries))
162 return false;
163 return true;
164}
165
168 fClusterPool(*this, ROOT::Internal::RNTupleReadOptionsManip::GetClusterBunchSize(options)),
169 fPagePool(*this),
170 fOptions(options)
171{
172}
173
175
176std::unique_ptr<ROOT::Internal::RPageSource>
177ROOT::Internal::RPageSource::Create(std::string_view ntupleName, std::string_view location,
178 const ROOT::RNTupleReadOptions &options)
179{
180 if (ntupleName.empty()) {
181 throw RException(R__FAIL("empty RNTuple name"));
182 }
183 if (location.empty()) {
184 throw RException(R__FAIL("empty storage location"));
185 }
186 if (location.find("daos://") == 0)
187#ifdef R__ENABLE_DAOS
188 return std::make_unique<ROOT::Experimental::Internal::RPageSourceDaos>(ntupleName, location, options);
189#else
190 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
191#endif
192
193 if (ROOT::StartsWith(location, "ntpl+s3+http://") || ROOT::StartsWith(location, "ntpl+s3+https://")) {
194#ifdef R__ENABLE_S3
195 return std::make_unique<ROOT::Experimental::Internal::RPageSourceS3>(ntupleName, location, options);
196#else
197 throw RException(R__FAIL("This RNTuple build does not support S3. Rebuild ROOT with the 'curl' "
198 "cmake option enabled (-Dcurl=ON) to enable the S3 backend."));
199#endif
200 }
201
202 return std::make_unique<ROOT::Internal::RPageSourceFile>(ntupleName, location, options);
203}
204
207{
209 auto physicalId =
210 GetSharedDescriptorGuard()->FindPhysicalColumnId(fieldId, column.GetIndex(), column.GetRepresentationIndex());
212 fActivePhysicalColumns.Insert(physicalId, column.GetElement()->GetIdentifier());
213 return ColumnHandle_t{physicalId, &column};
214}
215
217{
218 fActivePhysicalColumns.Erase(columnHandle.fPhysicalId, columnHandle.fColumn->GetElement()->GetIdentifier());
219}
220
222{
223 if ((range.fFirstEntry + range.fNEntries) > GetNEntries()) {
224 throw RException(R__FAIL("invalid entry range"));
225 }
226 fEntryRange = range;
227}
228
230{
231 if (!fHasStructure)
232 LoadStructureImpl();
233 fHasStructure = true;
234}
235
237{
238 if (fIsAttached)
239 return;
240
241 fDeserializationMode = mode;
242
243 LoadStructure();
244
245 {
246 auto descGuard = GetExclDescriptorGuard();
247 descGuard.MoveIn(AttachImpl());
248 fStructureBuffer.Reset();
249
250 NTupleSize_t sumClusterCount = 0;
251 fCumulativeClusterCounts.reserve(descGuard->GetNClusterGroups());
252 for (const auto &cgDesc : descGuard->GetClusterGroupIterable()) {
253 sumClusterCount += cgDesc.GetNClusters();
254 fCumulativeClusterCounts.emplace_back(sumClusterCount);
255 }
256 }
257
258 if (fOptions.GetMetadataMode() == RNTupleReadOptions::EMetadataMode::kEager)
259 LoadAllPageLists();
260
261 fIsAttached = true;
262}
263
265{
266 auto descGuard = GetExclDescriptorGuard();
267
268 for (const auto &cgDesc : descGuard->GetClusterGroupIterable()) {
269 LoadPageList(cgDesc.GetId(), descGuard);
270 }
271 // By clearing the access sequence with all page lists loaded, we make EnsureClusterDetails() always take the
272 // fast path that doesn't try further maintaining the list.
273 fClusterGroupAccessSequence.clear();
274}
275
277{
278 const auto &cgDesc = exclGuard->GetClusterGroupDescriptor(clusterGroupId);
279 if (cgDesc.HasClusterDetails())
280 return;
281
282 auto buffer =
283 MakeUninitArray<unsigned char>(cgDesc.GetPageListLength() + cgDesc.GetPageListLocator().GetNBytesOnStorage());
284 auto zipBuffer = buffer.get() + cgDesc.GetPageListLength();
285 LoadPageListImpl(cgDesc.GetPageListLocator(), zipBuffer);
286 RNTupleDecompressor::Unzip(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(), cgDesc.GetPageListLength(),
287 buffer.get());
288 RNTupleSerializer::DeserializePageList(buffer.get(), cgDesc.GetPageListLength(), cgDesc.GetId(), *exclGuard,
289 fDeserializationMode);
290}
291
292std::unique_ptr<ROOT::Internal::RPageSource> ROOT::Internal::RPageSource::Clone() const
293{
294 auto clone = CloneImpl();
295 if (fIsAttached) {
296 clone->GetExclDescriptorGuard().MoveIn(GetSharedDescriptorGuard()->Clone());
297 clone->fHasStructure = true;
298 clone->fIsAttached = true;
299 clone->fDeserializationMode = fDeserializationMode;
300 clone->fCumulativeClusterCounts = fCumulativeClusterCounts;
301 }
302 return clone;
303}
304
307{
308 assert(descGuard.IsValid());
309 if (cgId == kInvalidDescriptorId)
310 return descGuard;
311
312 // After LoadAllPages(), itrSeq == fClusterGroupAccessSequence.begin() == fClusterGroupAccessSequence.end()
313 auto itrSeq = std::find(fClusterGroupAccessSequence.begin(), fClusterGroupAccessSequence.end(), cgId);
314
315 if (descGuard->GetClusterGroupDescriptor(cgId).HasClusterDetails() &&
316 (itrSeq == fClusterGroupAccessSequence.begin())) {
317 return descGuard;
318 }
319
320 descGuard.Release();
321
322 auto exclGuard = GetExclDescriptorGuard();
323
324 itrSeq = std::find(fClusterGroupAccessSequence.begin(), fClusterGroupAccessSequence.end(), cgId);
325
326 if (exclGuard->GetClusterGroupDescriptor(cgId).HasClusterDetails()) {
327 // Either between releasing the shared guard and acquiring the exclusive guard the cluster group details appeared
328 // or the cluster group details were present initially but we need to update the access sequence.
329 assert(itrSeq != fClusterGroupAccessSequence.end());
330 fClusterGroupAccessSequence.erase(itrSeq);
331 fClusterGroupAccessSequence.push_front(cgId);
332 return exclGuard;
333 }
334
335 assert(itrSeq == fClusterGroupAccessSequence.end());
336 const auto nClusterGroups = fClusterGroupAccessSequence.size();
337 const auto clusterGroupBudget = Internal::RNTupleReadOptionsManip::GetClusterGroupBudget(fOptions);
338 assert(clusterGroupBudget > 0);
339 if ((nClusterGroups >= clusterGroupBudget) && (nClusterGroups > fPinnedClusterGroups.size())) {
340 // Remove as many cluster group details as necessary to allow for adding one more and still staying within
341 // the budget. Take into account, however, that pinned cluster groups cannot be removed.
342 auto nRemove = std::min(nClusterGroups - fPinnedClusterGroups.size(), nClusterGroups - clusterGroupBudget + 1);
343 // We iterate from the back to the front of the list, in reverse order of last access (i.e.: starting from the
344 // least recently used), and we skip pinned cluster groups.
345 for (auto lru = fClusterGroupAccessSequence.rbegin(); nRemove > 0;) {
346 if (fPinnedClusterGroups.count(*lru) > 0) {
347 ++lru;
348 continue;
349 }
350 exclGuard->DropClusterGroupDetails(*lru);
351 // The forward iterator corresponding to the reverse iterator lru is prev(lru.base).
352 // After removal, next points to the element following the removed one. All other iterators are invalid.
353 auto next = fClusterGroupAccessSequence.erase(std::prev(lru.base()));
354 // make_reverse_iterator brings lru back to the element before the removed one
355 lru = std::make_reverse_iterator(next);
356 nRemove--;
357 };
358 assert(fClusterGroupAccessSequence.size() > 0);
359 }
360 fClusterGroupAccessSequence.push_front(cgId);
361
362 LoadPageList(cgId, exclGuard);
363
364 return exclGuard;
365}
366
368{
369 // We use `cluster + 1` because fCumulativeClusterCounts stores the cumulative _number_ of clusters and not
370 // the last cluster IDs.
371 auto iter = std::lower_bound(fCumulativeClusterCounts.begin(), fCumulativeClusterCounts.end(), clusterId + 1);
372 if (iter == fCumulativeClusterCounts.end())
374 return std::distance(fCumulativeClusterCounts.begin(), iter);
375}
376
378{
379 return GetSharedDescriptorGuard()->GetNEntries();
380}
381
383{
384 auto sharedGuard = GetSharedDescriptorGuard();
385 if (sharedGuard->GetNClusters() == 0)
386 return 0;
387
388 const auto lastClusterGroupId = sharedGuard->GetNClusterGroups() - 1;
389 auto descGuard = EnsureClusterDetails(lastClusterGroupId, std::move(sharedGuard));
390
391 const auto &cgDesc = descGuard->GetClusterGroupDescriptor(lastClusterGroupId);
392 const auto &cd = descGuard->GetClusterDescriptor(cgDesc.GetClusterIds().back());
393 R__ASSERT(cd.ContainsColumn(physicalColumnId));
394
395 const auto &columnRange = cd.GetColumnRange(physicalColumnId);
396 return columnRange.GetFirstElementIndex() + columnRange.GetNElements();
397}
398
400{
401 fPinnedClusters.insert(clusterId);
402 ++fPinnedClusterGroups[FindClusterGroupId(clusterId)];
403}
404
406{
407 fPinnedClusters.erase(clusterId);
408 auto itr = fPinnedClusterGroups.find(FindClusterGroupId(clusterId));
409 assert(itr != fPinnedClusterGroups.end() && itr->second > 0);
410 if (--itr->second == 0)
411 fPinnedClusterGroups.erase(itr);
412}
413
416{
417 // At the cost of an additional cluster descriptor lock, we could use a more general approach that looks for the
418 // cluster containing the entry index after the last entry of the current cluster.
419 // At the moment, however, we know that cluster IDs are consecutive and clusters are not sharded.
420 auto sharedGuard = GetSharedDescriptorGuard();
421 assert(clusterId < sharedGuard->GetNClusters());
422 nextId = clusterId + 1;
423 if (nextId == sharedGuard->GetNClusters()) {
424 nextId = kInvalidDescriptorId;
425 return sharedGuard;
426 }
427 return EnsureClusterDetails(FindClusterGroupId(nextId), std::move(sharedGuard));
428}
429
432{
434 RAnyDescriptorGuard descGuard = GetSharedDescriptorGuard();
435
436 if (descGuard->GetNClusterGroups() == 0)
437 return descGuard;
438
439 // Binary search in the cluster group list, followed by a binary search in the clusters of that cluster group
440
441 const auto cgIter = descGuard->GetClusterGroupIterable().begin();
442 std::size_t cgLeft = 0;
443 std::size_t cgRight = descGuard->GetNClusterGroups() - 1;
444 while (cgLeft <= cgRight) {
445 const std::size_t cgMidpoint = (cgLeft + cgRight) / 2;
446 const auto &cgDesc = *(cgIter + cgMidpoint);
447
448 if (cgDesc.GetMinEntry() > entryIdx) {
449 R__ASSERT(cgMidpoint > 0);
450 cgRight = cgMidpoint - 1;
451 continue;
452 }
453
454 if (cgDesc.GetMinEntry() + cgDesc.GetEntrySpan() <= entryIdx) {
455 cgLeft = cgMidpoint + 1;
456 continue;
457 }
458
459 // Binary search in the current cluster group; since we already checked the element range boundaries,
460 // the element must be in that cluster group.
461 const auto tgtClusterGroupId = cgDesc.GetId();
462 descGuard = EnsureClusterDetails(tgtClusterGroupId, std::move(descGuard));
463
464 const auto &clusterIds = descGuard->GetClusterGroupDescriptor(tgtClusterGroupId).GetClusterIds();
465 std::size_t clusterLeft = 0;
466 std::size_t clusterRight = clusterIds.size() - 1;
467 while (clusterLeft <= clusterRight) {
468 const std::size_t clusterMidpoint = (clusterLeft + clusterRight) / 2;
469 const auto &clusterDesc = descGuard->GetClusterDescriptor(clusterIds[clusterMidpoint]);
470
471 if (clusterDesc.GetFirstEntryIndex() > entryIdx) {
472 R__ASSERT(clusterMidpoint > 0);
473 clusterRight = clusterMidpoint - 1;
474 continue;
475 }
476
477 if (clusterDesc.GetFirstEntryIndex() + clusterDesc.GetNEntries() <= entryIdx) {
478 clusterLeft = clusterMidpoint + 1;
479 continue;
480 }
481
482 cid = clusterIds[clusterMidpoint];
483 return descGuard;
484 }
485 R__ASSERT(false);
486 }
487 return descGuard;
488}
489
490namespace {
491
492// Searches the index of a column in a certain cluster group. If the column index is found, returns the list
493// of cluster IDs in that group. Otherwise, increment or decrement the given clusterGroupId to indicate to which
494// side of the cluster groups the search should continue.
495const std::vector<ROOT::DescriptorId_t> *
496SearchInClusterGroup(ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t index,
498{
499 const auto &clusterIds = descGuard->GetClusterGroupDescriptor(clusterGroupId).GetClusterIds();
500 R__ASSERT(!clusterIds.empty());
501
502 const auto &firstColumnRange = descGuard->GetClusterDescriptor(clusterIds.front()).GetColumnRange(physicalColumnId);
503 if (firstColumnRange.GetFirstElementIndex() > index) {
504 // Look into previous cluster groups
505 R__ASSERT(clusterGroupId > 0);
506 clusterGroupId--;
507 return nullptr;
508 }
509
510 const auto &lastColumnRange = descGuard->GetClusterDescriptor(clusterIds.back()).GetColumnRange(physicalColumnId);
511 if ((lastColumnRange.GetFirstElementIndex() + lastColumnRange.GetNElements()) <= index) {
512 // Look into the following cluster groups (if applicable)
513 if (++clusterGroupId == descGuard->GetNClusterGroups())
514 clusterGroupId = ROOT::kInvalidDescriptorId;
515 return nullptr;
516 }
517
518 return &clusterIds;
519}
520
521// Binary search in the sorted list clusterIdList. The caller has to ensure that the index of the given column
522// is in fact in the list of provided cluster IDs.
523ROOT::DescriptorId_t FindClusterIdInList(ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t index,
524 const std::vector<ROOT::DescriptorId_t> &clusterIds,
526{
527 std::size_t clusterLeft = 0;
528 std::size_t clusterRight = clusterIds.size() - 1;
529 while (clusterLeft <= clusterRight) {
530 const std::size_t clusterMidpoint = (clusterLeft + clusterRight) / 2;
531 const auto clusterId = clusterIds[clusterMidpoint];
532 const auto &columnRange = descGuard->GetClusterDescriptor(clusterId).GetColumnRange(physicalColumnId);
533
534 if (columnRange.Contains(index)) {
535 return clusterId;
536 }
537
538 if (columnRange.GetFirstElementIndex() > index) {
539 R__ASSERT(clusterMidpoint > 0);
540 clusterRight = clusterMidpoint - 1;
541 continue;
542 }
543
544 if (columnRange.GetFirstElementIndex() + columnRange.GetNElements() <= index) {
545 clusterLeft = clusterMidpoint + 1;
546 continue;
547 }
548 }
549
550 R__ASSERT(false);
552}
553
554} // anonymous namespace
555
558{
560 RAnyDescriptorGuard descGuard = GetSharedDescriptorGuard();
561
562 if (descGuard->GetNClusterGroups() == 0)
563 return descGuard;
564
565 // Shortcuts: most cases do not require a full binary search in the list of cluster groups.
566
567 // Case 1: look around the last used cluster
568 const std::vector<DescriptorId_t> *clusterIds = nullptr;
569 const DescriptorId_t cgGuess =
570 ((index == 0) || (fLastUsedCluster == kInvalidDescriptorId)) ? 0 : FindClusterGroupId(fLastUsedCluster);
571 DescriptorId_t cgHint = cgGuess;
572 // Try twice: if the column index is not found in the cluster group of the last used cluster, cgHint will point
573 // to the adjacent cluster group either preceeding or following the cluster group and we'll try again.
574 for (int i = 0; i < 2; ++i) {
575 descGuard = EnsureClusterDetails(cgHint, std::move(descGuard));
576 if ((clusterIds = SearchInClusterGroup(physicalColumnId, index, cgHint, descGuard))) {
577 cid = FindClusterIdInList(physicalColumnId, index, *clusterIds, descGuard);
578 return descGuard;
579 }
580 if (cgHint == kInvalidDescriptorId)
581 return descGuard;
582 }
583 assert(cgHint != cgGuess);
584
585 // Case 2: look into the pinned cluster groups
586 // We expect only few pinned cluster groups, so we do a linear search with pruning
587 const bool isRightSided = cgHint > cgGuess; // whether to look to the right or to the left of cgHint (cgGuess)
588 for (auto [cgPinned, _] : fPinnedClusterGroups) {
589 if (isRightSided && (cgPinned < cgHint)) {
590 // Skip the left side of pinned cluster groups if we already know the column index cannot be here
591 continue;
592 } else if (!isRightSided && (cgPinned > cgHint)) {
593 // Prune the search on the right side of pinned cluster groups
594 break;
595 }
596
597 descGuard = EnsureClusterDetails(cgPinned, std::move(descGuard));
598 cgHint = cgPinned;
599 if ((clusterIds = SearchInClusterGroup(physicalColumnId, index, cgHint, descGuard))) {
600 cid = FindClusterIdInList(physicalColumnId, index, *clusterIds, descGuard);
601 return descGuard;
602 }
603
604 if (cgHint < cgPinned) {
605 // No point in moving further to the right of the pinned cluster groups
606 break;
607 }
608 }
609
610 // Case 3: top-level columns can use the entry-based search
611 const auto &columnDesc = descGuard->GetColumnDescriptor(physicalColumnId);
612 if (columnDesc.GetIndex() == 0) {
613 // Start search with the parent field of the current column
614 auto fieldId = descGuard->GetFieldDescriptor(columnDesc.GetFieldId()).GetParentId();
615 while (fieldId != descGuard->GetFieldZeroId()) {
616 const auto &fieldDesc = descGuard->GetFieldDescriptor(fieldId);
617 if ((fieldDesc.GetStructure() != ENTupleStructure::kPlain) &&
618 (fieldDesc.GetStructure() != ENTupleStructure::kRecord)) {
619 break;
620 }
621 fieldId = fieldDesc.GetParentId();
622 }
623 if (fieldId == descGuard->GetFieldZeroId()) {
624 descGuard.Release();
625 return FindClusterId(index, cid);
626 }
627 }
628
629 // Binary search in the cluster group list, followed by a binary search in the clusters of that cluster group
630 // We could use the information from the shortcut search to restrict the left and right borders further.
631 // Left out for simplicity.
632
633 std::size_t cgLeft = 0;
634 std::size_t cgRight = descGuard->GetNClusterGroups() - 1;
635 while (cgLeft <= cgRight) {
636 const std::size_t cgMidpoint = (cgLeft + cgRight) / 2;
637 descGuard = EnsureClusterDetails(cgMidpoint, std::move(descGuard));
638
639 cgHint = cgMidpoint;
640 clusterIds = SearchInClusterGroup(physicalColumnId, index, cgHint, descGuard);
641
642 if (clusterIds) {
643 cid = FindClusterIdInList(physicalColumnId, index, *clusterIds, descGuard);
644 break;
645 }
646
647 if (cgHint == ROOT::kInvalidDescriptorId) {
648 break;
649 } else if (cgHint < cgMidpoint) {
650 // Look into the lower half of cluster groups
651 cgRight = cgMidpoint - 1;
652 } else {
653 // Look into the upper half of cluster groups
654 cgLeft = cgMidpoint + 1;
655 }
656 }
657 return descGuard;
658}
659
661{
662 if (!fTaskScheduler)
663 return;
664
665 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
666
667 const auto clusterId = cluster->GetId();
668 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
669 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
670
671 fPreloadedClusters[clusterDescriptor.GetFirstEntryIndex()] = clusterId;
672
673 std::atomic<bool> foundChecksumFailure{false};
674
675 const auto &columnsInCluster = cluster->GetAvailPhysicalColumns();
676
677 std::vector<std::pair<Internal::RPage, Internal::RPagePool::RKey>> allPages;
678 // We allocate the space for all the pages upfront so that the TBB tasks do not need to lock the vector.
679 // In general, the number of uncompressed pages is larger than the number of sealed pages because we
680 // may decompress the same page into different in-memory representations.
681 std::size_t nPages = 0;
682 // We run a short version of the main loop to determine the number of uncompressed pages
683 for (const auto columnId : columnsInCluster) {
684 if (fActivePhysicalColumns.HasColumnInfos(columnId)) {
685 nPages += fActivePhysicalColumns.GetColumnInfos(columnId).size() *
686 clusterDescriptor.GetPageRange(columnId).GetPageInfos().size();
687 }
688 }
689 allPages.resize(nPages);
690 std::size_t iPage = 0;
691
692 std::vector<std::unique_ptr<RColumnElementBase>> allElements;
693 for (const auto columnId : columnsInCluster) {
694 // By the time we unzip a cluster, the set of active columns may have already changed wrt. to the moment when
695 // we requested reading the cluster. That doesn't matter much, we simply decompress what is now in the list
696 // of active columns.
697 if (!fActivePhysicalColumns.HasColumnInfos(columnId))
698 continue;
699 const auto &columnInfos = fActivePhysicalColumns.GetColumnInfos(columnId);
700 const auto indexOffset = clusterDescriptor.GetColumnRange(columnId).GetFirstElementIndex();
701
702 allElements.reserve(allElements.size() + columnInfos.size());
703 for (const auto &info : columnInfos) {
704 allElements.emplace_back(GenerateColumnElement(info.fElementId));
705 const auto element = allElements.back().get();
706
707 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
708 std::uint64_t pageNo = 0;
709 std::uint64_t firstInPage = 0;
710 for (const auto &pi : pageRange.GetPageInfos()) {
711 auto onDiskPage = cluster->GetOnDiskPage(ROnDiskPage::Key{columnId, pageNo});
712 RSealedPage sealedPage;
713 sealedPage.SetNElements(pi.GetNElements());
714 sealedPage.SetHasChecksum(pi.HasChecksum());
715 sealedPage.SetBufferSize(pi.GetLocator().GetNBytesOnStorage() + pi.HasChecksum() * kNBytesPageChecksum);
716 sealedPage.SetBuffer(onDiskPage->GetAddress());
717 R__ASSERT(onDiskPage->GetSize() == sealedPage.GetBufferSize());
718
719 auto taskFunc = [this, columnId, clusterId, firstInPage, sealedPage, element, iPage, indexOffset,
720 &foundChecksumFailure, &allPages]() {
721 auto rv = UnsealPage(sealedPage, *element);
722 if (!rv) {
723 foundChecksumFailure = true;
724 return;
725 }
726 auto newPage = rv.Unwrap();
727 fCounters->fSzUnzip.Add(element->GetSize() * sealedPage.GetNElements());
728
729 newPage.SetWindow(indexOffset + firstInPage,
730 ROOT::Internal::RPage::RClusterInfo(clusterId, indexOffset));
731 const ROOT::Internal::RPagePool::RKey keyPagePool{columnId, element->GetIdentifier().fInMemoryType};
732 allPages[iPage] = std::make_pair(std::move(newPage), keyPagePool);
733 };
734
735 if (sealedPage.GetNElements() * element->GetSize() < kInlineDecompressionThreshold) {
736 taskFunc();
737 } else {
738 fTaskScheduler->AddTask(taskFunc);
739 }
740
741 firstInPage += pi.GetNElements();
742 pageNo++;
743 iPage++;
744 } // for all pages in column
745 } // for all in-memory types of the column
746 } // for all columns in cluster
747
748 fTaskScheduler->Wait();
749 fCounters->fNPageUnsealed.Add(nPages);
750
751 if (foundChecksumFailure) {
752 throw RException(R__FAIL("page checksum verification failed, data corruption detected"));
753 }
754
755 fPagePool.PreloadPageV(allPages);
756}
757
759 const RCluster::RKey &clusterKey, ROnDiskPageMap &pageZeroMap,
761 &perPageFunc)
762{
763 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterKey.fClusterId), GetSharedDescriptorGuard());
764 const auto &clusterDesc = descriptorGuard->GetClusterDescriptor(clusterKey.fClusterId);
765
766 for (auto physicalColumnId : clusterKey.fPhysicalColumnSet) {
767 if (clusterDesc.GetColumnRange(physicalColumnId).IsSuppressed())
768 continue;
769
770 const auto &pageRange = clusterDesc.GetPageRange(physicalColumnId);
771 ROOT::NTupleSize_t pageNo = 0;
772 for (const auto &pageInfo : pageRange.GetPageInfos()) {
773 if (pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
774 pageZeroMap.Register(ROnDiskPage::Key{physicalColumnId, pageNo},
776 pageInfo.GetLocator().GetNBytesOnStorage()));
777 } else {
778 perPageFunc(physicalColumnId, pageNo, pageInfo);
779 }
780 ++pageNo;
781 }
782 }
783}
784
786{
787 if (fLastUsedCluster == clusterId)
788 return;
789
790 ROOT::NTupleSize_t firstEntryIndex = kInvalidNTupleIndex;
791 {
792 auto descGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
793 firstEntryIndex = descGuard->GetClusterDescriptor(clusterId).GetFirstEntryIndex();
794 }
795 auto itr = fPreloadedClusters.begin();
796 while ((itr != fPreloadedClusters.end()) && (itr->first < firstEntryIndex)) {
797 if (fPinnedClusters.count(itr->second) > 0) {
798 ++itr;
799 } else {
800 fPagePool.Evict(itr->second);
801 itr = fPreloadedClusters.erase(itr);
802 }
803 }
804 std::size_t poolWindow = 0;
805 while ((itr != fPreloadedClusters.end()) &&
807 ++itr;
808 ++poolWindow;
809 }
810 while (itr != fPreloadedClusters.end()) {
811 if (fPinnedClusters.count(itr->second) > 0) {
812 ++itr;
813 } else {
814 fPagePool.Evict(itr->second);
815 itr = fPreloadedClusters.erase(itr);
816 }
817 }
818
819 fLastUsedCluster = clusterId;
820}
821
823 RSealedPage &sealedPage)
824{
825 const auto clusterId = localIndex.GetClusterId();
826
828 {
829 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
830 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
831 pageInfo = clusterDescriptor.GetPageRange(physicalColumnId).Find(localIndex.GetIndexInCluster());
832 }
833
834 assert(pageInfo.GetLocator().GetType() != RNTupleLocator::kTypePageZero);
835
836 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
837 sealedPage.SetNElements(pageInfo.GetNElements());
838 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
839
840 if (!sealedPage.GetBuffer())
841 return;
842
843 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
845}
846
849{
850 const auto &pageInfo = pageSummary.fPageInfo;
851 assert(pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero);
852
853 const auto element = columnHandle.fColumn->GetElement();
854 const auto elementSize = element->GetSize();
855 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
856
857 auto pageZero = fPageAllocator->NewPage(elementSize, pageInfo.GetNElements());
858 pageZero.GrowUnchecked(pageInfo.GetNElements());
859 std::memset(pageZero.GetBuffer(), 0, pageZero.GetNBytes());
860 pageZero.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
861 RPage::RClusterInfo(pageSummary.fClusterId, pageSummary.fColumnOffset));
862 return fPagePool.RegisterPage(std::move(pageZero), RPagePool::RKey{columnHandle.fPhysicalId, elementInMemoryType});
863}
864
867{
869 throw RException(R__FAIL("tried to read a page with an unknown locator"));
870 } else if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
871 return LoadZeroPage(columnHandle, pageSummary);
872 }
873
874 const auto &columnId = columnHandle.fPhysicalId;
875 const auto &clusterId = pageSummary.fClusterId;
876 const auto &pageInfo = pageSummary.fPageInfo;
877
878 const auto element = columnHandle.fColumn->GetElement();
879 const auto elementSize = element->GetSize();
880 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
881
882 UpdateLastUsedCluster(clusterId);
883
884 RSealedPage sealedPage;
885 sealedPage.SetNElements(pageInfo.GetNElements());
886 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
887 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
888 std::unique_ptr<unsigned char[]> directReadBuffer; // only used if cluster pool is turned off
889
890 if (fOptions.GetClusterCache() == ROOT::RNTupleReadOptions::EClusterCache::kOff) {
891 directReadBuffer = MakeUninitArray<unsigned char>(sealedPage.GetBufferSize());
892 sealedPage.SetBuffer(directReadBuffer.get());
893 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
894
895 fCounters->fNPageRead.Inc();
896 fCounters->fNRead.Inc();
897 fCounters->fSzReadPayload.Add(sealedPage.GetBufferSize());
898 } else {
899 if (!fCurrentCluster || (fCurrentCluster->GetId() != clusterId) || !fCurrentCluster->ContainsColumn(columnId))
900 fCurrentCluster = fClusterPool.GetCluster(clusterId, fActivePhysicalColumns.ToColumnSet());
901 R__ASSERT(fCurrentCluster->ContainsColumn(columnId));
902
903 // The cluster pool may have unzipped the required page into the page pool
904 auto cachedPageRef = fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, elementInMemoryType},
905 RNTupleLocalIndex(clusterId, pageInfo.GetFirstElementIndex()));
906 if (!cachedPageRef.Get().IsNull())
907 return cachedPageRef;
908
909 ROnDiskPage::Key key(columnId, pageInfo.GetPageNumber());
910 auto onDiskPage = fCurrentCluster->GetOnDiskPage(key);
911 R__ASSERT(onDiskPage && (sealedPage.GetBufferSize() == onDiskPage->GetSize()));
912 sealedPage.SetBuffer(onDiskPage->GetAddress());
913 }
914
915 ROOT::Internal::RPage newPage;
916 {
917 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
918 newPage = UnsealPage(sealedPage, *element).Unwrap();
919 fCounters->fSzUnzip.Add(elementSize * pageInfo.GetNElements());
920 }
921
922 newPage.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
923 ROOT::Internal::RPage::RClusterInfo(clusterId, pageSummary.fColumnOffset));
924 fCounters->fNPageUnsealed.Inc();
925
926 return fPagePool.RegisterPage(std::move(newPage), RPagePool::RKey{columnId, elementInMemoryType});
927}
928
931{
932 const auto columnId = columnHandle.fPhysicalId;
933 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
934 auto cachedPageRef =
935 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, globalIndex);
936 if (!cachedPageRef.Get().IsNull()) {
937 UpdateLastUsedCluster(cachedPageRef.Get().GetClusterInfo().GetId());
938 return cachedPageRef;
939 }
940
941 RPageSummary pageSummary;
942 {
943 auto descriptorGuard = FindClusterId(columnId, globalIndex, pageSummary.fClusterId);
944
945 if (pageSummary.fClusterId == ROOT::kInvalidDescriptorId)
946 throw RException(R__FAIL("entry with index " + std::to_string(globalIndex) + " out of bounds"));
947
948 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(pageSummary.fClusterId);
949 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
950 if (columnRange.IsSuppressed())
952
953 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
954 R__ASSERT(pageSummary.fColumnOffset <= globalIndex);
955 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(globalIndex - pageSummary.fColumnOffset);
956 }
957
958 return LoadPageFromSummary(columnHandle, pageSummary);
959}
960
963{
964 const auto clusterId = localIndex.GetClusterId();
965 const auto columnId = columnHandle.fPhysicalId;
966 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
967 auto cachedPageRef =
968 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, localIndex);
969 if (!cachedPageRef.Get().IsNull()) {
970 UpdateLastUsedCluster(clusterId);
971 return cachedPageRef;
972 }
973
974 if (clusterId == kInvalidDescriptorId)
975 throw RException(R__FAIL("entry out of bounds"));
976
977 RPageSummary pageSummary;
978 {
979 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
980 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
981 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
982 if (columnRange.IsSuppressed())
984
985 pageSummary.fClusterId = clusterId;
986 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
987 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(localIndex.GetIndexInCluster());
988 }
989
990 return LoadPageFromSummary(columnHandle, pageSummary);
991}
992
994{
995 fMetrics = RNTupleMetrics(prefix);
996 fMetrics.ObserveMetrics(fClusterPool.GetMetrics());
997 fMetrics.ObserveMetrics(fPagePool.GetMetrics());
998 fCounters = std::make_unique<RCounters>(RCounters{
999 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nReadV", "", "number of vector read requests"),
1000 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nRead", "", "number of byte ranges read"),
1001 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadPayload", "B", "volume read from storage (required)"),
1002 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadOverhead", "B", "volume read from storage (overhead)"),
1003 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szUnzip", "B", "volume after unzipping"),
1004 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nClusterLoaded", "",
1005 "number of partial clusters preloaded from storage"),
1006 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageRead", "", "number of pages read from storage"),
1007 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageUnsealed", "", "number of pages unzipped and decoded"),
1008 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallRead", "ns", "wall clock time spent reading"),
1009 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallUnzip", "ns", "wall clock time spent decompressing"),
1010 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuRead", "ns", "CPU time spent reading"),
1011 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuUnzip", "ns",
1012 "CPU time spent decompressing"),
1013 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
1014 "bwRead", "MB/s", "bandwidth compressed bytes read per second", fMetrics,
1015 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
1016 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
1017 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
1018 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
1019 if (auto walltime = timeWallRead->GetValueAsInt()) {
1020 double payload = szReadPayload->GetValueAsInt();
1021 double overhead = szReadOverhead->GetValueAsInt();
1022 // unit: bytes / nanosecond = GB/s
1023 return {true, (1000. * (payload + overhead) / walltime)};
1024 }
1025 }
1026 }
1027 }
1028 return {false, -1.};
1029 }),
1030 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
1031 "bwReadUnzip", "MB/s", "bandwidth uncompressed bytes read per second", fMetrics,
1032 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
1033 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
1034 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
1035 if (auto walltime = timeWallRead->GetValueAsInt()) {
1036 double unzip = szUnzip->GetValueAsInt();
1037 // unit: bytes / nanosecond = GB/s
1038 return {true, 1000. * unzip / walltime};
1039 }
1040 }
1041 }
1042 return {false, -1.};
1043 }),
1044 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
1045 "bwUnzip", "MB/s", "decompression bandwidth of uncompressed bytes per second", fMetrics,
1046 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
1047 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
1048 if (const auto timeWallUnzip = metrics.GetLocalCounter("timeWallUnzip")) {
1049 if (auto walltime = timeWallUnzip->GetValueAsInt()) {
1050 double unzip = szUnzip->GetValueAsInt();
1051 // unit: bytes / nanosecond = GB/s
1052 return {true, 1000. * unzip / walltime};
1053 }
1054 }
1055 }
1056 return {false, -1.};
1057 }),
1058 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
1059 "rtReadEfficiency", "", "ratio of payload over all bytes read", fMetrics,
1060 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
1061 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
1062 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
1063 if (auto payload = szReadPayload->GetValueAsInt()) {
1064 // r/(r+o) = 1/((r+o)/r) = 1/(1 + o/r)
1065 return {true, 1. / (1. + (1. * szReadOverhead->GetValueAsInt()) / payload)};
1066 }
1067 }
1068 }
1069 return {false, -1.};
1070 }),
1071 *fMetrics.MakeCounter<RNTupleCalcPerf *>("rtCompression", "", "ratio of compressed bytes / uncompressed bytes",
1072 fMetrics, [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
1073 if (const auto szReadPayload =
1074 metrics.GetLocalCounter("szReadPayload")) {
1075 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
1076 if (auto unzip = szUnzip->GetValueAsInt()) {
1077 return {true, (1. * szReadPayload->GetValueAsInt()) / unzip};
1078 }
1079 }
1080 }
1081 return {false, -1.};
1082 })});
1083}
1084
1087{
1088 return UnsealPage(sealedPage, element, *fPageAllocator);
1089}
1090
1092 const RColumnElementBase &element,
1094{
1095 // Unsealing a page zero is a no-op. `RPageRange::ExtendToFitColumnRange()` guarantees that the page zero buffer is
1096 // large enough to hold `sealedPage.fNElements`
1098 auto page = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
1099 page.GrowUnchecked(sealedPage.GetNElements());
1100 memset(page.GetBuffer(), 0, page.GetNBytes());
1101 return page;
1102 }
1103
1104 auto rv = sealedPage.VerifyChecksumIfEnabled();
1105 if (!rv)
1106 return R__FORWARD_ERROR(rv);
1107
1108 const auto bytesPacked = element.GetPackedSize(sealedPage.GetNElements());
1109 auto page = pageAlloc.NewPage(element.GetPackedSize(), sealedPage.GetNElements());
1110 if (sealedPage.GetDataSize() != bytesPacked) {
1111 ROOT::Internal::RNTupleDecompressor::Unzip(sealedPage.GetBuffer(), sealedPage.GetDataSize(), bytesPacked,
1112 page.GetBuffer());
1113 } else {
1114 // We cannot simply map the sealed page as we don't know its life time. Specialized page sources
1115 // may decide to implement to not use UnsealPage but to custom mapping / decompression code.
1116 // Note that usually pages are compressed.
1117 memcpy(page.GetBuffer(), sealedPage.GetBuffer(), bytesPacked);
1118 }
1119
1120 if (!element.IsMappable()) {
1121 auto tmp = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
1122 element.Unpack(tmp.GetBuffer(), page.GetBuffer(), sealedPage.GetNElements());
1123 page = std::move(tmp);
1124 }
1125
1126 page.GrowUnchecked(sealedPage.GetNElements());
1127 return page;
1128}
1129
1131{
1132 if (fHasStreamerInfosRegistered)
1133 return;
1134
1135 for (const auto &extraTypeInfo : fDescriptor.GetExtraTypeInfoIterable()) {
1136 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
1137 continue;
1138 // We don't need the result, it's enough that during deserialization, BuildCheck() is called for every
1139 // streamer info record.
1140 RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap();
1141 }
1142
1143 fHasStreamerInfosRegistered = true;
1144}
1145
1146//------------------------------------------------------------------------------
1147
1149{
1150 // Make the sort order unique by adding the physical on-disk column id as a secondary key
1151 if (fCurrentPageSize == other.fCurrentPageSize)
1152 return fColumn->GetOnDiskId() > other.fColumn->GetOnDiskId();
1153 return fCurrentPageSize > other.fCurrentPageSize;
1154}
1155
1156bool ROOT::Internal::RWritePageMemoryManager::TryEvict(std::size_t targetAvailableSize, std::size_t pageSizeLimit)
1157{
1158 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
1159 return true;
1160
1161 auto itr = fColumnsSortedByPageSize.begin();
1162 while (itr != fColumnsSortedByPageSize.end()) {
1163 if (itr->fCurrentPageSize <= pageSizeLimit)
1164 break;
1165 if (itr->fCurrentPageSize == itr->fInitialPageSize) {
1166 ++itr;
1167 continue;
1168 }
1169
1170 // Flushing the current column will invalidate itr
1171 auto itrFlush = itr++;
1172
1173 RColumnInfo next;
1174 if (itr != fColumnsSortedByPageSize.end())
1175 next = *itr;
1176
1177 itrFlush->fColumn->Flush();
1178 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
1179 return true;
1180
1181 if (next.fColumn == nullptr)
1182 return false;
1183 itr = fColumnsSortedByPageSize.find(next);
1184 };
1185
1186 return false;
1187}
1188
1189bool ROOT::Internal::RWritePageMemoryManager::TryUpdate(RColumn &column, std::size_t newWritePageSize)
1190{
1191 const RColumnInfo key{&column, column.GetWritePageCapacity(), 0};
1192 auto itr = fColumnsSortedByPageSize.find(key);
1193 if (itr == fColumnsSortedByPageSize.end()) {
1194 if (!TryEvict(newWritePageSize, 0))
1195 return false;
1196 fColumnsSortedByPageSize.insert({&column, newWritePageSize, newWritePageSize});
1197 fCurrentAllocatedBytes += newWritePageSize;
1198 return true;
1199 }
1200
1201 RColumnInfo elem{*itr};
1202 assert(newWritePageSize >= elem.fInitialPageSize);
1203
1204 if (newWritePageSize == elem.fCurrentPageSize)
1205 return true;
1206
1207 fColumnsSortedByPageSize.erase(itr);
1208
1209 if (newWritePageSize < elem.fCurrentPageSize) {
1210 // Page got smaller
1211 fCurrentAllocatedBytes -= elem.fCurrentPageSize - newWritePageSize;
1212 elem.fCurrentPageSize = newWritePageSize;
1213 fColumnsSortedByPageSize.insert(elem);
1214 return true;
1215 }
1216
1217 // Page got larger, we may need to make space available
1218 const auto diffBytes = newWritePageSize - elem.fCurrentPageSize;
1219 if (!TryEvict(diffBytes, elem.fCurrentPageSize)) {
1220 // Don't change anything, let the calling column flush itself
1221 // TODO(jblomer): we may consider skipping the column in TryEvict and thus avoiding erase+insert
1222 fColumnsSortedByPageSize.insert(elem);
1223 return false;
1224 }
1225 fCurrentAllocatedBytes += diffBytes;
1226 elem.fCurrentPageSize = newWritePageSize;
1227 fColumnsSortedByPageSize.insert(elem);
1228 return true;
1229}
1230
1231//------------------------------------------------------------------------------
1232
1234 : RPageStorage(name), fOptions(options.Clone()), fWritePageMemoryManager(options.GetPageBufferBudget())
1235{
1237}
1238
1240
1242{
1243 assert(config.fPage);
1244 assert(config.fElement);
1245 assert(config.fBuffer);
1246
1247 unsigned char *pageBuf = reinterpret_cast<unsigned char *>(config.fPage->GetBuffer());
1248 bool isAdoptedBuffer = true;
1249 auto nBytesPacked = config.fPage->GetNBytes();
1250 auto nBytesChecksum = config.fWriteChecksum * kNBytesPageChecksum;
1251
1252 if (!config.fElement->IsMappable()) {
1253 nBytesPacked = config.fElement->GetPackedSize(config.fPage->GetNElements());
1254 pageBuf = new unsigned char[nBytesPacked];
1255 isAdoptedBuffer = false;
1256 config.fElement->Pack(pageBuf, config.fPage->GetBuffer(), config.fPage->GetNElements());
1257 }
1258 auto nBytesZipped = nBytesPacked;
1259
1260 if ((config.fCompressionSettings != 0) || !config.fElement->IsMappable() || !config.fAllowAlias ||
1261 config.fWriteChecksum) {
1262 nBytesZipped =
1263 ROOT::Internal::RNTupleCompressor::Zip(pageBuf, nBytesPacked, config.fCompressionSettings, config.fBuffer);
1264 if (!isAdoptedBuffer)
1265 delete[] pageBuf;
1266 pageBuf = reinterpret_cast<unsigned char *>(config.fBuffer);
1267 isAdoptedBuffer = true;
1268 }
1269
1270 R__ASSERT(isAdoptedBuffer);
1271
1272 RSealedPage sealedPage{pageBuf, nBytesZipped + nBytesChecksum, config.fPage->GetNElements(), config.fWriteChecksum};
1273 sealedPage.ChecksumIfEnabled();
1274
1275 return sealedPage;
1276}
1277
1280{
1281 const auto nBytes = page.GetNBytes() + GetWriteOptions().GetEnablePageChecksums() * kNBytesPageChecksum;
1282 if (fSealPageBuffer.size() < nBytes)
1283 fSealPageBuffer.resize(nBytes);
1284
1285 RSealPageConfig config;
1286 config.fPage = &page;
1287 config.fElement = &element;
1288 config.fCompressionSettings = GetWriteOptions().GetCompression();
1289 config.fWriteChecksum = GetWriteOptions().GetEnablePageChecksums();
1290 config.fAllowAlias = true;
1291 config.fBuffer = fSealPageBuffer.data();
1292
1293 return SealPage(config);
1294}
1295
1297{
1298 for (const auto &cb : fOnDatasetCommitCallbacks)
1299 cb(*this);
1300 return CommitDatasetImpl();
1301}
1302
1304{
1305 R__ASSERT(nElements > 0);
1306 const auto elementSize = columnHandle.fColumn->GetElement()->GetSize();
1307 const auto nBytes = elementSize * nElements;
1308 if (!fWritePageMemoryManager.TryUpdate(*columnHandle.fColumn, nBytes))
1309 return ROOT::Internal::RPage();
1310 return fPageAllocator->NewPage(elementSize, nElements);
1311}
1312
1313//------------------------------------------------------------------------------
1314
1315std::unique_ptr<ROOT::Internal::RPageSink>
1316ROOT::Internal::RPagePersistentSink::Create(std::string_view ntupleName, std::string_view location,
1317 const ROOT::RNTupleWriteOptions &options)
1318{
1319 if (ntupleName.empty()) {
1320 throw RException(R__FAIL("empty RNTuple name"));
1321 }
1322 if (location.empty()) {
1323 throw RException(R__FAIL("empty storage location"));
1324 }
1325 if (location.find("daos://") == 0) {
1326#ifdef R__ENABLE_DAOS
1327 return std::make_unique<ROOT::Experimental::Internal::RPageSinkDaos>(ntupleName, location, options);
1328#else
1329 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
1330#endif
1331 }
1332
1333 if (ROOT::StartsWith(location, "ntpl+s3+http://") || ROOT::StartsWith(location, "ntpl+s3+https://")) {
1334#ifdef R__ENABLE_S3
1335 return std::make_unique<ROOT::Experimental::Internal::RPageSinkS3>(ntupleName, location, options);
1336#else
1337 throw RException(R__FAIL("This RNTuple build does not support S3. Rebuild ROOT with the 'curl' "
1338 "cmake option enabled (-Dcurl=ON) to enable the S3 backend."));
1339#endif
1340 }
1341
1342 // Otherwise assume that the user wants us to create a file.
1343 return std::make_unique<ROOT::Internal::RPageSinkFile>(ntupleName, location, options);
1344}
1345
1347 const ROOT::RNTupleWriteOptions &options)
1348 : RPageSink(name, options)
1349{
1350}
1351
1353
1356{
1357 auto columnId = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
1358 RColumnDescriptorBuilder columnBuilder;
1359 columnBuilder.LogicalColumnId(columnId)
1360 .PhysicalColumnId(columnId)
1361 .FieldId(fieldId)
1363 .ValueRange(column.GetValueRange())
1364 .Type(column.GetType())
1365 .Index(column.GetIndex())
1368 // For late model extension, we assume that the primary column representation is the active one for the
1369 // deferred range. All other representations are suppressed.
1370 if (column.GetFirstElementIndex() > 0 && column.GetRepresentationIndex() > 0)
1371 columnBuilder.SetSuppressedDeferred();
1372 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1373 return ColumnHandle_t{columnId, &column};
1374}
1375
1377 ROOT::NTupleSize_t firstEntry)
1378{
1379 if (fIsInitialized) {
1380 for (const auto &field : changeset.fAddedFields) {
1381 if (field->GetStructure() == ENTupleStructure::kStreamer) {
1382 throw ROOT::RException(R__FAIL("a Model cannot be extended with Streamer fields"));
1383 }
1384 }
1385 }
1386
1387 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1388
1389 if (descriptor.GetNLogicalColumns() > descriptor.GetNPhysicalColumns()) {
1390 // If we already have alias columns, add an offset to the alias columns so that the new physical columns
1391 // of the changeset follow immediately the already existing physical columns
1392 auto getNColumns = [](const ROOT::RFieldBase &f) -> std::size_t {
1393 const auto &reps = f.GetColumnRepresentatives();
1394 if (reps.empty())
1395 return 0;
1396 return reps.size() * reps[0].size();
1397 };
1398 std::uint32_t nNewPhysicalColumns = 0;
1399 for (auto f : changeset.fAddedFields) {
1400 nNewPhysicalColumns += getNColumns(*f);
1401 for (const auto &descendant : *f)
1402 nNewPhysicalColumns += getNColumns(descendant);
1403 }
1404 fDescriptorBuilder.ShiftAliasColumns(nNewPhysicalColumns);
1405 }
1406
1407 auto addField = [&](ROOT::RFieldBase &f) {
1408 auto fieldId = descriptor.GetNFields();
1409 fDescriptorBuilder.AddField(f, fieldId);
1410 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1411 f.SetOnDiskId(fieldId);
1412 ROOT::Internal::CallConnectPageSinkOnField(f, *this, firstEntry); // issues in turn calls to `AddColumn()`
1413 };
1414 auto addProjectedField = [&](ROOT::RFieldBase &f) {
1415 auto fieldId = descriptor.GetNFields();
1416 auto sourceFieldId =
1418 fDescriptorBuilder.AddField(f, fieldId);
1419 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1420 fDescriptorBuilder.AddFieldProjection(sourceFieldId, fieldId);
1421 f.SetOnDiskId(fieldId);
1422 for (const auto &source : descriptor.GetColumnIterable(sourceFieldId)) {
1423 auto targetId = descriptor.GetNLogicalColumns();
1424 RColumnDescriptorBuilder columnBuilder;
1425 columnBuilder.LogicalColumnId(targetId)
1426 .PhysicalColumnId(source.GetLogicalId())
1427 .FieldId(fieldId)
1428 .BitsOnStorage(source.GetBitsOnStorage())
1429 .ValueRange(source.GetValueRange())
1430 .Type(source.GetType())
1431 .Index(source.GetIndex())
1432 .RepresentationIndex(source.GetRepresentationIndex());
1433 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1434 }
1435 };
1436
1437 R__ASSERT(firstEntry >= fPrevClusterNEntries);
1438 const auto nColumnsBeforeUpdate = descriptor.GetNPhysicalColumns();
1439 for (auto f : changeset.fAddedFields) {
1440 addField(*f);
1441 for (auto &descendant : *f)
1442 addField(descendant);
1443 }
1444 for (auto f : changeset.fAddedProjectedFields) {
1445 addProjectedField(*f);
1446 for (auto &descendant : *f)
1447 addProjectedField(descendant);
1448 }
1449
1450 const auto nColumns = descriptor.GetNPhysicalColumns();
1451 fOpenColumnRanges.reserve(fOpenColumnRanges.size() + (nColumns - nColumnsBeforeUpdate));
1452 fOpenPageRanges.reserve(fOpenPageRanges.size() + (nColumns - nColumnsBeforeUpdate));
1453 for (ROOT::DescriptorId_t i = nColumnsBeforeUpdate; i < nColumns; ++i) {
1455 columnRange.SetPhysicalColumnId(i);
1456 // We set the first element index in the current cluster to the first element that is part of a materialized page
1457 // (i.e., that is part of a page list). For columns created during late model extension, however, the column range
1458 // is fixed up as needed by `RClusterDescriptorBuilder::AddExtendedColumnRanges()` on read back.
1459 columnRange.SetFirstElementIndex(descriptor.GetColumnDescriptor(i).GetFirstElementIndex());
1460 columnRange.SetNElements(0);
1461 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1462 fOpenColumnRanges.emplace_back(columnRange);
1464 pageRange.SetPhysicalColumnId(i);
1465 fOpenPageRanges.emplace_back(std::move(pageRange));
1466 }
1467
1468 // Mapping of memory to on-disk column IDs usually happens during serialization of the ntuple header. If the
1469 // header was already serialized, this has to be done manually as it is required for page list serialization.
1470 if (fSerializationContext.GetHeaderSize() > 0)
1471 fSerializationContext.MapSchema(descriptor, /*forHeaderExtension=*/true);
1472}
1473
1475{
1476 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
1477 throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()"));
1478
1479 fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap());
1480}
1481
1483{
1484 fDescriptorBuilder.SetNTuple(fNTupleName, model.GetDescription());
1485 fDescriptorBuilder.SetVersionForWriting();
1486 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1487
1488 auto &fieldZero = ROOT::Internal::GetFieldZeroOfModel(model);
1489 fDescriptorBuilder.AddField(fieldZero, 0);
1490 fieldZero.SetOnDiskId(0);
1491 auto &projectedFields = ROOT::Internal::GetProjectedFieldsOfModel(model);
1492 projectedFields.GetFieldZero().SetOnDiskId(0);
1493
1494 ROOT::Internal::RNTupleModelChangeset initialChangeset{model};
1495 initialChangeset.fAddedFields.reserve(fieldZero.GetMutableSubfields().size());
1496 for (auto f : fieldZero.GetMutableSubfields())
1497 initialChangeset.fAddedFields.emplace_back(f);
1498 initialChangeset.fAddedProjectedFields.reserve(projectedFields.GetFieldZero().GetMutableSubfields().size());
1499 for (auto f : projectedFields.GetFieldZero().GetMutableSubfields())
1500 initialChangeset.fAddedProjectedFields.emplace_back(f);
1501 UpdateSchema(initialChangeset, 0U);
1502
1503 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1504 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1505 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1506 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1507
1508 fDescriptorBuilder.BeginHeaderExtension();
1509}
1510
1511std::unique_ptr<ROOT::RNTupleModel>
1513{
1514 // Create new descriptor
1515 fDescriptorBuilder.SetSchemaFromExisting(srcDescriptor);
1516 // This is needed to be able to use GetTypeNameForComparison()
1517 fDescriptorBuilder.SetVersionForWriting();
1518 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1519
1520 // Create column/page ranges
1521 const auto nColumns = descriptor.GetNPhysicalColumns();
1522 R__ASSERT(fOpenColumnRanges.empty() && fOpenPageRanges.empty());
1523 fOpenColumnRanges.reserve(nColumns);
1524 fOpenPageRanges.reserve(nColumns);
1525 for (ROOT::DescriptorId_t i = 0; i < nColumns; ++i) {
1526 const auto &column = descriptor.GetColumnDescriptor(i);
1528 columnRange.SetPhysicalColumnId(i);
1529 columnRange.SetFirstElementIndex(column.GetFirstElementIndex());
1530 columnRange.SetNElements(0);
1531 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1532 fOpenColumnRanges.emplace_back(columnRange);
1534 pageRange.SetPhysicalColumnId(i);
1535 fOpenPageRanges.emplace_back(std::move(pageRange));
1536 }
1537
1538 if (copyClusters) {
1539 // Clone and add all cluster descriptors
1540 R__ASSERT(srcDescriptor.GetNClusters() == srcDescriptor.GetNActiveClusters());
1541 for (const auto &cluster : srcDescriptor.GetActiveClusterIterable()) {
1542 auto nEntries = cluster.GetNEntries();
1543 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1544 R__ASSERT(fOpenColumnRanges[i].GetPhysicalColumnId() == i);
1545 if (!cluster.ContainsColumn(i)) // a cluster may not contain a column if that column is deferred
1546 break;
1547 const auto &columnRange = cluster.GetColumnRange(i);
1548 R__ASSERT(columnRange.GetPhysicalColumnId() == i);
1549 // TODO: properly handle suppressed columns (check MarkSuppressedColumnRange())
1550 fOpenColumnRanges[i].IncrementFirstElementIndex(columnRange.GetNElements());
1551 }
1552 fDescriptorBuilder.AddCluster(cluster.Clone());
1553 fPrevClusterNEntries += nEntries;
1554 }
1555 }
1556
1557 // Create model
1559 modelOpts.SetReconstructProjections(true);
1560 // We want to emulate unknown types to allow merging RNTuples containing types that we lack dictionaries for.
1561 modelOpts.SetEmulateUnknownTypes(true);
1562 auto model = descriptor.CreateModel(modelOpts);
1563 if (!copyClusters) {
1564 auto &projectedFields = ROOT::Internal::GetProjectedFieldsOfModel(*model);
1565 projectedFields.GetFieldZero().SetOnDiskId(model->GetConstFieldZero().GetOnDiskId());
1566 }
1567
1568 // Serialize header and init from it
1569 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1570 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1571 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1572 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1573
1574 fDescriptorBuilder.BeginHeaderExtension();
1575
1576 // mark this sink as initialized
1577 fIsInitialized = true;
1578
1579 return model;
1580}
1581
1584 std::span<const RColumnFormat> newRepresentation,
1585 std::uint64_t clusterOffset)
1586{
1587 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1588
1589 assert(&descriptor.GetFieldDescriptor(field.GetId()) == &field);
1590 assert(!field.IsProjectedField());
1591 assert(field.GetColumnCardinality() > 0);
1592 assert(!field.GetLogicalColumnIds().empty());
1593 assert(newRepresentation.size() == field.GetColumnCardinality());
1594
1595 const std::size_t firstPhysicalIndex = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
1596 const std::uint16_t reprIndex = field.GetLogicalColumnIds().size() / field.GetColumnCardinality();
1597
1598 fDescriptorBuilder.ShiftAliasColumns(newRepresentation.size());
1599
1600 std::uint16_t columnIndex = 0; // index into the representation
1601 for (auto columnRepr : newRepresentation) {
1602 std::size_t bitsOnStorage = columnRepr.fBitWidth;
1603 if (!bitsOnStorage) {
1604 const auto [rangeMin, rangeMax] = ROOT::Internal::RColumnElementBase::GetValidBitRange(columnRepr.fType);
1605 if (rangeMin != rangeMax) {
1606 throw ROOT::RException(R__FAIL("bit width must be given for columns of variable bit width"));
1607 }
1608 bitsOnStorage = rangeMin;
1609 }
1610
1611 const ROOT::DescriptorId_t firstReprColumnId = field.GetLogicalColumnIds()[columnIndex];
1612 const auto &firstReprColumnRange = fOpenColumnRanges.at(firstReprColumnId);
1613 const ROOT::DescriptorId_t columnId = firstPhysicalIndex + columnIndex;
1614 // NOTE: this is always non-negative because it's the sum of two unsigned integers.
1615 const std::uint64_t newReprFirstElemIndex = firstReprColumnRange.GetFirstElementIndex() + clusterOffset;
1616
1617 RColumnDescriptorBuilder columnBuilder;
1618 columnBuilder.LogicalColumnId(columnId)
1619 .PhysicalColumnId(columnId)
1620 .FieldId(field.GetId())
1621 .BitsOnStorage(bitsOnStorage)
1622 .Type(columnRepr.fType)
1623 .Index(columnIndex)
1624 .FirstElementIndex(newReprFirstElemIndex)
1625 .RepresentationIndex(reprIndex)
1626 .ValueRange(columnRepr.fValueRange);
1627 if (newReprFirstElemIndex)
1628 columnBuilder.SetSuppressedDeferred();
1629 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1630
1631 if (newReprFirstElemIndex != 0) {
1632 for (auto parentId = field.GetParentId(); parentId != ROOT::kInvalidDescriptorId;) {
1633 const ROOT::RFieldDescriptor &parent = descriptor.GetFieldDescriptor(parentId);
1636 fDescriptorBuilder.SetFeature(RNTupleDescriptor::kFeatureFlag_NestedDeferredColumns);
1637 break;
1638 }
1639 parentId = parent.GetParentId();
1640 }
1641 }
1642
1644 columnRange.SetPhysicalColumnId(columnId);
1645 columnRange.SetFirstElementIndex(firstReprColumnRange.GetFirstElementIndex());
1646 columnRange.SetNElements(0);
1647 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1648 fOpenColumnRanges.emplace_back(columnRange);
1649
1651 pageRange.SetPhysicalColumnId(columnId);
1652 fOpenPageRanges.emplace_back(std::move(pageRange));
1653
1654 fSerializationContext.MapPhysicalColumnId(columnId);
1655
1656 ++columnIndex;
1657 }
1658
1659 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1660
1661 return firstPhysicalIndex;
1662}
1663
1665 const ROOT::RFieldDescriptor &field,
1666 ROOT::DescriptorId_t physicalId)
1667{
1668 const auto &pointedColumn = desc.GetColumnDescriptor(physicalId);
1669 assert(!pointedColumn.IsAliasColumn());
1670 assert(field.IsProjectedField());
1671
1672 const auto columnId = fDescriptorBuilder.GetDescriptor().GetNLogicalColumns();
1673 RColumnDescriptorBuilder columnBuilder;
1674 columnBuilder.LogicalColumnId(columnId)
1675 .PhysicalColumnId(physicalId)
1676 .FieldId(field.GetId())
1677 .Type(pointedColumn.GetType())
1678 .Index(pointedColumn.GetIndex())
1679 .BitsOnStorage(pointedColumn.GetBitsOnStorage())
1680 .ValueRange(pointedColumn.GetValueRange())
1681 .FirstElementIndex(pointedColumn.GetFirstElementIndex())
1682 .RepresentationIndex(pointedColumn.GetRepresentationIndex());
1683 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1684
1685 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1686}
1687
1689{
1690 fOpenColumnRanges.at(columnHandle.fPhysicalId).SetIsSuppressed(true);
1691}
1692
1694{
1695 fOpenColumnRanges.at(columnHandle.fPhysicalId).IncrementNElements(page.GetNElements());
1696
1697 auto element = columnHandle.fColumn->GetElement();
1698 RPageStorage::RSealedPage sealedPage;
1699 {
1700 RNTupleAtomicTimer timer(fCounters->fTimeWallZip, fCounters->fTimeCpuZip);
1701 sealedPage = SealPage(page, *element);
1702 }
1703 fCounters->fSzZip.Add(page.GetNBytes());
1704
1706 pageInfo.SetNElements(page.GetNElements());
1707 pageInfo.SetLocator(CommitSealedPageImpl(columnHandle.fPhysicalId, sealedPage));
1708 pageInfo.SetHasChecksum(GetWriteOptions().GetEnablePageChecksums());
1709 fOpenPageRanges.at(columnHandle.fPhysicalId).GetPageInfos().emplace_back(pageInfo);
1710}
1711
1713 const RPageStorage::RSealedPage &sealedPage)
1714{
1715 fOpenColumnRanges.at(physicalColumnId).IncrementNElements(sealedPage.GetNElements());
1716
1718 pageInfo.SetNElements(sealedPage.GetNElements());
1719 pageInfo.SetLocator(CommitSealedPageImpl(physicalColumnId, sealedPage));
1720 pageInfo.SetHasChecksum(sealedPage.GetHasChecksum());
1721 fOpenPageRanges.at(physicalColumnId).GetPageInfos().emplace_back(pageInfo);
1722}
1723
1724std::vector<ROOT::RNTupleLocator>
1725ROOT::Internal::RPagePersistentSink::CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges,
1726 const std::vector<bool> &mask)
1727{
1728 std::vector<ROOT::RNTupleLocator> locators;
1729 locators.reserve(mask.size());
1730 std::size_t i = 0;
1731 for (auto &range : ranges) {
1732 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1733 if (mask[i++])
1734 locators.push_back(CommitSealedPageImpl(range.fPhysicalColumnId, *sealedPageIt));
1735 }
1736 }
1737 locators.shrink_to_fit();
1738 return locators;
1739}
1740
1741void ROOT::Internal::RPagePersistentSink::CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges)
1742{
1743 /// Used in the `originalPages` map
1744 struct RSealedPageLink {
1745 const RSealedPage *fSealedPage = nullptr; ///< Points to the first occurrence of a page with a specific checksum
1746 std::size_t fLocatorIdx = 0; ///< The index in the locator vector returned by CommitSealedPageVImpl()
1747 };
1748
1749 std::vector<bool> mask;
1750 // For every sealed page, stores the corresponding index in the locator vector returned by CommitSealedPageVImpl()
1751 std::vector<std::size_t> locatorIndexes;
1752 // Maps page checksums to the first sealed page with that checksum
1753 std::unordered_map<std::uint64_t, RSealedPageLink> originalPages;
1754 std::size_t iLocator = 0;
1755 for (auto &range : ranges) {
1756 const auto rangeSize = std::distance(range.fFirst, range.fLast);
1757 mask.reserve(mask.size() + rangeSize);
1758 locatorIndexes.reserve(locatorIndexes.size() + rangeSize);
1759
1760 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1761 if (!fFeatures.fCanMergePages || !fOptions->GetEnableSamePageMerging()) {
1762 mask.emplace_back(true);
1763 locatorIndexes.emplace_back(iLocator++);
1764 continue;
1765 }
1766 // Same page merging requires page checksums - this is checked in the write options
1767 R__ASSERT(sealedPageIt->GetHasChecksum());
1768
1769 const auto chk = sealedPageIt->GetChecksum().Unwrap();
1770 auto itr = originalPages.find(chk);
1771 if (itr == originalPages.end()) {
1772 originalPages.insert({chk, {&(*sealedPageIt), iLocator}});
1773 mask.emplace_back(true);
1774 locatorIndexes.emplace_back(iLocator++);
1775 continue;
1776 }
1777
1778 const auto *p = itr->second.fSealedPage;
1779 if ((sealedPageIt->GetDataSize() != p->GetDataSize()) ||
1780 (memcmp(sealedPageIt->GetBuffer(), p->GetBuffer(), p->GetDataSize()) != 0)) {
1781 mask.emplace_back(true);
1782 locatorIndexes.emplace_back(iLocator++);
1783 continue;
1784 }
1785
1786 mask.emplace_back(false);
1787 locatorIndexes.emplace_back(itr->second.fLocatorIdx);
1788 }
1789
1790 mask.shrink_to_fit();
1791 locatorIndexes.shrink_to_fit();
1792 }
1793
1794 auto locators = CommitSealedPageVImpl(ranges, mask);
1795 unsigned i = 0;
1796
1797 for (auto &range : ranges) {
1798 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1799 fOpenColumnRanges.at(range.fPhysicalColumnId).IncrementNElements(sealedPageIt->GetNElements());
1800
1802 pageInfo.SetNElements(sealedPageIt->GetNElements());
1803 pageInfo.SetLocator(locators[locatorIndexes[i++]]);
1804 pageInfo.SetHasChecksum(sealedPageIt->GetHasChecksum());
1805 fOpenPageRanges.at(range.fPhysicalColumnId).GetPageInfos().emplace_back(pageInfo);
1806 }
1807 }
1808}
1809
1812{
1813 RStagedCluster stagedCluster;
1814 stagedCluster.fNBytesWritten = StageClusterImpl();
1815 stagedCluster.fNEntries = nNewEntries;
1816
1817 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1818 RStagedCluster::RColumnInfo columnInfo;
1819 columnInfo.fCompressionSettings = fOpenColumnRanges[i].GetCompressionSettings().value();
1820 if (fOpenColumnRanges[i].IsSuppressed()) {
1821 assert(fOpenPageRanges[i].GetPageInfos().empty());
1822 columnInfo.fPageRange.SetPhysicalColumnId(i);
1823 columnInfo.fIsSuppressed = true;
1824 // We reset suppressed columns to the state they would have if they were active (not suppressed).
1825 fOpenColumnRanges[i].SetNElements(0);
1826 fOpenColumnRanges[i].SetIsSuppressed(false);
1827 } else {
1828 std::swap(columnInfo.fPageRange, fOpenPageRanges[i]);
1829 fOpenPageRanges[i].SetPhysicalColumnId(i);
1830
1831 columnInfo.fNElements = fOpenColumnRanges[i].GetNElements();
1832 fOpenColumnRanges[i].SetNElements(0);
1833 }
1834 stagedCluster.fColumnInfos.push_back(std::move(columnInfo));
1835 }
1836
1837 return stagedCluster;
1838}
1839
1841{
1842 for (const auto &cluster : clusters) {
1843 RClusterDescriptorBuilder clusterBuilder;
1844 clusterBuilder.ClusterId(fDescriptorBuilder.GetDescriptor().GetNActiveClusters())
1845 .FirstEntryIndex(fPrevClusterNEntries)
1846 .NEntries(cluster.fNEntries);
1847 for (const auto &columnInfo : cluster.fColumnInfos) {
1848 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1849 if (columnInfo.fIsSuppressed) {
1850 assert(columnInfo.fPageRange.GetPageInfos().empty());
1851 clusterBuilder.MarkSuppressedColumnRange(colId);
1852 } else {
1853 clusterBuilder.CommitColumnRange(colId, fOpenColumnRanges[colId].GetFirstElementIndex(),
1854 columnInfo.fCompressionSettings, columnInfo.fPageRange);
1855 fOpenColumnRanges[colId].IncrementFirstElementIndex(columnInfo.fNElements);
1856 }
1857 }
1858
1859 clusterBuilder.CommitSuppressedColumnRanges(fDescriptorBuilder.GetDescriptor()).ThrowOnError();
1860 for (const auto &columnInfo : cluster.fColumnInfos) {
1861 if (!columnInfo.fIsSuppressed)
1862 continue;
1863 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1864 // For suppressed columns, we need to reset the first element index to the first element of the next (upcoming)
1865 // cluster. This information has been determined for the committed cluster descriptor through
1866 // CommitSuppressedColumnRanges(), so we can use the information from the descriptor.
1867 const auto &columnRangeFromDesc = clusterBuilder.GetColumnRange(colId);
1868 fOpenColumnRanges[colId].SetFirstElementIndex(columnRangeFromDesc.GetFirstElementIndex() +
1869 columnRangeFromDesc.GetNElements());
1870 }
1871
1872 fDescriptorBuilder.AddCluster(clusterBuilder.MoveDescriptor().Unwrap());
1873 fPrevClusterNEntries += cluster.fNEntries;
1874 }
1875}
1876
1878{
1879 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1880
1881 const auto nClusters = descriptor.GetNActiveClusters();
1882 std::vector<ROOT::DescriptorId_t> physClusterIDs;
1883 physClusterIDs.reserve(nClusters);
1884 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1885 physClusterIDs.emplace_back(fSerializationContext.MapClusterId(i));
1886 }
1887
1888 auto szPageList =
1889 RNTupleSerializer::SerializePageList(nullptr, descriptor, physClusterIDs, fSerializationContext).Unwrap();
1890 auto bufPageList = MakeUninitArray<unsigned char>(szPageList);
1891 RNTupleSerializer::SerializePageList(bufPageList.get(), descriptor, physClusterIDs, fSerializationContext);
1892
1893 const auto clusterGroupId = descriptor.GetNClusterGroups();
1894 const auto locator = CommitClusterGroupImpl(bufPageList.get(), szPageList);
1896 cgBuilder.ClusterGroupId(clusterGroupId).PageListLocator(locator).PageListLength(szPageList);
1897 if (fNextClusterInGroup == nClusters) {
1898 cgBuilder.MinEntry(0).EntrySpan(0).NClusters(0);
1899 } else {
1900 const auto &firstClusterDesc = descriptor.GetClusterDescriptor(fNextClusterInGroup);
1901 const auto &lastClusterDesc = descriptor.GetClusterDescriptor(nClusters - 1);
1902 cgBuilder.MinEntry(firstClusterDesc.GetFirstEntryIndex())
1903 .EntrySpan(lastClusterDesc.GetFirstEntryIndex() + lastClusterDesc.GetNEntries() -
1904 firstClusterDesc.GetFirstEntryIndex())
1905 .NClusters(nClusters - fNextClusterInGroup);
1906 }
1907 std::vector<ROOT::DescriptorId_t> clusterIds;
1908 clusterIds.reserve(nClusters);
1909 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1910 clusterIds.emplace_back(i);
1911 }
1912 cgBuilder.AddSortedClusters(clusterIds);
1913 fDescriptorBuilder.AddClusterGroup(cgBuilder.MoveDescriptor().Unwrap());
1914 fSerializationContext.MapClusterGroupId(clusterGroupId);
1915
1916 fNextClusterInGroup = nClusters;
1917}
1918
1920 const RNTupleLink &attrAnchorInfo)
1921{
1922 using namespace ROOT::Internal::RNTupleAttributes;
1923
1924 RNTupleAttrSetDescriptorBuilder attrSetDescBuilder;
1925 auto attrSetDesc = attrSetDescBuilder.SchemaVersion(kSchemaVersionMajor, kSchemaVersionMinor)
1926 .AnchorLength(attrAnchorInfo.fLength)
1927 .AnchorLocator(attrAnchorInfo.fLocator)
1928 .Name(attrSetName)
1930 .Unwrap();
1931 fDescriptorBuilder.AddAttributeSet(std::move(attrSetDesc)).ThrowOnError();
1932}
1933
1935{
1936 if (!fInfosOfStreamerFields.empty()) {
1937 // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but
1938 // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging.
1939 for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) {
1940 if (etDesc.GetContentId() == EExtraTypeInfoIds::kStreamerInfo) {
1941 // The specification mandates that the type name for a kStreamerInfo should be empty and the type version
1942 // should be zero.
1943 R__ASSERT(etDesc.GetTypeName().empty());
1944 R__ASSERT(etDesc.GetTypeVersion() == 0);
1945 auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap();
1946 fInfosOfStreamerFields.merge(etInfo);
1947 }
1948 }
1949
1950 RExtraTypeInfoDescriptorBuilder extraInfoBuilder;
1952 .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields));
1953 fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap());
1954 }
1955
1956 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1957
1958 auto szFooter = RNTupleSerializer::SerializeFooter(nullptr, descriptor, fSerializationContext).Unwrap();
1959 auto bufFooter = MakeUninitArray<unsigned char>(szFooter);
1960 RNTupleSerializer::SerializeFooter(bufFooter.get(), descriptor, fSerializationContext);
1961
1962 return CommitDatasetImpl(bufFooter.get(), szFooter);
1963}
1964
1966{
1967 fMetrics = RNTupleMetrics(prefix);
1968 fCounters = std::make_unique<RCounters>(RCounters{
1969 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageCommitted", "", "number of pages committed to storage"),
1970 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szWritePayload", "B", "volume written for committed pages"),
1971 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szZip", "B", "volume before zipping"),
1972 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallWrite", "ns", "wall clock time spent writing"),
1973 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallZip", "ns", "wall clock time spent compressing"),
1974 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuWrite", "ns", "CPU time spent writing"),
1975 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuZip", "ns",
1976 "CPU time spent compressing")});
1977}
fBuffer
#define R__FORWARD_ERROR(res)
Short-hand to return an RResult<T> in an error state (i.e. after checking)
Definition RError.hxx:326
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:322
#define f(i)
Definition RSha256.hxx:104
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:139
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t mask
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:142
#define _(A, B)
Definition cfortran.h:112
A thread-safe integral performance counter.
A metric element that computes its floating point value from other counters.
std::int64_t GetValueAsInt() const override
A collection of Counter objects with a name, a unit, and a description.
const RNTuplePerfCounter * GetLocalCounter(std::string_view name) const
Searches counters registered in this object only. Returns nullptr if name is not found.
Record wall time and CPU time between construction and destruction.
A helper class for piece-wise construction of an RClusterDescriptor.
RResult< void > MarkSuppressedColumnRange(ROOT::DescriptorId_t physicalId)
Books the given column ID as being suppressed in this cluster.
RResult< void > CommitColumnRange(ROOT::DescriptorId_t physicalId, std::uint64_t firstElementIndex, std::uint32_t compressionSettings, const RClusterDescriptor::RPageRange &pageRange)
RClusterDescriptorBuilder & NEntries(std::uint64_t nEntries)
RResult< void > CommitSuppressedColumnRanges(const RNTupleDescriptor &desc)
Sets the first element index and number of elements for all the suppressed column ranges.
RResult< RClusterDescriptor > MoveDescriptor()
Move out the full cluster descriptor including page locations.
const RClusterDescriptor::RColumnRange & GetColumnRange(ROOT::DescriptorId_t physicalId)
RClusterDescriptorBuilder & ClusterId(ROOT::DescriptorId_t clusterId)
A helper class for piece-wise construction of an RClusterGroupDescriptor.
RClusterGroupDescriptorBuilder & EntrySpan(std::uint64_t entrySpan)
RClusterGroupDescriptorBuilder & PageListLocator(const RNTupleLocator &pageListLocator)
RClusterGroupDescriptorBuilder & PageListLength(std::uint64_t pageListLength)
RClusterGroupDescriptorBuilder & MinEntry(std::uint64_t minEntry)
void AddSortedClusters(const std::vector< ROOT::DescriptorId_t > &clusterIds)
RResult< RClusterGroupDescriptor > MoveDescriptor()
RClusterGroupDescriptorBuilder & ClusterGroupId(ROOT::DescriptorId_t clusterGroupId)
RClusterGroupDescriptorBuilder & NClusters(std::uint32_t nClusters)
An in-memory subset of the packed and compressed pages of a cluster.
Definition RCluster.hxx:147
std::unordered_set< ROOT::DescriptorId_t > ColumnSet_t
Definition RCluster.hxx:149
const ColumnSet_t & GetAvailPhysicalColumns() const
Definition RCluster.hxx:191
const ROnDiskPage * GetOnDiskPage(const ROnDiskPage::Key &key) const
Definition RCluster.cxx:30
ROOT::DescriptorId_t GetId() const
Definition RCluster.hxx:190
A helper class for piece-wise construction of an RColumnDescriptor.
RColumnDescriptorBuilder & SetSuppressedDeferred()
RColumnDescriptorBuilder & LogicalColumnId(ROOT::DescriptorId_t logicalColumnId)
RColumnDescriptorBuilder & FieldId(ROOT::DescriptorId_t fieldId)
RColumnDescriptorBuilder & BitsOnStorage(std::uint16_t bitsOnStorage)
RColumnDescriptorBuilder & ValueRange(double min, double max)
RColumnDescriptorBuilder & Type(ROOT::ENTupleColumnType type)
RColumnDescriptorBuilder & PhysicalColumnId(ROOT::DescriptorId_t physicalColumnId)
RColumnDescriptorBuilder & FirstElementIndex(std::uint64_t firstElementIdx)
RColumnDescriptorBuilder & Index(std::uint32_t index)
RColumnDescriptorBuilder & RepresentationIndex(std::uint16_t representationIndex)
RResult< RColumnDescriptor > MoveDescriptor()
Attempt to make a column descriptor.
A column element encapsulates the translation between basic C++ types and their column representation...
virtual bool IsMappable() const
Derived, typed classes tell whether the on-storage layout is bitwise identical to the memory layout.
static std::pair< std::uint16_t, std::uint16_t > GetValidBitRange(ROOT::ENTupleColumnType type)
Most types have a fixed on-disk bit width.
virtual void Unpack(void *destination, const void *source, std::size_t count) const
If the on-storage layout and the in-memory layout differ, unpacking creates a memory page from an on-...
virtual RIdentifier GetIdentifier() const =0
virtual void Pack(void *destination, const void *source, std::size_t count) const
If the on-storage layout and the in-memory layout differ, packing creates an on-disk page from an in-...
std::size_t GetPackedSize(std::size_t nElements=1U) const
A column is a storage-backed array of a simple, fixed-size type, from which pages can be mapped into ...
Definition RColumn.hxx:37
std::optional< std::pair< double, double > > GetValueRange() const
Definition RColumn.hxx:343
std::uint16_t GetRepresentationIndex() const
Definition RColumn.hxx:349
ROOT::Internal::RColumnElementBase * GetElement() const
Definition RColumn.hxx:336
ROOT::ENTupleColumnType GetType() const
Definition RColumn.hxx:337
ROOT::NTupleSize_t GetFirstElementIndex() const
Definition RColumn.hxx:351
std::size_t GetWritePageCapacity() const
Definition RColumn.hxx:358
ROOT::DescriptorId_t GetOnDiskId() const
Definition RColumn.hxx:350
std::uint16_t GetBitsOnStorage() const
Definition RColumn.hxx:338
std::uint32_t GetIndex() const
Definition RColumn.hxx:348
A helper class for piece-wise construction of an RExtraTypeInfoDescriptor.
RResult< RExtraTypeInfoDescriptor > MoveDescriptor()
RExtraTypeInfoDescriptorBuilder & ContentId(EExtraTypeInfoIds contentId)
RExtraTypeInfoDescriptorBuilder & Content(const std::string &content)
RNTupleAttrSetDescriptorBuilder & Name(std::string_view name)
RNTupleAttrSetDescriptorBuilder & SchemaVersion(std::uint16_t major, std::uint16_t minor)
RResult< ROOT::RNTupleAttrSetDescriptor > MoveDescriptor()
Attempt to make an AttributeSet descriptor.
RNTupleAttrSetDescriptorBuilder & AnchorLocator(const RNTupleLocator &loc)
RNTupleAttrSetDescriptorBuilder & AnchorLength(std::uint32_t length)
static std::size_t Zip(const void *from, std::size_t nbytes, int compression, void *to)
Returns the size of the compressed data, written into the provided output buffer.
static void Unzip(const void *from, size_t nbytes, size_t dataLen, void *to)
The nbytes parameter provides the size ls of the from buffer.
static unsigned int GetClusterBunchSize(const RNTupleReadOptions &options)
static unsigned int GetClusterGroupBudget(const RNTupleReadOptions &options)
static std::uint32_t SerializeXxHash3(const unsigned char *data, std::uint64_t length, std::uint64_t &xxhash3, void *buffer)
Writes a XxHash-3 64bit checksum of the byte range given by data and length.
static RResult< void > DeserializePageList(const void *buffer, std::uint64_t bufSize, ROOT::DescriptorId_t clusterGroupId, RNTupleDescriptor &desc, EDescriptorDeserializeMode mode)
static RResult< StreamerInfoMap_t > DeserializeStreamerInfos(const std::string &extraTypeInfoContent)
static RResult< void > VerifyXxHash3(const unsigned char *data, std::uint64_t length, std::uint64_t &xxhash3)
Expects an xxhash3 checksum in the 8 bytes following data + length and verifies it.
static RResult< std::uint32_t > SerializePageList(void *buffer, const RNTupleDescriptor &desc, std::span< ROOT::DescriptorId_t > physClusterIDs, const RContext &context)
static RResult< std::uint32_t > SerializeFooter(void *buffer, const RNTupleDescriptor &desc, const RContext &context)
static std::uint32_t DeserializeUInt64(const void *buffer, std::uint64_t &val)
static RResult< RContext > SerializeHeader(void *buffer, const RNTupleDescriptor &desc)
static std::string SerializeStreamerInfos(const StreamerInfoMap_t &infos)
A memory region that contains packed and compressed pages.
Definition RCluster.hxx:98
void Register(const ROnDiskPage::Key &key, const ROnDiskPage &onDiskPage)
Inserts information about a page stored in fMemory.
Definition RCluster.hxx:115
A page as being stored on disk, that is packed and compressed.
Definition RCluster.hxx:40
Uses standard C++ memory allocation for the column data pages.
Abstract interface to allocate and release pages.
virtual RPage NewPage(std::size_t elementSize, std::size_t nElements)=0
Reserves memory large enough to hold nElements of the given size.
RStagedCluster StageCluster(ROOT::NTupleSize_t nNewEntries) final
Stage the current cluster and create a new one for the following data.
void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) override
Incorporate incremental changes to the model into the ntuple descriptor.
void CommitSealedPage(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) final
Write a preprocessed page to storage. The column must have been added before.
std::unique_ptr< RNTupleModel > InitFromDescriptor(const ROOT::RNTupleDescriptor &descriptor, bool copyClusters)
Initialize sink based on an existing descriptor and fill into the descriptor builder,...
void UpdateExtraTypeInfo(const ROOT::RExtraTypeInfoDescriptor &extraTypeInfo) final
Adds an extra type information record to schema.
void CommitAttributeSet(std::string_view attrSetName, const RNTupleLink &attrAnchorInfo) final
Adds the given anchor information (name + locator) into the main RNTuple's descriptor as an attribute...
ColumnHandle_t AddColumn(ROOT::DescriptorId_t fieldId, ROOT::Internal::RColumn &column) final
Register a new column.
virtual std::vector< RNTupleLocator > CommitSealedPageVImpl(std::span< RPageStorage::RSealedPageGroup > ranges, const std::vector< bool > &mask)
Vector commit of preprocessed pages.
RPagePersistentSink(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options)
void CommitSuppressedColumn(ColumnHandle_t columnHandle) final
Commits a suppressed column for the current cluster.
void AddAliasColumn(const ROOT::RNTupleDescriptor &desc, const ROOT::RFieldDescriptor &field, ROOT::DescriptorId_t physicalId)
Adds a new alias column pointing to an existing column with the given physical id to the given field.
void CommitStagedClusters(std::span< RStagedCluster > clusters) final
Commit staged clusters, logically appending them to the ntuple descriptor.
static std::unique_ptr< RPageSink > Create(std::string_view ntupleName, std::string_view location, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Guess the concrete derived page source from the location.
void CommitPage(ColumnHandle_t columnHandle, const ROOT::Internal::RPage &page) final
Write a page to the storage. The column must have been added before.
virtual void InitImpl(unsigned char *serializedHeader, std::uint32_t length)=0
void CommitClusterGroup() final
Write out the page locations (page list envelope) for all the committed clusters since the last call ...
void CommitSealedPageV(std::span< RPageStorage::RSealedPageGroup > ranges) final
Write a vector of preprocessed pages to storage. The corresponding columns must have been added befor...
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSink.
ROOT::DescriptorId_t AddColumnRepresentation(const ROOT::RFieldDescriptor &field, std::span< const ROOT::Internal::RColumnFormat > newRepresentation, std::uint64_t clusterOffset)
Adds a new column representation to the given field.
Reference to a page stored in the page pool.
Abstract interface to write data into an ntuple.
RNTupleLink CommitDataset()
Run the registered callbacks and finalize the current cluster and the entrire data set.
virtual ROOT::Internal::RPage ReservePage(ColumnHandle_t columnHandle, std::size_t nElements)
Get a new, empty page for the given column that can be filled with up to nElements; nElements must be...
RSealedPage SealPage(const ROOT::Internal::RPage &page, const ROOT::Internal::RColumnElementBase &element)
Helper for streaming a page.
RPageSink(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options)
void Insert(ROOT::DescriptorId_t physicalColumnId, ROOT::Internal::RColumnElementBase::RIdentifier elementId)
ROOT::Internal::RCluster::ColumnSet_t ToColumnSet() const
void Erase(ROOT::DescriptorId_t physicalColumnId, ROOT::Internal::RColumnElementBase::RIdentifier elementId)
Either a shared or an exclusive lock guard.
An RAII wrapper used for the writable access to RPageSource::fDescriptor. See GetSharedDescriptorGuar...
RAnyDescriptorGuard FindNextClusterId(ROOT::DescriptorId_t clusterId, ROOT::DescriptorId_t &nextId)
Uses FindClusterId to search for the cluster with the entry index following the last entry index of t...
void LoadStructure()
Loads header and footer without decompressing or deserializing them.
virtual ROOT::Internal::RPageRef LoadPage(ColumnHandle_t columnHandle, ROOT::NTupleSize_t globalIndex)
Allocates and fills a page that contains the index-th element.
void LoadAllPageLists()
Load all remaining cluster details.
void RegisterStreamerInfos()
Builds the streamer info records from the descriptor's extra type info section.
ROOT::NTupleSize_t GetNElements(ROOT::DescriptorId_t physicalColumnId)
RAnyDescriptorGuard EnsureClusterDetails(DescriptorId_t cgId, RAnyDescriptorGuard descGuard)
Ensures that fDescriptor has cluster details loaded for the given cluster group ID.
void Attach(ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode mode=ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode::kForReading)
Open the physical storage container and deserialize header and footer.
ColumnHandle_t AddColumn(ROOT::DescriptorId_t fieldId, ROOT::Internal::RColumn &column) override
Register a new column.
void UnzipCluster(ROOT::Internal::RCluster *cluster)
Parallel decompression and unpacking of the pages in the given cluster.
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSource.
ROOT::NTupleSize_t GetNEntries()
ROOT::Internal::RPageRef LoadZeroPage(ColumnHandle_t columnHandle, const RPageSummary &pageSummary)
void UpdateLastUsedCluster(ROOT::DescriptorId_t clusterId)
Does nothing if fLastUsedCluster == clusterId.
void UnpinCluster(ROOT::DescriptorId_t clusterId)
Allows the given cluster to be evicted from the cluster pool and page pool.
ROOT::Internal::RPageRef LoadPageFromSummary(ColumnHandle_t columnHandle, const RPageSummary &pageSummary)
void PinCluster(ROOT::DescriptorId_t clusterId)
Instructs the cluster pool and page pool to consider the given cluster as active (should stay cached)...
void DropColumn(ColumnHandle_t columnHandle) override
Unregisters a column.
RAnyDescriptorGuard FindClusterId(ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t index, ROOT::DescriptorId_t &cid)
Returns a shared descriptor guard to ensure that the returned cluster id is useable,...
void LoadSealedPage(ROOT::DescriptorId_t physicalColumnId, RNTupleLocalIndex localIndex, RSealedPage &sealedPage)
Read the packed and compressed bytes of a page into the memory buffer provided by sealedPage.
void LoadPageList(DescriptorId_t clusterGroupId, const RExclDescriptorGuard &exclGuard)
RPageSource(std::string_view ntupleName, const ROOT::RNTupleReadOptions &fOptions)
DescriptorId_t FindClusterGroupId(DescriptorId_t clusterId) const
Uses binary search in fCumulativeClusterCounts to determine the cluster group that the cluster ID bel...
void PrepareLoadCluster(const ROOT::Internal::RCluster::RKey &clusterKey, ROOT::Internal::ROnDiskPageMap &pageZeroMap, const std::function< void(ROOT::DescriptorId_t, ROOT::NTupleSize_t, const ROOT::RClusterDescriptor::RPageInfo &)> &perPageFunc)
Prepare a page range read for the column set in clusterKey.
void SetEntryRange(const REntryRange &range)
Promise to only read from the given entry range.
std::unique_ptr< RPageSource > Clone() const
Open the same storage multiple time, e.g.
static std::unique_ptr< RPageSource > Create(std::string_view ntupleName, std::string_view location, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Guess the concrete derived page source from the file name (location)
static RResult< ROOT::Internal::RPage > UnsealPage(const RSealedPage &sealedPage, const ROOT::Internal::RColumnElementBase &element, ROOT::Internal::RPageAllocator &pageAlloc)
Helper for unstreaming a page.
Common functionality of an ntuple storage for both reading and writing.
RPageStorage(std::string_view name)
Stores information about the cluster in which this page resides.
Definition RPage.hxx:52
A page is a slice of a column that is mapped into memory.
Definition RPage.hxx:43
std::uint32_t GetNElements() const
Definition RPage.hxx:120
std::size_t GetNBytes() const
The space taken by column elements in the buffer.
Definition RPage.hxx:111
void * GrowUnchecked(std::uint32_t nElements)
Increases the number elements in the page.
Definition RPage.hxx:149
void * GetBuffer() const
Definition RPage.hxx:142
static const void * GetPageZeroBuffer()
Return a pointer to the page zero buffer used if there is no on-disk data for a particular deferred c...
Definition RPage.cxx:22
void SetWindow(const ROOT::NTupleSize_t rangeFirst, const RClusterInfo &clusterInfo)
Seek the page to a certain position of the column.
Definition RPage.hxx:157
const ROOT::RFieldBase * GetSourceField(const ROOT::RFieldBase *target) const
bool TryEvict(std::size_t targetAvailableSize, std::size_t pageSizeLimit)
Flush columns in order of allocated write page size until the sum of all write page allocations leave...
bool TryUpdate(ROOT::Internal::RColumn &column, std::size_t newWritePageSize)
Try to register the new write page size for the given column.
The window of element indexes of a particular column in a particular cluster.
void SetCompressionSettings(std::optional< std::uint32_t > comp)
void SetPhysicalColumnId(ROOT::DescriptorId_t id)
void SetFirstElementIndex(ROOT::NTupleSize_t idx)
Records the partition of data into pages for a particular column in a particular cluster.
void SetPhysicalColumnId(ROOT::DescriptorId_t id)
Metadata for RNTuple clusters.
ROOT::NTupleSize_t GetNEntries() const
const RColumnRange & GetColumnRange(ROOT::DescriptorId_t physicalId) const
ROOT::NTupleSize_t GetFirstEntryIndex() const
bool HasClusterDetails() const
A cluster group is loaded in two stages.
const std::vector< ROOT::DescriptorId_t > & GetClusterIds() const
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Field specific extra type information from the header / extenstion header.
const std::string & GetContent() const
EExtraTypeInfoIds GetContentId() const
A field translates read and write calls from/to underlying columns to/from tree values.
ROOT::DescriptorId_t GetOnDiskId() const
Metadata stored for every field of an RNTuple.
ROOT::DescriptorId_t GetId() const
const std::vector< ROOT::DescriptorId_t > & GetLogicalColumnIds() const
ROOT::ENTupleStructure GetStructure() const
std::uint32_t GetColumnCardinality() const
ROOT::DescriptorId_t GetParentId() const
The on-storage metadata of an RNTuple.
const RClusterGroupDescriptor & GetClusterGroupDescriptor(ROOT::DescriptorId_t clusterGroupId) const
const RColumnDescriptor & GetColumnDescriptor(ROOT::DescriptorId_t columnId) const
const RFieldDescriptor & GetFieldDescriptor(ROOT::DescriptorId_t fieldId) const
RClusterDescriptorIterable GetActiveClusterIterable() const
RClusterGroupDescriptorIterable GetClusterGroupIterable() const
std::size_t GetNActiveClusters() const
ROOT::NTupleSize_t R__DEPRECATED(6, 46, "This function is ill-conceived in the descriptor " "as not all cluster descriptors may be present. This interface is no longer publicly exposed.") GetNElements(ROOT ROOT::DescriptorId_t GetFieldZeroId() const
Returns the logical parent of all top-level RNTuple data fields.
std::size_t GetNClusters() const
@ kFeatureFlag_NestedDeferredColumns
Signals that the RNTuple contains at least one deferred column that is part of a collection and was e...
const RClusterDescriptor & GetClusterDescriptor(ROOT::DescriptorId_t clusterId) const
std::size_t GetNClusterGroups() const
Addresses a column element or field item relative to a particular cluster, instead of a global NTuple...
ROOT::NTupleSize_t GetIndexInCluster() const
ROOT::DescriptorId_t GetClusterId() const
std::uint64_t GetNBytesOnStorage() const
ELocatorType GetType() const
For non-disk locators, the value for the Type field.
The RNTupleModel encapulates the schema of an RNTuple.
Common user-tunable settings for reading RNTuples.
Common user-tunable settings for storing RNTuples.
void ThrowOnError()
Short-hand method to throw an exception in the case of errors.
Definition RError.hxx:312
The class is used as a return type for operations that can fail; wraps a value of type T or an RError...
Definition RError.hxx:222
ROOT::RFieldZero & GetFieldZeroOfModel(RNTupleModel &model)
RResult< void > EnsureValidNameForRNTuple(std::string_view name, std::string_view where)
Check whether a given string is a valid name according to the RNTuple specification.
RProjectedFields & GetProjectedFieldsOfModel(RNTupleModel &model)
std::unique_ptr< RColumnElementBase > GenerateColumnElement(std::type_index inMemoryType, ROOT::ENTupleColumnType onDiskType)
void CallConnectPageSinkOnField(RFieldBase &, ROOT::Internal::RPageSink &, ROOT::NTupleSize_t firstEntry=0)
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
constexpr NTupleSize_t kInvalidNTupleIndex
bool StartsWith(std::string_view string, std::string_view prefix)
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
constexpr DescriptorId_t kInvalidDescriptorId
The identifiers that specifies the content of a (partial) cluster.
Definition RCluster.hxx:151
ROOT::DescriptorId_t fClusterId
Definition RCluster.hxx:152
Every concrete RColumnElement type is identified by its on-disk type (column type) and the in-memory ...
The incremental changes to a RNTupleModel
std::vector< ROOT::RFieldBase * > fAddedProjectedFields
Points to the projected fields in fModel that were added as part of an updater transaction.
std::vector< ROOT::RFieldBase * > fAddedFields
Points to the fields in fModel that were added as part of an updater transaction.
On-disk pages within a page source are identified by the column and page number.
Definition RCluster.hxx:50
Default I/O performance counters that get registered in fMetrics.
Parameters for the SealPage() method.
bool fWriteChecksum
Adds a 8 byte little-endian xxhash3 checksum to the page payload.
std::uint32_t fCompressionSettings
Compression algorithm and level to apply.
void * fBuffer
Location for sealed output. The memory buffer has to be large enough.
const ROOT::Internal::RPage * fPage
Input page to be sealed.
bool fAllowAlias
If false, the output buffer must not point to the input page buffer, which would otherwise be an opti...
const ROOT::Internal::RColumnElementBase * fElement
Corresponds to the page's elements, for size calculation etc.
Cluster that was staged, but not yet logically appended to the RNTuple.
std::vector< RColumnInfo > fColumnInfos
Default I/O performance counters that get registered in fMetrics
Used in SetEntryRange / GetEntryRange.
bool IntersectsWith(const ROOT::RClusterDescriptor &clusterDesc) const
Returns true if the given cluster has entries within the entry range.
Summarizes meta-data necessary to load a certain page. Used by LoadPageFromSummary().
ROOT::RClusterDescriptor::RPageInfoExtended fPageInfo
Location of the page on disk.
std::uint64_t fColumnOffset
The first element number of the page's column in the given cluster.
A sealed page contains the bytes of a page as written to storage (packed & compressed).
RResult< void > VerifyChecksumIfEnabled() const
void SetNElements(std::uint32_t nElements)
RResult< std::uint64_t > GetChecksum() const
Returns a failure if the sealed page has no checksum.
void SetBufferSize(std::size_t bufferSize)
bool operator>(const RColumnInfo &other) const
Information about a single page in the context of a cluster's page range.
void SetLocator(const RNTupleLocator &locator)
const RNTupleLocator & GetLocator() const