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;
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
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 throw RException(R__FAIL("S3 read support is not yet implemented."));
195
196 return std::make_unique<ROOT::Internal::RPageSourceFile>(ntupleName, location, options);
197}
198
201{
203 auto physicalId =
204 GetSharedDescriptorGuard()->FindPhysicalColumnId(fieldId, column.GetIndex(), column.GetRepresentationIndex());
206 fActivePhysicalColumns.Insert(physicalId, column.GetElement()->GetIdentifier());
207 return ColumnHandle_t{physicalId, &column};
208}
209
211{
212 fActivePhysicalColumns.Erase(columnHandle.fPhysicalId, columnHandle.fColumn->GetElement()->GetIdentifier());
213}
214
216{
217 if ((range.fFirstEntry + range.fNEntries) > GetNEntries()) {
218 throw RException(R__FAIL("invalid entry range"));
219 }
220 fEntryRange = range;
221}
222
224{
225 if (!fHasStructure)
226 LoadStructureImpl();
227 fHasStructure = true;
228}
229
231{
232 if (fIsAttached)
233 return;
234
235 LoadStructure();
236
237 auto descGuard = GetExclDescriptorGuard();
238 descGuard.MoveIn(AttachImpl());
239 fStructureBuffer.Reset();
240
241 std::vector<unsigned char> buffer;
242 for (const auto &cgDesc : descGuard->GetClusterGroupIterable()) {
243 buffer.resize(cgDesc.GetPageListLength() + cgDesc.GetPageListLocator().GetNBytesOnStorage());
244 auto zipBuffer = buffer.data() + cgDesc.GetPageListLength();
245
246 LoadPageListImpl(cgDesc.GetPageListLocator(), zipBuffer);
247 RNTupleDecompressor::Unzip(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(),
248 cgDesc.GetPageListLength(), buffer.data());
249 RNTupleSerializer::DeserializePageList(buffer.data(), cgDesc.GetPageListLength(), cgDesc.GetId(), *descGuard,
250 mode);
251 }
252
253 fIsAttached = true;
254}
255
256std::unique_ptr<ROOT::Internal::RPageSource> ROOT::Internal::RPageSource::Clone() const
257{
258 auto clone = CloneImpl();
259 if (fIsAttached) {
260 clone->GetExclDescriptorGuard().MoveIn(GetSharedDescriptorGuard()->Clone());
261 clone->fHasStructure = true;
262 clone->fIsAttached = true;
263 }
264 return clone;
265}
266
268{
269 return GetSharedDescriptorGuard()->GetNEntries();
270}
271
273{
274 return GetSharedDescriptorGuard()->GetNElements(columnHandle.fPhysicalId);
275}
276
278{
279 if (fTaskScheduler)
280 UnzipClusterImpl(cluster);
281}
282
284{
285 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
286
287 const auto clusterId = cluster->GetId();
288 auto descriptorGuard = GetSharedDescriptorGuard();
289 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
290
291 fPreloadedClusters[clusterDescriptor.GetFirstEntryIndex()] = clusterId;
292
293 std::atomic<bool> foundChecksumFailure{false};
294
295 std::vector<std::unique_ptr<RColumnElementBase>> allElements;
296 const auto &columnsInCluster = cluster->GetAvailPhysicalColumns();
297 for (const auto columnId : columnsInCluster) {
298 // By the time we unzip a cluster, the set of active columns may have already changed wrt. to the moment when
299 // we requested reading the cluster. That doesn't matter much, we simply decompress what is now in the list
300 // of active columns.
301 if (!fActivePhysicalColumns.HasColumnInfos(columnId))
302 continue;
303 const auto &columnInfos = fActivePhysicalColumns.GetColumnInfos(columnId);
304
305 allElements.reserve(allElements.size() + columnInfos.size());
306 for (const auto &info : columnInfos) {
307 allElements.emplace_back(GenerateColumnElement(info.fElementId));
308
309 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
310 std::uint64_t pageNo = 0;
311 std::uint64_t firstInPage = 0;
312 for (const auto &pi : pageRange.GetPageInfos()) {
313 auto onDiskPage = cluster->GetOnDiskPage(ROnDiskPage::Key{columnId, pageNo});
315 sealedPage.SetNElements(pi.GetNElements());
316 sealedPage.SetHasChecksum(pi.HasChecksum());
317 sealedPage.SetBufferSize(pi.GetLocator().GetNBytesOnStorage() + pi.HasChecksum() * kNBytesPageChecksum);
318 sealedPage.SetBuffer(onDiskPage->GetAddress());
319 R__ASSERT(onDiskPage && (onDiskPage->GetSize() == sealedPage.GetBufferSize()));
320
321 auto taskFunc = [this, columnId, clusterId, firstInPage, sealedPage, element = allElements.back().get(),
323 indexOffset = clusterDescriptor.GetColumnRange(columnId).GetFirstElementIndex()]() {
324 const ROOT::Internal::RPagePool::RKey keyPagePool{columnId, element->GetIdentifier().fInMemoryType};
325 auto rv = UnsealPage(sealedPage, *element);
326 if (!rv) {
328 return;
329 }
330 auto newPage = rv.Unwrap();
331 fCounters->fSzUnzip.Add(element->GetSize() * sealedPage.GetNElements());
332
333 newPage.SetWindow(indexOffset + firstInPage,
335 fPagePool.PreloadPage(std::move(newPage), keyPagePool);
336 };
337
338 fTaskScheduler->AddTask(taskFunc);
339
340 firstInPage += pi.GetNElements();
341 pageNo++;
342 } // for all pages in column
343
344 fCounters->fNPageUnsealed.Add(pageNo);
345 } // for all in-memory types of the column
346 } // for all columns in cluster
347
348 fTaskScheduler->Wait();
349
351 throw RException(R__FAIL("page checksum verification failed, data corruption detected"));
352 }
353}
354
359{
360 auto descriptorGuard = GetSharedDescriptorGuard();
361 const auto &clusterDesc = descriptorGuard->GetClusterDescriptor(clusterKey.fClusterId);
362
363 for (auto physicalColumnId : clusterKey.fPhysicalColumnSet) {
364 if (clusterDesc.GetColumnRange(physicalColumnId).IsSuppressed())
365 continue;
366
367 const auto &pageRange = clusterDesc.GetPageRange(physicalColumnId);
369 for (const auto &pageInfo : pageRange.GetPageInfos()) {
370 if (pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
373 pageInfo.GetLocator().GetNBytesOnStorage()));
374 } else {
376 }
377 ++pageNo;
378 }
379 }
380}
381
383{
384 if (fLastUsedCluster == clusterId)
385 return;
386
388 GetSharedDescriptorGuard()->GetClusterDescriptor(clusterId).GetFirstEntryIndex();
389 auto itr = fPreloadedClusters.begin();
390 while ((itr != fPreloadedClusters.end()) && (itr->first < firstEntryIndex)) {
391 if (fPinnedClusters.count(itr->second) > 0) {
392 ++itr;
393 } else {
394 fPagePool.Evict(itr->second);
395 itr = fPreloadedClusters.erase(itr);
396 }
397 }
398 std::size_t poolWindow = 0;
399 while ((itr != fPreloadedClusters.end()) &&
401 ++itr;
402 ++poolWindow;
403 }
404 while (itr != fPreloadedClusters.end()) {
405 if (fPinnedClusters.count(itr->second) > 0) {
406 ++itr;
407 } else {
408 fPagePool.Evict(itr->second);
409 itr = fPreloadedClusters.erase(itr);
410 }
411 }
412
413 fLastUsedCluster = clusterId;
414}
415
418{
419 const auto clusterId = localIndex.GetClusterId();
420
422 {
423 auto descriptorGuard = GetSharedDescriptorGuard();
424 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
425 pageInfo = clusterDescriptor.GetPageRange(physicalColumnId).Find(localIndex.GetIndexInCluster());
426 }
427
428 assert(pageInfo.GetLocator().GetType() != RNTupleLocator::kTypePageZero);
429
430 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
431 sealedPage.SetNElements(pageInfo.GetNElements());
432 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
433
434 if (!sealedPage.GetBuffer())
435 return;
436
437 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
438 sealedPage.VerifyChecksumIfEnabled().ThrowOnError();
439}
440
443{
444 const auto &pageInfo = pageSummary.fPageInfo;
445 assert(pageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero);
446
447 const auto element = columnHandle.fColumn->GetElement();
448 const auto elementSize = element->GetSize();
449 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
450
451 auto pageZero = fPageAllocator->NewPage(elementSize, pageInfo.GetNElements());
452 pageZero.GrowUnchecked(pageInfo.GetNElements());
453 std::memset(pageZero.GetBuffer(), 0, pageZero.GetNBytes());
454 pageZero.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
455 RPage::RClusterInfo(pageSummary.fClusterId, pageSummary.fColumnOffset));
456 return fPagePool.RegisterPage(std::move(pageZero), RPagePool::RKey{columnHandle.fPhysicalId, elementInMemoryType});
457}
458
461{
462 if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypeUnknown) {
463 throw RException(R__FAIL("tried to read a page with an unknown locator"));
464 } else if (pageSummary.fPageInfo.GetLocator().GetType() == RNTupleLocator::kTypePageZero) {
465 return LoadZeroPage(columnHandle, pageSummary);
466 }
467
468 const auto &columnId = columnHandle.fPhysicalId;
469 const auto &clusterId = pageSummary.fClusterId;
470 const auto &pageInfo = pageSummary.fPageInfo;
471
472 const auto element = columnHandle.fColumn->GetElement();
473 const auto elementSize = element->GetSize();
474 const auto elementInMemoryType = element->GetIdentifier().fInMemoryType;
475
476 UpdateLastUsedCluster(clusterId);
477
479 sealedPage.SetNElements(pageInfo.GetNElements());
480 sealedPage.SetHasChecksum(pageInfo.HasChecksum());
481 sealedPage.SetBufferSize(pageInfo.GetLocator().GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum);
482 std::unique_ptr<unsigned char[]> directReadBuffer; // only used if cluster pool is turned off
483
484 if (fOptions.GetClusterCache() == ROOT::RNTupleReadOptions::EClusterCache::kOff) {
486 sealedPage.SetBuffer(directReadBuffer.get());
487 LoadSealedPageImpl(pageInfo.GetLocator(), sealedPage);
489 fCounters->fNPageRead.Inc();
490 fCounters->fNRead.Inc();
491 fCounters->fSzReadPayload.Add(sealedPage.GetBufferSize());
492 } else {
493 if (!fCurrentCluster || (fCurrentCluster->GetId() != clusterId) || !fCurrentCluster->ContainsColumn(columnId))
494 fCurrentCluster = fClusterPool.GetCluster(clusterId, fActivePhysicalColumns.ToColumnSet());
495 R__ASSERT(fCurrentCluster->ContainsColumn(columnId));
496
497 // The cluster pool may have unzipped the required page into the page pool
499 RNTupleLocalIndex(clusterId, pageInfo.GetFirstElementIndex()));
500 if (!cachedPageRef.Get().IsNull())
501 return cachedPageRef;
502
503 ROnDiskPage::Key key(columnId, pageInfo.GetPageNumber());
504 auto onDiskPage = fCurrentCluster->GetOnDiskPage(key);
505 R__ASSERT(onDiskPage && (sealedPage.GetBufferSize() == onDiskPage->GetSize()));
506 sealedPage.SetBuffer(onDiskPage->GetAddress());
507 }
508
511 RNTupleAtomicTimer timer(fCounters->fTimeWallUnzip, fCounters->fTimeCpuUnzip);
512 newPage = UnsealPage(sealedPage, *element).Unwrap();
513 fCounters->fSzUnzip.Add(elementSize * pageInfo.GetNElements());
514 }
515
516 newPage.SetWindow(pageSummary.fColumnOffset + pageInfo.GetFirstElementIndex(),
518 fCounters->fNPageUnsealed.Inc();
519
520 return fPagePool.RegisterPage(std::move(newPage), RPagePool::RKey{columnId, elementInMemoryType});
521}
522
525{
526 const auto columnId = columnHandle.fPhysicalId;
527 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
528 auto cachedPageRef =
529 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, globalIndex);
530 if (!cachedPageRef.Get().IsNull()) {
531 UpdateLastUsedCluster(cachedPageRef.Get().GetClusterInfo().GetId());
532 return cachedPageRef;
533 }
534
536 {
537 auto descriptorGuard = GetSharedDescriptorGuard();
538 pageSummary.fClusterId = descriptorGuard->FindClusterId(columnId, globalIndex);
539
540 if (pageSummary.fClusterId == ROOT::kInvalidDescriptorId)
541 throw RException(R__FAIL("entry with index " + std::to_string(globalIndex) + " out of bounds"));
542
543 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(pageSummary.fClusterId);
544 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
545 if (columnRange.IsSuppressed())
547
548 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
549 R__ASSERT(pageSummary.fColumnOffset <= globalIndex);
550 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(globalIndex - pageSummary.fColumnOffset);
551 }
552
553 return LoadPageFromSummary(columnHandle, pageSummary);
554}
555
558{
559 const auto clusterId = localIndex.GetClusterId();
560 const auto columnId = columnHandle.fPhysicalId;
561 const auto columnElementId = columnHandle.fColumn->GetElement()->GetIdentifier();
562 auto cachedPageRef =
563 fPagePool.GetPage(ROOT::Internal::RPagePool::RKey{columnId, columnElementId.fInMemoryType}, localIndex);
564 if (!cachedPageRef.Get().IsNull()) {
565 UpdateLastUsedCluster(clusterId);
566 return cachedPageRef;
567 }
568
570 throw RException(R__FAIL("entry out of bounds"));
571
573 {
574 auto descriptorGuard = GetSharedDescriptorGuard();
575 const auto &clusterDescriptor = descriptorGuard->GetClusterDescriptor(clusterId);
576 const auto &columnRange = clusterDescriptor.GetColumnRange(columnId);
577 if (columnRange.IsSuppressed())
579
580 pageSummary.fClusterId = clusterId;
581 pageSummary.fColumnOffset = columnRange.GetFirstElementIndex();
582 pageSummary.fPageInfo = clusterDescriptor.GetPageRange(columnId).Find(localIndex.GetIndexInCluster());
583 }
584
585 return LoadPageFromSummary(columnHandle, pageSummary);
586}
587
589{
590 fMetrics = RNTupleMetrics(prefix);
591 fMetrics.ObserveMetrics(fClusterPool.GetMetrics());
592 fMetrics.ObserveMetrics(fPagePool.GetMetrics());
593 fCounters = std::make_unique<RCounters>(RCounters{
594 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nReadV", "", "number of vector read requests"),
595 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nRead", "", "number of byte ranges read"),
596 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadPayload", "B", "volume read from storage (required)"),
597 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szReadOverhead", "B", "volume read from storage (overhead)"),
598 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szUnzip", "B", "volume after unzipping"),
599 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nClusterLoaded", "",
600 "number of partial clusters preloaded from storage"),
601 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageRead", "", "number of pages read from storage"),
602 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageUnsealed", "", "number of pages unzipped and decoded"),
603 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallRead", "ns", "wall clock time spent reading"),
604 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallUnzip", "ns", "wall clock time spent decompressing"),
605 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuRead", "ns", "CPU time spent reading"),
606 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuUnzip", "ns",
607 "CPU time spent decompressing"),
608 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
609 "bwRead", "MB/s", "bandwidth compressed bytes read per second", fMetrics,
610 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
611 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
612 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
613 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
614 if (auto walltime = timeWallRead->GetValueAsInt()) {
615 double payload = szReadPayload->GetValueAsInt();
616 double overhead = szReadOverhead->GetValueAsInt();
617 // unit: bytes / nanosecond = GB/s
618 return {true, (1000. * (payload + overhead) / walltime)};
619 }
620 }
621 }
622 }
623 return {false, -1.};
624 }),
625 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
626 "bwReadUnzip", "MB/s", "bandwidth uncompressed bytes read per second", fMetrics,
627 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
628 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
629 if (const auto timeWallRead = metrics.GetLocalCounter("timeWallRead")) {
630 if (auto walltime = timeWallRead->GetValueAsInt()) {
631 double unzip = szUnzip->GetValueAsInt();
632 // unit: bytes / nanosecond = GB/s
633 return {true, 1000. * unzip / walltime};
634 }
635 }
636 }
637 return {false, -1.};
638 }),
639 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
640 "bwUnzip", "MB/s", "decompression bandwidth of uncompressed bytes per second", fMetrics,
641 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
642 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
643 if (const auto timeWallUnzip = metrics.GetLocalCounter("timeWallUnzip")) {
644 if (auto walltime = timeWallUnzip->GetValueAsInt()) {
645 double unzip = szUnzip->GetValueAsInt();
646 // unit: bytes / nanosecond = GB/s
647 return {true, 1000. * unzip / walltime};
648 }
649 }
650 }
651 return {false, -1.};
652 }),
653 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
654 "rtReadEfficiency", "", "ratio of payload over all bytes read", fMetrics,
655 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
656 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
657 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
658 if (auto payload = szReadPayload->GetValueAsInt()) {
659 // r/(r+o) = 1/((r+o)/r) = 1/(1 + o/r)
660 return {true, 1. / (1. + (1. * szReadOverhead->GetValueAsInt()) / payload)};
661 }
662 }
663 }
664 return {false, -1.};
665 }),
666 *fMetrics.MakeCounter<RNTupleCalcPerf *>("rtCompression", "", "ratio of compressed bytes / uncompressed bytes",
667 fMetrics, [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
668 if (const auto szReadPayload =
669 metrics.GetLocalCounter("szReadPayload")) {
670 if (const auto szUnzip = metrics.GetLocalCounter("szUnzip")) {
671 if (auto unzip = szUnzip->GetValueAsInt()) {
672 return {true, (1. * szReadPayload->GetValueAsInt()) / unzip};
673 }
674 }
675 }
676 return {false, -1.};
677 })});
678}
679
682{
683 return UnsealPage(sealedPage, element, *fPageAllocator);
684}
685
689{
690 // Unsealing a page zero is a no-op. `RPageRange::ExtendToFitColumnRange()` guarantees that the page zero buffer is
691 // large enough to hold `sealedPage.fNElements`
693 auto page = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
694 page.GrowUnchecked(sealedPage.GetNElements());
695 memset(page.GetBuffer(), 0, page.GetNBytes());
696 return page;
697 }
698
699 auto rv = sealedPage.VerifyChecksumIfEnabled();
700 if (!rv)
701 return R__FORWARD_ERROR(rv);
702
703 const auto bytesPacked = element.GetPackedSize(sealedPage.GetNElements());
704 auto page = pageAlloc.NewPage(element.GetPackedSize(), sealedPage.GetNElements());
705 if (sealedPage.GetDataSize() != bytesPacked) {
707 page.GetBuffer());
708 } else {
709 // We cannot simply map the sealed page as we don't know its life time. Specialized page sources
710 // may decide to implement to not use UnsealPage but to custom mapping / decompression code.
711 // Note that usually pages are compressed.
712 memcpy(page.GetBuffer(), sealedPage.GetBuffer(), bytesPacked);
713 }
714
715 if (!element.IsMappable()) {
716 auto tmp = pageAlloc.NewPage(element.GetSize(), sealedPage.GetNElements());
717 element.Unpack(tmp.GetBuffer(), page.GetBuffer(), sealedPage.GetNElements());
718 page = std::move(tmp);
719 }
720
721 page.GrowUnchecked(sealedPage.GetNElements());
722 return page;
723}
724
726{
727 if (fHasStreamerInfosRegistered)
728 return;
729
730 for (const auto &extraTypeInfo : fDescriptor.GetExtraTypeInfoIterable()) {
732 continue;
733 // We don't need the result, it's enough that during deserialization, BuildCheck() is called for every
734 // streamer info record.
736 }
737
738 fHasStreamerInfosRegistered = true;
739}
740
741//------------------------------------------------------------------------------
742
744{
745 // Make the sort order unique by adding the physical on-disk column id as a secondary key
746 if (fCurrentPageSize == other.fCurrentPageSize)
747 return fColumn->GetOnDiskId() > other.fColumn->GetOnDiskId();
748 return fCurrentPageSize > other.fCurrentPageSize;
749}
750
752{
753 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
754 return true;
755
756 auto itr = fColumnsSortedByPageSize.begin();
757 while (itr != fColumnsSortedByPageSize.end()) {
758 if (itr->fCurrentPageSize <= pageSizeLimit)
759 break;
760 if (itr->fCurrentPageSize == itr->fInitialPageSize) {
761 ++itr;
762 continue;
763 }
764
765 // Flushing the current column will invalidate itr
766 auto itrFlush = itr++;
767
768 RColumnInfo next;
769 if (itr != fColumnsSortedByPageSize.end())
770 next = *itr;
771
772 itrFlush->fColumn->Flush();
773 if (fMaxAllocatedBytes - fCurrentAllocatedBytes >= targetAvailableSize)
774 return true;
775
776 if (next.fColumn == nullptr)
777 return false;
778 itr = fColumnsSortedByPageSize.find(next);
779 };
780
781 return false;
782}
783
785{
786 const RColumnInfo key{&column, column.GetWritePageCapacity(), 0};
787 auto itr = fColumnsSortedByPageSize.find(key);
788 if (itr == fColumnsSortedByPageSize.end()) {
789 if (!TryEvict(newWritePageSize, 0))
790 return false;
791 fColumnsSortedByPageSize.insert({&column, newWritePageSize, newWritePageSize});
792 fCurrentAllocatedBytes += newWritePageSize;
793 return true;
794 }
795
797 assert(newWritePageSize >= elem.fInitialPageSize);
798
799 if (newWritePageSize == elem.fCurrentPageSize)
800 return true;
801
802 fColumnsSortedByPageSize.erase(itr);
803
804 if (newWritePageSize < elem.fCurrentPageSize) {
805 // Page got smaller
806 fCurrentAllocatedBytes -= elem.fCurrentPageSize - newWritePageSize;
807 elem.fCurrentPageSize = newWritePageSize;
808 fColumnsSortedByPageSize.insert(elem);
809 return true;
810 }
811
812 // Page got larger, we may need to make space available
813 const auto diffBytes = newWritePageSize - elem.fCurrentPageSize;
814 if (!TryEvict(diffBytes, elem.fCurrentPageSize)) {
815 // Don't change anything, let the calling column flush itself
816 // TODO(jblomer): we may consider skipping the column in TryEvict and thus avoiding erase+insert
817 fColumnsSortedByPageSize.insert(elem);
818 return false;
819 }
820 fCurrentAllocatedBytes += diffBytes;
821 elem.fCurrentPageSize = newWritePageSize;
822 fColumnsSortedByPageSize.insert(elem);
823 return true;
824}
825
826//------------------------------------------------------------------------------
827
829 : RPageStorage(name), fOptions(options.Clone()), fWritePageMemoryManager(options.GetPageBufferBudget())
830{
832}
833
835
837{
838 assert(config.fPage);
839 assert(config.fElement);
840 assert(config.fBuffer);
841
842 unsigned char *pageBuf = reinterpret_cast<unsigned char *>(config.fPage->GetBuffer());
843 bool isAdoptedBuffer = true;
844 auto nBytesPacked = config.fPage->GetNBytes();
845 auto nBytesChecksum = config.fWriteChecksum * kNBytesPageChecksum;
846
847 if (!config.fElement->IsMappable()) {
848 nBytesPacked = config.fElement->GetPackedSize(config.fPage->GetNElements());
849 pageBuf = new unsigned char[nBytesPacked];
850 isAdoptedBuffer = false;
851 config.fElement->Pack(pageBuf, config.fPage->GetBuffer(), config.fPage->GetNElements());
852 }
854
855 if ((config.fCompressionSettings != 0) || !config.fElement->IsMappable() || !config.fAllowAlias ||
856 config.fWriteChecksum) {
859 if (!isAdoptedBuffer)
860 delete[] pageBuf;
861 pageBuf = reinterpret_cast<unsigned char *>(config.fBuffer);
862 isAdoptedBuffer = true;
863 }
864
866
868 sealedPage.ChecksumIfEnabled();
869
870 return sealedPage;
871}
872
875{
876 const auto nBytes = page.GetNBytes() + GetWriteOptions().GetEnablePageChecksums() * kNBytesPageChecksum;
877 if (fSealPageBuffer.size() < nBytes)
878 fSealPageBuffer.resize(nBytes);
879
880 RSealPageConfig config;
881 config.fPage = &page;
882 config.fElement = &element;
883 config.fCompressionSettings = GetWriteOptions().GetCompression();
884 config.fWriteChecksum = GetWriteOptions().GetEnablePageChecksums();
885 config.fAllowAlias = true;
886 config.fBuffer = fSealPageBuffer.data();
887
888 return SealPage(config);
889}
890
892{
893 for (const auto &cb : fOnDatasetCommitCallbacks)
894 cb(*this);
895 return CommitDatasetImpl();
896}
897
899{
900 R__ASSERT(nElements > 0);
901 const auto elementSize = columnHandle.fColumn->GetElement()->GetSize();
902 const auto nBytes = elementSize * nElements;
903 if (!fWritePageMemoryManager.TryUpdate(*columnHandle.fColumn, nBytes))
904 return ROOT::Internal::RPage();
905 return fPageAllocator->NewPage(elementSize, nElements);
906}
907
908//------------------------------------------------------------------------------
909
910std::unique_ptr<ROOT::Internal::RPageSink>
911ROOT::Internal::RPagePersistentSink::Create(std::string_view ntupleName, std::string_view location,
912 const ROOT::RNTupleWriteOptions &options)
913{
914 if (ntupleName.empty()) {
915 throw RException(R__FAIL("empty RNTuple name"));
916 }
917 if (location.empty()) {
918 throw RException(R__FAIL("empty storage location"));
919 }
920 if (location.find("daos://") == 0) {
921#ifdef R__ENABLE_DAOS
922 return std::make_unique<ROOT::Experimental::Internal::RPageSinkDaos>(ntupleName, location, options);
923#else
924 throw RException(R__FAIL("This RNTuple build does not support DAOS."));
925#endif
926 }
927
928 if (ROOT::StartsWith(location, "ntpl+s3+http://") || ROOT::StartsWith(location, "ntpl+s3+https://")) {
929#ifdef R__ENABLE_S3
930 return std::make_unique<ROOT::Experimental::Internal::RPageSinkS3>(ntupleName, location, options);
931#else
932 throw RException(R__FAIL("This RNTuple build does not support S3. Rebuild ROOT with the 'curl' "
933 "cmake option enabled (-Dcurl=ON) to enable the S3 backend."));
934#endif
935 }
936
937 // Otherwise assume that the user wants us to create a file.
938 return std::make_unique<ROOT::Internal::RPageSinkFile>(ntupleName, location, options);
939}
940
942 const ROOT::RNTupleWriteOptions &options)
943 : RPageSink(name, options)
944{
945}
946
948
951{
952 auto columnId = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
954 columnBuilder.LogicalColumnId(columnId)
955 .PhysicalColumnId(columnId)
956 .FieldId(fieldId)
957 .BitsOnStorage(column.GetBitsOnStorage())
958 .ValueRange(column.GetValueRange())
959 .Type(column.GetType())
960 .Index(column.GetIndex())
961 .RepresentationIndex(column.GetRepresentationIndex())
962 .FirstElementIndex(column.GetFirstElementIndex());
963 // For late model extension, we assume that the primary column representation is the active one for the
964 // deferred range. All other representations are suppressed.
965 if (column.GetFirstElementIndex() > 0 && column.GetRepresentationIndex() > 0)
966 columnBuilder.SetSuppressedDeferred();
967 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
968 return ColumnHandle_t{columnId, &column};
969}
970
973{
974 if (fIsInitialized) {
975 for (const auto &field : changeset.fAddedFields) {
976 if (field->GetStructure() == ENTupleStructure::kStreamer) {
977 throw ROOT::RException(R__FAIL("a Model cannot be extended with Streamer fields"));
978 }
979 }
980 }
981
982 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
983
984 if (descriptor.GetNLogicalColumns() > descriptor.GetNPhysicalColumns()) {
985 // If we already have alias columns, add an offset to the alias columns so that the new physical columns
986 // of the changeset follow immediately the already existing physical columns
987 auto getNColumns = [](const ROOT::RFieldBase &f) -> std::size_t {
988 const auto &reps = f.GetColumnRepresentatives();
989 if (reps.empty())
990 return 0;
991 return reps.size() * reps[0].size();
992 };
993 std::uint32_t nNewPhysicalColumns = 0;
994 for (auto f : changeset.fAddedFields) {
996 for (const auto &descendant : *f)
998 }
999 fDescriptorBuilder.ShiftAliasColumns(nNewPhysicalColumns);
1000 }
1001
1002 auto addField = [&](ROOT::RFieldBase &f) {
1003 auto fieldId = descriptor.GetNFields();
1004 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(f).FieldId(fieldId).MakeDescriptor().Unwrap());
1005 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1006 f.SetOnDiskId(fieldId);
1007 ROOT::Internal::CallConnectPageSinkOnField(f, *this, firstEntry); // issues in turn calls to `AddColumn()`
1008 };
1009 auto addProjectedField = [&](ROOT::RFieldBase &f) {
1010 auto fieldId = descriptor.GetNFields();
1011 auto sourceFieldId =
1013 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(f).FieldId(fieldId).MakeDescriptor().Unwrap());
1014 fDescriptorBuilder.AddFieldLink(f.GetParent()->GetOnDiskId(), fieldId);
1015 fDescriptorBuilder.AddFieldProjection(sourceFieldId, fieldId);
1016 f.SetOnDiskId(fieldId);
1017 for (const auto &source : descriptor.GetColumnIterable(sourceFieldId)) {
1018 auto targetId = descriptor.GetNLogicalColumns();
1020 columnBuilder.LogicalColumnId(targetId)
1021 .PhysicalColumnId(source.GetLogicalId())
1022 .FieldId(fieldId)
1023 .BitsOnStorage(source.GetBitsOnStorage())
1024 .ValueRange(source.GetValueRange())
1025 .Type(source.GetType())
1026 .Index(source.GetIndex())
1027 .RepresentationIndex(source.GetRepresentationIndex());
1028 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
1029 }
1030 };
1031
1032 R__ASSERT(firstEntry >= fPrevClusterNEntries);
1033 const auto nColumnsBeforeUpdate = descriptor.GetNPhysicalColumns();
1034 for (auto f : changeset.fAddedFields) {
1035 addField(*f);
1036 for (auto &descendant : *f)
1038 }
1039 for (auto f : changeset.fAddedProjectedFields) {
1041 for (auto &descendant : *f)
1043 }
1044
1045 const auto nColumns = descriptor.GetNPhysicalColumns();
1046 fOpenColumnRanges.reserve(fOpenColumnRanges.size() + (nColumns - nColumnsBeforeUpdate));
1047 fOpenPageRanges.reserve(fOpenPageRanges.size() + (nColumns - nColumnsBeforeUpdate));
1050 columnRange.SetPhysicalColumnId(i);
1051 // We set the first element index in the current cluster to the first element that is part of a materialized page
1052 // (i.e., that is part of a page list). For columns created during late model extension, however, the column range
1053 // is fixed up as needed by `RClusterDescriptorBuilder::AddExtendedColumnRanges()` on read back.
1054 columnRange.SetFirstElementIndex(descriptor.GetColumnDescriptor(i).GetFirstElementIndex());
1055 columnRange.SetNElements(0);
1056 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1057 fOpenColumnRanges.emplace_back(columnRange);
1059 pageRange.SetPhysicalColumnId(i);
1060 fOpenPageRanges.emplace_back(std::move(pageRange));
1061 }
1062
1063 // Mapping of memory to on-disk column IDs usually happens during serialization of the ntuple header. If the
1064 // header was already serialized, this has to be done manually as it is required for page list serialization.
1065 if (fSerializationContext.GetHeaderSize() > 0)
1066 fSerializationContext.MapSchema(descriptor, /*forHeaderExtension=*/true);
1067}
1068
1070{
1071 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
1072 throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()"));
1073
1074 fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap());
1075}
1076
1078{
1079 fDescriptorBuilder.SetNTuple(fNTupleName, model.GetDescription());
1080 fDescriptorBuilder.SetVersionForWriting();
1081 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1082
1084 fDescriptorBuilder.AddField(RFieldDescriptorBuilder::FromField(fieldZero).FieldId(0).MakeDescriptor().Unwrap());
1085 fieldZero.SetOnDiskId(0);
1087 projectedFields.GetFieldZero().SetOnDiskId(0);
1088
1090 initialChangeset.fAddedFields.reserve(fieldZero.GetMutableSubfields().size());
1091 for (auto f : fieldZero.GetMutableSubfields())
1092 initialChangeset.fAddedFields.emplace_back(f);
1093 initialChangeset.fAddedProjectedFields.reserve(projectedFields.GetFieldZero().GetMutableSubfields().size());
1094 for (auto f : projectedFields.GetFieldZero().GetMutableSubfields())
1095 initialChangeset.fAddedProjectedFields.emplace_back(f);
1096 UpdateSchema(initialChangeset, 0U);
1097
1098 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1099 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1100 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1101 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1102
1103 fDescriptorBuilder.BeginHeaderExtension();
1104}
1105
1106std::unique_ptr<ROOT::RNTupleModel>
1108{
1109 // Create new descriptor
1110 fDescriptorBuilder.SetSchemaFromExisting(srcDescriptor);
1111 fDescriptorBuilder.SetVersionForWriting();
1112 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1113
1114 // Create column/page ranges
1115 const auto nColumns = descriptor.GetNPhysicalColumns();
1116 R__ASSERT(fOpenColumnRanges.empty() && fOpenPageRanges.empty());
1117 fOpenColumnRanges.reserve(nColumns);
1118 fOpenPageRanges.reserve(nColumns);
1119 for (ROOT::DescriptorId_t i = 0; i < nColumns; ++i) {
1120 const auto &column = descriptor.GetColumnDescriptor(i);
1122 columnRange.SetPhysicalColumnId(i);
1123 columnRange.SetFirstElementIndex(column.GetFirstElementIndex());
1124 columnRange.SetNElements(0);
1125 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1126 fOpenColumnRanges.emplace_back(columnRange);
1128 pageRange.SetPhysicalColumnId(i);
1129 fOpenPageRanges.emplace_back(std::move(pageRange));
1130 }
1131
1132 if (copyClusters) {
1133 // Clone and add all cluster descriptors
1134 auto clusterId = srcDescriptor.FindClusterId(0, 0);
1136 auto &cluster = srcDescriptor.GetClusterDescriptor(clusterId);
1137 auto nEntries = cluster.GetNEntries();
1138 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1139 R__ASSERT(fOpenColumnRanges[i].GetPhysicalColumnId() == i);
1140 if (!cluster.ContainsColumn(i)) // a cluster may not contain a column if that column is deferred
1141 break;
1142 const auto &columnRange = cluster.GetColumnRange(i);
1143 R__ASSERT(columnRange.GetPhysicalColumnId() == i);
1144 // TODO: properly handle suppressed columns (check MarkSuppressedColumnRange())
1145 fOpenColumnRanges[i].IncrementFirstElementIndex(columnRange.GetNElements());
1146 }
1147 fDescriptorBuilder.AddCluster(cluster.Clone());
1148 fPrevClusterNEntries += nEntries;
1149
1150 clusterId = srcDescriptor.FindNextClusterId(clusterId);
1151 }
1152 }
1153
1154 // Create model
1156 modelOpts.SetReconstructProjections(true);
1157 // We want to emulate unknown types to allow merging RNTuples containing types that we lack dictionaries for.
1158 modelOpts.SetEmulateUnknownTypes(true);
1159 auto model = descriptor.CreateModel(modelOpts);
1160 if (!copyClusters) {
1162 projectedFields.GetFieldZero().SetOnDiskId(model->GetConstFieldZero().GetOnDiskId());
1163 }
1164
1165 // Serialize header and init from it
1166 fSerializationContext = RNTupleSerializer::SerializeHeader(nullptr, descriptor).Unwrap();
1167 auto buffer = MakeUninitArray<unsigned char>(fSerializationContext.GetHeaderSize());
1168 fSerializationContext = RNTupleSerializer::SerializeHeader(buffer.get(), descriptor).Unwrap();
1169 InitImpl(buffer.get(), fSerializationContext.GetHeaderSize());
1170
1171 fDescriptorBuilder.BeginHeaderExtension();
1172
1173 // mark this sink as initialized
1174 fIsInitialized = true;
1175
1176 return model;
1177}
1178
1181 std::span<const RColumnFormat> newRepresentation,
1182 std::uint64_t clusterOffset)
1183{
1184 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1185
1186 assert(&descriptor.GetFieldDescriptor(field.GetId()) == &field);
1187 assert(!field.IsProjectedField());
1188 assert(field.GetColumnCardinality() > 0);
1189 assert(!field.GetLogicalColumnIds().empty());
1190 assert(newRepresentation.size() == field.GetColumnCardinality());
1191
1192 const std::size_t firstPhysicalIndex = fDescriptorBuilder.GetDescriptor().GetNPhysicalColumns();
1193 const std::uint16_t reprIndex = field.GetLogicalColumnIds().size() / field.GetColumnCardinality();
1194
1195 fDescriptorBuilder.ShiftAliasColumns(newRepresentation.size());
1196
1197 std::uint16_t columnIndex = 0; // index into the representation
1198 for (auto columnRepr : newRepresentation) {
1199 std::size_t bitsOnStorage = columnRepr.fBitWidth;
1200 if (!bitsOnStorage) {
1202 if (rangeMin != rangeMax) {
1203 throw ROOT::RException(R__FAIL("bit width must be given for columns of variable bit width"));
1204 }
1206 }
1207
1208 const ROOT::DescriptorId_t firstReprColumnId = field.GetLogicalColumnIds()[columnIndex];
1209 const auto &firstReprColumnRange = fOpenColumnRanges.at(firstReprColumnId);
1211 // NOTE: this is always non-negative because it's the sum of two unsigned integers.
1212 const std::uint64_t newReprFirstElemIndex = firstReprColumnRange.GetFirstElementIndex() + clusterOffset;
1213
1215 columnBuilder.LogicalColumnId(columnId)
1216 .PhysicalColumnId(columnId)
1217 .FieldId(field.GetId())
1218 .BitsOnStorage(bitsOnStorage)
1219 .Type(columnRepr.fType)
1220 .Index(columnIndex)
1221 .FirstElementIndex(newReprFirstElemIndex)
1222 .RepresentationIndex(reprIndex)
1223 .ValueRange(columnRepr.fValueRange);
1225 columnBuilder.SetSuppressedDeferred();
1226 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
1227
1228 if (newReprFirstElemIndex != 0) {
1229 for (auto parentId = field.GetParentId(); parentId != ROOT::kInvalidDescriptorId;) {
1230 const ROOT::RFieldDescriptor &parent = descriptor.GetFieldDescriptor(parentId);
1233 fDescriptorBuilder.SetFeature(RNTupleDescriptor::kFeatureFlag_NestedDeferredColumns);
1234 break;
1235 }
1236 parentId = parent.GetParentId();
1237 }
1238 }
1239
1241 columnRange.SetPhysicalColumnId(columnId);
1242 columnRange.SetFirstElementIndex(firstReprColumnRange.GetFirstElementIndex());
1243 columnRange.SetNElements(0);
1244 columnRange.SetCompressionSettings(GetWriteOptions().GetCompression());
1245 fOpenColumnRanges.emplace_back(columnRange);
1246
1248 pageRange.SetPhysicalColumnId(columnId);
1249 fOpenPageRanges.emplace_back(std::move(pageRange));
1250
1251 fSerializationContext.MapPhysicalColumnId(columnId);
1252
1253 ++columnIndex;
1254 }
1255
1256 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1257
1258 return firstPhysicalIndex;
1259}
1260
1264{
1265 const auto &pointedColumn = desc.GetColumnDescriptor(physicalId);
1266 assert(!pointedColumn.IsAliasColumn());
1267 assert(field.IsProjectedField());
1268
1269 const auto columnId = fDescriptorBuilder.GetDescriptor().GetNLogicalColumns();
1271 columnBuilder.LogicalColumnId(columnId)
1272 .PhysicalColumnId(physicalId)
1273 .FieldId(field.GetId())
1274 .Type(pointedColumn.GetType())
1275 .Index(pointedColumn.GetIndex())
1276 .BitsOnStorage(pointedColumn.GetBitsOnStorage())
1277 .ValueRange(pointedColumn.GetValueRange())
1278 .FirstElementIndex(pointedColumn.GetFirstElementIndex())
1279 .RepresentationIndex(pointedColumn.GetRepresentationIndex());
1280 fDescriptorBuilder.AddColumn(columnBuilder.MakeDescriptor().Unwrap());
1281
1282 fDescriptorBuilder.EnsureValidDescriptor().ThrowOnError();
1283}
1284
1286{
1287 fOpenColumnRanges.at(columnHandle.fPhysicalId).SetIsSuppressed(true);
1288}
1289
1291{
1292 fOpenColumnRanges.at(columnHandle.fPhysicalId).IncrementNElements(page.GetNElements());
1293
1294 auto element = columnHandle.fColumn->GetElement();
1296 {
1297 RNTupleAtomicTimer timer(fCounters->fTimeWallZip, fCounters->fTimeCpuZip);
1298 sealedPage = SealPage(page, *element);
1299 }
1300 fCounters->fSzZip.Add(page.GetNBytes());
1301
1303 pageInfo.SetNElements(page.GetNElements());
1304 pageInfo.SetLocator(CommitSealedPageImpl(columnHandle.fPhysicalId, sealedPage));
1305 pageInfo.SetHasChecksum(GetWriteOptions().GetEnablePageChecksums());
1306 fOpenPageRanges.at(columnHandle.fPhysicalId).GetPageInfos().emplace_back(pageInfo);
1307}
1308
1311{
1312 fOpenColumnRanges.at(physicalColumnId).IncrementNElements(sealedPage.GetNElements());
1313
1315 pageInfo.SetNElements(sealedPage.GetNElements());
1316 pageInfo.SetLocator(CommitSealedPageImpl(physicalColumnId, sealedPage));
1317 pageInfo.SetHasChecksum(sealedPage.GetHasChecksum());
1318 fOpenPageRanges.at(physicalColumnId).GetPageInfos().emplace_back(pageInfo);
1319}
1320
1321std::vector<ROOT::RNTupleLocator>
1322ROOT::Internal::RPagePersistentSink::CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges,
1323 const std::vector<bool> &mask)
1324{
1325 std::vector<ROOT::RNTupleLocator> locators;
1326 locators.reserve(mask.size());
1327 std::size_t i = 0;
1328 for (auto &range : ranges) {
1329 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1330 if (mask[i++])
1331 locators.push_back(CommitSealedPageImpl(range.fPhysicalColumnId, *sealedPageIt));
1332 }
1333 }
1334 locators.shrink_to_fit();
1335 return locators;
1336}
1337
1338void ROOT::Internal::RPagePersistentSink::CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges)
1339{
1340 /// Used in the `originalPages` map
1341 struct RSealedPageLink {
1342 const RSealedPage *fSealedPage = nullptr; ///< Points to the first occurrence of a page with a specific checksum
1343 std::size_t fLocatorIdx = 0; ///< The index in the locator vector returned by CommitSealedPageVImpl()
1344 };
1345
1346 std::vector<bool> mask;
1347 // For every sealed page, stores the corresponding index in the locator vector returned by CommitSealedPageVImpl()
1348 std::vector<std::size_t> locatorIndexes;
1349 // Maps page checksums to the first sealed page with that checksum
1350 std::unordered_map<std::uint64_t, RSealedPageLink> originalPages;
1351 std::size_t iLocator = 0;
1352 for (auto &range : ranges) {
1353 const auto rangeSize = std::distance(range.fFirst, range.fLast);
1354 mask.reserve(mask.size() + rangeSize);
1355 locatorIndexes.reserve(locatorIndexes.size() + rangeSize);
1356
1357 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1358 if (!fFeatures.fCanMergePages || !fOptions->GetEnableSamePageMerging()) {
1359 mask.emplace_back(true);
1360 locatorIndexes.emplace_back(iLocator++);
1361 continue;
1362 }
1363 // Same page merging requires page checksums - this is checked in the write options
1364 R__ASSERT(sealedPageIt->GetHasChecksum());
1365
1366 const auto chk = sealedPageIt->GetChecksum().Unwrap();
1367 auto itr = originalPages.find(chk);
1368 if (itr == originalPages.end()) {
1369 originalPages.insert({chk, {&(*sealedPageIt), iLocator}});
1370 mask.emplace_back(true);
1371 locatorIndexes.emplace_back(iLocator++);
1372 continue;
1373 }
1374
1375 const auto *p = itr->second.fSealedPage;
1376 if ((sealedPageIt->GetDataSize() != p->GetDataSize()) ||
1377 (memcmp(sealedPageIt->GetBuffer(), p->GetBuffer(), p->GetDataSize()) != 0)) {
1378 mask.emplace_back(true);
1379 locatorIndexes.emplace_back(iLocator++);
1380 continue;
1381 }
1382
1383 mask.emplace_back(false);
1384 locatorIndexes.emplace_back(itr->second.fLocatorIdx);
1385 }
1386
1387 mask.shrink_to_fit();
1388 locatorIndexes.shrink_to_fit();
1389 }
1390
1391 auto locators = CommitSealedPageVImpl(ranges, mask);
1392 unsigned i = 0;
1393
1394 for (auto &range : ranges) {
1395 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt) {
1396 fOpenColumnRanges.at(range.fPhysicalColumnId).IncrementNElements(sealedPageIt->GetNElements());
1397
1399 pageInfo.SetNElements(sealedPageIt->GetNElements());
1400 pageInfo.SetLocator(locators[locatorIndexes[i++]]);
1401 pageInfo.SetHasChecksum(sealedPageIt->GetHasChecksum());
1402 fOpenPageRanges.at(range.fPhysicalColumnId).GetPageInfos().emplace_back(pageInfo);
1403 }
1404 }
1405}
1406
1409{
1411 stagedCluster.fNBytesWritten = StageClusterImpl();
1412 stagedCluster.fNEntries = nNewEntries;
1413
1414 for (unsigned int i = 0; i < fOpenColumnRanges.size(); ++i) {
1415 RStagedCluster::RColumnInfo columnInfo;
1416 columnInfo.fCompressionSettings = fOpenColumnRanges[i].GetCompressionSettings().value();
1417 if (fOpenColumnRanges[i].IsSuppressed()) {
1418 assert(fOpenPageRanges[i].GetPageInfos().empty());
1419 columnInfo.fPageRange.SetPhysicalColumnId(i);
1420 columnInfo.fIsSuppressed = true;
1421 // We reset suppressed columns to the state they would have if they were active (not suppressed).
1422 fOpenColumnRanges[i].SetNElements(0);
1423 fOpenColumnRanges[i].SetIsSuppressed(false);
1424 } else {
1425 std::swap(columnInfo.fPageRange, fOpenPageRanges[i]);
1426 fOpenPageRanges[i].SetPhysicalColumnId(i);
1427
1428 columnInfo.fNElements = fOpenColumnRanges[i].GetNElements();
1429 fOpenColumnRanges[i].SetNElements(0);
1430 }
1431 stagedCluster.fColumnInfos.push_back(std::move(columnInfo));
1432 }
1433
1434 return stagedCluster;
1435}
1436
1438{
1439 for (const auto &cluster : clusters) {
1441 clusterBuilder.ClusterId(fDescriptorBuilder.GetDescriptor().GetNActiveClusters())
1442 .FirstEntryIndex(fPrevClusterNEntries)
1443 .NEntries(cluster.fNEntries);
1444 for (const auto &columnInfo : cluster.fColumnInfos) {
1445 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1446 if (columnInfo.fIsSuppressed) {
1447 assert(columnInfo.fPageRange.GetPageInfos().empty());
1448 clusterBuilder.MarkSuppressedColumnRange(colId);
1449 } else {
1450 clusterBuilder.CommitColumnRange(colId, fOpenColumnRanges[colId].GetFirstElementIndex(),
1451 columnInfo.fCompressionSettings, columnInfo.fPageRange);
1452 fOpenColumnRanges[colId].IncrementFirstElementIndex(columnInfo.fNElements);
1453 }
1454 }
1455
1456 clusterBuilder.CommitSuppressedColumnRanges(fDescriptorBuilder.GetDescriptor()).ThrowOnError();
1457 for (const auto &columnInfo : cluster.fColumnInfos) {
1458 if (!columnInfo.fIsSuppressed)
1459 continue;
1460 const auto colId = columnInfo.fPageRange.GetPhysicalColumnId();
1461 // For suppressed columns, we need to reset the first element index to the first element of the next (upcoming)
1462 // cluster. This information has been determined for the committed cluster descriptor through
1463 // CommitSuppressedColumnRanges(), so we can use the information from the descriptor.
1464 const auto &columnRangeFromDesc = clusterBuilder.GetColumnRange(colId);
1465 fOpenColumnRanges[colId].SetFirstElementIndex(columnRangeFromDesc.GetFirstElementIndex() +
1466 columnRangeFromDesc.GetNElements());
1467 }
1468
1469 fDescriptorBuilder.AddCluster(clusterBuilder.MoveDescriptor().Unwrap());
1470 fPrevClusterNEntries += cluster.fNEntries;
1471 }
1472}
1473
1475{
1476 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1477
1478 const auto nClusters = descriptor.GetNActiveClusters();
1479 std::vector<ROOT::DescriptorId_t> physClusterIDs;
1480 physClusterIDs.reserve(nClusters);
1481 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1482 physClusterIDs.emplace_back(fSerializationContext.MapClusterId(i));
1483 }
1484
1485 auto szPageList =
1486 RNTupleSerializer::SerializePageList(nullptr, descriptor, physClusterIDs, fSerializationContext).Unwrap();
1488 RNTupleSerializer::SerializePageList(bufPageList.get(), descriptor, physClusterIDs, fSerializationContext);
1489
1490 const auto clusterGroupId = descriptor.GetNClusterGroups();
1491 const auto locator = CommitClusterGroupImpl(bufPageList.get(), szPageList);
1493 cgBuilder.ClusterGroupId(clusterGroupId).PageListLocator(locator).PageListLength(szPageList);
1494 if (fNextClusterInGroup == nClusters) {
1495 cgBuilder.MinEntry(0).EntrySpan(0).NClusters(0);
1496 } else {
1497 const auto &firstClusterDesc = descriptor.GetClusterDescriptor(fNextClusterInGroup);
1498 const auto &lastClusterDesc = descriptor.GetClusterDescriptor(nClusters - 1);
1499 cgBuilder.MinEntry(firstClusterDesc.GetFirstEntryIndex())
1500 .EntrySpan(lastClusterDesc.GetFirstEntryIndex() + lastClusterDesc.GetNEntries() -
1501 firstClusterDesc.GetFirstEntryIndex())
1502 .NClusters(nClusters - fNextClusterInGroup);
1503 }
1504 std::vector<ROOT::DescriptorId_t> clusterIds;
1505 clusterIds.reserve(nClusters);
1506 for (auto i = fNextClusterInGroup; i < nClusters; ++i) {
1507 clusterIds.emplace_back(i);
1508 }
1509 cgBuilder.AddSortedClusters(clusterIds);
1510 fDescriptorBuilder.AddClusterGroup(cgBuilder.MoveDescriptor().Unwrap());
1511 fSerializationContext.MapClusterGroupId(clusterGroupId);
1512
1513 fNextClusterInGroup = nClusters;
1514}
1515
1518{
1520
1522 auto attrSetDesc = attrSetDescBuilder.SchemaVersion(kSchemaVersionMajor, kSchemaVersionMinor)
1523 .AnchorLength(attrAnchorInfo.fLength)
1524 .AnchorLocator(attrAnchorInfo.fLocator)
1525 .Name(attrSetName)
1526 .MoveDescriptor()
1527 .Unwrap();
1528 fDescriptorBuilder.AddAttributeSet(std::move(attrSetDesc)).ThrowOnError();
1529}
1530
1532{
1533 if (!fInfosOfStreamerFields.empty()) {
1534 // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but
1535 // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging.
1536 for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) {
1537 if (etDesc.GetContentId() == EExtraTypeInfoIds::kStreamerInfo) {
1538 // The specification mandates that the type name for a kStreamerInfo should be empty and the type version
1539 // should be zero.
1540 R__ASSERT(etDesc.GetTypeName().empty());
1541 R__ASSERT(etDesc.GetTypeVersion() == 0);
1542 auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap();
1543 fInfosOfStreamerFields.merge(etInfo);
1544 }
1545 }
1546
1549 .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields));
1550 fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap());
1551 }
1552
1553 const auto &descriptor = fDescriptorBuilder.GetDescriptor();
1554
1555 auto szFooter = RNTupleSerializer::SerializeFooter(nullptr, descriptor, fSerializationContext).Unwrap();
1557 RNTupleSerializer::SerializeFooter(bufFooter.get(), descriptor, fSerializationContext);
1558
1559 return CommitDatasetImpl(bufFooter.get(), szFooter);
1560}
1561
1563{
1564 fMetrics = RNTupleMetrics(prefix);
1565 fCounters = std::make_unique<RCounters>(RCounters{
1566 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("nPageCommitted", "", "number of pages committed to storage"),
1567 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szWritePayload", "B", "volume written for committed pages"),
1568 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szZip", "B", "volume before zipping"),
1569 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallWrite", "ns", "wall clock time spent writing"),
1570 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("timeWallZip", "ns", "wall clock time spent compressing"),
1571 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuWrite", "ns", "CPU time spent writing"),
1572 *fMetrics.MakeCounter<RNTupleTickCounter<RNTupleAtomicCounter> *>("timeCpuZip", "ns",
1573 "CPU time spent compressing")});
1574}
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:130
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:142
#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...
static std::pair< std::uint16_t, std::uint16_t > GetValidBitRange(ROOT::ENTupleColumnType type)
Most types have a fixed on-disk bit width.
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.
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)
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 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)
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.
Metadata stored for every field of an RNTuple.
ROOT::ENTupleStructure GetStructure() const
ROOT::DescriptorId_t GetParentId() const
The on-storage metadata of an RNTuple.
@ kFeatureFlag_NestedDeferredColumns
Signals that the RNTuple contains at least one deferred column that is part of a collection and was e...
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.
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
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
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.