Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleParallelWriter.cxx
Go to the documentation of this file.
1/// \file RNTupleParallelWriter.cxx
2/// \ingroup NTuple ROOT7
3/// \author Jonas Hahnfeld <jonas.hahnfeld@cern.ch>
4/// \date 2024-02-01
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2024, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
17
18#include <ROOT/RNTupleModel.hxx>
20#include <ROOT/RPageSinkBuf.hxx>
21#include <ROOT/RPageStorage.hxx>
23
24#include <TError.h>
25
26namespace {
27
37
38/// An internal RPageSink that enables multiple RNTupleFillContext to write into a single common RPageSink.
39///
40/// The setup with two contexts looks as follows:
41///
42/// +------ owned by RNTupleFillContext ------+
43/// | |
44/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
45/// (and owns) |
46/// (via raw fInnerSink ptr) +-- RPageSink (usually a persistent sink)
47/// |
48/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
49/// | (and owns) |
50/// | |
51/// +------ owned by RNTupleFillContext ------+
52///
53/// The mutex used by the synchronizing sinks is owned by the RNTupleParallelWriter that also owns the original model,
54/// the "final" sink (usually a persistent sink) and keeps weak_ptr's of the contexts (to make sure they are destroyed
55/// before the writer is destructed).
56class RPageSynchronizingSink : public RPageSink {
57private:
58 /// The wrapped inner sink, not owned by this class.
59 RPageSink *fInnerSink;
60 std::mutex *fMutex;
61
62public:
63 explicit RPageSynchronizingSink(RPageSink &inner, std::mutex &mutex)
64 : RPageSink(inner.GetNTupleName(), inner.GetWriteOptions()), fInnerSink(&inner), fMutex(&mutex)
65 {
66 // Do not observe the sink's metrics: It will contain some counters for all threads, which is misleading for the
67 // users.
68 // fMetrics.ObserveMetrics(fSink->GetMetrics());
69 }
70 RPageSynchronizingSink(const RPageSynchronizingSink &) = delete;
71 RPageSynchronizingSink &operator=(const RPageSynchronizingSink &) = delete;
72
73 const RNTupleDescriptor &GetDescriptor() const final { return fInnerSink->GetDescriptor(); }
74
75 ColumnHandle_t AddColumn(DescriptorId_t, RColumn &) final { return {}; }
76 void InitImpl(RNTupleModel &) final {}
77 void UpdateSchema(const RNTupleModelChangeset &, NTupleSize_t) final
78 {
79 throw ROOT::RException(R__FAIL("UpdateSchema not supported via RPageSynchronizingSink"));
80 }
81 void UpdateExtraTypeInfo(const RExtraTypeInfoDescriptor &) final
82 {
83 throw ROOT::RException(R__FAIL("UpdateExtraTypeInfo not supported via RPageSynchronizingSink"));
84 }
85
86 void CommitSuppressedColumn(ColumnHandle_t handle) final { fInnerSink->CommitSuppressedColumn(handle); }
87 void CommitPage(ColumnHandle_t, const RPage &) final
88 {
89 throw ROOT::RException(R__FAIL("should never commit single pages via RPageSynchronizingSink"));
90 }
91 void CommitSealedPage(DescriptorId_t, const RSealedPage &) final
92 {
93 throw ROOT::RException(R__FAIL("should never commit sealed pages via RPageSynchronizingSink"));
94 }
95 void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) final
96 {
97 fInnerSink->CommitSealedPageV(ranges);
98 }
99 std::uint64_t CommitCluster(NTupleSize_t nNewEntries) final { return fInnerSink->CommitCluster(nNewEntries); }
100 RStagedCluster StageCluster(NTupleSize_t nNewEntries) final { return fInnerSink->StageCluster(nNewEntries); }
101 void CommitStagedClusters(std::span<RStagedCluster> clusters) final { fInnerSink->CommitStagedClusters(clusters); }
102 void CommitClusterGroup() final
103 {
104 throw ROOT::RException(R__FAIL("should never commit cluster group via RPageSynchronizingSink"));
105 }
106 void CommitDatasetImpl() final
107 {
108 throw ROOT::RException(R__FAIL("should never commit dataset via RPageSynchronizingSink"));
109 }
110
111 RSinkGuard GetSinkGuard() final { return RSinkGuard(fMutex); }
112};
113
114} // namespace
115
117 std::unique_ptr<Internal::RPageSink> sink)
118 : fSink(std::move(sink)), fModel(std::move(model)), fMetrics("RNTupleParallelWriter")
119{
120 if (fModel->GetRegisteredSubfields().size() > 0) {
121 throw RException(R__FAIL("cannot create an RNTupleWriter from a model with registered subfields"));
122 }
123 fModel->Freeze();
124 fSink->Init(*fModel.get());
125 fMetrics.ObserveMetrics(fSink->GetMetrics());
126}
127
129{
130 try {
131 CommitDataset();
132 } catch (const RException &err) {
133 R__LOG_ERROR(NTupleLog()) << "failure committing ntuple: " << err.GetError().GetReport();
134 }
135}
136
138{
139 if (fModel->IsExpired())
140 return;
141
142 for (const auto &context : fFillContexts) {
143 if (!context.expired()) {
144 throw RException(R__FAIL("RNTupleFillContext has not been destructed"));
145 }
146 }
147
148 // Now commit all clusters as a cluster group and then the dataset.
149 fSink->CommitClusterGroup();
150 fSink->CommitDataset();
151 fModel->Expire();
152}
153
154std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
155ROOT::Experimental::RNTupleParallelWriter::Recreate(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
156 std::string_view storage, const RNTupleWriteOptions &options)
157{
158 if (!options.GetUseBufferedWrite()) {
159 throw RException(R__FAIL("parallel writing requires buffering"));
160 }
161
162 auto sink = Internal::RPagePersistentSink::Create(ntupleName, storage, options);
163 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
164 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
165}
166
167std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
168ROOT::Experimental::RNTupleParallelWriter::Append(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
169 TDirectory &fileOrDirectory, const RNTupleWriteOptions &options)
170{
171 if (!options.GetUseBufferedWrite()) {
172 throw RException(R__FAIL("parallel writing requires buffering"));
173 }
174
175 auto sink = std::make_unique<Internal::RPageSinkFile>(ntupleName, fileOrDirectory, options);
176 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
177 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
178}
179
180std::shared_ptr<ROOT::Experimental::RNTupleFillContext> ROOT::Experimental::RNTupleParallelWriter::CreateFillContext()
181{
182 std::lock_guard g(fMutex);
183
184 auto model = fModel->Clone();
185
186 // TODO: Think about honoring RNTupleWriteOptions::SetUseBufferedWrite(false); this requires synchronization on every
187 // call to CommitPage() *and* preparing multiple cluster descriptors in parallel!
188 auto sink = std::make_unique<Internal::RPageSinkBuf>(std::make_unique<RPageSynchronizingSink>(*fSink, fSinkMutex));
189
190 // Cannot use std::make_shared because the constructor of RNTupleFillContext is private. Also it would mean that the
191 // (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
192 std::shared_ptr<RNTupleFillContext> context(new RNTupleFillContext(std::move(model), std::move(sink)));
193 fFillContexts.push_back(context);
194 return context;
195}
#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:299
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define g(i)
Definition RSha256.hxx:105
Binding & operator=(OUT(*fun)(void))
void ObserveMetrics(RNTupleMetrics &observee)
A column is a storage-backed array of a simple, fixed-size type, from which pages can be mapped into ...
Definition RColumn.hxx:40
static std::unique_ptr< RPageSink > Create(std::string_view ntupleName, std::string_view location, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Guess the concrete derived page source from the location.
Abstract interface to write data into an ntuple.
A page is a slice of a column that is mapped into memory.
Definition RPage.hxx:47
Field specific extra type information from the header / extenstion header.
The on-storage meta-data of an ntuple.
A context for filling entries (data) into clusters of an RNTuple.
The RNTupleModel encapulates the schema of an ntuple.
A writer to fill an RNTuple from multiple contexts.
RNTupleParallelWriter(std::unique_ptr< RNTupleModel > model, std::unique_ptr< Internal::RPageSink > sink)
static std::unique_ptr< RNTupleParallelWriter > Append(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, TDirectory &fileOrDirectory, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Append an ntuple to the existing file, which must not be accessed while data is filled into any creat...
std::unique_ptr< RNTupleModel > fModel
The original RNTupleModel connected to fSink; needs to be destructed before it.
std::unique_ptr< Internal::RPageSink > fSink
The final RPageSink that represents the synchronization point.
static std::unique_ptr< RNTupleParallelWriter > Recreate(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, std::string_view storage, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Recreate a new file and return a writer to write an ntuple.
std::shared_ptr< RNTupleFillContext > CreateFillContext()
Create a new RNTupleFillContext that can be used to fill entries and prepare clusters in parallel.
void CommitDataset()
Automatically called by the destructor.
Common user-tunable settings for storing ntuples.
std::string GetReport() const
Format a dignostics report, e.g. for an exception message.
Definition RError.cxx:22
Base class for all ROOT issued exceptions.
Definition RError.hxx:79
const RError & GetError() const
Definition RError.hxx:84
Describe directory structure in memory.
Definition TDirectory.h:45
RLogChannel & NTupleLog()
Log channel for RNTuple diagnostics.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
The incremental changes to a RNTupleModel