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