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}
272
274{
275 const auto &cgDesc = exclGuard->GetClusterGroupDescriptor(clusterGroupId);
276 if (cgDesc.HasClusterDetails())
277 return;
278
279 auto buffer =
280 MakeUninitArray<unsigned char>(cgDesc.GetPageListLength() + cgDesc.GetPageListLocator().GetNBytesOnStorage());
281 auto zipBuffer = buffer.get() + cgDesc.GetPageListLength();
282 LoadPageListImpl(cgDesc.GetPageListLocator(), zipBuffer);
283 RNTupleDecompressor::Unzip(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(), cgDesc.GetPageListLength(),
284 buffer.get());
285 RNTupleSerializer::DeserializePageList(buffer.get(), cgDesc.GetPageListLength(), cgDesc.GetId(), *exclGuard,
286 fDeserializationMode);
287}
288
289std::unique_ptr<ROOT::Internal::RPageSource> ROOT::Internal::RPageSource::Clone() const
290{
291 auto clone = CloneImpl();
292 if (fIsAttached) {
293 clone->GetExclDescriptorGuard().MoveIn(GetSharedDescriptorGuard()->Clone());
294 clone->fHasStructure = true;
295 clone->fIsAttached = true;
296 clone->fDeserializationMode = fDeserializationMode;
297 clone->fCumulativeClusterCounts = fCumulativeClusterCounts;
298 }
299 return clone;
300}
301
304{
305 assert(descGuard.IsValid());
306 if ((cgId == kInvalidDescriptorId) || descGuard->GetClusterGroupDescriptor(cgId).HasClusterDetails())
307 return descGuard;
308
309 descGuard.Release();
310
311 auto exclGuard = GetExclDescriptorGuard();
312
313 if (exclGuard->GetClusterGroupDescriptor(cgId).HasClusterDetails()) {
314 // unlikely, but between releasing the shared guard and acquiring the exclusive guard, the cluster details
315 // may have appeared
316 return exclGuard;
317 }
318
319 LoadPageList(cgId, exclGuard);
320
321 return exclGuard;
322}
323
325{
326 // We use `cluster + 1` because fCumulativeClusterCounts stores the cumulative _number_ of clusters and not
327 // the last cluster IDs.
328 auto iter = std::lower_bound(fCumulativeClusterCounts.begin(), fCumulativeClusterCounts.end(), clusterId + 1);
329 if (iter == fCumulativeClusterCounts.end())
331 return std::distance(fCumulativeClusterCounts.begin(), iter);
332}
333
335{
336 return GetSharedDescriptorGuard()->GetNEntries();
337}
338
340{
341 auto sharedGuard = GetSharedDescriptorGuard();
342 if (sharedGuard->GetNClusters() == 0)
343 return 0;
344
345 const auto lastClusterGroupId = sharedGuard->GetNClusterGroups() - 1;
346 auto descGuard = EnsureClusterDetails(lastClusterGroupId, std::move(sharedGuard));
347
348 const auto &cgDesc = descGuard->GetClusterGroupDescriptor(lastClusterGroupId);
349 const auto &cd = descGuard->GetClusterDescriptor(cgDesc.GetClusterIds().back());
350 R__ASSERT(cd.ContainsColumn(physicalColumnId));
351
352 const auto &columnRange = cd.GetColumnRange(physicalColumnId);
353 return columnRange.GetFirstElementIndex() + columnRange.GetNElements();
354}
355
358{
359 // At the cost of an additional cluster descriptor lock, we could use a more general approach that looks for the
360 // cluster containing the entry index after the last entry of the current cluster.
361 // At the moment, however, we know that cluster IDs are consecutive and clusters are not sharded.
362 auto sharedGuard = GetSharedDescriptorGuard();
363 assert(clusterId < sharedGuard->GetNClusters());
364 nextId = clusterId + 1;
365 if (nextId == sharedGuard->GetNClusters()) {
366 nextId = kInvalidDescriptorId;
367 return sharedGuard;
368 }
369 return EnsureClusterDetails(FindClusterGroupId(nextId), std::move(sharedGuard));
370}
371
374{
376 RAnyDescriptorGuard descGuard = GetSharedDescriptorGuard();
377
378 if (descGuard->GetNClusterGroups() == 0)
379 return descGuard;
380
381 // Binary search in the cluster group list, followed by a binary search in the clusters of that cluster group
382
383 const auto cgIter = descGuard->GetClusterGroupIterable().begin();
384 std::size_t cgLeft = 0;
385 std::size_t cgRight = descGuard->GetNClusterGroups() - 1;
386 while (cgLeft <= cgRight) {
387 const std::size_t cgMidpoint = (cgLeft + cgRight) / 2;
388 const auto &cgDesc = *(cgIter + cgMidpoint);
389
390 if (cgDesc.GetMinEntry() > entryIdx) {
391 R__ASSERT(cgMidpoint > 0);
392 cgRight = cgMidpoint - 1;
393 continue;
394 }
395
396 if (cgDesc.GetMinEntry() + cgDesc.GetEntrySpan() <= entryIdx) {
397 cgLeft = cgMidpoint + 1;
398 continue;
399 }
400
401 // Binary search in the current cluster group; since we already checked the element range boundaries,
402 // the element must be in that cluster group.
403 const auto tgtClusterGroupId = cgDesc.GetId();
404 descGuard = EnsureClusterDetails(tgtClusterGroupId, std::move(descGuard));
405
406 const auto &clusterIds = descGuard->GetClusterGroupDescriptor(tgtClusterGroupId).GetClusterIds();
407 std::size_t clusterLeft = 0;
408 std::size_t clusterRight = clusterIds.size() - 1;
409 while (clusterLeft <= clusterRight) {
410 const std::size_t clusterMidpoint = (clusterLeft + clusterRight) / 2;
411 const auto &clusterDesc = descGuard->GetClusterDescriptor(clusterIds[clusterMidpoint]);
412
413 if (clusterDesc.GetFirstEntryIndex() > entryIdx) {
414 R__ASSERT(clusterMidpoint > 0);
415 clusterRight = clusterMidpoint - 1;
416 continue;
417 }
418
419 if (clusterDesc.GetFirstEntryIndex() + clusterDesc.GetNEntries() <= entryIdx) {
420 clusterLeft = clusterMidpoint + 1;
421 continue;
422 }
423
424 cid = clusterIds[clusterMidpoint];
425 return descGuard;
426 }
427 R__ASSERT(false);
428 }
429 return descGuard;
430}
431
434{
436 RAnyDescriptorGuard descGuard = GetSharedDescriptorGuard();
437
438 if (descGuard->GetNClusterGroups() == 0)
439 return descGuard;
440
441 // Binary search in the cluster group list, followed by a binary search in the clusters of that cluster group
442
443 std::size_t cgLeft = 0;
444 std::size_t cgRight = descGuard->GetNClusterGroups() - 1;
445 while (cgLeft <= cgRight) {
446 const std::size_t cgMidpoint = (cgLeft + cgRight) / 2;
447 descGuard = EnsureClusterDetails(cgMidpoint, std::move(descGuard));
448
449 const auto cgIter = descGuard->GetClusterGroupIterable().begin();
450 const auto &clusterIds = (cgIter + cgMidpoint)->GetClusterIds();
451 R__ASSERT(!clusterIds.empty());
452
453 const auto &firstColumnRange =
454 descGuard->GetClusterDescriptor(clusterIds.front()).GetColumnRange(physicalColumnId);
455 if (firstColumnRange.GetFirstElementIndex() > index) {
456 // Look into the lower half of cluster groups
457 R__ASSERT(cgMidpoint > 0);
458 cgRight = cgMidpoint - 1;
459 continue;
460 }
461
462 const auto &lastColumnRange = descGuard->GetClusterDescriptor(clusterIds.back()).GetColumnRange(physicalColumnId);
463 if ((lastColumnRange.GetFirstElementIndex() + lastColumnRange.GetNElements()) <= index) {
464 // Look into the upper half of cluster groups
465 cgLeft = cgMidpoint + 1;
466 continue;
467 }
468
469 // Binary search in the current cluster group; since we already checked the element range boundaries,
470 // the element must be in that cluster group.
471 std::size_t clusterLeft = 0;
472 std::size_t clusterRight = clusterIds.size() - 1;
473 while (clusterLeft <= clusterRight) {
474 const std::size_t clusterMidpoint = (clusterLeft + clusterRight) / 2;
475 const auto clusterId = clusterIds[clusterMidpoint];
476 const auto &columnRange = descGuard->GetClusterDescriptor(clusterId).GetColumnRange(physicalColumnId);
477
478 if (columnRange.Contains(index)) {
479 cid = clusterId;
480 return descGuard;
481 }
482
483 if (columnRange.GetFirstElementIndex() > index) {
484 R__ASSERT(clusterMidpoint > 0);
485 clusterRight = clusterMidpoint - 1;
486 continue;
487 }
488
489 if (columnRange.GetFirstElementIndex() + columnRange.GetNElements() <= index) {
490 clusterLeft = clusterMidpoint + 1;
491 continue;
492 }
494 R__ASSERT(false);
495 }
496 return descGuard;
497}
498
500{
501 if (!fTaskScheduler)
502 return;
503
504 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
505
506 const auto clusterId = cluster->GetId();
507 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
508 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
509
510 fPreloadedClusters[clusterDescriptor.GetFirstEntryIndex()] = clusterId;
511
512 std::atomic<bool> foundChecksumFailure{false};
513
514 const auto &columnsInCluster = cluster->GetAvailPhysicalColumns();
516 std::vector<std::pair<Internal::RPage, Internal::RPagePool::RKey>> allPages;
517 // We allocate the space for all the pages upfront so that the TBB tasks do not need to lock the vector.
518 // In general, the number of uncompressed pages is larger than the number of sealed pages because we
519 // may decompress the same page into different in-memory representations.
520 std::size_t nPages = 0;
521 // We run a short version of the main loop to determine the number of uncompressed pages
522 for (const auto columnId : columnsInCluster) {
523 if (fActivePhysicalColumns.HasColumnInfos(columnId)) {
524 nPages += fActivePhysicalColumns.GetColumnInfos(columnId).size() *
525 clusterDescriptor.GetPageRange(columnId).GetPageInfos().size();
526 }
527 }
528 allPages.resize(nPages);
529 std::size_t iPage = 0;
530
531 std::vector<std::unique_ptr<RColumnElementBase>> allElements;
532 for (const auto columnId : columnsInCluster) {
533 // By the time we unzip a cluster, the set of active columns may have already changed wrt. to the moment when
534 // we requested reading the cluster. That doesn't matter much, we simply decompress what is now in the list
535 // of active columns.
536 if (!fActivePhysicalColumns.HasColumnInfos(columnId))
537 continue;
538 const auto &columnInfos = fActivePhysicalColumns.GetColumnInfos(columnId);
539 const auto indexOffset = clusterDescriptor.GetColumnRange(columnId).GetFirstElementIndex();
540
541 allElements.reserve(allElements.size() + columnInfos.size());
542 for (const auto &info : columnInfos) {
543 allElements.emplace_back(GenerateColumnElement(info.fElementId));
544 const auto element = allElements.back().get();
545
546 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
547 std::uint64_t pageNo = 0;
548 std::uint64_t firstInPage = 0;
549 for (const auto &pi : pageRange.GetPageInfos()) {
550 auto onDiskPage = cluster->GetOnDiskPage(ROnDiskPage::Key{columnId, pageNo});
551 RSealedPage sealedPage;
552 sealedPage.SetNElements(pi.GetNElements());
553 sealedPage.SetHasChecksum(pi.HasChecksum());
554 sealedPage.SetBufferSize(pi.GetLocator().GetNBytesOnStorage() + pi.HasChecksum() * kNBytesPageChecksum);
555 sealedPage.SetBuffer(onDiskPage->GetAddress());
556 R__ASSERT(onDiskPage->GetSize() == sealedPage.GetBufferSize());
557
558 auto taskFunc = [this, columnId, clusterId, firstInPage, sealedPage, element, iPage, indexOffset,
559 &foundChecksumFailure, &allPages]() {
560 auto rv = UnsealPage(sealedPage, *element);
561 if (!rv) {
562 foundChecksumFailure = true;
563 return;
564 }
565 auto newPage = rv.Unwrap();
566 fCounters->fSzUnzip.Add(element->GetSize() * sealedPage.GetNElements());
567
568 newPage.SetWindow(indexOffset + firstInPage,
569 ROOT::Internal::RPage::RClusterInfo(clusterId, indexOffset));
570 const ROOT::Internal::RPagePool::RKey keyPagePool{columnId, element->GetIdentifier().fInMemoryType};
571 allPages[iPage] = std::make_pair(std::move(newPage), keyPagePool);
572 };
573
574 if (sealedPage.GetNElements() * element->GetSize() < kInlineDecompressionThreshold) {
575 taskFunc();
576 } else {
577 fTaskScheduler->AddTask(taskFunc);
578 }
579
580 firstInPage += pi.GetNElements();
581 pageNo++;
582 iPage++;
583 } // for all pages in column
584 } // for all in-memory types of the column
585 } // for all columns in cluster
586
587 fTaskScheduler->Wait();
588 fCounters->fNPageUnsealed.Add(nPages);
589
590 if (foundChecksumFailure) {
591 throw RException(R__FAIL("page checksum verification failed, data corruption detected"));
592 }
593
594 fPagePool.PreloadPageV(allPages);
595}
596
598 const RCluster::RKey &clusterKey, ROnDiskPageMap &pageZeroMap,
600 &perPageFunc)
601{
602 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterKey.fClusterId), GetSharedDescriptorGuard());
603 const auto &clusterDesc = descriptorGuard->GetClusterDescriptor(clusterKey.fClusterId);
604
605 for (auto physicalColumnId : clusterKey.fPhysicalColumnSet) {
606 if (clusterDesc.GetColumnRange(physicalColumnId).IsSuppressed())
607 continue;
608
609 const auto &pageRange = clusterDesc.GetPageRange(physicalColumnId);
610 ROOT::NTupleSize_t pageNo = 0;
611 for (const auto &pageInfo : pageRange.GetPageInfos()) {
612 if (pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
613 pageZeroMap.Register(ROnDiskPage::Key{physicalColumnId, pageNo},
615 pageInfo.GetLocator().GetNBytesOnStorage()));
616 } else {
617 perPageFunc(physicalColumnId, pageNo, pageInfo);
618 }
619 ++pageNo;
620 }
621 }
622}
623
625{
626 if (fLastUsedCluster == clusterId)
627 return;
628
629 ROOT::NTupleSize_t firstEntryIndex = kInvalidNTupleIndex;
630 {
631 auto descGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
632 firstEntryIndex = descGuard->GetClusterDescriptor(clusterId).GetFirstEntryIndex();
633 }
634 auto itr = fPreloadedClusters.begin();
635 while ((itr != fPreloadedClusters.end()) && (itr->first < firstEntryIndex)) {
636 if (fPinnedClusters.count(itr->second) > 0) {
637 ++itr;
638 } else {
639 fPagePool.Evict(itr->second);
640 itr = fPreloadedClusters.erase(itr);
641 }
642 }
643 std::size_t poolWindow = 0;
644 while ((itr != fPreloadedClusters.end()) &&
646 ++itr;
647 ++poolWindow;
648 }
649 while (itr != fPreloadedClusters.end()) {
650 if (fPinnedClusters.count(itr->second) > 0) {
651 ++itr;
652 } else {
653 fPagePool.Evict(itr->second);
654 itr = fPreloadedClusters.erase(itr);
655 }
656 }
657
658 fLastUsedCluster = clusterId;
659}
660
662 RSealedPage &sealedPage)
663{
664 const auto clusterId = localIndex.GetClusterId();
665
667 {
668 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
669 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
670 pageInfo = clusterDescriptor.GetPageRange(physicalColumnId).Find(localIndex.GetIndexInCluster());
671 }
672
673 assert(pageInfo.GetLocator().GetType() != RNTupleLocator::kTypePageZero);
674
675 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
676 sealedPage.SetNElements(pageInfo.GetNElements());
677 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
678
679 if (!sealedPage.GetBuffer())
680 return;
681
682 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
684}
685
688{
689 const auto &pageInfo = pageSummary.fPageInfo;
690 assert(pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero);
691
692 const auto element = columnHandle.fColumn->GetElement();
693 const auto elementSize = element->GetSize();
694 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
695
696 auto pageZero = fPageAllocator->NewPage(elementSize, pageInfo.GetNElements());
697 pageZero.GrowUnchecked(pageInfo.GetNElements());
698 std::memset(pageZero.GetBuffer(), 0, pageZero.GetNBytes());
699 pageZero.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
700 RPage::RClusterInfo(pageSummary.fClusterId, pageSummary.fColumnOffset));
701 return fPagePool.RegisterPage(std::move(pageZero), RPagePool::RKey{columnHandle.fPhysicalId, elementInMemoryType});
702}
703
706{
708 throw RException(R__FAIL("tried to read a page with an unknown locator"));
709 } else if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
710 return LoadZeroPage(columnHandle, pageSummary);
711 }
712
713 const auto &columnId = columnHandle.fPhysicalId;
714 const auto &clusterId = pageSummary.fClusterId;
715 const auto &pageInfo = pageSummary.fPageInfo;
716
717 const auto element = columnHandle.fColumn->GetElement();
718 const auto elementSize = element->GetSize();
719 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
720
721 UpdateLastUsedCluster(clusterId);
722
723 RSealedPage sealedPage;
724 sealedPage.SetNElements(pageInfo.GetNElements());
725 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
726 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
727 std::unique_ptr<unsigned char[]> directReadBuffer; // only used if cluster pool is turned off
728
729 if (fOptions.GetClusterCache() == ROOT::RNTupleReadOptions::EClusterCache::kOff) {
730 directReadBuffer = MakeUninitArray<unsigned char>(sealedPage.GetBufferSize());
731 sealedPage.SetBuffer(directReadBuffer.get());
732 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
733
734 fCounters->fNPageRead.Inc();
735 fCounters->fNRead.Inc();
736 fCounters->fSzReadPayload.Add(sealedPage.GetBufferSize());
737 } else {
738 if (!fCurrentCluster || (fCurrentCluster->GetId() != clusterId) || !fCurrentCluster->ContainsColumn(columnId))
739 fCurrentCluster = fClusterPool.GetCluster(clusterId, fActivePhysicalColumns.ToColumnSet());
740 R__ASSERT(fCurrentCluster->ContainsColumn(columnId));
741
742 // The cluster pool may have unzipped the required page into the page pool
743 auto cachedPageRef = fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, elementInMemoryType},
744 RNTupleLocalIndex(clusterId, pageInfo.GetFirstElementIndex()));
745 if (!cachedPageRef.Get().IsNull())
746 return cachedPageRef;
747
748 ROnDiskPage::Key key(columnId, pageInfo.GetPageNumber());
749 auto onDiskPage = fCurrentCluster->GetOnDiskPage(key);
750 R__ASSERT(onDiskPage && (sealedPage.GetBufferSize() == onDiskPage->GetSize()));
751 sealedPage.SetBuffer(onDiskPage->GetAddress());
752 }
753
754 ROOT::Internal::RPage newPage;
755 {
756 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
757 newPage = UnsealPage(sealedPage, *element).Unwrap();
758 fCounters->fSzUnzip.Add(elementSize * pageInfo.GetNElements());
759 }
760
761 newPage.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
762 ROOT::Internal::RPage::RClusterInfo(clusterId, pageSummary.fColumnOffset));
763 fCounters->fNPageUnsealed.Inc();
764
765 return fPagePool.RegisterPage(std::move(newPage), RPagePool::RKey{columnId, elementInMemoryType});
766}
767
770{
771 const auto columnId = columnHandle.fPhysicalId;
772 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
773 auto cachedPageRef =
774 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, globalIndex);
775 if (!cachedPageRef.Get().IsNull()) {
776 UpdateLastUsedCluster(cachedPageRef.Get().GetClusterInfo().GetId());
777 return cachedPageRef;
778 }
779
780 RPageSummary pageSummary;
781 {
782 auto descriptorGuard = FindClusterId(columnId, globalIndex, pageSummary.fClusterId);
783
784 if (pageSummary.fClusterId == ROOT::kInvalidDescriptorId)
785 throw RException(R__FAIL("entry with index " + std::to_string(globalIndex) + " out of bounds"));
786
787 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(pageSummary.fClusterId);
788 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
789 if (columnRange.IsSuppressed())
791
792 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
793 R__ASSERT(pageSummary.fColumnOffset <= globalIndex);
794 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(globalIndex - pageSummary.fColumnOffset);
795 }
796
797 return LoadPageFromSummary(columnHandle, pageSummary);
798}
799
802{
803 const auto clusterId = localIndex.GetClusterId();
804 const auto columnId = columnHandle.fPhysicalId;
805 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
806 auto cachedPageRef =
807 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, localIndex);
808 if (!cachedPageRef.Get().IsNull()) {
809 UpdateLastUsedCluster(clusterId);
810 return cachedPageRef;
811 }
812
813 if (clusterId == kInvalidDescriptorId)
814 throw RException(R__FAIL("entry out of bounds"));
815
816 RPageSummary pageSummary;
817 {
818 auto descriptorGuard = EnsureClusterDetails(FindClusterGroupId(clusterId), GetSharedDescriptorGuard());
819 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
820 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
821 if (columnRange.IsSuppressed())
823
824 pageSummary.fClusterId = clusterId;
825 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
826 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(localIndex.GetIndexInCluster());
827 }
828
829 return LoadPageFromSummary(columnHandle, pageSummary);
830}
831
833{
834 fMetrics = RNTupleMetrics(prefix);
835 fMetrics.ObserveMetrics(fClusterPool.GetMetrics());
836 fMetrics.ObserveMetrics(fPagePool.GetMetrics());
837 fCounters = std::make_unique<RCounters>(RCounters{
838 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nReadV", "", "number of vector read requests"),
839 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nRead", "", "number of byte ranges read"),
840 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadPayload", "B", "volume read from storage (required)"),
841 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadOverhead", "B", "volume read from storage (overhead)"),
842 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szUnzip", "B", "volume after unzipping"),
843 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nClusterLoaded", "",
844 "number of partial clusters preloaded from storage"),
845 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageRead", "", "number of pages read from storage"),
846 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageUnsealed", "", "number of pages unzipped and decoded"),
847 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallRead", "ns", "wall clock time spent reading"),
848 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallUnzip", "ns", "wall clock time spent decompressing"),
849 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuRead", "ns", "CPU time spent reading"),
850 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuUnzip", "ns",
851 "CPU time spent decompressing"),
852 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
853 "bwRead", "MB/s", "bandwidth compressed bytes read per second", fMetrics,
854 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
855 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
856 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
857 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
858 if (auto walltime = timeWallRead->GetValueAsInt()) {
859 double payload = szReadPayload->GetValueAsInt();
860 double overhead = szReadOverhead->GetValueAsInt();
861 // unit: bytes / nanosecond = GB/s
862 return {true, (1000. * (payload + overhead) / walltime)};
863 }
864 }
865 }
866 }
867 return {false, -1.};
868 }),
869 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
870 "bwReadUnzip", "MB/s", "bandwidth uncompressed bytes read per second", fMetrics,
871 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
872 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
873 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
874 if (auto walltime = timeWallRead->GetValueAsInt()) {
875 double unzip = szUnzip->GetValueAsInt();
876 // unit: bytes / nanosecond = GB/s
877 return {true, 1000. * unzip / walltime};
878 }
879 }
880 }
881 return {false, -1.};
882 }),
883 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
884 "bwUnzip", "MB/s", "decompression bandwidth of uncompressed bytes per second", fMetrics,
885 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
886 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
887 if (const auto timeWallUnzip = metrics.GetLocalCounter("timeWallUnzip")) {
888 if (auto walltime = timeWallUnzip->GetValueAsInt()) {
889 double unzip = szUnzip->GetValueAsInt();
890 // unit: bytes / nanosecond = GB/s
891 return {true, 1000. * unzip / walltime};
892 }
893 }
894 }
895 return {false, -1.};
896 }),
897 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
898 "rtReadEfficiency", "", "ratio of payload over all bytes read", fMetrics,
899 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
900 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
901 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
902 if (auto payload = szReadPayload->GetValueAsInt()) {
903 // r/(r+o) = 1/((r+o)/r) = 1/(1 + o/r)
904 return {true, 1. / (1. + (1. * szReadOverhead->GetValueAsInt()) / payload)};
905 }
906 }
907 }
908 return {false, -1.};
909 }),
910 *fMetrics.MakeCounter<RNTupleCalcPerf *>("rtCompression", "", "ratio of compressed bytes / uncompressed bytes",
911 fMetrics, [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
912 if (const auto szReadPayload =
913 metrics.GetLocalCounter("szReadPayload")) {
914 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
915 if (auto unzip = szUnzip->GetValueAsInt()) {
916 return {true, (1. * szReadPayload->GetValueAsInt()) / unzip};
917 }
918 }
919 }
920 return {false, -1.};
921 })});
922}
923
926{
927 return UnsealPage(sealedPage, element, *fPageAllocator);
928}
929
931 const RColumnElementBase &element,
933{
934 // Unsealing a page zero is a no-op. `RPageRange::ExtendToFitColumnRange()` guarantees that the page zero buffer is
935 // large enough to hold `sealedPage.fNElements`
937 auto page = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
938 page.GrowUnchecked(sealedPage.GetNElements());
939 memset(page.GetBuffer(), 0, page.GetNBytes());
940 return page;
941 }
942
943 auto rv = sealedPage.VerifyChecksumIfEnabled();
944 if (!rv)
945 return R__FORWARD_ERROR(rv);
946
947 const auto bytesPacked = element.GetPackedSize(sealedPage.GetNElements());
948 auto page = pageAlloc.NewPage(element.GetPackedSize(), sealedPage.GetNElements());
949 if (sealedPage.GetDataSize() != bytesPacked) {
950 ROOT::Internal::RNTupleDecompressor::Unzip(sealedPage.GetBuffer(), sealedPage.GetDataSize(), bytesPacked,
951 page.GetBuffer());
952 } else {
953 // We cannot simply map the sealed page as we don't know its life time. Specialized page sources
954 // may decide to implement to not use UnsealPage but to custom mapping / decompression code.
955 // Note that usually pages are compressed.
956 memcpy(page.GetBuffer(), sealedPage.GetBuffer(), bytesPacked);
957 }
958
959 if (!element.IsMappable()) {
960 auto tmp = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
961 element.Unpack(tmp.GetBuffer(), page.GetBuffer(), sealedPage.GetNElements());
962 page = std::move(tmp);
963 }
964
965 page.GrowUnchecked(sealedPage.GetNElements());
966 return page;
967}
968
970{
971 if (fHasStreamerInfosRegistered)
972 return;
973
974 for (const auto &extraTypeInfo : fDescriptor.GetExtraTypeInfoIterable()) {
975 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
976 continue;
977 // We don't need the result, it's enough that during deserialization, BuildCheck() is called for every
978 // streamer info record.
979 RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap();
980 }
981
982 fHasStreamerInfosRegistered = true;
983}
984
985//------------------------------------------------------------------------------
986
988{
989 // Make the sort order unique by adding the physical on-disk column id as a secondary key
990 if (fCurrentPageSize == other.fCurrentPageSize)
991 return fColumn->GetOnDiskId() > other.fColumn->GetOnDiskId();
992 return fCurrentPageSize > other.fCurrentPageSize;
993}
994
995bool ROOT::Internal::RWritePageMemoryManager::TryEvict(std::size_t targetAvailableSize, std::size_t pageSizeLimit)
996{
997 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
998 return true;
999
1000 auto itr = fColumnsSortedByPageSize.begin();
1001 while (itr != fColumnsSortedByPageSize.end()) {
1002 if (itr->fCurrentPageSize <= pageSizeLimit)
1003 break;
1004 if (itr->fCurrentPageSize == itr->fInitialPageSize) {
1005 ++itr;
1006 continue;
1007 }
1008
1009 // Flushing the current column will invalidate itr
1010 auto itrFlush = itr++;
1011
1012 RColumnInfo next;
1013 if (itr != fColumnsSortedByPageSize.end())
1014 next = *itr;
1015
1016 itrFlush->fColumn->Flush();
1017 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
1018 return true;
1019
1020 if (next.fColumn == nullptr)
1021 return false;
1022 itr = fColumnsSortedByPageSize.find(next);
1023 };
1024
1025 return false;
1026}
1027
1028bool ROOT::Internal::RWritePageMemoryManager::TryUpdate(RColumn &column, std::size_t newWritePageSize)
1029{
1030 const RColumnInfo key{&column, column.GetWritePageCapacity(), 0};
1031 auto itr = fColumnsSortedByPageSize.find(key);
1032 if (itr == fColumnsSortedByPageSize.end()) {
1033 if (!TryEvict(newWritePageSize, 0))
1034 return false;
1035 fColumnsSortedByPageSize.insert({&column, newWritePageSize, newWritePageSize});
1036 fCurrentAllocatedBytes += newWritePageSize;
1037 return true;
1038 }
1039
1040 RColumnInfo elem{*itr};
1041 assert(newWritePageSize >= elem.fInitialPageSize);
1042
1043 if (newWritePageSize == elem.fCurrentPageSize)
1044 return true;
1045
1046 fColumnsSortedByPageSize.erase(itr);
1047
1048 if (newWritePageSize < elem.fCurrentPageSize) {
1049 // Page got smaller
1050 fCurrentAllocatedBytes -= elem.fCurrentPageSize - newWritePageSize;
1051 elem.fCurrentPageSize = newWritePageSize;
1052 fColumnsSortedByPageSize.insert(elem);
1053 return true;
1054 }
1055
1056 // Page got larger, we may need to make space available
1057 const auto diffBytes = newWritePageSize - elem.fCurrentPageSize;
1058 if (!TryEvict(diffBytes, elem.fCurrentPageSize)) {
1059 // Don't change anything, let the calling column flush itself
1060 // TODO(jblomer): we may consider skipping the column in TryEvict and thus avoiding erase+insert
1061 fColumnsSortedByPageSize.insert(elem);
1062 return false;
1063 }
1064 fCurrentAllocatedBytes += diffBytes;
1065 elem.fCurrentPageSize = newWritePageSize;
1066 fColumnsSortedByPageSize.insert(elem);
1067 return true;
1068}
1069
1070//------------------------------------------------------------------------------
1071
1073 : RPageStorage(name), fOptions(options.Clone()), fWritePageMemoryManager(options.GetPageBufferBudget())
1074{
1076}
1077
1079
1081{
1082 assert(config.fPage);
1083 assert(config.fElement);
1084 assert(config.fBuffer);
1085
1086 unsigned char *pageBuf = reinterpret_cast<unsigned char *>(config.fPage->GetBuffer());
1087 bool isAdoptedBuffer = true;
1088 auto nBytesPacked = config.fPage->GetNBytes();
1089 auto nBytesChecksum = config.fWriteChecksum * kNBytesPageChecksum;
1090
1091 if (!config.fElement->IsMappable()) {
1092 nBytesPacked = config.fElement->GetPackedSize(config.fPage->GetNElements());
1093 pageBuf = new unsigned char[nBytesPacked];
1094 isAdoptedBuffer = false;
1095 config.fElement->Pack(pageBuf, config.fPage->GetBuffer(), config.fPage->GetNElements());
1096 }
1097 auto nBytesZipped = nBytesPacked;
1098
1099 if ((config.fCompressionSettings != 0) || !config.fElement->IsMappable() || !config.fAllowAlias ||
1100 config.fWriteChecksum) {
1101 nBytesZipped =
1102 ROOT::Internal::RNTupleCompressor::Zip(pageBuf, nBytesPacked, config.fCompressionSettings, config.fBuffer);
1103 if (!isAdoptedBuffer)
1104 delete[] pageBuf;
1105 pageBuf = reinterpret_cast<unsigned char *>(config.fBuffer);
1106 isAdoptedBuffer = true;
1107 }
1108
1109 R__ASSERT(isAdoptedBuffer);
1110
1111 RSealedPage sealedPage{pageBuf, nBytesZipped + nBytesChecksum, config.fPage->GetNElements(), config.fWriteChecksum};
1112 sealedPage.ChecksumIfEnabled();
1113
1114 return sealedPage;
1115}
1116
1119{
1120 const auto nBytes = page.GetNBytes() + GetWriteOptions().GetEnablePageChecksums() * kNBytesPageChecksum;
1121 if (fSealPageBuffer.size() < nBytes)
1122 fSealPageBuffer.resize(nBytes);
1123
1124 RSealPageConfig config;
1125 config.fPage = &page;
1126 config.fElement = &element;
1127 config.fCompressionSettings = GetWriteOptions().GetCompression();
1128 config.fWriteChecksum = GetWriteOptions().GetEnablePageChecksums();
1129 config.fAllowAlias = true;
1130 config.fBuffer = fSealPageBuffer.data();
1131
1132 return SealPage(config);
1133}
1134
1136{
1137 for (const auto &cb : fOnDatasetCommitCallbacks)
1138 cb(*this);
1139 return CommitDatasetImpl();
1140}
1141
1143{
1144 R__ASSERT(nElements > 0);
1145 const auto elementSize = columnHandle.fColumn->GetElement()->GetSize();
1146 const auto nBytes = elementSize * nElements;
1147 if (!fWritePageMemoryManager.TryUpdate(*columnHandle.fColumn, nBytes))
1148 return ROOT::Internal::RPage();
1149 return fPageAllocator->NewPage(elementSize, nElements);
1150}
1151
1152//------------------------------------------------------------------------------
1153
1154std::unique_ptr<ROOT::Internal::RPageSink>
1155ROOT::Internal::RPagePersistentSink::Create(std::string_view ntupleName, std::string_view location,
1156 const ROOT::RNTupleWriteOptions &options)
1157{
1158 if (ntupleName.empty()) {
1159 throw RException(R__FAIL("empty RNTuple name"));
1160 }
1161 if (location.empty()) {
1162 throw RException(R__FAIL("empty storage location"));
1163 }
1164 if (location.find("daos://") == 0) {
1165#ifdef R__ENABLE_DAOS
1166 return std::make_unique<ROOT::Experimental::Internal::RPageSinkDaos>(ntupleName, location, options);
1167#else
1168 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
1169#endif
1170 }
1171
1172 if (ROOT::StartsWith(location, "ntpl+s3+http://") || ROOT::StartsWith(location, "ntpl+s3+https://")) {
1173#ifdef R__ENABLE_S3
1174 return std::make_unique<ROOT::Experimental::Internal::RPageSinkS3>(ntupleName, location, options);
1175#else
1176 throw RException(R__FAIL("This RNTuple build does not support S3. Rebuild ROOT with the 'curl' "
1177 "cmake option enabled (-Dcurl=ON) to enable the S3 backend."));
1178#endif
1179 }
1180
1181 // Otherwise assume that the user wants us to create a file.
1182 return std::make_unique<ROOT::Internal::RPageSinkFile>(ntupleName, location, options);
1183}
1184
1186 const ROOT::RNTupleWriteOptions &options)
1187 : RPageSink(name, options)
1188{
1189}
1190
1192
1195{
1196 auto columnId = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
1197 RColumnDescriptorBuilder columnBuilder;
1198 columnBuilder.LogicalColumnId(columnId)
1199 .PhysicalColumnId(columnId)
1200 .FieldId(fieldId)
1202 .ValueRange(column.GetValueRange())
1203 .Type(column.GetType())
1204 .Index(column.GetIndex())
1207 // For late model extension, we assume that the primary column representation is the active one for the
1208 // deferred range. All other representations are suppressed.
1209 if (column.GetFirstElementIndex() > 0 && column.GetRepresentationIndex() > 0)
1210 columnBuilder.SetSuppressedDeferred();
1211 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1212 return ColumnHandle_t{columnId, &column};
1213}
1214
1216 ROOT::NTupleSize_t firstEntry)
1217{
1218 if (fIsInitialized) {
1219 for (const auto &field : changeset.fAddedFields) {
1220 if (field->GetStructure() == ENTupleStructure::kStreamer) {
1221 throw ROOT::RException(R__FAIL("a Model cannot be extended with Streamer fields"));
1222 }
1223 }
1224 }
1225
1226 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1227
1228 if (descriptor.GetNLogicalColumns() > descriptor.GetNPhysicalColumns()) {
1229 // If we already have alias columns, add an offset to the alias columns so that the new physical columns
1230 // of the changeset follow immediately the already existing physical columns
1231 auto getNColumns = [](const ROOT::RFieldBase &f) -> std::size_t {
1232 const auto &reps = f.GetColumnRepresentatives();
1233 if (reps.empty())
1234 return 0;
1235 return reps.size() * reps[0].size();
1236 };
1237 std::uint32_t nNewPhysicalColumns = 0;
1238 for (auto f : changeset.fAddedFields) {
1239 nNewPhysicalColumns += getNColumns(*f);
1240 for (const auto &descendant : *f)
1241 nNewPhysicalColumns += getNColumns(descendant);
1242 }
1243 fDescriptorBuilder.ShiftAliasColumns(nNewPhysicalColumns);
1244 }
1245
1246 auto addField = [&](ROOT::RFieldBase &f) {
1247 auto fieldId = descriptor.GetNFields();
1248 fDescriptorBuilder.AddField(f, fieldId);
1249 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1250 f.SetOnDiskId(fieldId);
1251 ROOT::Internal::CallConnectPageSinkOnField(f, *this, firstEntry); // issues in turn calls to `AddColumn()`
1252 };
1253 auto addProjectedField = [&](ROOT::RFieldBase &f) {
1254 auto fieldId = descriptor.GetNFields();
1255 auto sourceFieldId =
1257 fDescriptorBuilder.AddField(f, fieldId);
1258 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1259 fDescriptorBuilder.AddFieldProjection(sourceFieldId, fieldId);
1260 f.SetOnDiskId(fieldId);
1261 for (const auto &source : descriptor.GetColumnIterable(sourceFieldId)) {
1262 auto targetId = descriptor.GetNLogicalColumns();
1263 RColumnDescriptorBuilder columnBuilder;
1264 columnBuilder.LogicalColumnId(targetId)
1265 .PhysicalColumnId(source.GetLogicalId())
1266 .FieldId(fieldId)
1267 .BitsOnStorage(source.GetBitsOnStorage())
1268 .ValueRange(source.GetValueRange())
1269 .Type(source.GetType())
1270 .Index(source.GetIndex())
1271 .RepresentationIndex(source.GetRepresentationIndex());
1272 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1273 }
1274 };
1275
1276 R__ASSERT(firstEntry >= fPrevClusterNEntries);
1277 const auto nColumnsBeforeUpdate = descriptor.GetNPhysicalColumns();
1278 for (auto f : changeset.fAddedFields) {
1279 addField(*f);
1280 for (auto &descendant : *f)
1281 addField(descendant);
1282 }
1283 for (auto f : changeset.fAddedProjectedFields) {
1284 addProjectedField(*f);
1285 for (auto &descendant : *f)
1286 addProjectedField(descendant);
1287 }
1288
1289 const auto nColumns = descriptor.GetNPhysicalColumns();
1290 fOpenColumnRanges.reserve(fOpenColumnRanges.size() + (nColumns - nColumnsBeforeUpdate));
1291 fOpenPageRanges.reserve(fOpenPageRanges.size() + (nColumns - nColumnsBeforeUpdate));
1292 for (ROOT::DescriptorId_t i = nColumnsBeforeUpdate; i < nColumns; ++i) {
1294 columnRange.SetPhysicalColumnId(i);
1295 // We set the first element index in the current cluster to the first element that is part of a materialized page
1296 // (i.e., that is part of a page list). For columns created during late model extension, however, the column range
1297 // is fixed up as needed by `RClusterDescriptorBuilder::AddExtendedColumnRanges()` on read back.
1298 columnRange.SetFirstElementIndex(descriptor.GetColumnDescriptor(i).GetFirstElementIndex());
1299 columnRange.SetNElements(0);
1300 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1301 fOpenColumnRanges.emplace_back(columnRange);
1303 pageRange.SetPhysicalColumnId(i);
1304 fOpenPageRanges.emplace_back(std::move(pageRange));
1305 }
1306
1307 // Mapping of memory to on-disk column IDs usually happens during serialization of the ntuple header. If the
1308 // header was already serialized, this has to be done manually as it is required for page list serialization.
1309 if (fSerializationContext.GetHeaderSize() > 0)
1310 fSerializationContext.MapSchema(descriptor, /*forHeaderExtension=*/true);
1311}
1312
1314{
1315 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
1316 throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()"));
1317
1318 fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap());
1319}
1320
1322{
1323 fDescriptorBuilder.SetNTuple(fNTupleName, model.GetDescription());
1324 fDescriptorBuilder.SetVersionForWriting();
1325 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1326
1327 auto &fieldZero = ROOT::Internal::GetFieldZeroOfModel(model);
1328 fDescriptorBuilder.AddField(fieldZero, 0);
1329 fieldZero.SetOnDiskId(0);
1330 auto &projectedFields = ROOT::Internal::GetProjectedFieldsOfModel(model);
1331 projectedFields.GetFieldZero().SetOnDiskId(0);
1332
1333 ROOT::Internal::RNTupleModelChangeset initialChangeset{model};
1334 initialChangeset.fAddedFields.reserve(fieldZero.GetMutableSubfields().size());
1335 for (auto f : fieldZero.GetMutableSubfields())
1336 initialChangeset.fAddedFields.emplace_back(f);
1337 initialChangeset.fAddedProjectedFields.reserve(projectedFields.GetFieldZero().GetMutableSubfields().size());
1338 for (auto f : projectedFields.GetFieldZero().GetMutableSubfields())
1339 initialChangeset.fAddedProjectedFields.emplace_back(f);
1340 UpdateSchema(initialChangeset, 0U);
1341
1342 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1343 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1344 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1345 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1346
1347 fDescriptorBuilder.BeginHeaderExtension();
1348}
1349
1350std::unique_ptr<ROOT::RNTupleModel>
1352{
1353 // Create new descriptor
1354 fDescriptorBuilder.SetSchemaFromExisting(srcDescriptor);
1355 // This is needed to be able to use GetTypeNameForComparison()
1356 fDescriptorBuilder.SetVersionForWriting();
1357 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1358
1359 // Create column/page ranges
1360 const auto nColumns = descriptor.GetNPhysicalColumns();
1361 R__ASSERT(fOpenColumnRanges.empty() && fOpenPageRanges.empty());
1362 fOpenColumnRanges.reserve(nColumns);
1363 fOpenPageRanges.reserve(nColumns);
1364 for (ROOT::DescriptorId_t i = 0; i < nColumns; ++i) {
1365 const auto &column = descriptor.GetColumnDescriptor(i);
1367 columnRange.SetPhysicalColumnId(i);
1368 columnRange.SetFirstElementIndex(column.GetFirstElementIndex());
1369 columnRange.SetNElements(0);
1370 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1371 fOpenColumnRanges.emplace_back(columnRange);
1373 pageRange.SetPhysicalColumnId(i);
1374 fOpenPageRanges.emplace_back(std::move(pageRange));
1375 }
1376
1377 if (copyClusters) {
1378 // Clone and add all cluster descriptors
1379 R__ASSERT(srcDescriptor.GetNClusters() == srcDescriptor.GetNActiveClusters());
1380 for (const auto &cluster : srcDescriptor.GetActiveClusterIterable()) {
1381 auto nEntries = cluster.GetNEntries();
1382 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1383 R__ASSERT(fOpenColumnRanges[i].GetPhysicalColumnId() == i);
1384 if (!cluster.ContainsColumn(i)) // a cluster may not contain a column if that column is deferred
1385 break;
1386 const auto &columnRange = cluster.GetColumnRange(i);
1387 R__ASSERT(columnRange.GetPhysicalColumnId() == i);
1388 // TODO: properly handle suppressed columns (check MarkSuppressedColumnRange())
1389 fOpenColumnRanges[i].IncrementFirstElementIndex(columnRange.GetNElements());
1390 }
1391 fDescriptorBuilder.AddCluster(cluster.Clone());
1392 fPrevClusterNEntries += nEntries;
1393 }
1394 }
1395
1396 // Create model
1398 modelOpts.SetReconstructProjections(true);
1399 // We want to emulate unknown types to allow merging RNTuples containing types that we lack dictionaries for.
1400 modelOpts.SetEmulateUnknownTypes(true);
1401 auto model = descriptor.CreateModel(modelOpts);
1402 if (!copyClusters) {
1403 auto &projectedFields = ROOT::Internal::GetProjectedFieldsOfModel(*model);
1404 projectedFields.GetFieldZero().SetOnDiskId(model->GetConstFieldZero().GetOnDiskId());
1405 }
1406
1407 // Serialize header and init from it
1408 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1409 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1410 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1411 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1412
1413 fDescriptorBuilder.BeginHeaderExtension();
1414
1415 // mark this sink as initialized
1416 fIsInitialized = true;
1417
1418 return model;
1419}
1420
1423 std::span<const RColumnFormat> newRepresentation,
1424 std::uint64_t clusterOffset)
1425{
1426 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1427
1428 assert(&descriptor.GetFieldDescriptor(field.GetId()) == &field);
1429 assert(!field.IsProjectedField());
1430 assert(field.GetColumnCardinality() > 0);
1431 assert(!field.GetLogicalColumnIds().empty());
1432 assert(newRepresentation.size() == field.GetColumnCardinality());
1433
1434 const std::size_t firstPhysicalIndex = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
1435 const std::uint16_t reprIndex = field.GetLogicalColumnIds().size() / field.GetColumnCardinality();
1436
1437 fDescriptorBuilder.ShiftAliasColumns(newRepresentation.size());
1438
1439 std::uint16_t columnIndex = 0; // index into the representation
1440 for (auto columnRepr : newRepresentation) {
1441 std::size_t bitsOnStorage = columnRepr.fBitWidth;
1442 if (!bitsOnStorage) {
1443 const auto [rangeMin, rangeMax] = ROOT::Internal::RColumnElementBase::GetValidBitRange(columnRepr.fType);
1444 if (rangeMin != rangeMax) {
1445 throw ROOT::RException(R__FAIL("bit width must be given for columns of variable bit width"));
1446 }
1447 bitsOnStorage = rangeMin;
1448 }
1449
1450 const ROOT::DescriptorId_t firstReprColumnId = field.GetLogicalColumnIds()[columnIndex];
1451 const auto &firstReprColumnRange = fOpenColumnRanges.at(firstReprColumnId);
1452 const ROOT::DescriptorId_t columnId = firstPhysicalIndex + columnIndex;
1453 // NOTE: this is always non-negative because it's the sum of two unsigned integers.
1454 const std::uint64_t newReprFirstElemIndex = firstReprColumnRange.GetFirstElementIndex() + clusterOffset;
1455
1456 RColumnDescriptorBuilder columnBuilder;
1457 columnBuilder.LogicalColumnId(columnId)
1458 .PhysicalColumnId(columnId)
1459 .FieldId(field.GetId())
1460 .BitsOnStorage(bitsOnStorage)
1461 .Type(columnRepr.fType)
1462 .Index(columnIndex)
1463 .FirstElementIndex(newReprFirstElemIndex)
1464 .RepresentationIndex(reprIndex)
1465 .ValueRange(columnRepr.fValueRange);
1466 if (newReprFirstElemIndex)
1467 columnBuilder.SetSuppressedDeferred();
1468 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1469
1470 if (newReprFirstElemIndex != 0) {
1471 for (auto parentId = field.GetParentId(); parentId != ROOT::kInvalidDescriptorId;) {
1472 const ROOT::RFieldDescriptor &parent = descriptor.GetFieldDescriptor(parentId);
1475 fDescriptorBuilder.SetFeature(RNTupleDescriptor::kFeatureFlag_NestedDeferredColumns);
1476 break;
1477 }
1478 parentId = parent.GetParentId();
1479 }
1480 }
1481
1483 columnRange.SetPhysicalColumnId(columnId);
1484 columnRange.SetFirstElementIndex(firstReprColumnRange.GetFirstElementIndex());
1485 columnRange.SetNElements(0);
1486 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1487 fOpenColumnRanges.emplace_back(columnRange);
1488
1490 pageRange.SetPhysicalColumnId(columnId);
1491 fOpenPageRanges.emplace_back(std::move(pageRange));
1492
1493 fSerializationContext.MapPhysicalColumnId(columnId);
1494
1495 ++columnIndex;
1496 }
1497
1498 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1499
1500 return firstPhysicalIndex;
1501}
1502
1504 const ROOT::RFieldDescriptor &field,
1505 ROOT::DescriptorId_t physicalId)
1506{
1507 const auto &pointedColumn = desc.GetColumnDescriptor(physicalId);
1508 assert(!pointedColumn.IsAliasColumn());
1509 assert(field.IsProjectedField());
1510
1511 const auto columnId = fDescriptorBuilder.GetDescriptor().GetNLogicalColumns();
1512 RColumnDescriptorBuilder columnBuilder;
1513 columnBuilder.LogicalColumnId(columnId)
1514 .PhysicalColumnId(physicalId)
1515 .FieldId(field.GetId())
1516 .Type(pointedColumn.GetType())
1517 .Index(pointedColumn.GetIndex())
1518 .BitsOnStorage(pointedColumn.GetBitsOnStorage())
1519 .ValueRange(pointedColumn.GetValueRange())
1520 .FirstElementIndex(pointedColumn.GetFirstElementIndex())
1521 .RepresentationIndex(pointedColumn.GetRepresentationIndex());
1522 fDescriptorBuilder.AddColumn(columnBuilder.MoveDescriptor().Unwrap());
1523
1524 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1525}
1526
1528{
1529 fOpenColumnRanges.at(columnHandle.fPhysicalId).SetIsSuppressed(true);
1530}
1531
1533{
1534 fOpenColumnRanges.at(columnHandle.fPhysicalId).IncrementNElements(page.GetNElements());
1535
1536 auto element = columnHandle.fColumn->GetElement();
1537 RPageStorage::RSealedPage sealedPage;
1538 {
1539 RNTupleAtomicTimer timer(fCounters->fTimeWallZip, fCounters->fTimeCpuZip);
1540 sealedPage = SealPage(page, *element);
1541 }
1542 fCounters->fSzZip.Add(page.GetNBytes());
1543
1545 pageInfo.SetNElements(page.GetNElements());
1546 pageInfo.SetLocator(CommitSealedPageImpl(columnHandle.fPhysicalId, sealedPage));
1547 pageInfo.SetHasChecksum(GetWriteOptions().GetEnablePageChecksums());
1548 fOpenPageRanges.at(columnHandle.fPhysicalId).GetPageInfos().emplace_back(pageInfo);
1549}
1550
1552 const RPageStorage::RSealedPage &sealedPage)
1553{
1554 fOpenColumnRanges.at(physicalColumnId).IncrementNElements(sealedPage.GetNElements());
1555
1557 pageInfo.SetNElements(sealedPage.GetNElements());
1558 pageInfo.SetLocator(CommitSealedPageImpl(physicalColumnId, sealedPage));
1559 pageInfo.SetHasChecksum(sealedPage.GetHasChecksum());
1560 fOpenPageRanges.at(physicalColumnId).GetPageInfos().emplace_back(pageInfo);
1561}
1562
1563std::vector<ROOT::RNTupleLocator>
1564ROOT::Internal::RPagePersistentSink::CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges,
1565 const std::vector<bool> &mask)
1566{
1567 std::vector<ROOT::RNTupleLocator> locators;
1568 locators.reserve(mask.size());
1569 std::size_t i = 0;
1570 for (auto &range : ranges) {
1571 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1572 if (mask[i++])
1573 locators.push_back(CommitSealedPageImpl(range.fPhysicalColumnId, *sealedPageIt));
1574 }
1575 }
1576 locators.shrink_to_fit();
1577 return locators;
1578}
1579
1580void ROOT::Internal::RPagePersistentSink::CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges)
1581{
1582 /// Used in the `originalPages` map
1583 struct RSealedPageLink {
1584 const RSealedPage *fSealedPage = nullptr; ///< Points to the first occurrence of a page with a specific checksum
1585 std::size_t fLocatorIdx = 0; ///< The index in the locator vector returned by CommitSealedPageVImpl()
1586 };
1587
1588 std::vector<bool> mask;
1589 // For every sealed page, stores the corresponding index in the locator vector returned by CommitSealedPageVImpl()
1590 std::vector<std::size_t> locatorIndexes;
1591 // Maps page checksums to the first sealed page with that checksum
1592 std::unordered_map<std::uint64_t, RSealedPageLink> originalPages;
1593 std::size_t iLocator = 0;
1594 for (auto &range : ranges) {
1595 const auto rangeSize = std::distance(range.fFirst, range.fLast);
1596 mask.reserve(mask.size() + rangeSize);
1597 locatorIndexes.reserve(locatorIndexes.size() + rangeSize);
1598
1599 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1600 if (!fFeatures.fCanMergePages || !fOptions->GetEnableSamePageMerging()) {
1601 mask.emplace_back(true);
1602 locatorIndexes.emplace_back(iLocator++);
1603 continue;
1604 }
1605 // Same page merging requires page checksums - this is checked in the write options
1606 R__ASSERT(sealedPageIt->GetHasChecksum());
1607
1608 const auto chk = sealedPageIt->GetChecksum().Unwrap();
1609 auto itr = originalPages.find(chk);
1610 if (itr == originalPages.end()) {
1611 originalPages.insert({chk, {&(*sealedPageIt), iLocator}});
1612 mask.emplace_back(true);
1613 locatorIndexes.emplace_back(iLocator++);
1614 continue;
1615 }
1616
1617 const auto *p = itr->second.fSealedPage;
1618 if ((sealedPageIt->GetDataSize() != p->GetDataSize()) ||
1619 (memcmp(sealedPageIt->GetBuffer(), p->GetBuffer(), p->GetDataSize()) != 0)) {
1620 mask.emplace_back(true);
1621 locatorIndexes.emplace_back(iLocator++);
1622 continue;
1623 }
1624
1625 mask.emplace_back(false);
1626 locatorIndexes.emplace_back(itr->second.fLocatorIdx);
1627 }
1628
1629 mask.shrink_to_fit();
1630 locatorIndexes.shrink_to_fit();
1631 }
1632
1633 auto locators = CommitSealedPageVImpl(ranges, mask);
1634 unsigned i = 0;
1635
1636 for (auto &range : ranges) {
1637 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1638 fOpenColumnRanges.at(range.fPhysicalColumnId).IncrementNElements(sealedPageIt->GetNElements());
1639
1641 pageInfo.SetNElements(sealedPageIt->GetNElements());
1642 pageInfo.SetLocator(locators[locatorIndexes[i++]]);
1643 pageInfo.SetHasChecksum(sealedPageIt->GetHasChecksum());
1644 fOpenPageRanges.at(range.fPhysicalColumnId).GetPageInfos().emplace_back(pageInfo);
1645 }
1646 }
1647}
1648
1651{
1652 RStagedCluster stagedCluster;
1653 stagedCluster.fNBytesWritten = StageClusterImpl();
1654 stagedCluster.fNEntries = nNewEntries;
1655
1656 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1657 RStagedCluster::RColumnInfo columnInfo;
1658 columnInfo.fCompressionSettings = fOpenColumnRanges[i].GetCompressionSettings().value();
1659 if (fOpenColumnRanges[i].IsSuppressed()) {
1660 assert(fOpenPageRanges[i].GetPageInfos().empty());
1661 columnInfo.fPageRange.SetPhysicalColumnId(i);
1662 columnInfo.fIsSuppressed = true;
1663 // We reset suppressed columns to the state they would have if they were active (not suppressed).
1664 fOpenColumnRanges[i].SetNElements(0);
1665 fOpenColumnRanges[i].SetIsSuppressed(false);
1666 } else {
1667 std::swap(columnInfo.fPageRange, fOpenPageRanges[i]);
1668 fOpenPageRanges[i].SetPhysicalColumnId(i);
1669
1670 columnInfo.fNElements = fOpenColumnRanges[i].GetNElements();
1671 fOpenColumnRanges[i].SetNElements(0);
1672 }
1673 stagedCluster.fColumnInfos.push_back(std::move(columnInfo));
1674 }
1675
1676 return stagedCluster;
1677}
1678
1680{
1681 for (const auto &cluster : clusters) {
1682 RClusterDescriptorBuilder clusterBuilder;
1683 clusterBuilder.ClusterId(fDescriptorBuilder.GetDescriptor().GetNActiveClusters())
1684 .FirstEntryIndex(fPrevClusterNEntries)
1685 .NEntries(cluster.fNEntries);
1686 for (const auto &columnInfo : cluster.fColumnInfos) {
1687 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1688 if (columnInfo.fIsSuppressed) {
1689 assert(columnInfo.fPageRange.GetPageInfos().empty());
1690 clusterBuilder.MarkSuppressedColumnRange(colId);
1691 } else {
1692 clusterBuilder.CommitColumnRange(colId, fOpenColumnRanges[colId].GetFirstElementIndex(),
1693 columnInfo.fCompressionSettings, columnInfo.fPageRange);
1694 fOpenColumnRanges[colId].IncrementFirstElementIndex(columnInfo.fNElements);
1695 }
1696 }
1697
1698 clusterBuilder.CommitSuppressedColumnRanges(fDescriptorBuilder.GetDescriptor()).ThrowOnError();
1699 for (const auto &columnInfo : cluster.fColumnInfos) {
1700 if (!columnInfo.fIsSuppressed)
1701 continue;
1702 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1703 // For suppressed columns, we need to reset the first element index to the first element of the next (upcoming)
1704 // cluster. This information has been determined for the committed cluster descriptor through
1705 // CommitSuppressedColumnRanges(), so we can use the information from the descriptor.
1706 const auto &columnRangeFromDesc = clusterBuilder.GetColumnRange(colId);
1707 fOpenColumnRanges[colId].SetFirstElementIndex(columnRangeFromDesc.GetFirstElementIndex() +
1708 columnRangeFromDesc.GetNElements());
1709 }
1710
1711 fDescriptorBuilder.AddCluster(clusterBuilder.MoveDescriptor().Unwrap());
1712 fPrevClusterNEntries += cluster.fNEntries;
1713 }
1714}
1715
1717{
1718 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1719
1720 const auto nClusters = descriptor.GetNActiveClusters();
1721 std::vector<ROOT::DescriptorId_t> physClusterIDs;
1722 physClusterIDs.reserve(nClusters);
1723 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1724 physClusterIDs.emplace_back(fSerializationContext.MapClusterId(i));
1725 }
1726
1727 auto szPageList =
1728 RNTupleSerializer::SerializePageList(nullptr, descriptor, physClusterIDs, fSerializationContext).Unwrap();
1729 auto bufPageList = MakeUninitArray<unsigned char>(szPageList);
1730 RNTupleSerializer::SerializePageList(bufPageList.get(), descriptor, physClusterIDs, fSerializationContext);
1731
1732 const auto clusterGroupId = descriptor.GetNClusterGroups();
1733 const auto locator = CommitClusterGroupImpl(bufPageList.get(), szPageList);
1735 cgBuilder.ClusterGroupId(clusterGroupId).PageListLocator(locator).PageListLength(szPageList);
1736 if (fNextClusterInGroup == nClusters) {
1737 cgBuilder.MinEntry(0).EntrySpan(0).NClusters(0);
1738 } else {
1739 const auto &firstClusterDesc = descriptor.GetClusterDescriptor(fNextClusterInGroup);
1740 const auto &lastClusterDesc = descriptor.GetClusterDescriptor(nClusters - 1);
1741 cgBuilder.MinEntry(firstClusterDesc.GetFirstEntryIndex())
1742 .EntrySpan(lastClusterDesc.GetFirstEntryIndex() + lastClusterDesc.GetNEntries() -
1743 firstClusterDesc.GetFirstEntryIndex())
1744 .NClusters(nClusters - fNextClusterInGroup);
1745 }
1746 std::vector<ROOT::DescriptorId_t> clusterIds;
1747 clusterIds.reserve(nClusters);
1748 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1749 clusterIds.emplace_back(i);
1750 }
1751 cgBuilder.AddSortedClusters(clusterIds);
1752 fDescriptorBuilder.AddClusterGroup(cgBuilder.MoveDescriptor().Unwrap());
1753 fSerializationContext.MapClusterGroupId(clusterGroupId);
1754
1755 fNextClusterInGroup = nClusters;
1756}
1757
1759 const RNTupleLink &attrAnchorInfo)
1760{
1762
1763 RNTupleAttrSetDescriptorBuilder attrSetDescBuilder;
1764 auto attrSetDesc = attrSetDescBuilder.SchemaVersion(kSchemaVersionMajor, kSchemaVersionMinor)
1765 .AnchorLength(attrAnchorInfo.fLength)
1766 .AnchorLocator(attrAnchorInfo.fLocator)
1767 .Name(attrSetName)
1769 .Unwrap();
1770 fDescriptorBuilder.AddAttributeSet(std::move(attrSetDesc)).ThrowOnError();
1771}
1772
1774{
1775 if (!fInfosOfStreamerFields.empty()) {
1776 // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but
1777 // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging.
1778 for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) {
1779 if (etDesc.GetContentId() == EExtraTypeInfoIds::kStreamerInfo) {
1780 // The specification mandates that the type name for a kStreamerInfo should be empty and the type version
1781 // should be zero.
1782 R__ASSERT(etDesc.GetTypeName().empty());
1783 R__ASSERT(etDesc.GetTypeVersion() == 0);
1784 auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap();
1785 fInfosOfStreamerFields.merge(etInfo);
1786 }
1787 }
1788
1789 RExtraTypeInfoDescriptorBuilder extraInfoBuilder;
1791 .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields));
1792 fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap());
1793 }
1794
1795 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1796
1797 auto szFooter = RNTupleSerializer::SerializeFooter(nullptr, descriptor, fSerializationContext).Unwrap();
1798 auto bufFooter = MakeUninitArray<unsigned char>(szFooter);
1799 RNTupleSerializer::SerializeFooter(bufFooter.get(), descriptor, fSerializationContext);
1800
1801 return CommitDatasetImpl(bufFooter.get(), szFooter);
1802}
1803
1805{
1806 fMetrics = RNTupleMetrics(prefix);
1807 fCounters = std::make_unique<RCounters>(RCounters{
1808 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageCommitted", "", "number of pages committed to storage"),
1809 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szWritePayload", "B", "volume written for committed pages"),
1810 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szZip", "B", "volume before zipping"),
1811 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallWrite", "ns", "wall clock time spent writing"),
1812 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallZip", "ns", "wall clock time spent compressing"),
1813 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuWrite", "ns", "CPU time spent writing"),
1814 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuZip", "ns",
1815 "CPU time spent compressing")});
1816}
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.
RNTupleAttrSetDescriptorBuilder & AnchorLocator(const RNTupleLocator &loc)
RNTupleAttrSetDescriptorBuilder & SchemaVersion(std::uint16_t major, std::uint16_t minor)
RResult< ROOT::Experimental::RNTupleAttrSetDescriptor > MoveDescriptor()
Attempt to make an AttributeSet descriptor.
RNTupleAttrSetDescriptorBuilder & Name(std::string_view name)
RNTupleAttrSetDescriptorBuilder & AnchorLength(std::uint32_t length)
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
bool ContainsColumn(ROOT::DescriptorId_t colId) const
Definition RCluster.hxx:192
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)
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 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.
ROOT::Internal::RPageRef LoadPageFromSummary(ColumnHandle_t columnHandle, const RPageSummary &pageSummary)
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
RClusterDescriptorIterable GetActiveClusterIterable() const
RClusterGroupDescriptorIterable GetClusterGroupIterable() const
std::size_t GetNActiveClusters() const
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