root/trunk/bknr/datastore/src/data/tutorial.lisp

Revision 3035, 36.4 kB (checked in by hans, 2 months ago)

Update tutorials to reflect reality better

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 ;;; BKNR Datastore
2
3 ;;;# Introduction
4 ;;;
5 ;;;## The prevalence model
6 ;;;
7 ;;; The BKNR datastore is a persistence solution for Lisp data. It
8 ;;; uses the prevalence model, which is based on the following
9 ;;; assumptions:
10 ;;;
11 ;;; All data is held in RAM.
12 ;;;
13 ;;; Data can be saved to disk at once into a snapshot file and is read
14 ;;; from that file at startup time.
15 ;;;
16 ;;; Changes to persistent data are written to a transaction log file
17 ;;; immediately, which can be replayed to restore all changes that
18 ;;; occured since the last snapshot was saved.
19 ;;;
20 ;;; Every kind of operation that needs to be logged is called a
21 ;;; "transaction", and such transactions are made explicit in the
22 ;;; program code. This is different from object-oriented databases,
23 ;;; where the fundamental transactions are object creation, object
24 ;;; deletion and slot access, which are not special cases in the
25 ;;; prevalence model at all.
26 ;;;
27 ;;; Isolation of transactions is achieved using thread locks. In the
28 ;;; simplest model used by the `mp-store', transactions are serialized
29 ;;; using a global lock.
30 ;;;
31 ;;; The transaction system is responsible for providing replay of
32 ;;; committed transactions after a server crash, but not for rollback
33 ;;; of failed transactions in a running server, except that failing
34 ;;; transactions are simply not logged onto disk. To roll back
35 ;;; transactions at points where exceptions might be excepted, use
36 ;;; ordinary Lisp programming techniques involving `unwind-protect'
37 ;;; and similar.
38 ;;;
39 ;;;## BKNR Datastore Design
40 ;;;
41 ;;; The design of the datastore aims to make explicit the
42 ;;; orthogonality of object system access (unlogged) and logging of
43 ;;; transactions (essentially independent of the object system). The
44 ;;; interface between transaction system and object system is
45 ;;; documented and allows for the implementation of alternative object
46 ;;; systems. For example, the blob subsystem is using the same
47 ;;; interface as the object subsystem.
48 ;;;
49 ;;; Previous versions of the BKNR Datastore allowed the creation of
50 ;;; multiple datastores in a single LISP process. However, this
51 ;;; feature was seldom used, and could be very confusing while
52 ;;; developing applications. The new version of the BKNR Datastore
53 ;;; supports only a single datastore, which is referenced by the
54 ;;; special variable `*STORE*'.
55 ;;;
56 ;;;## BKNR Object Datastore
57 ;;;
58 ;;; In addition to the transaction layer (in the file `txn.lisp'), the
59 ;;; BKNR datastore provides persistent CLOS objects using the
60 ;;; Metaobject Protocol. It provides a metaclass with which slots can
61 ;;; be defined as persistent (stored on snapshot) or transient. The
62 ;;; metaclass also prohibits slot accesses outside transactions,
63 ;;; provides unique IDs for all objects, and provides standard query
64 ;;; functions like `STORE-OBJECTS-WITH-CLASS' and
65 ;;; `STORE-OBJECTS-OF-CLASS'. The object datastore can be seamlessly
66 ;;; combined with BKNR indices and XML import/export.
67
68 ;;;# Obtaining and loading BKNR Datastore
69 ;;;
70 ;;; You can obtain the current CVS sources of BKNR by following the
71 ;;; instructions at `http://bknr.net/blog/bknr-devel'. Add the `experimental'
72 ;;; directory of BKNR to your `asdf:*central-registry*', and load the
73 ;;; indices module by evaluating the following form:
74
75 (asdf:oos 'asdf:load-op :bknr.datastore)
76
77 ;;; Then switch to the `bknr.datastore' package to try out the tutorial.
78
79 (in-package :bknr.datastore)
80
81 ;;;# A transaction system example
82
83 ;;; The first datastore we will build is very simple. We have a
84 ;;; counter variable for the store, and this counter variable can be
85 ;;; decremented and indecremented. We want this variable to be
86 ;;; persistent, so decrementing and incrementing it has to be done
87 ;;; through transactions that will be logged by the datastore. We also
88 ;;; define a `:BEFORE' method for the generic function `RESTORE-STORE'
89 ;;; to set the counter to `0' initially. This method will be called
90 ;;; every time the store is created or restored from disk.
91
92 (defclass tutorial-store (mp-store)
93   ((counter :initform 0 :accessor tutorial-store-counter)))
94
95 (defmethod restore-store :before ((store tutorial-store) &key until)
96   (declare (ignore until))
97   (setf (tutorial-store-counter store) 0))
98
99 ;;; The two transactions are declared like normal functions, but using
100 ;;; the `DEFTRANSACTION' macro.
101
102 (deftransaction incf-counter ()
103   (incf (tutorial-store-counter *store*)))
104
105 (deftransaction decf-counter ()
106   (decf (tutorial-store-counter *store*)))
107
108 ;;; When looking at the macro-expanded form of `DEFTRANSACTION', we
109 ;;; see that `DEFTRANSACTION' defines two functions, a toplevel
110 ;;; function that creates a transaction object and calls the method
111 ;;; `EXECUTE' on it, and a function that contains the actual
112 ;;; transaction code and that will be called in the context of the
113 ;;; transaction, and logged to disk.
114
115 (PROGN (DEFUN TX-DECF-COUNTER ()
116          (UNLESS (IN-TRANSACTION-P) (ERROR 'NOT-IN-TRANSACTION))
117          (DECF (TUTORIAL-STORE-COUNTER *STORE*)))
118        (DEFUN DECF-COUNTER ()
119          (EXECUTE (MAKE-INSTANCE 'TRANSACTION
120                                  :FUNCTION-SYMBOL 'TX-DECF-COUNTER
121                                  :TIMESTAMP (GET-UNIVERSAL-TIME)
122                                  :ARGS (LIST)))))
123
124 ;;; The new datastore only supports a single datastore instance per
125 ;;; LISP instance. When creating a `STORE' object, the `*STORE*'
126 ;;; special variable is modified to point to the datastore. Thus, we
127 ;;; can create our simple datastore by creating an object of type
128 ;;; `TUTORIAL-STORE'. The transaction log will be store in the
129 ;;; directory "/tmp/tutorial-store".
130
131 (close-store)
132 (make-instance 'tutorial-store :directory "/tmp/tutorial-store/"
133                :subsystems nil)
134 ; Warning:  restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
135 ; => #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
136
137 (tutorial-store-counter *store*)
138 ; => 0
139 (incf-counter)
140 ; => 1
141 (incf-counter)
142 ; => 2
143 (decf-counter)
144 ; => 1
145
146 ;;; The three transactions have been logged to the transaction log in
147 ;;; "/tmp/tutorial-store/", as we can see:
148
149 (with-open-file (s "/tmp/tutorial-store/current/transaction-log"
150                             :direction :input)
151              (file-length s))
152 ; => 126
153 (incf-counter)
154 ; => 2
155 (with-open-file (s "/tmp/tutorial-store/current/transaction-log"
156                             :direction :input)
157              (file-length s))
158 ; => 168
159
160 ;;; The transaction log is kept in a directory called "current", which
161 ;;; is where the currently active version of the snapshots and log
162 ;;; files are kept. When a datastore is snapshotted, the "current"
163 ;;; directory is backupped to another directory with the current date,
164 ;;; and snapshots are created in the new "current" directory. However,
165 ;;; we cannot snapshot our tutorial datastore, as we cannot snapshot
166 ;;; the persistent data (the counter value).
167
168 (snapshot)
169 ; => Error in function (METHOD SNAPSHOT-STORE NIL (STORE)):
170 ; => Cannot snapshot store without subsystems...
171 ; => [Condition of type SIMPLE-ERROR]
172
173 ;;; We can close the store by using the function `CLOSE-STORE'.
174 *store*
175 ; => #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
176 (close-store)
177 ; => NIL
178 (boundp '*store*)
179 ; => NIL
180
181 ;;; The store can then be recreated, and the transaction log will be
182 ;;; read and executed upon restore.
183 (make-instance 'tutorial-store :directory "/tmp/tutorial-store/"
184                :subsystems nil)
185
186 ; Warning:  restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
187 ; Warning:  loading transaction log
188 ; /tmp/tutorial-store/current/transaction-log
189 ; => #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
190 (tutorial-store-counter *store*)
191 ; => 2
192
193 ;;; The store can also be restored in a later LISP session. Make sure
194 ;;; that all the code necessary to the execution of the transaction
195 ;;; log has been loaded before restoring the datastore. A later
196 ;;; version of the datastore will log all the code necessary in the
197 ;;; datastore itself, so that code and data are synchronized.
198
199 ;;;## Debugging the datastore
200 ;;;
201 ;;; By setting the `*STORE-DEBUG*' special variable to `T', the
202 ;;; datastore prints a lot of useful warnings. For example
203
204 ;;; You can also restore to a certain point in time, by specifying the
205 ;;; `UNTIL' argument of `RESTORE-STORE'.
206
207 (setf *store-debug* t)
208 ; => T
209 (restore-store *store*)
210 ; restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
211 ; loading transaction log /tmp/tutorial-store/current/transaction-log
212 ; executing transaction #<TRANSACTION 21.04.2008 07:08:22 TX-INCF-COUNTER > at timestamp 3417743302
213 ; executing transaction #<TRANSACTION 21.04.2008 07:08:25 TX-INCF-COUNTER > at timestamp 3417743305
214 ; executing transaction #<TRANSACTION 21.04.2008 07:08:26 TX-DECF-COUNTER > at timestamp 3417743306
215 ; executing transaction #<TRANSACTION 21.04.2008 07:08:34 TX-INCF-COUNTER > at timestamp 3417743314
216 ; => NIL
217 (tutorial-store-counter *store*)
218 ; => 2
219 ; !! Update the timestamp below to correspond to the fist transaction executed above !!
220 (restore-store *store* :until 3417743302) ...
221 ; restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
222 ; loading transaction log /tmp/tutorial-store/current/transaction-log
223 ; executing transaction #<TRANSACTION 21.04.2008 07:08:22 TX-INCF-COUNTER > at timestamp 3417743302
224 ; creating log file backup: /tmp/tutorial-store/current/transaction-log.backup
225 ; truncating transaction log at position 42.
226 (tutorial-store-counter *store*)
227 ; => 1
228
229 ;;;## Adding a subsystem
230
231 ;;; Now that we can restore the counter state by loading the
232 ;;; transaction log, we want to add a subsystem to be able to snapshot
233 ;;; the state of the counter. Thus, we won't need to execute every
234 ;;; single incrementing or decrementing transaction to restore our
235 ;;; persistent state.
236
237 ;;; To do this, we have to create a store-subsystem that will be able
238 ;;; to write the counter number to a file and to reload it on restore.
239
240 (defclass counter-subsystem ()
241   ())
242
243 ;;; Three methods are used to interact with the subsystem.
244 ;;; The first method is `INITIALIZE-SUBSYSTEM', which is called after
245 ;;; the store has been created and restored. It is used to initialize
246 ;;; certain parameters of the subsystem. We won't use this method
247 ;;; here, as our subsystem is very simple.
248 ;;; The second method is `SNAPSHOT-SUBSYSTEM', which is called when
249 ;;; the store is snapshotted. The subsystem has to store the
250 ;;; persistent data it handles to a snapshot file inside the current
251 ;;; directory of the store. Our `COUNTER-SUBSYSTEM' writes the current
252 ;;; value of the counter to a file named "counter" in the current
253 ;;; directory of the store (the old directory has been renamed).
254
255 (defmethod snapshot-subsystem ((store tutorial-store)
256                                (subsystem counter-subsystem))
257   (let* ((store-dir (ensure-store-current-directory store))
258          (counter-pathname
259           (make-pathname :name "counter" :defaults store-dir)))
260     (with-open-file (s counter-pathname :direction :output)
261       (write (tutorial-store-counter store) :stream s))))
262
263 ;;; Finally, the method `RESTORE-SUBSYSTEM' is called at restore time
264 ;;; to tell the subsystem to read back its persistent state from the
265 ;;; current directory of the store. Our `COUNTER-SUBSYSTEM' reads back
266 ;;; the counter value from the file named "counter". If it can't find
267 ;;; the file (for example if this is the first time that our datastore
268 ;;; is created, the file won't be there, so we issue a warning and set
269 ;;; the counter value to 0.
270
271 (defmethod restore-subsystem ((store tutorial-store)
272                               (subsystem counter-subsystem) &key
273                               until)
274   (declare (ignore until))
275   (let* ((store-dir (ensure-store-current-directory store))
276          (counter-pathname
277           (make-pathname :name "counter" :defaults store-dir)))
278     (if (probe-file counter-pathname)
279         (with-open-file (s counter-pathname :direction :input)
280           (let ((counter (read s)))
281             (setf (tutorial-store-counter store) counter)))
282         (progn
283           (warn "Could not find store counter value, setting to 0.")
284           (setf (tutorial-store-counter store) 0)))))
285
286 ;;; Now we can close our current store, and instantiate it anew with a
287 ;;; `COUNTER-SUBSYSTEM'.
288
289 (close-store)
290 ; => NIL
291 (make-instance 'tutorial-store :directory "/tmp/tutorial-store/"
292                :subsystems (list (make-instance 'counter-subsystem)))
293 ; Warning:  restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
294 ; Warning:  Could not find store counter value, setting to 0.
295 ; Warning:  loading transaction log
296 ; /tmp/tutorial-store/current/transaction-log
297 ; => #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
298 (snapshot)
299 ; Snapshotting subsystem #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
300 ; Successfully snapshotted #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
301 ; => NIL
302 (restore)
303 ; restoring #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
304 ; Restoring the subsystem #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: "/tmp/tutorial-store/">
305 ; => NUL
306
307 ;;;# An object store example
308
309 ;;; The BKNR object datastore is implemented using a special subsystem
310 ;;; `STORE-OBJECT-SUBSYSTEM'. Every object referenced by the store
311 ;;; object subsystem has a unique ID, and must be of the class
312 ;;; `STORE-OBJECT'. The ID counter in the store-object subsystem is
313 ;;; incremented on every object creation.
314 ;;;
315 ;;; All store objects have to be of the metaclass `PERSISTENT-CLASS',
316 ;;; which will ensure the object is referenced in the base indices of
317 ;;; the object datastore, and that slot access is only done inside a
318 ;;; transaction. The subsystem makes heavy use of BKNR indices, and
319 ;;; indexes object by ID and by class.
320
321 ;;; The ID index can be queried using the functions
322 ;;; `STORE-OBJECT-WITH-ID', which returns the object with the
323 ;;; requested ID, `ALL-STORE-OBJECTS' which returns all current store
324 ;;; objects, and `MAP-STORE-OBJECTS', which applies a function
325 ;;; iteratively to each store object. The class index can be queried
326 ;;; using the functions `ALL-STORE-CLASSES', which returns the names
327 ;;; of all the classes currently present in the datastore, and
328 ;;; `STORE-OBJECTS-WITH-CLASS', which returns all the objects of a
329 ;;; specific class (across superclasses also, so
330 ;;; `(STORE-OBJECTS-WITH-CLASS \'STORE-OBJECT)' returns all the
331 ;;; existing store objects.
332
333 ;;;## Store and object creation
334
335 ;;; We can create an object datastore by creating a `STORE' with the
336 ;;; subsystem `STORE-OBJECT-SUBSYSTEM'.
337
338 (close-store)
339 (make-instance 'mp-store :directory "/tmp/object-store/"
340                :subsystems (list
341                             (make-instance 'store-object-subsystem)))
342
343 ; Warning:  restoring #<MP-STORE DIR: "/tmp/object-store/">
344 ; restoring #<MP-STORE DIR: "/tmp/object-store/">
345 ; Restoring the subsystem #<STORE-OBJECT-SUBSYSTEM #xE63F866> of #<MP-STORE DIR: "/tmp/object-store/">
346 (all-store-objects)
347 ; => NIL
348
349 ;;; We can now create a few store objects (which is not very
350 ;;; interesting in itself). Store objects have to be created inside a
351 ;;; transaction so that the object creation is logged into the
352 ;;; transaction log. This is done by using the transaction
353 ;;; `MAKE-OBJECT'. The transaction also automatically gets a unique ID
354 ;;; from the store object subsystem.
355
356 (make-object 'store-object)
357 ; => #<STORE-OBJECT ID: 0>
358 (make-object 'store-object)
359 ; => #<STORE-OBJECT ID: 1>
360 (all-store-objects)
361 ; => (#<STORE-OBJECT ID: 0> #<STORE-OBJECT ID: 1>)
362 (all-store-classes)
363 ; => (STORE-OBJECT)
364
365 ;;; Object deletion also has to be done through the transaction
366 ;;; `DELETE-OBJECT', which will log the deletion of the object in the
367 ;;; transaction log, and remove the object from all its indices.
368
369 (make-object 'store-object)
370 ; executing transaction #<TRANSACTION 21.04.2008 08:02:10 TX-MAKE-OBJECT STORE-OBJECT ID 2> at timestamp 3417746530
371 ; => #<STORE-OBJECT ID: 12>
372 (store-object-with-id 2)
373 ; => #<STORE-OBJECT ID: 2>
374 (delete-object (store-object-with-id 2))
375 ; executing transaction #<TRANSACTION 21.04.2008 08:52:14 TX-DELETE-OBJECT 2> at timestamp 3417749534
376 ; => T
377 (store-object-with-id 2)
378 ; => NIL
379
380 ;;;## Defining persistent classes
381
382 ;;; A more interesting thing is to create our own persistent class,
383 ;;; which we will call `TUTORIAL-OBJECT'.
384
385 (defclass tutorial-object (store-object)
386   ((a :initarg :a :reader tutorial-object-a))
387   (:metaclass persistent-class))
388
389 ;;; We can also use the `DEFINE-PERSISTENT-CLASS' to define the class
390 ;;; `TUTORIAL-OBJECT':
391
392 (define-persistent-class tutorial-object ()
393   ((a :read)))
394
395 ;;; This gets macroexpanded to the following form.
396
397 (DEFINE-BKNR-CLASS TUTORIAL-OBJECT
398                    (STORE-OBJECT)
399                    ((A :READ))
400                    (:METACLASS PERSISTENT-CLASS))
401
402 ;;; The macro DEFINE-BKNR-CLASS is a short form of the macro DEFCLASS,
403 ;;; it expands to the following code. The `EVAL-WHEN' is
404 ;;; there to ensure timely definition of the accessor methods.
405
406 (EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
407   (DEFCLASS TUTORIAL-OBJECT
408             (STORE-OBJECT)
409             ((A :READER TUTORIAL-OBJECT-A :INITARG :A))
410             (:METACLASS PERSISTENT-CLASS)))
411
412 ;;; We can now create a few instance of `TUTORIAL-OBJECT':
413
414 (make-object 'tutorial-object :a 2)
415 ; => #<TUTORIAL-OBJECT ID: 3>
416 (make-object 'tutorial-object :a 2)
417 ; => #<TUTORIAL-OBJECT ID: 4>
418 (make-object 'tutorial-object :a 2)
419 ; => #<TUTORIAL-OBJECT ID: 5>
420
421 (store-object-with-id 5)
422 ; => #<TUTORIAL-OBJECT ID: 5>
423
424 (all-store-classes)
425 ; => (STORE-OBJECT TUTORIAL-OBJECT)
426
427 (store-objects-with-class 'tutorial-object)
428 ; => (#<TUTORIAL-OBJECT ID: 3> #<TUTORIAL-OBJECT ID: 4>
429 ;     #<TUTORIAL-OBJECT ID: 5>)
430 (store-objects-with-class 'store-object)
431 ; => (#<STORE-OBJECT ID: 0> #<STORE-OBJECT ID: 1>
432 ;  #<FOO ID: 2> #<TUTORIAL-OBJECT ID: 3>
433 ;  #<TUTORIAL-OBJECT ID: 4> #<TUTORIAL-OBJECT ID: 5>)
434
435 ;;; In order to change the slot values of persistent object, the
436 ;;; application needs to be in a transaction context.  This can be
437 ;;; done either by invoking a named transaction as above, or by
438 ;;; creating an anonymous transaction.  In an anonymous transaction,
439 ;;; all write accesses to persistent objects are logged.
440
441 (define-persistent-class tutorial-object2 ()
442   ((b :update)))
443
444 (make-object 'tutorial-object2 :b 3)
445 ; executing transaction #<TRANSACTION 21.04.2008 08:03:27 TX-MAKE-OBJECT TUTORIAL-OBJECT2 ID 6 B 3> at timestamp 3417746607
446 ; => #<TUTORIAL-OBJECT2 ID: 6>
447 (setf (slot-value (store-object-with-id 6) 'b) 4)
448 ; => Error
449 ; Attempt to set persistent slot B of #<TUTORIAL-OBJECT2 ID: 6> outside of a transaction
450 (with-transaction ()
451   (setf (slot-value (store-object-with-id 6) 'b) 4))
452 ; => 4
453 (tutorial-object2-b (store-object-with-id 6))
454 ; => 4
455
456 ;;;## Object creation and deletion protocol
457
458 ;;; Persistent objects have the metaclass `PERSISTENT-CLASS', and have
459 ;;; to be created using the function `MAKE-OBJECT'. This creates an
460 ;;; instance of the object inside a transaction, sets its ID slot
461 ;;; appropriately, and then calls `INITIALIZE-PERSISTENT-INSTANCE' and
462 ;;; `INITIALIZE-TRANSIENT-INSTANCE'. The first method is called when
463 ;;; the object is created inside a transaction, but not if the object
464 ;;; is being restored from the snapshot file. This method has to be
465 ;;; overridden in order to initialize persistent
466 ;;; slots. `INITIALIZE-TRANSIENT-INSTANCE' is called at object
467 ;;; creation inside a transaction and at object creation during
468 ;;; restore. It is used to initialize the transient slots (not logged
469 ;;; to the snapshot file) of a persistent object.
470 ;;;
471 ;;; We can define the following class with a transient and a
472 ;;; persistent slot.
473
474 (define-persistent-class protocol-object ()
475   ((a :update :transient t)
476    (b :update)))
477
478 ;;; We can modify the slot `A' outside a transaction:
479 (make-object 'protocol-object :a 1 :b 2)
480 ; executing transaction #<TRANSACTION 21.04.2008 08:10:49 TX-MAKE-OBJECT PROTOCOL-OBJECT ID 7 A 1 B 2> at timestamp 3417747049
481 ; => #<PROTOCOL-OBJECT ID: 7>
482 (setf (protocol-object-a (store-object-with-id 7)) 2)
483 ; => 2
484
485 ;;; However, we cannot modify the slot `B', as it is persistent and
486 ;;; has to be changed inside a transaction.
487
488 (setf (protocol-object-b (store-object-with-id 7)) 4)
489 ; => Error
490 ; Attempt to set persistent slot B of #<PROTOCOL-OBJECT ID: 7> outside of a transaction
491
492 ;;; An object can be removed from the datastore using the transaction
493 ;;; `DELETE-OBJECT', which calls the method `DESTROY-OBJECT' on the
494 ;;; object. Special actions at deletion time have to be added by
495 ;;; overriding `DESTROY-OBJECT'. The basic action is to remove the
496 ;;; object from all its indices.
497
498 ;;;## Snapshotting an object datastore
499
500 ;;; We can snapshot the persistent state of all created objects by
501 ;;; using `SNAPSHOT'.
502 (snapshot)
503 ; Snapshotting subsystem #<STORE-OBJECT-SUBSYSTEM #xE54991E> of #<MP-STORE DIR: "/tmp/object-store/">
504 ; Successfully snapshotted #<STORE-OBJECT-SUBSYSTEM #xE54991E> of #<MP-STORE DIR: "/tmp/object-store/">
505
506 ;;; This will create a backup directory containing the old transaction
507 ;;; log, and the creation of a snapshot file in the "current"
508 ;;; directory.
509
510 (directory "/tmp/object-store/**/*.*")
511 ; => (#P"/tmp/object-store/20080421T061210/random-state"
512 ;     #P"/tmp/object-store/20080421T061210/transaction-log"
513 ;     #P"/tmp/object-store/current/random-state"
514 ;     #P"/tmp/object-store/current/store-object-subsystem-snapshot"
515 ;     #P"/tmp/object-store/current/transaction-log")
516
517 ;;; The snapshot file contains all persistent objects present at
518 ;;; snapshotting time, and the value of their persistent
519 ;;; slots. Further transaction are recorded in a new transaction log.
520
521 ;;;## Adding indices to store objects
522
523 ;;; The object datastore builds upon the functionality of the BKNR
524 ;;; indices system. All store objects are of the metaclass
525 ;;; `INDEXED-CLASS', so adding indices is seamless. Indices are
526 ;;; transient, and are rebuilt every time the datastore is
527 ;;; restored. Adding an index on a transient slot or on a persistent
528 ;;; slot makes no difference.
529
530 (define-persistent-class gorilla ()
531   ((name :read :index-type string-unique-index
532          :index-reader gorilla-with-name
533          :index-values all-gorillas)
534    (mood :read :index-type hash-index
535          :index-reader gorillas-with-mood
536          :index-keys all-gorilla-moods)))
537
538 (make-object 'gorilla :name "lucy" :mood :aggressive)
539 ; => #<GORILLA ID: 8>
540 (make-object 'gorilla :name "john" :mood :playful)
541 ; => #<GORILLA ID: 9>
542 (make-object 'gorilla :name "peter" :mood :playful)
543 ; => #<GORILLA ID: 10>
544 (gorilla-with-name "lucy")
545 ; => #<GORILLA ID: 8>
546 (gorillas-with-mood :playful)
547 ; => (#<GORILLA ID: 10> #<GORILLA ID: 9>)
548
549 ;;;## Adding blobs
550
551 ;;; A blob is a Binary Large OBject, that means it is a normal
552 ;;; persistent object with an associated binary data (that most of the
553 ;;; time is quite large). The object datastore supports storing this
554 ;;; large binary data outside the transaction log and the snapshot
555 ;;; file in order not to strain the store memory footprint too much,
556 ;;; and to be able to access the binary data from outside the LISP
557 ;;; session. This can be useful in order to copy the binary data using
558 ;;; the operating system calls directly. Blobs are used to store
559 ;;; images in the BKNR Web Framework (in fact, eboy.com contains more
560 ;;; than 40000 images). They have also been used to store MP3 files
561 ;;; for the GPN interactive DJ.
562 ;;;
563 ;;; In addition to the binary data, a blob object also holds a `TYPE'
564 ;;; and a `TIMESTAMP'. The type of a blob object is a keyword somehow
565 ;;; identifying the type of binary data it stores. For example, for
566 ;;; the images of the eboy datastore, we used the keywords `:JPEG',
567 ;;; `:PNG', `:GIF' to identity the different file formats used to
568 ;;; store images. The timestamp identifies the time of creation of the
569 ;;; blob object (this can be useful to cache binary data of blob
570 ;;; objects in a web server context).
571 ;;;
572 ;;; Stores are implemented in a custom subsystem, which takes as key
573 ;;; argument `:DIRECTORY' the name of a directory where the binary
574 ;;; data of the blob objects is stored as a simple file. This
575 ;;; directory can be further partitioned dynamically by the datastore,
576 ;;; when provided with the argument `:N-BLOBS-PER-DIRECTORY'. The
577 ;;; value of this argument is stored in the directory of the
578 ;;; datastore, so that a future instance of the blob subsystem is
579 ;;; initialised correctly.
580 ;;;
581 ;;; We can now add blob support to our existing object datastore by
582 ;;; adding the blob subsystem to its list of subsystems.
583
584 (close-store)
585 (make-instance 'mp-store :directory "/tmp/object-store/"
586                :subsystems (list
587                             (make-instance 'store-object-subsystem)
588                             (make-instance 'blob-subsystem)))
589
590 ;;; The blob subsystem provides a few functions and transactions to
591 ;;; work with blobs. To show how to use these functions, we will
592 ;;; define a blob class in our example store. A photo is simply a
593 ;;; binary object with a name.
594
595 (define-persistent-class photo (blob)
596   ((name :read)))
597
598 ;;; A blob can be created using the function `MAKE-BLOB-FROM-FILE',
599 ;;; which is a wrapper around `TX-MAKE-OBJECT' and the function
600 ;;; `BLOB-FROM-FILE'. The method `BLOB-FROM-FILE' fills the binary
601 ;;; data of a blob object by reading the content of a file. This
602 ;;; binary data is then stored in a file named after the ID of the
603 ;;; object in the blob root directory of the blob subsystem.
604
605 (make-blob-from-file "/tmp/bla.jpg" 'photo :name "foobar"
606                      :type :jpg)
607 ; => #<PHOTO ID: 11, TYPE: jpg>
608
609 ;;; We can work with the photo object in the same way as when we work
610 ;;; with a normal object. However, we can access the binary data using
611 ;;; the methods `BLOB-PATHNAME', which returns the pathname to the
612 ;;; file in the blob root that holds the binary data of the
613 ;;; object.
614
615 (blob-pathname (store-object-with-id 11))
616 ; => #P"/tmp/object-store/blob-root/11"
617
618 ;;; The method `BLOB-TO-FILE' and `BLOB-TO-STREAM' write the binary
619 ;;; data of the object to the specified file or stream (the stream has
620 ;;; to be of the type `(UNSIGNED-BYTE 8)'). The macro `WITH-OPEN-BLOB'
621 ;;; is provided as wrapper around the `WITH-OPEN-FILE' macro.
622
623 ;;;## Relaxed references
624
625 ;;; It sometimes happens that a persistent object is deleted while it
626 ;;; still is referenced by another object. This can lead to problems
627 ;;; when snapshotting and restoring the datastore, as the referenced
628 ;;; object is not available anymore.
629 ;;;
630 ;;; When a slot is specified as being a relaxed object reference slot
631 ;;; using the slot option `:RELAXED-OBJECT-REFERENCE', a reference to
632 ;;; an unexistent object can be encoded during snapshot. The object
633 ;;; subsystem issues a warning when a reference to a non-existent
634 ;;; object is encoded. When a reference to a deleted object is decoded
635 ;;; form the snapshot file, a `NIL' value is returned if the slot from
636 ;;; where the object is referenced supports relaxed references. Else,
637 ;;; an error is thrown.
638
639 (define-persistent-class relaxed-object ()
640   ((a :update :relaxed-object-reference t)))
641
642 (make-object 'relaxed-object)
643 ; => #<RELAXED-OBJECT ID: 12>
644 (make-object 'relaxed-object)
645 ; => #<RELAXED-OBJECT ID: 13>
646 (with-transaction ()
647   (setf (slot-value (store-object-with-id 12) 'a) (store-object-with-id 13)))
648 ; => #<RELAXED-OBJECT ID: 13>
649 (delete-object (store-object-with-id 13))
650 ; => T
651 (snapshot)
652 ; Warning: Backup of the datastore in /tmp/object-store/20080421T064811/.
653 ; While executing: (:INTERNAL (SNAPSHOT-STORE (STORE))), in process worker(1750).
654 ; Snapshotting subsystem #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: "/tmp/object-store/">
655 ; Warning: Encoding reference to destroyed object with ID 13 from slot A of object RELAXED-OBJECT with ID 12.
656 ; While executing: #<STANDARD-METHOD ENCODE-OBJECT (STORE-OBJECT T)>, in process worker(1750).
657 ; Successfully snapshotted #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: "/tmp/object-store/">
658 ; Snapshotting subsystem #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: "/tmp/object-store/">
659 ; Successfully snapshotted #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: "/tmp/object-store/">
660 ; => NIL
661 (restore)
662 ; restoring #<MP-STORE DIR: "/tmp/object-store/">
663 ; Restoring the subsystem #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: "/tmp/object-store/">
664 ; loading snapshot file /tmp/object-store/current/store-object-subsystem-snapshot
665 ; Warning: internal inconsistency during restore: can't find store object 13 in loaded store
666 ; While executing: %DECODE-STORE-OBJECT, in process worker(1754).
667 ; Warning: Reference to inexistent object with id 13 from unnamed container, returning NIL.
668 ; While executing: %DECODE-STORE-OBJECT, in process worker(1754).
669 ; Restoring the subsystem #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: "/tmp/object-store/">
670 ; loading transaction log /tmp/object-store/current/transaction-log
671 ; executing transaction #<ANONYMOUS-TRANSACTION 21.04.2008 08:48:48 PREPARE-FOR-SNAPSHOT NIL> at timestamp 3417749328
672 (relaxed-object-a (store-object-with-id 12))
673 ; => NIL
674
675 ;;;# Store internals
676
677 ;;;## Binary data files
678 ;;;
679 ;;; This implementation of the BKNR datastore uses a binary encoding
680 ;;; of Lisp data. The encoding library is used by both the transaction
681 ;;; system and the object system and is mostly independent of
682 ;;; them. Users need not be aware of the details of this encoding,
683 ;;; except that (1) primitive data stored needs to be supported by the
684 ;;; encoding library and (2) user-defined object systems need to
685 ;;; register their own encoder and decoder methods to allow their
686 ;;; objects to be used as part of transaction arguments.
687
688 Function ENCODE (OBJECT STREAM)
689
690 Function DECODE (STREAM) =<  OBJECT
691
692 ;;; The `STREAM' must be specialized on `(unsigned-byte 8)'.
693
694 ;;; The object store subsystem uses the encoding library to encode the
695 ;;; persistent state of all the objects in the store. It does this by
696 ;;; first serializing the layout of a class (which is a list of
697 ;;; slot-names), then by first serializing the class and the id of
698 ;;; each object, and finally by serializing the slots of each
699 ;;; object. This two-step system is necessary to correctly serialize
700 ;;; circular of forward references.
701
702 ;;; When the snapshot is loaded, an empty instance of each object is
703 ;;; created, and can be referenced only using the `ID'. After each
704 ;;; object has been instantiated, it can be referenced by another
705 ;;; object. The objects are serialized in the order they have been
706 ;;; created.
707
708 ;;;## Datastore state
709
710 ;;; The store always is in one of the following states:
711
712 ;;; :closed - No store is open, no persistent operations are
713 ;;; possible.  It is an error, which will eventually be signaled, to
714 ;;; execute transactions in this state.
715
716 ;;; :restore - The store is currently recovering from a restart
717
718 ;;; :idle - The store has been opened and is prepared to execute
719 ;;; transactions.
720
721 ;;; :transaction - The store is currently executing a transaction
722
723 ;;; Note that all transactions are serialized and thus only one
724 ;;; transaction can be active at any time.
725
726 ;;; :snapshot - The store is currently writing a snapshot.
727
728 ;;;## Transactions
729
730 ;;; Transactions are objects of the class `TRANSACTION', and have a
731 ;;; slot containing the symbol of their transaction function, as well
732 ;;; as a list of the arguments that have to be passed to this
733 ;;; function. When a transaction is executed, a timestamp
734 ;;; of the execution time is stored in the object.
735
736 ;;; Transactions can be grouped by enclosing them with a
737 ;;; with-transaction block.  All subtransactions will be logged to the
738 ;;; transaction log as a group and only re-executed on restore time
739 ;;; when the complete group could be read from the file.
740
741 (defun foo-bar ()
742   (with-transaction ()
743     (invoke-transaction-a)
744     (invoke-transaction-b)))
745
746 ;;; The persistent object store further supports the use of anonymous
747 ;;; transactions by automatically converting instance creation and
748 ;;; slot writes to transaction invocations which are atomically
749 ;;; executed upon restore time.
750
751 ;;;## Snapshot and restore procedures
752
753 ;;; When the datastore is snapshotted, the transaction layer ensures
754 ;;; that the store is opened, and that there are subsystems in the
755 ;;; store. Without subsystems, the transaction log is the only way for
756 ;;; the store to achieve persistence, and no snapshot can be made. The
757 ;;; store is then switched to read-only, and a backup directory is
758 ;;; created, containing the current transaction log and previous
759 ;;; snapshot files. This way, the older state of the datastore is not
760 ;;; lost. Then, each subsystem is asked to save its persistent state
761 ;;; by calling the method `SNAPSHOT-SUBSYSTEM'. When an error is
762 ;;; thrown during the snapshot of the subsystems, the backup directory
763 ;;; is renamed to be the current directory, and the store is
764 ;;; reopened.
765
766 ;;; When the datastore is restored, the store is switched to read
767 ;;; only, and each subsystem is asked to restore its persistent
768 ;;; state. Note that the subsystems are restored in the order in which
769 ;;; they are listed in the `SUBSYSTEMS' slot of the store, so that
770 ;;; dependent subsystems are restored last. When an error is thrown
771 ;;; while restoring the subsystems, the store is closed, and already
772 ;;; opened subsystems are closed using the method
773 ;;; `CLOSE-SUBSYSTEM'. After the restoring of all the subsystems, the
774 ;;; transaction log file is read, and each transaction recorded is
775 ;;; executed. This is where the `UNTIL' parameter comes into
776 ;;; play. Transactions that have been executed after the time of
777 ;;; `UNTIL' are discarded.
778
779 ;;;## Filesystem syncing
780
781 ;;; By default, the transaction log file is synced after a transaction
782 ;;; has been executed, so that all the data is correctly written on
783 ;;; disk. However, this can be a major performance stopper when
784 ;;; executing a big batch of transactions (for example, deleting a few
785 ;;; thousands objects). You can disable the mandatory syncing by
786 ;;; executing your transactions inside the form `WITHOUT-SYNC'.
787
788 (without-sync ()
789    (execute-a-lot-of-transactions))
790
791 ;;;## Snapshotting and restoring the object subsystem
792
793 ;;; Snapshotting and restoring the object subsystem is a bit tricky,
794 ;;; as additional systems come into play. When the object subsystem is
795 ;;; snapshotted using the method `SNAPSHOT-SUBSYSTEM', a snapshot file
796 ;;; containing a binary dump of all the current store objects is
797 ;;; created. First, the layouts of the objects (the name of their
798 ;;; slots) is stored, and minimal information about each object is
799 ;;; stored in the order of their creation (the minimal information
800 ;;; consists of the class of the object, and its ID). After this
801 ;;; information has been stored for each object, the slot values of
802 ;;; the store objects (again in the order of their creation) are
803 ;;; stored in the snapshot file. These two phases are necessary to
804 ;;; allow the snapshotting of circular or forward-referencing
805 ;;; structures.
806
807 ;;; When the object subsystem is restored, all the indices for classes
808 ;;; contained in the store are cleared in order to accomodate for the
809 ;;; new objects. Be very careful when using class indices that are not
810 ;;; related to store objects. The ID counter of the store subsystem is
811 ;;; reset to 0, and the class-layouts are read from the snapshot
812 ;;; file. Then, the minimal information for each object is read, and
813 ;;; an "empty version" of each object is instantiated. Thus, the
814 ;;; objects can be referenced by their ID. Then, the slot values for
815 ;;; each object are read from the snapshot file, references are
816 ;;; resolved (check the section about relaxed references). Finally,
817 ;;; after each slot value has been set, the method
818 ;;; `INITIALIZE-TRANSIENT-INSTANCE' is called for each created
819 ;;; object. The method `INITIALIZE-PERSISTENT-INSTANCE' is not called,
820 ;;; as it has to be executed only once at the time the persistent
821 ;;; object is created.
822
823 ;;;## Garbage collecting blobs
824
825 ;;; The binary data of deleted blob objects is kept in the blob root
826 ;;; directory by default. If you want to purge the binary data of
827 ;;; deleted objects, you can use the function
828 ;;; `DELETE-ORPHANED-BLOB-FILES'. However, take note that you won't be
829 ;;; able to restore the persistent state anteriour to the deletion of
830 ;;; the blobs, as their binary data is not stored in the transaction
831 ;;; log and not backed up by the snapshot method of the blob
832 ;;; subsystem.
833
834 ;;;## Schema evolution in the datastore
835
836 ;;; The transaction log only stores when a transaction is called, and
837 ;;; with which arguments. However, it doesn't store the definition of
838 ;;; the transaction itself. When the transaction definition is
839 ;;; changed, the transaction log may be restored in a different way,
840 ;;; according to the changes made in the code.
841 ;;;
842 ;;; In the same way, class definition changes are not recorded in the
843 ;;; transaction log. When a class definition is changed (for example a
844 ;;; slot initform is changed), the existing instances of the class are
845 ;;; updated accordingly. However, when the snapshot is restored in a
846 ;;; future session, the objects may be different than those created at
847 ;;; the last restore.
848 ;;;
849 ;;; The only way to cleanly upgrade transaction definitions and class
850 ;;; definitions is to make a snapshot after the changes have been
851 ;;; made. In a future version of the datastore, we hope to store all
852 ;;; the application sourcecode, so that a restore to a certain point
853 ;;; in time does not depend on the latest version of the code. The
854 ;;; object subsystem warns when a class definition is changed, and
855 ;;; urges the developer to make a snapshot of the database. Please be
856 ;;; careful, this can be a pretty tricky source of bugs.
Note: See TracBrowser for help on using the browser.