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#ifdef R__ENABLE_DAOS
28#endif
29
30#include <Compression.h>
31#include <TError.h>
32
33#include <algorithm>
34#include <atomic>
35#include <cassert>
36#include <cstring>
37#include <functional>
38#include <memory>
39#include <string_view>
40#include <unordered_map>
41#include <utility>
42
51
53
57
63
65 : fMetrics(""), fPageAllocator(std::make_unique<ROOT::Internal::RPageAllocatorHeap>()), fNTupleName(name)
66{
67}
68
70
72{
73 if (!fHasChecksum)
74 return;
75
76 auto charBuf = reinterpret_cast<const unsigned char *>(fBuffer);
77 auto checksumBuf = const_cast<unsigned char *>(charBuf) + GetDataSize();
78 std::uint64_t xxhash3;
80}
81
83{
84 if (!fHasChecksum)
86
87 auto success = RNTupleSerializer::VerifyXxHash3(reinterpret_cast<const unsigned char *>(fBuffer), GetDataSize());
88 if (!success)
89 return R__FAIL("page checksum verification failed, data corruption detected");
91}
92
94{
95 if (!fHasChecksum)
96 return R__FAIL("invalid attempt to extract non-existing page checksum");
97
98 assert(fBufferSize >= kNBytesPageChecksum);
99 std::uint64_t checksum;
101 reinterpret_cast<const unsigned char *>(fBuffer) + fBufferSize - kNBytesPageChecksum, checksum);
102 return checksum;
103}
104
105//------------------------------------------------------------------------------
106
109{
110 auto [itr, _] = fColumnInfos.emplace(physicalColumnId, std::vector<RColumnInfo>());
111 for (auto &columnInfo : itr->second) {
112 if (columnInfo.fElementId == elementId) {
113 columnInfo.fRefCounter++;
114 return;
115 }
116 }
117 itr->second.emplace_back(RColumnInfo{elementId, 1});
118}
119
122{
123 auto itr = fColumnInfos.find(physicalColumnId);
124 R__ASSERT(itr != fColumnInfos.end());
125 for (std::size_t i = 0; i < itr->second.size(); ++i) {
126 if (itr->second[i].fElementId != elementId)
127 continue;
128
129 itr->second[i].fRefCounter--;
130 if (itr->second[i].fRefCounter == 0) {
131 itr->second.erase(itr->second.begin() + i);
132 if (itr->second.empty()) {
133 fColumnInfos.erase(itr);
134 }
135 }
136 break;
137 }
138}
139
147
149{
150 if (fFirstEntry == ROOT::kInvalidNTupleIndex) {
151 /// Entry range unset, we assume that the entry range covers the complete source
152 return true;
153 }
154
155 if (clusterDesc.GetNEntries() == 0)
156 return true;
157 if ((clusterDesc.GetFirstEntryIndex() + clusterDesc.GetNEntries()) <= fFirstEntry)
158 return false;
159 if (clusterDesc.GetFirstEntryIndex() >= (fFirstEntry + fNEntries))
160 return false;
161 return true;
162}
163
166 fClusterPool(*this, ROOT::Internal::RNTupleReadOptionsManip::GetClusterBunchSize(options)),
167 fPagePool(*this),
168 fOptions(options)
169{
170}
171
173
174std::unique_ptr<ROOT::Internal::RPageSource>
175ROOT::Internal::RPageSource::Create(std::string_view ntupleName, std::string_view location,
176 const ROOT::RNTupleReadOptions &options)
177{
178 if (ntupleName.empty()) {
179 throw RException(R__FAIL("empty RNTuple name"));
180 }
181 if (location.empty()) {
182 throw RException(R__FAIL("empty storage location"));
183 }
184 if (location.find("daos://") == 0)
185#ifdef R__ENABLE_DAOS
186 return std::make_unique<ROOT::Experimental::Internal::RPageSourceDaos>(ntupleName, location, options);
187#else
188 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
189#endif
190
191 return std::make_unique<ROOT::Internal::RPageSourceFile>(ntupleName, location, options);
192}
193
196{
198 auto physicalId =
199 GetSharedDescriptorGuard()->FindPhysicalColumnId(fieldId, column.GetIndex(), column.GetRepresentationIndex());
201 fActivePhysicalColumns.Insert(physicalId, column.GetElement()->GetIdentifier());
202 return ColumnHandle_t{physicalId, &column};
203}
204
206{
207 fActivePhysicalColumns.Erase(columnHandle.fPhysicalId, columnHandle.fColumn->GetElement()->GetIdentifier());
208}
209
211{
212 if ((range.fFirstEntry + range.fNEntries) > GetNEntries()) {
213 throw RException(R__FAIL("invalid entry range"));
214 }
215 fEntryRange = range;
216}
217
219{
220 if (!fHasStructure)
221 LoadStructureImpl();
222 fHasStructure = true;
223}
224
226{
227 if (fIsAttached)
228 return;
229
230 LoadStructure();
231
232 auto descGuard = GetExclDescriptorGuard();
233 descGuard.MoveIn(AttachImpl());
234 fStructureBuffer.Reset();
235
236 std::vector<unsigned char> buffer;
237 for (const auto &cgDesc : descGuard->GetClusterGroupIterable()) {
238 buffer.resize(cgDesc.GetPageListLength() + cgDesc.GetPageListLocator().GetNBytesOnStorage());
239 auto zipBuffer = buffer.data() + cgDesc.GetPageListLength();
240
241 LoadPageListImpl(cgDesc.GetPageListLocator(), zipBuffer);
242 RNTupleDecompressor::Unzip(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(),
243 cgDesc.GetPageListLength(), buffer.data());
244 RNTupleSerializer::DeserializePageList(buffer.data(), cgDesc.GetPageListLength(), cgDesc.GetId(), *descGuard,
245 mode);
246 }
247
248 fIsAttached = true;
249}
250
251std::unique_ptr<ROOT::Internal::RPageSource> ROOT::Internal::RPageSource::Clone() const
252{
253 auto clone = CloneImpl();
254 if (fIsAttached) {
255 clone->GetExclDescriptorGuard().MoveIn(GetSharedDescriptorGuard()->Clone());
256 clone->fHasStructure = true;
257 clone->fIsAttached = true;
258 }
259 return clone;
260}
261
263{
264 return GetSharedDescriptorGuard()->GetNEntries();
265}
266
268{
269 return GetSharedDescriptorGuard()->GetNElements(columnHandle.fPhysicalId);
270}
271
273{
274 if (fTaskScheduler)
275 UnzipClusterImpl(cluster);
276}
277
279{
280 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
281
282 const auto clusterId = cluster->GetId();
283 auto descriptorGuard = GetSharedDescriptorGuard();
284 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
285
286 fPreloadedClusters[clusterDescriptor.GetFirstEntryIndex()] = clusterId;
287
288 std::atomic<bool> foundChecksumFailure{false};
289
290 std::vector<std::unique_ptr<RColumnElementBase>> allElements;
291 const auto &columnsInCluster = cluster->GetAvailPhysicalColumns();
292 for (const auto columnId : columnsInCluster) {
293 // By the time we unzip a cluster, the set of active columns may have already changed wrt. to the moment when
294 // we requested reading the cluster. That doesn't matter much, we simply decompress what is now in the list
295 // of active columns.
296 if (!fActivePhysicalColumns.HasColumnInfos(columnId))
297 continue;
298 const auto &columnInfos = fActivePhysicalColumns.GetColumnInfos(columnId);
299
300 allElements.reserve(allElements.size() + columnInfos.size());
301 for (const auto &info : columnInfos) {
302 allElements.emplace_back(GenerateColumnElement(info.fElementId));
303
304 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
305 std::uint64_t pageNo = 0;
306 std::uint64_t firstInPage = 0;
307 for (const auto &pi : pageRange.GetPageInfos()) {
308 auto onDiskPage = cluster->GetOnDiskPage(ROnDiskPage::Key{columnId, pageNo});
310 sealedPage.SetNElements(pi.GetNElements());
311 sealedPage.SetHasChecksum(pi.HasChecksum());
312 sealedPage.SetBufferSize(pi.GetLocator().GetNBytesOnStorage() + pi.HasChecksum() * kNBytesPageChecksum);
313 sealedPage.SetBuffer(onDiskPage->GetAddress());
314 R__ASSERT(onDiskPage && (onDiskPage->GetSize() == sealedPage.GetBufferSize()));
315
316 auto taskFunc = [this, columnId, clusterId, firstInPage, sealedPage, element = allElements.back().get(),
318 indexOffset = clusterDescriptor.GetColumnRange(columnId).GetFirstElementIndex()]() {
319 const ROOT::Internal::RPagePool::RKey keyPagePool{columnId, element->GetIdentifier().fInMemoryType};
320 auto rv = UnsealPage(sealedPage, *element);
321 if (!rv) {
323 return;
324 }
325 auto newPage = rv.Unwrap();
326 fCounters->fSzUnzip.Add(element->GetSize() * sealedPage.GetNElements());
327
328 newPage.SetWindow(indexOffset + firstInPage,
330 fPagePool.PreloadPage(std::move(newPage), keyPagePool);
331 };
332
333 fTaskScheduler->AddTask(taskFunc);
334
335 firstInPage += pi.GetNElements();
336 pageNo++;
337 } // for all pages in column
338
339 fCounters->fNPageUnsealed.Add(pageNo);
340 } // for all in-memory types of the column
341 } // for all columns in cluster
342
343 fTaskScheduler->Wait();
344
346 throw RException(R__FAIL("page checksum verification failed, data corruption detected"));
347 }
348}
349
354{
355 auto descriptorGuard = GetSharedDescriptorGuard();
356 const auto &clusterDesc = descriptorGuard->GetClusterDescriptor(clusterKey.fClusterId);
357
358 for (auto physicalColumnId : clusterKey.fPhysicalColumnSet) {
359 if (clusterDesc.GetColumnRange(physicalColumnId).IsSuppressed())
360 continue;
361
362 const auto &pageRange = clusterDesc.GetPageRange(physicalColumnId);
364 for (const auto &pageInfo : pageRange.GetPageInfos()) {
365 if (pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
368 pageInfo.GetLocator().GetNBytesOnStorage()));
369 } else {
371 }
372 ++pageNo;
373 }
374 }
375}
376
378{
379 if (fLastUsedCluster == clusterId)
380 return;
381
383 GetSharedDescriptorGuard()->GetClusterDescriptor(clusterId).GetFirstEntryIndex();
384 auto itr = fPreloadedClusters.begin();
385 while ((itr != fPreloadedClusters.end()) && (itr->first < firstEntryIndex)) {
386 if (fPinnedClusters.count(itr->second) > 0) {
387 ++itr;
388 } else {
389 fPagePool.Evict(itr->second);
390 itr = fPreloadedClusters.erase(itr);
391 }
392 }
393 std::size_t poolWindow = 0;
394 while ((itr != fPreloadedClusters.end()) &&
396 ++itr;
397 ++poolWindow;
398 }
399 while (itr != fPreloadedClusters.end()) {
400 if (fPinnedClusters.count(itr->second) > 0) {
401 ++itr;
402 } else {
403 fPagePool.Evict(itr->second);
404 itr = fPreloadedClusters.erase(itr);
405 }
406 }
407
408 fLastUsedCluster = clusterId;
409}
410
413{
414 const auto clusterId = localIndex.GetClusterId();
415
417 {
418 auto descriptorGuard = GetSharedDescriptorGuard();
419 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
420 pageInfo = clusterDescriptor.GetPageRange(physicalColumnId).Find(localIndex.GetIndexInCluster());
421 }
422
423 assert(pageInfo.GetLocator().GetType() != RNTupleLocator::kTypePageZero);
424
425 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
426 sealedPage.SetNElements(pageInfo.GetNElements());
427 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
428
429 if (!sealedPage.GetBuffer())
430 return;
431
432 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
433 sealedPage.VerifyChecksumIfEnabled().ThrowOnError();
434}
435
438{
439 const auto &pageInfo = pageSummary.fPageInfo;
440 assert(pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero);
441
442 const auto element = columnHandle.fColumn->GetElement();
443 const auto elementSize = element->GetSize();
444 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
445
446 auto pageZero = fPageAllocator->NewPage(elementSize, pageInfo.GetNElements());
447 pageZero.GrowUnchecked(pageInfo.GetNElements());
448 std::memset(pageZero.GetBuffer(), 0, pageZero.GetNBytes());
449 pageZero.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
450 RPage::RClusterInfo(pageSummary.fClusterId, pageSummary.fColumnOffset));
451 return fPagePool.RegisterPage(std::move(pageZero), RPagePool::RKey{columnHandle.fPhysicalId, elementInMemoryType});
452}
453
456{
457 if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypeUnknown) {
458 throw RException(R__FAIL("tried to read a page with an unknown locator"));
459 } else if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
460 return LoadZeroPage(columnHandle, pageSummary);
461 }
462
463 const auto &columnId = columnHandle.fPhysicalId;
464 const auto &clusterId = pageSummary.fClusterId;
465 const auto &pageInfo = pageSummary.fPageInfo;
466
467 const auto element = columnHandle.fColumn->GetElement();
468 const auto elementSize = element->GetSize();
469 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
470
471 UpdateLastUsedCluster(clusterId);
472
474 sealedPage.SetNElements(pageInfo.GetNElements());
475 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
476 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
477 std::unique_ptr<unsigned char[]> directReadBuffer; // only used if cluster pool is turned off
478
479 if (fOptions.GetClusterCache() == ROOT::RNTupleReadOptions::EClusterCache::kOff) {
481 sealedPage.SetBuffer(directReadBuffer.get());
482 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
483
484 fCounters->fNPageRead.Inc();
485 fCounters->fNRead.Inc();
486 fCounters->fSzReadPayload.Add(sealedPage.GetBufferSize());
487 } else {
488 if (!fCurrentCluster || (fCurrentCluster->GetId() != clusterId) || !fCurrentCluster->ContainsColumn(columnId))
489 fCurrentCluster = fClusterPool.GetCluster(clusterId, fActivePhysicalColumns.ToColumnSet());
490 R__ASSERT(fCurrentCluster->ContainsColumn(columnId));
491
492 // The cluster pool may have unzipped the required page into the page pool
494 RNTupleLocalIndex(clusterId, pageInfo.GetFirstElementIndex()));
495 if (!cachedPageRef.Get().IsNull())
496 return cachedPageRef;
497
498 ROnDiskPage::Key key(columnId, pageInfo.GetPageNumber());
499 auto onDiskPage = fCurrentCluster->GetOnDiskPage(key);
500 R__ASSERT(onDiskPage && (sealedPage.GetBufferSize() == onDiskPage->GetSize()));
501 sealedPage.SetBuffer(onDiskPage->GetAddress());
502 }
503
505 {
506 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
507 newPage = UnsealPage(sealedPage, *element).Unwrap();
508 fCounters->fSzUnzip.Add(elementSize * pageInfo.GetNElements());
509 }
510
511 newPage.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
513 fCounters->fNPageUnsealed.Inc();
514
515 return fPagePool.RegisterPage(std::move(newPage), RPagePool::RKey{columnId, elementInMemoryType});
516}
517
520{
521 const auto columnId = columnHandle.fPhysicalId;
522 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
523 auto cachedPageRef =
524 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, globalIndex);
525 if (!cachedPageRef.Get().IsNull()) {
526 UpdateLastUsedCluster(cachedPageRef.Get().GetClusterInfo().GetId());
527 return cachedPageRef;
528 }
529
531 {
532 auto descriptorGuard = GetSharedDescriptorGuard();
533 pageSummary.fClusterId = descriptorGuard->FindClusterId(columnId, globalIndex);
534
535 if (pageSummary.fClusterId == ROOT::kInvalidDescriptorId)
536 throw RException(R__FAIL("entry with index " + std::to_string(globalIndex) + " out of bounds"));
537
538 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(pageSummary.fClusterId);
539 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
540 if (columnRange.IsSuppressed())
542
543 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
544 R__ASSERT(pageSummary.fColumnOffset <= globalIndex);
545 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(globalIndex - pageSummary.fColumnOffset);
546 }
547
548 return LoadPageFromSummary(columnHandle, pageSummary);
549}
550
553{
554 const auto clusterId = localIndex.GetClusterId();
555 const auto columnId = columnHandle.fPhysicalId;
556 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
557 auto cachedPageRef =
558 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, localIndex);
559 if (!cachedPageRef.Get().IsNull()) {
560 UpdateLastUsedCluster(clusterId);
561 return cachedPageRef;
562 }
563
565 throw RException(R__FAIL("entry out of bounds"));
566
568 {
569 auto descriptorGuard = GetSharedDescriptorGuard();
570 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
571 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
572 if (columnRange.IsSuppressed())
574
575 pageSummary.fClusterId = clusterId;
576 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
577 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(localIndex.GetIndexInCluster());
578 }
579
580 return LoadPageFromSummary(columnHandle, pageSummary);
581}
582
584{
585 fMetrics = RNTupleMetrics(prefix);
586 fMetrics.ObserveMetrics(fClusterPool.GetMetrics());
587 fMetrics.ObserveMetrics(fPagePool.GetMetrics());
588 fCounters = std::make_unique<RCounters>(RCounters{
589 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nReadV", "", "number of vector read requests"),
590 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nRead", "", "number of byte ranges read"),
591 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadPayload", "B", "volume read from storage (required)"),
592 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadOverhead", "B", "volume read from storage (overhead)"),
593 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szUnzip", "B", "volume after unzipping"),
594 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nClusterLoaded", "",
595 "number of partial clusters preloaded from storage"),
596 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageRead", "", "number of pages read from storage"),
597 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageUnsealed", "", "number of pages unzipped and decoded"),
598 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallRead", "ns", "wall clock time spent reading"),
599 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallUnzip", "ns", "wall clock time spent decompressing"),
600 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuRead", "ns", "CPU time spent reading"),
601 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuUnzip", "ns",
602 "CPU time spent decompressing"),
603 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
604 "bwRead", "MB/s", "bandwidth compressed bytes read per second", fMetrics,
605 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
606 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
607 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
608 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
609 if (auto walltime = timeWallRead->GetValueAsInt()) {
610 double payload = szReadPayload->GetValueAsInt();
611 double overhead = szReadOverhead->GetValueAsInt();
612 // unit: bytes / nanosecond = GB/s
613 return {true, (1000. * (payload + overhead) / walltime)};
614 }
615 }
616 }
617 }
618 return {false, -1.};
619 }),
620 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
621 "bwReadUnzip", "MB/s", "bandwidth uncompressed bytes read per second", fMetrics,
622 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
623 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
624 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
625 if (auto walltime = timeWallRead->GetValueAsInt()) {
626 double unzip = szUnzip->GetValueAsInt();
627 // unit: bytes / nanosecond = GB/s
628 return {true, 1000. * unzip / walltime};
629 }
630 }
631 }
632 return {false, -1.};
633 }),
634 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
635 "bwUnzip", "MB/s", "decompression bandwidth of uncompressed bytes per second", fMetrics,
636 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
637 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
638 if (const auto timeWallUnzip = metrics.GetLocalCounter("timeWallUnzip")) {
639 if (auto walltime = timeWallUnzip->GetValueAsInt()) {
640 double unzip = szUnzip->GetValueAsInt();
641 // unit: bytes / nanosecond = GB/s
642 return {true, 1000. * unzip / walltime};
643 }
644 }
645 }
646 return {false, -1.};
647 }),
648 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
649 "rtReadEfficiency", "", "ratio of payload over all bytes read", fMetrics,
650 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
651 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
652 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
653 if (auto payload = szReadPayload->GetValueAsInt()) {
654 // r/(r+o) = 1/((r+o)/r) = 1/(1 + o/r)
655 return {true, 1. / (1. + (1. * szReadOverhead->GetValueAsInt()) / payload)};
656 }
657 }
658 }
659 return {false, -1.};
660 }),
661 *fMetrics.MakeCounter<RNTupleCalcPerf *>("rtCompression", "", "ratio of compressed bytes / uncompressed bytes",
662 fMetrics, [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
663 if (const auto szReadPayload =
664 metrics.GetLocalCounter("szReadPayload")) {
665 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
666 if (auto unzip = szUnzip->GetValueAsInt()) {
667 return {true, (1. * szReadPayload->GetValueAsInt()) / unzip};
668 }
669 }
670 }
671 return {false, -1.};
672 })});
673}
674
677{
678 return UnsealPage(sealedPage, element, *fPageAllocator);
679}
680
684{
685 // Unsealing a page zero is a no-op. `RPageRange::ExtendToFitColumnRange()` guarantees that the page zero buffer is
686 // large enough to hold `sealedPage.fNElements`
688 auto page = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
689 page.GrowUnchecked(sealedPage.GetNElements());
690 memset(page.GetBuffer(), 0, page.GetNBytes());
691 return page;
692 }
693
694 auto rv = sealedPage.VerifyChecksumIfEnabled();
695 if (!rv)
696 return R__FORWARD_ERROR(rv);
697
698 const auto bytesPacked = element.GetPackedSize(sealedPage.GetNElements());
699 auto page = pageAlloc.NewPage(element.GetPackedSize(), sealedPage.GetNElements());
700 if (sealedPage.GetDataSize() != bytesPacked) {
702 page.GetBuffer());
703 } else {
704 // We cannot simply map the sealed page as we don't know its life time. Specialized page sources
705 // may decide to implement to not use UnsealPage but to custom mapping / decompression code.
706 // Note that usually pages are compressed.
707 memcpy(page.GetBuffer(), sealedPage.GetBuffer(), bytesPacked);
708 }
709
710 if (!element.IsMappable()) {
711 auto tmp = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
712 element.Unpack(tmp.GetBuffer(), page.GetBuffer(), sealedPage.GetNElements());
713 page = std::move(tmp);
714 }
715
716 page.GrowUnchecked(sealedPage.GetNElements());
717 return page;
718}
719
721{
722 if (fHasStreamerInfosRegistered)
723 return;
724
725 for (const auto &extraTypeInfo : fDescriptor.GetExtraTypeInfoIterable()) {
727 continue;
728 // We don't need the result, it's enough that during deserialization, BuildCheck() is called for every
729 // streamer info record.
731 }
732
733 fHasStreamerInfosRegistered = true;
734}
735
736//------------------------------------------------------------------------------
737
739{
740 // Make the sort order unique by adding the physical on-disk column id as a secondary key
741 if (fCurrentPageSize == other.fCurrentPageSize)
742 return fColumn->GetOnDiskId() > other.fColumn->GetOnDiskId();
743 return fCurrentPageSize > other.fCurrentPageSize;
744}
745
747{
748 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
749 return true;
750
751 auto itr = fColumnsSortedByPageSize.begin();
752 while (itr != fColumnsSortedByPageSize.end()) {
753 if (itr->fCurrentPageSize <= pageSizeLimit)
754 break;
755 if (itr->fCurrentPageSize == itr->fInitialPageSize) {
756 ++itr;
757 continue;
758 }
759
760 // Flushing the current column will invalidate itr
761 auto itrFlush = itr++;
762
763 RColumnInfo next;
764 if (itr != fColumnsSortedByPageSize.end())
765 next = *itr;
766
767 itrFlush->fColumn->Flush();
768 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
769 return true;
770
771 if (next.fColumn == nullptr)
772 return false;
773 itr = fColumnsSortedByPageSize.find(next);
774 };
775
776 return false;
777}
778
780{
781 const RColumnInfo key{&column, column.GetWritePageCapacity(), 0};
782 auto itr = fColumnsSortedByPageSize.find(key);
783 if (itr == fColumnsSortedByPageSize.end()) {
784 if (!TryEvict(newWritePageSize, 0))
785 return false;
786 fColumnsSortedByPageSize.insert({&column, newWritePageSize, newWritePageSize});
787 fCurrentAllocatedBytes += newWritePageSize;
788 return true;
789 }
790
792 assert(newWritePageSize >= elem.fInitialPageSize);
793
794 if (newWritePageSize == elem.fCurrentPageSize)
795 return true;
796
797 fColumnsSortedByPageSize.erase(itr);
798
799 if (newWritePageSize < elem.fCurrentPageSize) {
800 // Page got smaller
801 fCurrentAllocatedBytes -= elem.fCurrentPageSize - newWritePageSize;
802 elem.fCurrentPageSize = newWritePageSize;
803 fColumnsSortedByPageSize.insert(elem);
804 return true;
805 }
806
807 // Page got larger, we may need to make space available
808 const auto diffBytes = newWritePageSize - elem.fCurrentPageSize;
809 if (!TryEvict(diffBytes, elem.fCurrentPageSize)) {
810 // Don't change anything, let the calling column flush itself
811 // TODO(jblomer): we may consider skipping the column in TryEvict and thus avoiding erase+insert
812 fColumnsSortedByPageSize.insert(elem);
813 return false;
814 }
815 fCurrentAllocatedBytes += diffBytes;
816 elem.fCurrentPageSize = newWritePageSize;
817 fColumnsSortedByPageSize.insert(elem);
818 return true;
819}
820
821//------------------------------------------------------------------------------
822
824 : RPageStorage(name), fOptions(options.Clone()), fWritePageMemoryManager(options.GetPageBufferBudget())
825{
827}
828
830
832{
833 assert(config.fPage);
834 assert(config.fElement);
835 assert(config.fBuffer);
836
837 unsigned char *pageBuf = reinterpret_cast<unsigned char *>(config.fPage->GetBuffer());
838 bool isAdoptedBuffer = true;
839 auto nBytesPacked = config.fPage->GetNBytes();
840 auto nBytesChecksum = config.fWriteChecksum * kNBytesPageChecksum;
841
842 if (!config.fElement->IsMappable()) {
843 nBytesPacked = config.fElement->GetPackedSize(config.fPage->GetNElements());
844 pageBuf = new unsigned char[nBytesPacked];
845 isAdoptedBuffer = false;
846 config.fElement->Pack(pageBuf, config.fPage->GetBuffer(), config.fPage->GetNElements());
847 }
849
850 if ((config.fCompressionSettings != 0) || !config.fElement->IsMappable() || !config.fAllowAlias ||
851 config.fWriteChecksum) {
854 if (!isAdoptedBuffer)
855 delete[] pageBuf;
856 pageBuf = reinterpret_cast<unsigned char *>(config.fBuffer);
857 isAdoptedBuffer = true;
858 }
859
861
863 sealedPage.ChecksumIfEnabled();
864
865 return sealedPage;
866}
867
870{
871 const auto nBytes = page.GetNBytes() + GetWriteOptions().GetEnablePageChecksums() * kNBytesPageChecksum;
872 if (fSealPageBuffer.size() < nBytes)
873 fSealPageBuffer.resize(nBytes);
874
875 RSealPageConfig config;
876 config.fPage = &page;
877 config.fElement = &element;
878 config.fCompressionSettings = GetWriteOptions().GetCompression();
879 config.fWriteChecksum = GetWriteOptions().GetEnablePageChecksums();
880 config.fAllowAlias = true;
881 config.fBuffer = fSealPageBuffer.data();
882
883 return SealPage(config);
884}
885
887{
888 for (const auto &cb : fOnDatasetCommitCallbacks)
889 cb(*this);
890 return CommitDatasetImpl();
891}
892
894{
895 R__ASSERT(nElements > 0);
896 const auto elementSize = columnHandle.fColumn->GetElement()->GetSize();
897 const auto nBytes = elementSize * nElements;
898 if (!fWritePageMemoryManager.TryUpdate(*columnHandle.fColumn, nBytes))
899 return ROOT::Internal::RPage();
900 return fPageAllocator->NewPage(elementSize, nElements);
901}
902
903//------------------------------------------------------------------------------
904
905std::unique_ptr<ROOT::Internal::RPageSink>
906ROOT::Internal::RPagePersistentSink::Create(std::string_view ntupleName, std::string_view location,
907 const ROOT::RNTupleWriteOptions &options)
908{
909 if (ntupleName.empty()) {
910 throw RException(R__FAIL("empty RNTuple name"));
911 }
912 if (location.empty()) {
913 throw RException(R__FAIL("empty storage location"));
914 }
915 if (location.find("daos://") == 0) {
916#ifdef R__ENABLE_DAOS
917 return std::make_unique<ROOT::Experimental::Internal::RPageSinkDaos>(ntupleName, location, options);
918#else
919 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
920#endif
921 }
922
923 // Otherwise assume that the user wants us to create a file.
924 return std::make_unique<ROOT::Internal::RPageSinkFile>(ntupleName, location, options);
925}
926
928 const ROOT::RNTupleWriteOptions &options)
929 : RPageSink(name, options)
930{
931}
932
934
937{
938 auto columnId = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
940 columnBuilder.LogicalColumnId(columnId)
941 .PhysicalColumnId(columnId)
942 .FieldId(fieldId)
943 .BitsOnStorage(column.GetBitsOnStorage())
944 .ValueRange(column.GetValueRange())
945 .Type(column.GetType())
946 .Index(column.GetIndex())
947 .RepresentationIndex(column.GetRepresentationIndex())
948 .FirstElementIndex(column.GetFirstElementIndex());
949 // For late model extension, we assume that the primary column representation is the active one for the
950 // deferred range. All other representations are suppressed.
951 if (column.GetFirstElementIndex() > 0 && column.GetRepresentationIndex() > 0)
952 columnBuilder.SetSuppressedDeferred();
953 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
954 return ColumnHandle_t{columnId, &column};
955}
956
959{
960 if (fIsInitialized) {
961 for (const auto &field : changeset.fAddedFields) {
962 if (field->GetStructure() == ENTupleStructure::kStreamer) {
963 throw ROOT::RException(R__FAIL("a Model cannot be extended with Streamer fields"));
964 }
965 }
966 }
967
968 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
969
970 if (descriptor.GetNLogicalColumns() > descriptor.GetNPhysicalColumns()) {
971 // If we already have alias columns, add an offset to the alias columns so that the new physical columns
972 // of the changeset follow immediately the already existing physical columns
973 auto getNColumns = [](const ROOT::RFieldBase &f) -> std::size_t {
974 const auto &reps = f.GetColumnRepresentatives();
975 if (reps.empty())
976 return 0;
977 return reps.size() * reps[0].size();
978 };
979 std::uint32_t nNewPhysicalColumns = 0;
980 for (auto f : changeset.fAddedFields) {
982 for (const auto &descendant : *f)
984 }
985 fDescriptorBuilder.ShiftAliasColumns(nNewPhysicalColumns);
986 }
987
988 auto addField = [&](ROOT::RFieldBase &f) {
989 auto fieldId = descriptor.GetNFields();
990 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(f).FieldId(fieldId).MakeDescriptor().Unwrap());
991 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
992 f.SetOnDiskId(fieldId);
993 ROOT::Internal::CallConnectPageSinkOnField(f, *this, firstEntry); // issues in turn calls to `AddColumn()`
994 };
996 auto fieldId = descriptor.GetNFields();
997 auto sourceFieldId =
999 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(f).FieldId(fieldId).MakeDescriptor().Unwrap());
1000 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1001 fDescriptorBuilder.AddFieldProjection(sourceFieldId, fieldId);
1002 f.SetOnDiskId(fieldId);
1003 for (const auto &source : descriptor.GetColumnIterable(sourceFieldId)) {
1004 auto targetId = descriptor.GetNLogicalColumns();
1006 columnBuilder.LogicalColumnId(targetId)
1007 .PhysicalColumnId(source.GetLogicalId())
1008 .FieldId(fieldId)
1009 .BitsOnStorage(source.GetBitsOnStorage())
1010 .ValueRange(source.GetValueRange())
1011 .Type(source.GetType())
1012 .Index(source.GetIndex())
1013 .RepresentationIndex(source.GetRepresentationIndex());
1014 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
1015 }
1016 };
1017
1018 R__ASSERT(firstEntry >= fPrevClusterNEntries);
1019 const auto nColumnsBeforeUpdate = descriptor.GetNPhysicalColumns();
1020 for (auto f : changeset.fAddedFields) {
1021 addField(*f);
1022 for (auto &descendant : *f)
1024 }
1025 for (auto f : changeset.fAddedProjectedFields) {
1027 for (auto &descendant : *f)
1029 }
1030
1031 const auto nColumns = descriptor.GetNPhysicalColumns();
1032 fOpenColumnRanges.reserve(fOpenColumnRanges.size() + (nColumns - nColumnsBeforeUpdate));
1033 fOpenPageRanges.reserve(fOpenPageRanges.size() + (nColumns - nColumnsBeforeUpdate));
1036 columnRange.SetPhysicalColumnId(i);
1037 // We set the first element index in the current cluster to the first element that is part of a materialized page
1038 // (i.e., that is part of a page list). For columns created during late model extension, however, the column range
1039 // is fixed up as needed by `RClusterDescriptorBuilder::AddExtendedColumnRanges()` on read back.
1040 columnRange.SetFirstElementIndex(descriptor.GetColumnDescriptor(i).GetFirstElementIndex());
1041 columnRange.SetNElements(0);
1042 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1043 fOpenColumnRanges.emplace_back(columnRange);
1045 pageRange.SetPhysicalColumnId(i);
1046 fOpenPageRanges.emplace_back(std::move(pageRange));
1047 }
1048
1049 // Mapping of memory to on-disk column IDs usually happens during serialization of the ntuple header. If the
1050 // header was already serialized, this has to be done manually as it is required for page list serialization.
1051 if (fSerializationContext.GetHeaderSize() > 0)
1052 fSerializationContext.MapSchema(descriptor, /*forHeaderExtension=*/true);
1053}
1054
1056{
1057 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
1058 throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()"));
1059
1060 fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap());
1061}
1062
1064{
1065 fDescriptorBuilder.SetNTuple(fNTupleName, model.GetDescription());
1066 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1067
1069 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(fieldZero).FieldId(0).MakeDescriptor().Unwrap());
1070 fieldZero.SetOnDiskId(0);
1072 projectedFields.GetFieldZero().SetOnDiskId(0);
1073
1075 initialChangeset.fAddedFields.reserve(fieldZero.GetMutableSubfields().size());
1076 for (auto f : fieldZero.GetMutableSubfields())
1077 initialChangeset.fAddedFields.emplace_back(f);
1078 initialChangeset.fAddedProjectedFields.reserve(projectedFields.GetFieldZero().GetMutableSubfields().size());
1079 for (auto f : projectedFields.GetFieldZero().GetMutableSubfields())
1080 initialChangeset.fAddedProjectedFields.emplace_back(f);
1081 UpdateSchema(initialChangeset, 0U);
1082
1083 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1084 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1085 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1086 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1087
1088 fDescriptorBuilder.BeginHeaderExtension();
1089}
1090
1091std::unique_ptr<ROOT::RNTupleModel>
1093{
1094 // Create new descriptor
1095 fDescriptorBuilder.SetSchemaFromExisting(srcDescriptor);
1096 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1097
1098 // Create column/page ranges
1099 const auto nColumns = descriptor.GetNPhysicalColumns();
1100 R__ASSERT(fOpenColumnRanges.empty() && fOpenPageRanges.empty());
1101 fOpenColumnRanges.reserve(nColumns);
1102 fOpenPageRanges.reserve(nColumns);
1103 for (ROOT::DescriptorId_t i = 0; i < nColumns; ++i) {
1104 const auto &column = descriptor.GetColumnDescriptor(i);
1106 columnRange.SetPhysicalColumnId(i);
1107 columnRange.SetFirstElementIndex(column.GetFirstElementIndex());
1108 columnRange.SetNElements(0);
1109 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1110 fOpenColumnRanges.emplace_back(columnRange);
1112 pageRange.SetPhysicalColumnId(i);
1113 fOpenPageRanges.emplace_back(std::move(pageRange));
1114 }
1115
1116 if (copyClusters) {
1117 // Clone and add all cluster descriptors
1118 auto clusterId = srcDescriptor.FindClusterId(0, 0);
1120 auto &cluster = srcDescriptor.GetClusterDescriptor(clusterId);
1121 auto nEntries = cluster.GetNEntries();
1122 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1123 R__ASSERT(fOpenColumnRanges[i].GetPhysicalColumnId() == i);
1124 if (!cluster.ContainsColumn(i)) // a cluster may not contain a column if that column is deferred
1125 break;
1126 const auto &columnRange = cluster.GetColumnRange(i);
1127 R__ASSERT(columnRange.GetPhysicalColumnId() == i);
1128 // TODO: properly handle suppressed columns (check MarkSuppressedColumnRange())
1129 fOpenColumnRanges[i].IncrementFirstElementIndex(columnRange.GetNElements());
1130 }
1131 fDescriptorBuilder.AddCluster(cluster.Clone());
1132 fPrevClusterNEntries += nEntries;
1133
1134 clusterId = srcDescriptor.FindNextClusterId(clusterId);
1135 }
1136 }
1137
1138 // Create model
1140 modelOpts.SetReconstructProjections(true);
1141 // We want to emulate unknown types to allow merging RNTuples containing types that we lack dictionaries for.
1142 modelOpts.SetEmulateUnknownTypes(true);
1143 auto model = descriptor.CreateModel(modelOpts);
1144 if (!copyClusters) {
1146 projectedFields.GetFieldZero().SetOnDiskId(model->GetConstFieldZero().GetOnDiskId());
1147 }
1148
1149 // Serialize header and init from it
1150 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1151 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1152 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1153 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1154
1155 fDescriptorBuilder.BeginHeaderExtension();
1156
1157 // mark this sink as initialized
1158 fIsInitialized = true;
1159
1160 return model;
1161}
1162
1164{
1165 fOpenColumnRanges.at(columnHandle.fPhysicalId).SetIsSuppressed(true);
1166}
1167
1169{
1170 fOpenColumnRanges.at(columnHandle.fPhysicalId).IncrementNElements(page.GetNElements());
1171
1173 pageInfo.SetNElements(page.GetNElements());
1174 pageInfo.SetLocator(CommitPageImpl(columnHandle, page));
1175 pageInfo.SetHasChecksum(GetWriteOptions().GetEnablePageChecksums());
1176 fOpenPageRanges.at(columnHandle.fPhysicalId).GetPageInfos().emplace_back(pageInfo);
1177}
1178
1181{
1182 fOpenColumnRanges.at(physicalColumnId).IncrementNElements(sealedPage.GetNElements());
1183
1185 pageInfo.SetNElements(sealedPage.GetNElements());
1186 pageInfo.SetLocator(CommitSealedPageImpl(physicalColumnId, sealedPage));
1187 pageInfo.SetHasChecksum(sealedPage.GetHasChecksum());
1188 fOpenPageRanges.at(physicalColumnId).GetPageInfos().emplace_back(pageInfo);
1189}
1190
1191std::vector<ROOT::RNTupleLocator>
1192ROOT::Internal::RPagePersistentSink::CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges,
1193 const std::vector<bool> &mask)
1194{
1195 std::vector<ROOT::RNTupleLocator> locators;
1196 locators.reserve(mask.size());
1197 std::size_t i = 0;
1198 for (auto &range : ranges) {
1199 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1200 if (mask[i++])
1201 locators.push_back(CommitSealedPageImpl(range.fPhysicalColumnId, *sealedPageIt));
1202 }
1203 }
1204 locators.shrink_to_fit();
1205 return locators;
1206}
1207
1208void ROOT::Internal::RPagePersistentSink::CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges)
1209{
1210 /// Used in the `originalPages` map
1211 struct RSealedPageLink {
1212 const RSealedPage *fSealedPage = nullptr; ///< Points to the first occurrence of a page with a specific checksum
1213 std::size_t fLocatorIdx = 0; ///< The index in the locator vector returned by CommitSealedPageVImpl()
1214 };
1215
1216 std::vector<bool> mask;
1217 // For every sealed page, stores the corresponding index in the locator vector returned by CommitSealedPageVImpl()
1218 std::vector<std::size_t> locatorIndexes;
1219 // Maps page checksums to the first sealed page with that checksum
1220 std::unordered_map<std::uint64_t, RSealedPageLink> originalPages;
1221 std::size_t iLocator = 0;
1222 for (auto &range : ranges) {
1223 const auto rangeSize = std::distance(range.fFirst, range.fLast);
1224 mask.reserve(mask.size() + rangeSize);
1225 locatorIndexes.reserve(locatorIndexes.size() + rangeSize);
1226
1227 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1228 if (!fFeatures.fCanMergePages || !fOptions->GetEnableSamePageMerging()) {
1229 mask.emplace_back(true);
1230 locatorIndexes.emplace_back(iLocator++);
1231 continue;
1232 }
1233 // Same page merging requires page checksums - this is checked in the write options
1234 R__ASSERT(sealedPageIt->GetHasChecksum());
1235
1236 const auto chk = sealedPageIt->GetChecksum().Unwrap();
1237 auto itr = originalPages.find(chk);
1238 if (itr == originalPages.end()) {
1239 originalPages.insert({chk, {&(*sealedPageIt), iLocator}});
1240 mask.emplace_back(true);
1241 locatorIndexes.emplace_back(iLocator++);
1242 continue;
1243 }
1244
1245 const auto *p = itr->second.fSealedPage;
1246 if ((sealedPageIt->GetDataSize() != p->GetDataSize()) ||
1247 (memcmp(sealedPageIt->GetBuffer(), p->GetBuffer(), p->GetDataSize()) != 0)) {
1248 mask.emplace_back(true);
1249 locatorIndexes.emplace_back(iLocator++);
1250 continue;
1251 }
1252
1253 mask.emplace_back(false);
1254 locatorIndexes.emplace_back(itr->second.fLocatorIdx);
1255 }
1256
1257 mask.shrink_to_fit();
1258 locatorIndexes.shrink_to_fit();
1259 }
1260
1261 auto locators = CommitSealedPageVImpl(ranges, mask);
1262 unsigned i = 0;
1263
1264 for (auto &range : ranges) {
1265 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1266 fOpenColumnRanges.at(range.fPhysicalColumnId).IncrementNElements(sealedPageIt->GetNElements());
1267
1269 pageInfo.SetNElements(sealedPageIt->GetNElements());
1270 pageInfo.SetLocator(locators[locatorIndexes[i++]]);
1271 pageInfo.SetHasChecksum(sealedPageIt->GetHasChecksum());
1272 fOpenPageRanges.at(range.fPhysicalColumnId).GetPageInfos().emplace_back(pageInfo);
1273 }
1274 }
1275}
1276
1279{
1281 stagedCluster.fNBytesWritten = StageClusterImpl();
1282 stagedCluster.fNEntries = nNewEntries;
1283
1284 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1285 RStagedCluster::RColumnInfo columnInfo;
1286 columnInfo.fCompressionSettings = fOpenColumnRanges[i].GetCompressionSettings().value();
1287 if (fOpenColumnRanges[i].IsSuppressed()) {
1288 assert(fOpenPageRanges[i].GetPageInfos().empty());
1289 columnInfo.fPageRange.SetPhysicalColumnId(i);
1290 columnInfo.fIsSuppressed = true;
1291 // We reset suppressed columns to the state they would have if they were active (not suppressed).
1292 fOpenColumnRanges[i].SetNElements(0);
1293 fOpenColumnRanges[i].SetIsSuppressed(false);
1294 } else {
1295 std::swap(columnInfo.fPageRange, fOpenPageRanges[i]);
1296 fOpenPageRanges[i].SetPhysicalColumnId(i);
1297
1298 columnInfo.fNElements = fOpenColumnRanges[i].GetNElements();
1299 fOpenColumnRanges[i].SetNElements(0);
1300 }
1301 stagedCluster.fColumnInfos.push_back(std::move(columnInfo));
1302 }
1303
1304 return stagedCluster;
1305}
1306
1308{
1309 for (const auto &cluster : clusters) {
1311 clusterBuilder.ClusterId(fDescriptorBuilder.GetDescriptor().GetNActiveClusters())
1312 .FirstEntryIndex(fPrevClusterNEntries)
1313 .NEntries(cluster.fNEntries);
1314 for (const auto &columnInfo : cluster.fColumnInfos) {
1315 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1316 if (columnInfo.fIsSuppressed) {
1317 assert(columnInfo.fPageRange.GetPageInfos().empty());
1318 clusterBuilder.MarkSuppressedColumnRange(colId);
1319 } else {
1320 clusterBuilder.CommitColumnRange(colId, fOpenColumnRanges[colId].GetFirstElementIndex(),
1321 columnInfo.fCompressionSettings, columnInfo.fPageRange);
1322 fOpenColumnRanges[colId].IncrementFirstElementIndex(columnInfo.fNElements);
1323 }
1324 }
1325
1326 clusterBuilder.CommitSuppressedColumnRanges(fDescriptorBuilder.GetDescriptor()).ThrowOnError();
1327 for (const auto &columnInfo : cluster.fColumnInfos) {
1328 if (!columnInfo.fIsSuppressed)
1329 continue;
1330 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1331 // For suppressed columns, we need to reset the first element index to the first element of the next (upcoming)
1332 // cluster. This information has been determined for the committed cluster descriptor through
1333 // CommitSuppressedColumnRanges(), so we can use the information from the descriptor.
1334 const auto &columnRangeFromDesc = clusterBuilder.GetColumnRange(colId);
1335 fOpenColumnRanges[colId].SetFirstElementIndex(columnRangeFromDesc.GetFirstElementIndex() +
1336 columnRangeFromDesc.GetNElements());
1337 }
1338
1339 fDescriptorBuilder.AddCluster(clusterBuilder.MoveDescriptor().Unwrap());
1340 fPrevClusterNEntries += cluster.fNEntries;
1341 }
1342}
1343
1345{
1346 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1347
1348 const auto nClusters = descriptor.GetNActiveClusters();
1349 std::vector<ROOT::DescriptorId_t> physClusterIDs;
1350 physClusterIDs.reserve(nClusters);
1351 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1352 physClusterIDs.emplace_back(fSerializationContext.MapClusterId(i));
1353 }
1354
1355 auto szPageList =
1356 RNTupleSerializer::SerializePageList(nullptr, descriptor, physClusterIDs, fSerializationContext).Unwrap();
1359
1360 const auto clusterGroupId = descriptor.GetNClusterGroups();
1361 const auto locator = CommitClusterGroupImpl(bufPageList.get(), szPageList);
1363 cgBuilder.ClusterGroupId(clusterGroupId).PageListLocator(locator).PageListLength(szPageList);
1364 if (fNextClusterInGroup == nClusters) {
1365 cgBuilder.MinEntry(0).EntrySpan(0).NClusters(0);
1366 } else {
1367 const auto &firstClusterDesc = descriptor.GetClusterDescriptor(fNextClusterInGroup);
1368 const auto &lastClusterDesc = descriptor.GetClusterDescriptor(nClusters - 1);
1369 cgBuilder.MinEntry(firstClusterDesc.GetFirstEntryIndex())
1370 .EntrySpan(lastClusterDesc.GetFirstEntryIndex() + lastClusterDesc.GetNEntries() -
1371 firstClusterDesc.GetFirstEntryIndex())
1372 .NClusters(nClusters - fNextClusterInGroup);
1373 }
1374 std::vector<ROOT::DescriptorId_t> clusterIds;
1375 clusterIds.reserve(nClusters);
1376 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1377 clusterIds.emplace_back(i);
1378 }
1379 cgBuilder.AddSortedClusters(clusterIds);
1380 fDescriptorBuilder.AddClusterGroup(cgBuilder.MoveDescriptor().Unwrap());
1381 fSerializationContext.MapClusterGroupId(clusterGroupId);
1382
1383 fNextClusterInGroup = nClusters;
1384}
1385
1388{
1390
1392 auto attrSetDesc = attrSetDescBuilder.SchemaVersion(kSchemaVersionMajor, kSchemaVersionMinor)
1393 .AnchorLength(attrAnchorInfo.fLength)
1394 .AnchorLocator(attrAnchorInfo.fLocator)
1395 .Name(attrSetName)
1396 .MoveDescriptor()
1397 .Unwrap();
1398 fDescriptorBuilder.AddAttributeSet(std::move(attrSetDesc)).ThrowOnError();
1399}
1400
1402{
1403 if (!fInfosOfStreamerFields.empty()) {
1404 // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but
1405 // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging.
1406 for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) {
1407 if (etDesc.GetContentId() == EExtraTypeInfoIds::kStreamerInfo) {
1408 // The specification mandates that the type name for a kStreamerInfo should be empty and the type version
1409 // should be zero.
1410 R__ASSERT(etDesc.GetTypeName().empty());
1411 R__ASSERT(etDesc.GetTypeVersion() == 0);
1412 auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap();
1413 fInfosOfStreamerFields.merge(etInfo);
1414 }
1415 }
1416
1419 .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields));
1420 fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap());
1421 }
1422
1423 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1424
1425 auto szFooter = RNTupleSerializer::SerializeFooter(nullptr, descriptor, fSerializationContext).Unwrap();
1427 RNTupleSerializer::SerializeFooter(bufFooter.get(), descriptor, fSerializationContext);
1428
1429 return CommitDatasetImpl(bufFooter.get(), szFooter);
1430}
1431
1433{
1434 fMetrics = RNTupleMetrics(prefix);
1435 fCounters = std::make_unique<RCounters>(RCounters{
1436 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageCommitted", "", "number of pages committed to storage"),
1437 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szWritePayload", "B", "volume written for committed pages"),
1438 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szZip", "B", "volume before zipping"),
1439 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallWrite", "ns", "wall clock time spent writing"),
1440 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallZip", "ns", "wall clock time spent compressing"),
1441 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuWrite", "ns", "CPU time spent writing"),
1442 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuZip", "ns",
1443 "CPU time spent compressing")});
1444}
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
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
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 mode
char name[80]
Definition TGX11.cxx:148
#define _(A, B)
Definition cfortran.h:108
A thread-safe integral performance counter.
A metric element that computes its floating point value from other counters.
A collection of Counter objects with a name, a unit, and a description.
A helper class for piece-wise construction of an RClusterDescriptor.
A helper class for piece-wise construction of an RClusterGroupDescriptor.
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
A helper class for piece-wise construction of an RColumnDescriptor.
A column element encapsulates the translation between basic C++ types and their column representation...
virtual RIdentifier GetIdentifier() const =0
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:345
std::uint16_t GetRepresentationIndex() const
Definition RColumn.hxx:351
ROOT::Internal::RColumnElementBase * GetElement() const
Definition RColumn.hxx:338
ROOT::ENTupleColumnType GetType() const
Definition RColumn.hxx:339
ROOT::NTupleSize_t GetFirstElementIndex() const
Definition RColumn.hxx:353
std::size_t GetWritePageCapacity() const
Definition RColumn.hxx:360
std::uint16_t GetBitsOnStorage() const
Definition RColumn.hxx:340
std::uint32_t GetIndex() const
Definition RColumn.hxx:350
A helper class for piece-wise construction of an RExtraTypeInfoDescriptor.
A helper class for piece-wise construction of an RFieldDescriptor.
static RFieldDescriptorBuilder FromField(const ROOT::RFieldBase &field)
Make a new RFieldDescriptorBuilder based off a live RNTuple field.
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)
A helper class for serializing and deserialization of the RNTuple binary format.
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
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.
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 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.
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)
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 RegisterStreamerInfos()
Builds the streamer info records from the descriptor's extra type info section.
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::NTupleSize_t GetNElements(ColumnHandle_t columnHandle)
ROOT::Internal::RPageRef LoadPageFromSummary(ColumnHandle_t columnHandle, const RPageSummary &pageSummary)
void DropColumn(ColumnHandle_t columnHandle) override
Unregisters a column.
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.
virtual void UnzipClusterImpl(ROOT::Internal::RCluster *cluster)
RPageSource(std::string_view ntupleName, const ROOT::RNTupleReadOptions &fOptions)
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
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
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.
Records the partition of data into pages for a particular column in a particular cluster.
Metadata for RNTuple clusters.
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Field specific extra type information from the header / extenstion header.
A field translates read and write calls from/to underlying columns to/from tree values.
The on-storage metadata of an RNTuple.
Addresses a column element or field item relative to a particular cluster, instead of a global NTuple...
The RNTupleModel encapulates the schema of an RNTuple.
const std::string & GetDescription() const
Common user-tunable settings for reading RNTuples.
Common user-tunable settings for storing RNTuples.
const_iterator begin() const
const_iterator end() const
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
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
Every concrete RColumnElement type is identified by its on-disk type (column type) and the in-memory ...
The incremental changes to a RNTupleModel
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.
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().
A sealed page contains the bytes of a page as written to storage (packed & compressed).
RResult< void > VerifyChecksumIfEnabled() const
RResult< std::uint64_t > GetChecksum() const
Returns a failure if the sealed page has no checksum.
bool operator>(const RColumnInfo &other) const
Information about a single page in the context of a cluster's page range.