data-source-classes.txt 17.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Data Source Library Classes
===========================

About this document
-------------------

This memo describes major classes used in the data source library,
mainly focusing on handling in-memory cache with consideration of the
shared memory support.  It will give an overview of the entire design
architecture and some specific details of how these classes are expected
to be used.

Before reading, the higher level inter-module protocol should be understood:
http://bind10.isc.org/wiki/SharedMemoryIPC

Overall relationships between classes
-------------------------------------

The following diagram shows major classes in the data source library
related to in-memory caches and their relationship.

22
image::overview.png[Class diagram showing overview of relationships]
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

Major design decisions of this architecture are:

* Keep each class as concise as possible, each focusing on one or
  small set of responsibilities.  Smaller classes are generally easier
  to understand (at the cost of understanding how they work in the
  "big picture" of course) and easier to test.

* On a related point, minimize dependency to any single class.  A
  monolithic class on which many others are dependent is generally
  difficult to maintain because you'll need to ensure a change to the
  monolithic class doesn't break anything on any other classes.

* Use polymorphism for any "fluid" behavior, and hide specific details
  under abstract interfaces so implementation details won't be
  directly referenced from any other part of the library.
  Specifically, the underlying memory segment type (local, mapped, and
  possibly others) and the source of in-memory data (master file or
  other data source) are hidden via a kind of polymorphism.

* Separate classes directly used by applications from classes that
  implement details.  Make the former classes as generic as possible,
  agnostic about implementation specific details such as the memory
  segment type (or, ideally and where possible, whether it's for
  in-memory cache or the underlying data source).

The following give a summarized description of these classes.

* `ConfigurableClientList`: The front end to application classes.  An
  application that uses the data source library generally maintains
  one or more `ConfigurableClientList` object (usually one per RR
  class, or when we support views, probably one per view).  This class
  is a container of sets of data source related classes, providing
  accessor to these classes and also acting as a factory of other
  related class objects.  Note: Due to internal implementation
  reasons, there is a base class for `ConfigurableClientList` named
  `ClientList` in the C++ version, and applications are expected to
  use the latter.  But conceptually `ConfigurableClientList` is an
  independent value class; the inheritance is not for polymorphism.
  Note also that the Python version doesn't have the base class.

* `DataSourceInfo`: this is a straightforward tuple of set of class
  objects corresponding to a single data source, including
  `DataSourceClient`, `CacheConfig`, and `ZoneTableSegment`.
  `ConfigurableClientList` maintains a list of `DataSourceInfo`, one
  for each data source specified in its configuration.

* `DataSourceClient`: The front end class to applications for a single
  data source.  Applications will get a specific `DataSourceClient`
  object by `ConfigurableClientList::find()`.
  `DataSourceClient` itself is a set of factories for various
  operations on the data source such as lookup or update.

* `CacheConfig`: library internal representation of in-memory cache
  configuration for a data source.  It knows which zones are to be
  cached and where the zone data (RRs) should come from, either from a
  master file or other data source.  With this knowledge it will
  create an appropriate `LoadAction` object.  Note that `CacheConfig`
  isn't aware of the underlying memory segment type for the in-memory
  data.  It's intentionally separated from this class (see the
  conciseness and minimal-dependency design decisions above).

* `ZoneTableSegment`: when in-memory cache is enabled, it provides
  memory-segment-type independent interface to the in-memory data.
  This is an abstract base class (see polymorphism in the design
  decisions) and inherited by segment-type specific subclasses:
  `ZoneTableSegmentLocal` and `ZoneTableSegmentMapped` (and possibly
  others).  Any subclass of `ZoneTableSegment` is expected to maintain
  the specific type of `MemorySegment` object.

* `ZoneWriter`: a frontend utility class for applications to update
  in-memory zone data (currently it can only load a whole zone and
  replace any existing zone content with a new one, but this should be
  extended so it can handle partial updates).
  Applications will get a specific `ZoneWriter`
  object by `ConfigurableClientList::getCachedZoneWriter()`.
  `ZoneWriter` is constructed with `ZoneableSegment` and `LoadAction`.
  Since these are abstract classes, `ZoneWriter` doesn't have to be
  aware of "fluid" details.  It's only responsible for "somehow" preparing
  `ZoneData` for a new version of a specified zone using `LoadAction`,
  and installing it in the `ZoneTable` (which can be accessed via
  `ZoneTableSegment`).

* `DataSourceStatus`: created by `ConfigurableClientList::getStatus()`,
  a straightforward tuple that represents some status information of a
  specific data source managed in the `ConfigurableClientList`.
  `getStatus()` generates `DataSourceStatus` for all data sources
  managed in it, and returns them as a vector.

* `ZoneTableAccessor`, `ZoneTableIterator`: frontend classes to get
  access to the conceptual "zone table" (a set of zones) stored in a
  specific data source.  In particular, `ZoneTableIterator` allows
  applications to iterate over all zones (by name) stored in the
  specific data source.
  Applications will get a specific `ZoneTableAccessor`
  object by `ConfigurableClientList::getZoneTableAccessor()`,
  and get an iterator object by calling `getIterator` on the accessor.
  These are abstract classes and provide unified interfaces
  independent from whether it's for in-memory cached zones or "real"
  underlying data source.  But the initial implementation only
  provides the in-memory cache version of subclass (see the next
  item).

* `ZoneTableAccessorCache`, `ZoneTableIteratorCache`: implementation
  classes of `ZoneTableAccessor` and `ZoneTableIterator` for in-memory
  cache.  They refer to `CacheConfig` to get a list of zones to be
  cached.

* `ZoneTableHeader`, `ZoneTable`: top-level interface to actual
  in-memory data.  These were separated based on a prior version of
  the design (http://bind10.isc.org/wiki/ScalableZoneLoadDesign) where
  `ZoneTableHeader` may contain multiple `ZoneTable`s.  It's
  one-to-one relationship in the latest version (of implementation),
  so we could probably unify them as a cleanup.

* `ZoneData`: representing the in-memory content of a single zone.
  `ZoneTable` contains (zero, one or) multiple `ZoneData` objects.

* `RdataSet`: representing the in-memory content of (data of) a single
  RRset.
  `ZoneData` contains `RdataSet`s corresponding to the RRsets stored
  in the zone.

* `LoadAction`: a "polymorphic" functor that implements loading zone
  data into memory.  It hides from its user (i.e., `ZoneWriter`)
  details about the source of the data: master file or other data
  source (and perhaps some others).  The "polymorphism" is actually
  realized as different implementations of the functor interface, not
  class inheritance (but conceptually the effect and goal is the
  same).  Note: there's a proposal to replace `LoadAction` with
  a revised `ZoneDataLoader`, although the overall concept doesn't
  change.  See Trac ticket #2912.

* `ZoneDataLoader` and `ZoneDataUpdater`: helper classes for the
  `LoadAction` functor(s).  These work independently from the source
  of data, taking a sequence of RRsets objects, converting them
  into the in-memory data structures (`RdataSet`), and installing them
  into a newly created `ZoneData` object.

Sequence for auth module using local memory segment
---------------------------------------------------

In the remaining sections, we explain how the classes shown in the
previous section work together through their methods for commonly
intended operations.

The following sequence diagram shows the case for the authoritative
DNS server module to maintain "local" in-memory data.  Note that
"auth" is a conceptual "class" (not actually implemented as a C++
class) to represent the server application behavior.  For the purpose
of this document that should be sufficient.  The same note applies to
all examples below.

176
image::auth-local.png[Sequence diagram for auth server using local memory segment]
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213

1. On startup, the auth module creates a `ConfigurableClientList`
   for each RR class specified in the configuration for "data_sources"
   module.  It then calls `ConfigurableClientList::configure()`
   for the given configuration of that RR class.

2. For each data source, `ConfigurableClientList` creates a
   `CacheConfig` object with the corresponding cache related
   configuration.

3. If in-memory cache is enabled for the data source,
   `ZoneTableSegment` is also created.  In this scenario the cache
   type is specified as "local" in the configuration, so a functor
   creates `ZoneTableSegmentLocal` as the actual instance.
   In this case its `ZoneTable` is immediately created, too.

4. `ConfigurableClientList` checks if the created `ZoneTableSegment` is
   writable.  It is always so for "local" type of segments.  So
   `ConfigurableClientList` immediately loads zones to be cached into
   memory.  For each such zone, it first gets the appropriate
   `LoadAction` through `CacheConfig`, then creates `ZoneWriter` with
   the `LoadAction`, and loads the data using the writer.

5. If the auth module receives a "reload" command for a cached zone
   from other module (xfrin, an end user, etc), it calls
   `ConfigurableClientList::getCachedZoneWriter` to load and install
   the new version of the zone.  The same loading sequence takes place
   except that the user of the writer is the auth module.
   Also, the old version of the zone data is destroyed at the end of
   the process.

Sequence for auth module using mapped memory segment
----------------------------------------------------

This is an example for the authoritative server module that uses
mapped type memory segment for in-memory data.

214
image::auth-mapped.png[Sequence diagram for auth server using mapped memory segment]
215
216
217
218

1. The sequence is the same to the point of creating `CacheConfig`.

2. But in this case a `ZoneTableSegmentMapped` object is created based
219
220
221
   on the configuration of the cache type.  This type of
   `ZoneTableSegment` is initially empty and isn't even associated
   with a `MemorySegment` (and therefore considered non-writable).
222
223
224

3. `ConfigurableClientList` checks if the zone table segment is
   writable to know whether to load zones into memory by itself,
225
   but as `ZoneTableSegment::isWritable()` returns false, it skips
226
227
   the loading.

228
229
4. The auth module gets the status of each data source, and notices
   there's a `WAITING` state of segment. So it subscribes to the
230
231
232
233
   "Memmgr" group on a command session and waits for an update
   from the memory manager (memmgr) module. (See also the note at the
   end of the section)

234
5. When the auth module receives an update command from memmgr, it
235
   calls `ConfigurableClientList::resetMemorySegment()` with the command
236
   argument and the segment mode of `READ_ONLY`.
237
   Note that the auth module handles the command argument as mostly
238
   opaque data; it's not expected to deal with details of segment
239
240
   type-specific behavior. If the reset fails, auth aborts (as there's
   no clear way to handle the failure).
241
242
243
244
245
246
247
248
249

6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
   `reset()` method on the corresponding `ZoneTableSegment` with the
   given parameters.
   In the case of `ZoneTableSegmentMapped`, it creates a new
   `MemorySegment` object for the mapped type, which internally maps
   the specific file into memory.
   memmgr is expected to have prepared all necessary data in the file,
   so all the data are immediately ready for use (i.e., there
250
   shouldn't be any explicit load operation).
251
252
253
254
255

7. When a change is made in the mapped data, memmgr will send another
   update command with parameters for new mapping.  The auth module
   calls `ConfigurableClientList::resetMemorySegment()`, and the
   underlying memory segment is swapped with a new one.  The old
256
   memory segment object is destroyed.  Note that
257
   this "destroy" just means unmapping the memory region; the data
258
259
   stored in the file are intact. Again, if mapping fails, auth
   aborts.
260
261
262
263
264
265
266

8. If the auth module happens to receive a reload command from other
   module, it could call
   `ConfigurableClientList::getCachedZoneWriter()`
   to reload the data by itself, just like in the previous section.
   In this case, however, the writability check of
   `getCachedZoneWriter()` fails (the segment was created as
267
   `READ_ONLY` and is non-writable), so loading won't happen.
268

269
NOTE: While less likely in practice, it's possible that the same auth
270
271
272
273
module uses both "local" and "mapped" (and even others) type of
segments for different data sources.  In such cases the sequence is
either the one in this or previous section depending on the specified
segment type in the configuration.  The auth module itself isn't aware
274
275
of per segment-type details, but changes the behavior depending on the
segment state of each data source at step 4 above: if it's `WAITING`,
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
it means the auth module needs help from memmgr (that's all the auth
module should know; it shouldn't be bothered with further details such
as mapped file names); if it's something else, the auth module doesn't
have to do anything further.

Sequence for memmgr module initialization using mapped memory segment
---------------------------------------------------------------------

This sequence shows the common initialization sequence for the
memory manager (memmgr) module using a mapped type memory segment.
This is a mixture of the sequences shown in Sections 2 and 3.

image::memmgr-mapped-init.png[]

1. Initial sequence is the same until the application module (memmgr)
   calls `ConfigurableClientList::getStatus()` as that for the
   previous section.

2. The memmgr module identifies the data sources whose in-memory cache
   type is "mapped".  (Unlike other application modules, the memmgr
   should know what such types means due to its exact responsibility).
   For each such data source, it calls
   `ConfigurableClientList::resetMemorySegment` with the READ_WRITE
   mode and other mapped-type specific parameters.  memmgr should be
   able to generate the parameters from its own configuration and
   other data source specific information (such as the RR class and
   data source name).

3. The `ConfigurableClientList` class calls
   `ZoneTableSegment::reset()` on the corresponding zone table
   segment with the given parameters.  In this case, since the mode is
   READ_WRITE, a new `ZoneTable` will be created (assuming this is a
   very first time initialization; if there's already a zone table
   in the segment, it will be used).

4. The memmgr module then calls
   `ConfigurableClientList::getZoneTableAccessor()`, and calls the
   `getItertor()` method on it to get a list of zones for which
   zone data are to be loaded into the memory segment.

5. The memmgr module loads the zone data for each such zone.  This
   sequence is the same as shown in Section 2.

6. On loading all zone data, the memmgr module sends an update command
   to all interested modules (such as auth) in the segment, and waits
   for acknowledgment from all of them.

7. Then it calls `ConfigurableClientList::resetMemorySegment()` for
   this data source with almost the same parameter as step 2 above,
   but with a different mapped file name.  This will make a swap of
   the underlying memory segment with a new mapping.  The old
   `MemorySegment` object will be destroyed, but as explained in the
   previous section, it simply means unmapping the file.

8. The memmgr loads the zone data into the newly mapped memory region
   by repeating the sequence shown in step 5.

9. The memmgr repeats all this sequence for data sources that use
   "mapped" segment for in-memory cache.  Note: it could handle
   multiple data sources in parallel, e.g., while waiting for
   acknowledgment from other modules.

Sequence for memmgr module to reload a zone using mapped memory segment
-----------------------------------------------------------------------

This example is a continuation of the previous section, describing how
the memory manager reloads a zone in mapped memory segment.

image::memmgr-mapped-reload.png[]

1. When the memmgr module receives a reload command from other module,
   it calls `ConfigurableClientList::getCachedZoneWriter()` for the
   specified zone name.  This method checks the writability of
   the segment, and since it's writable (as memmgr created it in the
   READ_WRITE mode), `getCachedZoneWriter()` succeeds and returns
   a `ZoneWriter`.

2. The memmgr module uses the writer to load the new version of zone
   data.  There is nothing specific to mapped-type segment here.

3. The memmgr module then sends an update command to other modules
   that would share this version, and waits for acknowledgment from
   all of them.

4. On getting acknowledgments, the memmgr module calls
  `ConfigurableClientList::resetMemorySegment()` with the parameter
   specifying the other mapped file.  This will swap the underlying
   `MemorySegment` with a newly created one, mapping the other file.

5. The memmgr updates this segment, too, so the two files will contain
   the same version of data.