Skip to content

Commit

Permalink
+per #3746 Remote sharing of LevelDB for testing purposes
Browse files Browse the repository at this point in the history
Further changes

- remove obsolete identity checks in Eventsourced
- fix wrong serialize-messages config in tests
  • Loading branch information
krasserm committed Dec 3, 2013
1 parent 4d05253 commit d0bc8a6
Show file tree
Hide file tree
Showing 20 changed files with 648 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,52 @@
package docs.persistence;

//#plugin-imports
import akka.actor.UntypedActor;
import scala.concurrent.Future;
import akka.japi.Option;
import akka.japi.Procedure;
import akka.persistence.*;
import akka.persistence.journal.japi.*;
import akka.persistence.snapshot.japi.*;
//#plugin-imports
import akka.actor.*;
import akka.persistence.journal.leveldb.SharedLeveldbJournal;
import akka.persistence.journal.leveldb.SharedLeveldbStore;

public class PersistencePluginDocTest {


static Object o1 = new Object() {
final ActorSystem system = null;
//#shared-store-creation
final ActorRef store = system.actorOf(Props.create(SharedLeveldbStore.class), "store");
//#shared-store-creation

//#shared-store-usage
class SharedStorageUsage extends UntypedActor {
@Override
public void preStart() throws Exception {
String path = "akka.tcp://[email protected]:2552/user/store";
ActorSelection selection = getContext().actorSelection(path);
selection.tell(new Identify(1), getSelf());
}

@Override
public void onReceive(Object message) throws Exception {
if (message instanceof ActorIdentity) {
ActorIdentity identity = (ActorIdentity) message;
if (identity.correlationId().equals(1)) {
ActorRef store = identity.getRef();
if (store != null) {
SharedLeveldbJournal.setStore(store, getContext().system());
}
}
}
}
}
//#shared-store-usage
};

class MySnapshotStore extends SnapshotStore {
@Override
public Future<Option<SelectedSnapshot>> doLoadAsync(String processorId, SnapshotSelectionCriteria criteria) {
Expand Down
83 changes: 66 additions & 17 deletions akka-docs/rst/java/persistence.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,6 @@ Architecture
* *Event sourcing*. Based on the building blocks described above, Akka persistence provides abstractions for the
development of event sourced applications (see section :ref:`event-sourcing-java`)

Configuration
=============

By default, journaled messages are written to a directory named ``journal`` in the current working directory. This
can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#journal-config

The default storage location of :ref:`snapshots-java` is a directory named ``snapshots`` in the current working directory.
This can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#snapshot-config

.. _processors-java:

Processors
Expand Down Expand Up @@ -407,10 +394,11 @@ will therefore never be done partially i.e. with only a subset of events persist
Storage plugins
===============

Storage backends for journals and snapshot stores are plugins in akka-persistence. The default journal plugin writes
messages to LevelDB. The default snapshot store plugin writes snapshots as individual files to the local filesystem.
Applications can provide their own plugins by implementing a plugin API and activate them by configuration. Plugin
development requires the following imports:
Storage backends for journals and snapshot stores are plugins in akka-persistence. The default journal plugin
writes messages to LevelDB (see :ref:`local-leveldb-journal-java`). The default snapshot store plugin writes snapshots
as individual files to the local filesystem (see :ref:`local-snapshot-store-java`). Applications can provide their own
plugins by implementing a plugin API and activate them by configuration. Plugin development requires the following
imports:

.. includecode:: code/docs/persistence/PersistencePluginDocTest.java#plugin-imports

Expand Down Expand Up @@ -454,6 +442,67 @@ A snapshot store plugin can be activated with the following minimal configuratio
The specified plugin ``class`` must have a no-arg constructor. The ``plugin-dispatcher`` is the dispatcher
used for the plugin actor. If not specified, it defaults to ``akka.persistence.dispatchers.default-plugin-dispatcher``.

Pre-packaged plugins
====================

.. _local-leveldb-journal-java:

Local LevelDB journal
---------------------

The default journal plugin is ``akka.persistence.journal.leveldb`` which writes messages to a local LevelDB
instance. The default location of the LevelDB files is a directory named ``journal`` in the current working
directory. This location can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#journal-config

With this plugin, each actor system runs its own private LevelDB instance.

Shared LevelDB journal
----------------------

A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
example, allows processors to failover to a backup node, assuming that the node, where the shared instance is
runnning, is accessible from the backup node.

.. warning::

A shared LevelDB instance is a single point of failure and should therefore only be used for testing
purposes.

A shared LevelDB instance can be created by instantiating the ``SharedLeveldbStore`` actor.

.. includecode:: code/docs/persistence/PersistencePluginDocTest.java#shared-store-creation

By default, the shared instance writes journaled messages to a local directory named ``journal`` in the current
working directory. The storage location can be changed by configuration:

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-config

Actor systems that use a shared LevelDB store must activate the ``akka.persistence.journal.leveldb-shared``
plugin.

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#shared-journal-config

This plugin must be initialized by injecting the (remote) ``SharedLeveldbStore`` actor reference. Injection is
done by calling the ``SharedLeveldbJournal.setStore`` method with the actor reference as argument.

.. includecode:: code/docs/persistence/PersistencePluginDocTest.java#shared-store-usage

Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
i.e. only the first injection is used.

.. _local-snapshot-store-java:

Local snapshot store
--------------------

The default snapshot store plugin is ``akka.persistence.snapshot-store.local`` which writes snapshot files to
the local filesystem. The default storage location is a directory named ``snapshots`` in the current working
directory. This can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#snapshot-config

Custom serialization
====================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,47 @@ class PersistencePluginDocSpec extends WordSpec {
}
}

object SharedLeveldbPluginDocSpec {
import akka.actor._
import akka.persistence.journal.leveldb.SharedLeveldbJournal

val config =
"""
//#shared-journal-config
akka.persistence.journal.plugin = "akka.persistence.journal.leveldb-shared"
//#shared-journal-config
//#shared-store-config
akka.persistence.journal.leveldb-shared.store.dir = "target/shared"
//#shared-store-config
"""

//#shared-store-usage
trait SharedStoreUsage extends Actor {
override def preStart(): Unit = {
context.actorSelection("akka.tcp://[email protected]:2552/user/store") ! Identify(1)
}

def receive = {
case ActorIdentity(1, Some(store))
SharedLeveldbJournal.setStore(store, context.system)
}
}
//#shared-store-usage
}

trait SharedLeveldbPluginDocSpec {
val system: ActorSystem

new AnyRef {
import akka.actor._
//#shared-store-creation
import akka.persistence.journal.leveldb.SharedLeveldbStore

val store = system.actorOf(Props[SharedLeveldbStore], "store")
//#shared-store-creation
}
}

class MyJournal extends AsyncWriteJournal {
def writeAsync(persistentBatch: Seq[PersistentRepr]): Future[Unit] = ???
def deleteAsync(processorId: String, fromSequenceNr: Long, toSequenceNr: Long, permanent: Boolean): Future[Unit] = ???
Expand Down
90 changes: 73 additions & 17 deletions akka-docs/rst/scala/persistence.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,6 @@ Architecture
* *Event sourcing*. Based on the building blocks described above, Akka persistence provides abstractions for the
development of event sourced applications (see section :ref:`event-sourcing`)

Configuration
=============

By default, journaled messages are written to a directory named ``journal`` in the current working directory. This
can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#journal-config

The default storage location of :ref:`snapshots` is a directory named ``snapshots`` in the current working directory.
This can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#snapshot-config

.. _processors:

Processors
Expand Down Expand Up @@ -418,10 +405,11 @@ will therefore never be done partially i.e. with only a subset of events persist
Storage plugins
===============

Storage backends for journals and snapshot stores are plugins in akka-persistence. The default journal plugin writes
messages to LevelDB. The default snapshot store plugin writes snapshots as individual files to the local filesystem.
Applications can provide their own plugins by implementing a plugin API and activate them by configuration. Plugin
development requires the following imports:
Storage backends for journals and snapshot stores are plugins in akka-persistence. The default journal plugin
writes messages to LevelDB (see :ref:`local-leveldb-journal`). The default snapshot store plugin writes snapshots
as individual files to the local filesystem (see :ref:`local-snapshot-store`). Applications can provide their own
plugins by implementing a plugin API and activate them by configuration. Plugin development requires the following
imports:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#plugin-imports

Expand Down Expand Up @@ -465,6 +453,74 @@ A snapshot store plugin can be activated with the following minimal configuratio
The specified plugin ``class`` must have a no-arg constructor. The ``plugin-dispatcher`` is the dispatcher
used for the plugin actor. If not specified, it defaults to ``akka.persistence.dispatchers.default-plugin-dispatcher``.

Pre-packaged plugins
====================

.. _local-leveldb-journal:

Local LevelDB journal
---------------------

The default journal plugin is ``akka.persistence.journal.leveldb`` which writes messages to a local LevelDB
instance. The default location of the LevelDB files is a directory named ``journal`` in the current working
directory. This location can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#journal-config

With this plugin, each actor system runs its own private LevelDB instance.

Shared LevelDB journal
----------------------

A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
example, allows processors to failover to a backup node, assuming that the node, where the shared instance is
runnning, is accessible from the backup node.

.. warning::

A shared LevelDB instance is a single point of failure and should therefore only be used for testing
purposes.

A shared LevelDB instance can be created by instantiating the ``SharedLeveldbStore`` actor.

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-creation

By default, the shared instance writes journaled messages to a local directory named ``journal`` in the current
working directory. The storage location can be changed by configuration:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-config

Actor systems that use a shared LevelDB store must activate the ``akka.persistence.journal.leveldb-shared``
plugin.

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-journal-config

This plugin must be initialized by injecting the (remote) ``SharedLeveldbStore`` actor reference. Injection is
done by calling the ``SharedLeveldbJournal.setStore`` method with the actor reference as argument.

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-usage

Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
i.e. only the first injection is used.

.. _local-snapshot-store:

Local snapshot store
--------------------

The default snapshot store plugin is ``akka.persistence.snapshot-store.local`` which writes snapshot files to
the local filesystem. The default storage location is a directory named ``snapshots`` in the current working
directory. This can be changed by configuration where the specified path can be relative or absolute:

.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#snapshot-config

Planned plugins
---------------

* Shared snapshot store (SPOF, for testing purposes)
* HA snapshot store backed by a distributed file system
* HA journal backed by a distributed (NoSQL) data store

Custom serialization
====================

Expand Down
31 changes: 31 additions & 0 deletions akka-persistence/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,37 @@ akka {
# Native LevelDB (via JNI) or LevelDB Java port
native = on
}

# Shared LevelDB journal plugin (for testing only).
leveldb-shared {

# Class name of the plugin.
class = "akka.persistence.journal.leveldb.SharedLeveldbJournal"

# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.actor.default-dispatcher"

store {

# Dispatcher for shared store actor.
store-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"

# Dispatcher for message replay.
replay-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"

# Storage location of LevelDB files.
dir = "journal"

# Use fsync on write
fsync = off

# Verify checksum on read.
checksum = off

# Native LevelDB (via JNI) or LevelDB Java port
native = on
}
}
}

snapshot-store {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ private[persistence] trait Eventsourced extends Processor {
case p: PersistentRepr
deleteMessage(p.sequenceNr, true)
throw new UnsupportedOperationException("Persistent commands not supported")
case WriteSuccess(p) if identical(p.payload, persistInvocations.head._1)
case WriteSuccess(p)
withCurrentPersistent(p)(p persistInvocations.head._2(p.payload))
onWriteComplete()
case e @ WriteFailure(p, _) if identical(p.payload, persistInvocations.head._1)
case e @ WriteFailure(p, _)
Eventsourced.super.aroundReceive(receive, message) // stops actor by default
onWriteComplete()
case s @ WriteBatchSuccess Eventsourced.super.aroundReceive(receive, s)
Expand All @@ -93,9 +93,6 @@ private[persistence] trait Eventsourced extends Processor {
processorStash.unstash()
}
}

def identical(a: Any, b: Any): Boolean =
a.asInstanceOf[AnyRef] eq b.asInstanceOf[AnyRef]
}

private var persistInvocations: List[(Any, Any Unit)] = Nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import scala.collection.immutable
import akka.actor._

/**
* INTERNAL API.
*
* Defines messages exchanged between processors, channels and a journal.
*/
private[persistence] object JournalProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ private[persistence] case class ConfirmablePersistentImpl(
copy(sequenceNr = sequenceNr, processorId = processorId, deleted = deleted, resolved = resolved, confirms = confirms, confirmMessage = confirmMessage, confirmTarget = confirmTarget, sender = sender)
}

/**
* INTERNAL API.
*/
private[persistence] object ConfirmablePersistentImpl {
def apply(persistent: PersistentRepr, confirmMessage: Confirm, confirmTarget: ActorRef): ConfirmablePersistentImpl =
ConfirmablePersistentImpl(persistent.payload, persistent.sequenceNr, persistent.processorId, persistent.deleted, persistent.resolved, persistent.confirms, confirmMessage, confirmTarget, persistent.sender)
Expand Down
Loading

0 comments on commit d0bc8a6

Please sign in to comment.