From 9868a18361a682730c292780a1938d5151ade42c Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 14 May 2018 16:57:58 +0200 Subject: [PATCH 1/2] Docs: improve appearance of the Kafka design doc. --- docs/source/design/hadr/design.md | 2 + .../kafka-notary/decisions/index-storage.md | 50 +++++++ .../decisions/replicated-storage.md | 57 ++++++-- .../{kafkaNotary => kafka-notary}/design.md | 129 ++++++++++-------- .../images/high-level.svg | 0 .../images/kafka-high-level.svg | 0 .../images/steps.svg | 0 .../images/store-comparison.png | Bin .../kafkaNotary/decisions/index-storage.md | 41 ------ docs/source/index.rst | 1 + 10 files changed, 166 insertions(+), 114 deletions(-) create mode 100644 docs/source/design/kafka-notary/decisions/index-storage.md rename docs/source/design/{kafkaNotary => kafka-notary}/decisions/replicated-storage.md (53%) rename docs/source/design/{kafkaNotary => kafka-notary}/design.md (72%) rename docs/source/design/{kafkaNotary => kafka-notary}/images/high-level.svg (100%) rename docs/source/design/{kafkaNotary => kafka-notary}/images/kafka-high-level.svg (100%) rename docs/source/design/{kafkaNotary => kafka-notary}/images/steps.svg (100%) rename docs/source/design/{kafkaNotary => kafka-notary}/images/store-comparison.png (100%) delete mode 100644 docs/source/design/kafkaNotary/decisions/index-storage.md diff --git a/docs/source/design/hadr/design.md b/docs/source/design/hadr/design.md index c6032a0a63..47cbffb1a0 100644 --- a/docs/source/design/hadr/design.md +++ b/docs/source/design/hadr/design.md @@ -1,5 +1,7 @@ # High availability support +.. important:: This design document describes a feature of Corda Enterprise. + ## Overview ### Background diff --git a/docs/source/design/kafka-notary/decisions/index-storage.md b/docs/source/design/kafka-notary/decisions/index-storage.md new file mode 100644 index 0000000000..008025aeae --- /dev/null +++ b/docs/source/design/kafka-notary/decisions/index-storage.md @@ -0,0 +1,50 @@ +# Design Decision: Storage engine for committed state index + +## Background / Context + +The storage engine for the committed state index needs to support a single operation: "insert all values with unique +keys, or abort if any key conflict found". A wide range of solutions could be used for that, from embedded key-value +stores to full-fledged relational databases. However, since we don't need any extra features a RDBMS provides over a +simple key-value store, we'll only consider lightweight embedded solutions to avoid extra operational costs. + +Most RDBMSs are also generally optimised for read performance (use B-tree based storage engines like InnoDB, MyISAM). +Our workload is write-heavy and uses "random" primary keys (state references), which leads to particularly poor write +performance for those types of engines – as we have seen with our Galera-based notary service. One exception is the +MyRocks storage engine, which is based on RocksDB and can handle write workloads well, and is supported by Percona +Server, and MariaDB. It is easier, however, to just use RocksDB directly. + +## Options Analysis + +### A. RocksDB + +An embedded key-value store based on log-structured merge-trees (LSM). It's highly configurable, provides lots of +configuration options for performance tuning. E.g. can be tuned to run on different hardware – flash, hard disks or +entirely in-memory. + +### B. LMDB + +An embedded key-value store using B+ trees, has ACID semantics and support for transactions. + +### C. MapDB + +An embedded Java database engine, providing persistent collection implementations. Uses memory mapped files. Simple to +use, implements Java collection interfaces. Provides a HashMap implementation that we can use for storing committed +states. + +### D. MVStore + +An embedded log structured key-value store. Provides a simple persistent map abstraction. Supports multiple map +implementations (B-tree, R-tree, concurrent B-tree). + +## Recommendation and justification + +Performance test results when running on a Macbook Pro with Intel Core i7-4980HQ CPU @ 2.80GHz, 16 GB RAM, SSD: + +![Comparison](../images/store-comparison.png) + +Multiple tests were run with varying number of transactions and input states per transaction: "1m x 1" denotes a million +transactions with one input state. + +Proceed with Option A, as RocksDB provides most tuning options and achieves by far the best write performance. + +Note that the index storage engine can be replaced in the future with minimal changes required on the notary service. \ No newline at end of file diff --git a/docs/source/design/kafkaNotary/decisions/replicated-storage.md b/docs/source/design/kafka-notary/decisions/replicated-storage.md similarity index 53% rename from docs/source/design/kafkaNotary/decisions/replicated-storage.md rename to docs/source/design/kafka-notary/decisions/replicated-storage.md index 6c5a0604e4..b0fd0f24b7 100644 --- a/docs/source/design/kafkaNotary/decisions/replicated-storage.md +++ b/docs/source/design/kafka-notary/decisions/replicated-storage.md @@ -1,12 +1,10 @@ -![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png) - --------------------------------------------- -Design Decision: Replication framework -================================ +# Design Decision: Replication framework ## Background / Context -Multiple libraries/platforms exist for implementing fault-tolerant systems. In existing CFT notary implementations we experimented with using a traditional relational database with active replication, as well as a pure state machine replication approach based on CFT consensus algorithms. +Multiple libraries/platforms exist for implementing fault-tolerant systems. In existing CFT notary implementations we +experimented with using a traditional relational database with active replication, as well as a pure state machine +replication approach based on CFT consensus algorithms. ## Options Analysis @@ -14,7 +12,12 @@ Multiple libraries/platforms exist for implementing fault-tolerant systems. In e *Raft-based fault-tolerant distributed coordination framework.* -Our first CFT notary notary implementation was based on Atomix. Atomix can be easily embedded into a Corda node and provides abstractions for implementing custom replicated state machines. In our case the state machine manages committed Corda contract states. When notarisation requests are sent to Atomix, they get forwarded to the leader node. The leader persists the request to a log, and replicates it to all followers. Once the majority of followers acknowledge receipt, it applies the request to the user-defined state machine. In our case we commit all input states in the request to a JDBC-backed map, or return an error if conflicts occur. +Our first CFT notary notary implementation was based on Atomix. Atomix can be easily embedded into a Corda node and +provides abstractions for implementing custom replicated state machines. In our case the state machine manages committed +Corda contract states. When notarisation requests are sent to Atomix, they get forwarded to the leader node. The leader +persists the request to a log, and replicates it to all followers. Once the majority of followers acknowledge receipt, +it applies the request to the user-defined state machine. In our case we commit all input states in the request to a +JDBC-backed map, or return an error if conflicts occur. #### Advantages @@ -32,7 +35,8 @@ Our first CFT notary notary implementation was based on Atomix. Atomix can be ea *Java persistence layer with a built-in Raft-based replicated key-value store.* -Conceptually similar to Atomix, but persists the state machine instead of the request log. Built around an abstract persistent key-value store: requests get cleaned up after replication and processing. +Conceptually similar to Atomix, but persists the state machine instead of the request log. Built around an abstract +persistent key-value store: requests get cleaned up after replication and processing. #### Advantages @@ -52,7 +56,12 @@ Conceptually similar to Atomix, but persists the state machine instead of the re *Paxos-based distributed streaming platform.* -Atomix and Permazen implement both the replicated request log and the state machine, but Kafka only provides the log component. In theory that means more complexity having to implement request log processing and state machine management, but for our use case it's fairly straightforward: consume requests and insert input states into a database, marking the position of the last processed request. If the database is lost, we can just replay the log from the beginning. The main benefit of this approach is that it gives a more granular control and performance tuning opportunities in different parts of the system. +Atomix and Permazen implement both the replicated request log and the state machine, but Kafka only provides the log +component. In theory that means more complexity having to implement request log processing and state machine management, +but for our use case it's fairly straightforward: consume requests and insert input states into a database, marking the +position of the last processed request. If the database is lost, we can just replay the log from the beginning. The main +benefit of this approach is that it gives a more granular control and performance tuning opportunities in different +parts of the system. #### Advantages @@ -67,11 +76,16 @@ Atomix and Permazen implement both the replicated request log and the state mach ### D. Custom Raft-based implementation -For even more granular control, we could replace Kafka with our own replicated log implementation. Kafka was started before the Raft consensus algorithm was introduced, and is using Zookeeper for coordination, which is based on Paxos for consensus. Paxos is known to be complex to understand and implement, and the main driver behind Raft was to create a much simpler algorithm with equivalent functionality. Hence, while reimplementing Zookeeper would be an onerous task, building a Raft-based alternative from scratch is somewhat feasible. +For even more granular control, we could replace Kafka with our own replicated log implementation. Kafka was started +before the Raft consensus algorithm was introduced, and is using Zookeeper for coordination, which is based on Paxos for +consensus. Paxos is known to be complex to understand and implement, and the main driver behind Raft was to create a +much simpler algorithm with equivalent functionality. Hence, while reimplementing Zookeeper would be an onerous task, +building a Raft-based alternative from scratch is somewhat feasible. #### Advantages -Most of the implementations above have many extra features our use-case does not require. We can implement a relatively simple clean optimised solution that will most likely outperform others (Thomas Schroeter already built a prototype). +Most of the implementations above have many extra features our use-case does not require. We can implement a relatively +simple clean optimised solution that will most likely outperform others (Thomas Schroeter already built a prototype). #### Disadvantages @@ -81,9 +95,17 @@ Large effort required to make it highly performant and reliable. *Synchronous replication plugin for MySQL, uses certification-based replication.* -All of the options discussed so far were based on abstract state machine replication. Another approach is simply using a more traditional RDBMS with active replication support. Note that most relational databases support some form replication in general, however, very few provide strong consistency guarantees and ensure no data loss. Galera is a plugin for MySQL enabling synchronous multi-master replication. +All of the options discussed so far were based on abstract state machine replication. Another approach is simply using a +more traditional RDBMS with active replication support. Note that most relational databases support some form +replication in general, however, very few provide strong consistency guarantees and ensure no data loss. Galera is a +plugin for MySQL enabling synchronous multi-master replication. -Galera uses certification-based replication, which operates on write-sets: a database server executes the (database) transaction, and only performs replication if the transaction requires write operations. If it does, the transaction is broadcasted to all other servers (using atomic broadcast). On delivery, each server executes a deterministic certification phase, which decides if the transaction can commit or must abort. If a conflict occurs, the entire cluster rolls back the transaction. This type of technique is quite efficient in low-conflict situations and allows read scaling (the latter is mostly irrelevant for our use case). +Galera uses certification-based replication, which operates on write-sets: a database server executes the (database) +transaction, and only performs replication if the transaction requires write operations. If it does, the transaction is +broadcasted to all other servers (using atomic broadcast). On delivery, each server executes a deterministic +certification phase, which decides if the transaction can commit or must abort. If a conflict occurs, the entire cluster +rolls back the transaction. This type of technique is quite efficient in low-conflict situations and allows read scaling +(the latter is mostly irrelevant for our use case). #### Advantages @@ -100,7 +122,11 @@ Galera uses certification-based replication, which operates on write-sets: a dat *Distributed SQL database built on a transactional and strongly-consistent key-value store. Uses Raft-based replication.* -On paper, CockroachDB looks like a great candidate, but it relies on sharding: data is automatically split into partitions, and each partition is replicated using Raft. It performs great for single-shard database transactions, and also natively supports cross-shard atomic commits. However, the majority of Corda transactions are likely to have more than one input state, which means that most transaction commits will require cross-shard database transactions. In our tests we were only able to achieve up to 30 TPS in a 3 DC deployment. +On paper, CockroachDB looks like a great candidate, but it relies on sharding: data is automatically split into +partitions, and each partition is replicated using Raft. It performs great for single-shard database transactions, and +also natively supports cross-shard atomic commits. However, the majority of Corda transactions are likely to have more +than one input state, which means that most transaction commits will require cross-shard database transactions. In our +tests we were only able to achieve up to 30 TPS in a 3 DC deployment. #### Advantages @@ -114,4 +140,5 @@ On paper, CockroachDB looks like a great candidate, but it relies on sharding: d ## Recommendation and justification -Proceed with Option C. A Kafka-based solution strikes the best balance between performance and the required effort to build a production-ready solution. \ No newline at end of file +Proceed with Option C. A Kafka-based solution strikes the best balance between performance and the required effort to +build a production-ready solution. \ No newline at end of file diff --git a/docs/source/design/kafkaNotary/design.md b/docs/source/design/kafka-notary/design.md similarity index 72% rename from docs/source/design/kafkaNotary/design.md rename to docs/source/design/kafka-notary/design.md index 46f40d5da8..e200b4b018 100644 --- a/docs/source/design/kafkaNotary/design.md +++ b/docs/source/design/kafka-notary/design.md @@ -1,46 +1,18 @@ -![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png) - # High Performance CFT Notary Service -DOCUMENT MANAGEMENT ---- - -## Document Control - -| Title | High Performance CFT Notary Service | -| -------------------- | ------------------------------------------------------------ | -| Date | 27 March 2018 | -| Author | Andrius Dagys, Thomas Schroeter | -| Distribution | Design Review Board, Product Management, Services - Technical (Consulting), Platform Delivery | -| Corda target version | Enterprise | -| JIRA reference | https://r3-cev.atlassian.net/browse/CID-294 | - -## Approvals - -#### Document Sign-off - -| Author | Andrius Dagys | -| ----------------- | -------------------------------------------------- | -| Reviewer(s) | (GitHub PR reviewers) | -| Final approver(s) | (GitHub PR approver(s) from Design Approval Board) | - -#### Design Decisions - -| Description | Recommendation | Approval | -| ---------------------------------------- | --------------- | ----------------------- | -| [Replication framework](decisions/replicated-storage.md) | Option C | (Design Approval Board) | -| [Index storage engine](decisions/index-storage.md) | Option A |(Design Approval Board) | - -HIGH LEVEL DESIGN ---- +.. important:: This design document describes a feature of Corda Enterprise. ## Overview -This proposal describes the architecture and an implementation for a high performance crash fault-tolerant notary service, operated by a single party. +This proposal describes the architecture and an implementation for a high performance crash fault-tolerant notary +service, operated by a single party. ## Background -For initial deployments, we expect to operate a single non-validating CFT notary service. The current Raft and Galera implementations cannot handle more than 100-200 TPS, which is likely to be a serious bottleneck in the near future. To support our clients and compete with other platforms we need a notary service that can handle TPS in the order of 1,000s. +For initial deployments, we expect to operate a single non-validating CFT notary service. The current Raft and Galera +implementations cannot handle more than 100-200 TPS, which is likely to be a serious bottleneck in the near future. To +support our clients and compete with other platforms we need a notary service that can handle TPS in the order of +1,000s. ## Scope @@ -69,28 +41,59 @@ The notary service should be able to: - Tolerate single datacenter failure. - Tolerate single disk failure/corruption. -## Target Solution - -Having explored different solutions for implementing notaries we propose the following architecture for a CFT notary, consisting of two components: - -1. A central replicated request log, which orders and stores all notarisation requests. Efficient append-only log storage can be used along with batched replication, making performance mainly dependent on network throughput. -2. Worker nodes that service clients and maintain a consumed state index. The state index is a simple key-value store containing committed state references and pointers to the corresponding request positions in the log. If lost, it can be reconstructed by replaying and applying request log entries. There is a range of fast key-value stores that can be used for implementation. - -![High level architecture](./images/high-level.svg) - -At high level, client notarisation requests first get forwarded to a central replicated request log. The requests are then applied in order to the consumed state index in each worker to verify input state uniqueness. Each individual request outcome (success/conflict) is then sent back to the initiating client by the worker responsible for it. To emphasise, each worker will process _all_ notarisation requests, but only respond to the ones it received directly. - -Messages (requests) in the request log are persisted and retained forever. The state index has a relatively low footprint and can in theory be kept entirely in memory. However, when a worker crashes, replaying the log to recover the index may take too long depending on the SLAs. Additionally, we expect applying the requests to the index to be much faster than consuming request batches even with persistence enabled. - -_Technically_, the request log can also be kept entirely in memory, and the cluster will still be able to tolerate up to $f < n/2$ node failures. However, if for some reason the entire cluster is shut down (e.g. administrator error), all requests will be forever lost! Therefore, we should avoid it. - -The request log does not need to be a separate cluster, and the worker nodes _could_ maintain the request log replicas locally. This would allow workers to consume ordered requests from the local copy rather than from a leader node across the network. It is hard to say, however, if this would have a significant performance impact without performing tests in the specific network environment (e.g. the bottleneck could be the replication step). - -One advantage of hosting the request log in a separate cluster is that it makes it easier to independently scale the number of worker nodes. If, for example, if transaction validation and resolution is required when receiving a notarisation request, we might find that a significant number of receivers is required to generate enough incoming traffic to the request log. On the flipside, increasing the number of workers adds additional consumers and load on the request log, so a balance needs to be found. ## Design Decisions -As the design decision documents below discuss, the most suitable platform for managing the request log was chosen to be [Apache Kafka](https://kafka.apache.org/), and [RocksDB](http://rocksdb.org/) as the storage engine for the committed state index. +.. toctree:: + :maxdepth: 2 + + decisions/replicated-storage.md + decisions/index-storage.md + +## Target Solution + +Having explored different solutions for implementing notaries we propose the following architecture for a CFT notary, +consisting of two components: + +1. A central replicated request log, which orders and stores all notarisation requests. Efficient append-only log + storage can be used along with batched replication, making performance mainly dependent on network throughput. +2. Worker nodes that service clients and maintain a consumed state index. The state index is a simple key-value store + containing committed state references and pointers to the corresponding request positions in the log. If lost, it can be + reconstructed by replaying and applying request log entries. There is a range of fast key-value stores that can be used + for implementation. + +![High level architecture](./images/high-level.svg) + +At high level, client notarisation requests first get forwarded to a central replicated request log. The requests are +then applied in order to the consumed state index in each worker to verify input state uniqueness. Each individual +request outcome (success/conflict) is then sent back to the initiating client by the worker responsible for it. To +emphasise, each worker will process _all_ notarisation requests, but only respond to the ones it received directly. + +Messages (requests) in the request log are persisted and retained forever. The state index has a relatively low +footprint and can in theory be kept entirely in memory. However, when a worker crashes, replaying the log to recover the +index may take too long depending on the SLAs. Additionally, we expect applying the requests to the index to be much +faster than consuming request batches even with persistence enabled. + +_Technically_, the request log can also be kept entirely in memory, and the cluster will still be able to tolerate up to +$f < n/2$ node failures. However, if for some reason the entire cluster is shut down (e.g. administrator error), all +requests will be forever lost! Therefore, we should avoid it. + +The request log does not need to be a separate cluster, and the worker nodes _could_ maintain the request log replicas +locally. This would allow workers to consume ordered requests from the local copy rather than from a leader node across +the network. It is hard to say, however, if this would have a significant performance impact without performing tests in +the specific network environment (e.g. the bottleneck could be the replication step). + +One advantage of hosting the request log in a separate cluster is that it makes it easier to independently scale the +number of worker nodes. If, for example, if transaction validation and resolution is required when receiving a +notarisation request, we might find that a significant number of receivers is required to generate enough incoming +traffic to the request log. On the flipside, increasing the number of workers adds additional consumers and load on the +request log, so a balance needs to be found. + +## Design Decisions + +As the design decision documents below discuss, the most suitable platform for managing the request log was chosen to be +[Apache Kafka](https://kafka.apache.org/), and [RocksDB](http://rocksdb.org/) as the storage engine for the committed +state index. | Heading | Recommendation | | ---------------------------------------- | -------------- | @@ -106,13 +109,23 @@ A Kafka-based notary service does not deviate much from the high-level target so ![Kafka overview](./images/kafka-high-level.svg) -For our purposes we can view Kafka as a replicated durable queue we can push messages (_records_) to and consume from. Consuming a record just increments the consumer's position pointer, and does not delete it. Old records eventually expire and get cleaned up, but the expiry time can be set to "indefinite" so all data is retained (it's a supported use-case). +For our purposes we can view Kafka as a replicated durable queue we can push messages (_records_) to and consume from. +Consuming a record just increments the consumer's position pointer, and does not delete it. Old records eventually +expire and get cleaned up, but the expiry time can be set to "indefinite" so all data is retained (it's a supported +use-case). -The main caveat is that Kafka does not allow consuming records from replicas directly – all communication has to be routed via a single leader node. +The main caveat is that Kafka does not allow consuming records from replicas directly – all communication has to be +routed via a single leader node. -In Kafka, logical queues are called _topics_. Each topic can be split into multiple partitions. Topics are assigned a _replication factor_, which specifies how many replicas Kafka should create for each partition. Each replicated partition has an assigned leader node which producers and consumers can connect to. Partitioning topics and evenly distributing partition leadership allows Kafka to scale well horizontally. +In Kafka, logical queues are called _topics_. Each topic can be split into multiple partitions. Topics are assigned a +_replication factor_, which specifies how many replicas Kafka should create for each partition. Each replicated +partition has an assigned leader node which producers and consumers can connect to. Partitioning topics and evenly +distributing partition leadership allows Kafka to scale well horizontally. -In our use-case, however, we can only use a single-partition topic for notarisation requests, which limits the total capacity and throughput to a single machine. Partitioning requests would break global transaction ordering guarantees for consumers. There is a [proposal](#kafka-throughput-scaling-via-partitioning) from Rick Parker on how we _could_ use partitioning to potentially avoid traffic contention on the single leader node. +In our use-case, however, we can only use a single-partition topic for notarisation requests, which limits the total +capacity and throughput to a single machine. Partitioning requests would break global transaction ordering guarantees +for consumers. There is a [proposal](#kafka-throughput-scaling-via-partitioning) from Rick Parker on how we _could_ use +partitioning to potentially avoid traffic contention on the single leader node. ### Data model diff --git a/docs/source/design/kafkaNotary/images/high-level.svg b/docs/source/design/kafka-notary/images/high-level.svg similarity index 100% rename from docs/source/design/kafkaNotary/images/high-level.svg rename to docs/source/design/kafka-notary/images/high-level.svg diff --git a/docs/source/design/kafkaNotary/images/kafka-high-level.svg b/docs/source/design/kafka-notary/images/kafka-high-level.svg similarity index 100% rename from docs/source/design/kafkaNotary/images/kafka-high-level.svg rename to docs/source/design/kafka-notary/images/kafka-high-level.svg diff --git a/docs/source/design/kafkaNotary/images/steps.svg b/docs/source/design/kafka-notary/images/steps.svg similarity index 100% rename from docs/source/design/kafkaNotary/images/steps.svg rename to docs/source/design/kafka-notary/images/steps.svg diff --git a/docs/source/design/kafkaNotary/images/store-comparison.png b/docs/source/design/kafka-notary/images/store-comparison.png similarity index 100% rename from docs/source/design/kafkaNotary/images/store-comparison.png rename to docs/source/design/kafka-notary/images/store-comparison.png diff --git a/docs/source/design/kafkaNotary/decisions/index-storage.md b/docs/source/design/kafkaNotary/decisions/index-storage.md deleted file mode 100644 index 8f2e22db6b..0000000000 --- a/docs/source/design/kafkaNotary/decisions/index-storage.md +++ /dev/null @@ -1,41 +0,0 @@ -![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png) - --------------------------------------------- -Design Decision: Storage engine for committed state index -============================================ - -## Background / Context - -The storage engine for the committed state index needs to support a single operation: "insert all values with unique keys, or abort if any key conflict found". A wide range of solutions could be used for that, from embedded key-value stores to full-fledged relational databases. However, since we don't need any extra features a RDBMS provides over a simple key-value store, we'll only consider lightweight embedded solutions to avoid extra operational costs. - -Most RDBMSs are also generally optimised for read performance (use B-tree based storage engines like InnoDB, MyISAM). Our workload is write-heavy and uses "random" primary keys (state references), which leads to particularly poor write performance for those types of engines – as we have seen with our Galera-based notary service. One exception is the MyRocks storage engine, which is based on RocksDB and can handle write workloads well, and is supported by Percona Server, and MariaDB. It is easier, however, to just use RocksDB directly. - -## Options Analysis - -### A. RocksDB - -An embedded key-value store based on log-structured merge-trees (LSM). It's highly configurable, provides lots of configuration options for performance tuning. E.g. can be tuned to run on different hardware – flash, hard disks or entirely in-memory. - -### B. LMDB - -An embedded key-value store using B+ trees, has ACID semantics and support for transactions. - -### C. MapDB - -An embedded Java database engine, providing persistent collection implementations. Uses memory mapped files. Simple to use, implements Java collection interfaces. Provides a HashMap implementation that we can use for storing committed states. - -### D. MVStore - -An embedded log structured key-value store. Provides a simple persistent map abstraction. Supports multiple map implementations (B-tree, R-tree, concurrent B-tree). - -## Recommendation and justification - -Performance test results when running on a Macbook Pro with Intel Core i7-4980HQ CPU @ 2.80GHz, 16 GB RAM, SSD: - -![Comparison](../images/store-comparison.png) - -Multiple tests were run with varying number of transactions and input states per transaction: "1m x 1" denotes a million transactions with one input state. - -Proceed with Option A, as RocksDB provides most tuning options and achieves by far the best write performance. - -Note that the index storage engine can be replaced in the future with minimal changes required on the notary service. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 58a87c71b9..ff33120206 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -61,6 +61,7 @@ We look forward to seeing what you can do with Corda! design/failure-detection-master-election/design.md design/float/design.md design/hadr/design.md + design/kafka-notary/design.md .. toctree:: :caption: Participate From c7bfc8f655a6f1159f4b9f809fadf4c28218f6bc Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 14 May 2018 17:01:06 +0200 Subject: [PATCH 2/2] Docs: import monitoring and management --- .../MonitoringLoggingOverview.png | Bin 0 -> 64265 bytes .../design/monitoring_management/design.md | 540 ++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 docs/source/design/monitoring_management/MonitoringLoggingOverview.png create mode 100644 docs/source/design/monitoring_management/design.md diff --git a/docs/source/design/monitoring_management/MonitoringLoggingOverview.png b/docs/source/design/monitoring_management/MonitoringLoggingOverview.png new file mode 100644 index 0000000000000000000000000000000000000000..768507853c50d56f61b63b89ed6c3542c221237f GIT binary patch literal 64265 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 W{`&!UFokj(7BPib8e-2l-~3-7RqS&B literal 0 HcmV?d00001 diff --git a/docs/source/design/monitoring_management/design.md b/docs/source/design/monitoring_management/design.md new file mode 100644 index 0000000000..48638d9f91 --- /dev/null +++ b/docs/source/design/monitoring_management/design.md @@ -0,0 +1,540 @@ +![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png) + +# Monitoring and Logging Design + +DOCUMENT MANAGEMENT +--- + +## Document Control + +| Title | Monitoring and Logging | +| -------------------- | ---------------------------------------- | +| Date | 20th November 2017 | +| Author | Jose Coll | +| Distribution | Design Approval Board, DevOps, Platform Development (Data Deployment) | +| Corda target version | Enterprise (primarily) | +| JIRA reference | https://r3-cev.atlassian.net/browse/ENT-1109 | + +## Approvals + +#### Document Sign-off + +| Author | Jose Coll | +| ----------------- | ---------------------------------------- | +| Reviewer(s) | DevOps, Product Management, Platform Development (Data Deployment) | +| Final approver(s) | Design Approval Board (DAB) | + +#### Design Decisions + +| Description | Recommendation | Approval | +| ---------------------------------------- | -------------- | -------- | +| JMX for Eventing, SLF4J for Logging | JMX, SLF4J | | +| Continue or discontinue usage of Jolokia? | TBC | | +| Separation of Corda Node and CorDapp log outputs | TBC | | + +## Document History + +To be managed by GitHub revision control. + +HIGH LEVEL DESIGN +--- + +## Overview + +The successful deployment and operation of Corda (and associated CorDapps) in a Production environment requires a supporting monitoring and management capability to ensure that both a Corda node (and its supporting middleware infrastructure) and deployed CorDapps execute in a functionally correct and consistent manner. A pro-active monitoring solution will enable the immediate alerting of unexpected behaviours and associated management tooling should enable swift corrective action. + +This design defines the monitoring metrics and logging outputs, and associated implementation approach, required to enable a proactive enterprise management and monitoring solution of Corda nodes and their associated CorDapps. This also includes a set of "liveliness" checks to verify and validate correct functioning of a Corda node (and associated CorDapp). + +![MonitoringLoggingOverview](./MonitoringLoggingOverview.png) + +In the above diagram, the left handside dotted box represents the components within scope for this design. It is anticipated that 3rd party enterprise-wide system management solutions will closely follow the architectural component breakdown in the right handside box, and thus seamlessly integrate with the proposed Corda event generation and logging design. The interface between the two is de-coupled and based on textual log file parsing and adoption of industry standard JMX MBean events. + +## Background + +Corda currently exposes several forms of monitorable content: + +* Application log files using the [SLF4J](https://www.slf4j.org/) (Simple Logging Facade for Java) which provides an abstraction over various concrete logging frameworks (several of which are used within other Corda dependent 3rd party libraries). Corda itself uses the [Apache Log4j 2](https://logging.apache.org/log4j/2.x/) framework for logging output to a set of configured loggers (to include a rolling file appender and the console). Currently the same set of rolling log files are used by both the node and CorDapp(s) deployed to the node. The log file policy specifies a 60 day rolling period (but preserving the most recent 10Gb) with a maximum of 10 log files per day. + +* Industry standard exposed JMX-based metrics, both standard JVM and custom application metrics are exposed directly using the [Dropwizard.io](http://metrics.dropwizard.io/3.2.3/) *JmxReporter* facility. In addition Corda also uses the [Jolokia](https://jolokia.org/) framework to make these accesible over an HTTP endpoint. Typically, these metrics are also collated by 3rd party tools to provide pro-active monitoring, visualisation and re-active management. + + A full list of currently exposed metrics can be found in the appendix A. + +The Corda flow framework also has *placeholder* support for recording additional Audit data in application flows using a simple *AuditService*. Audit event types are currently loosely defined and data is stored in string form (as a description and contextual map of name-value pairs) together with a timestamp and principal name. This service does not currently have an implementation of the audit event data to a persistent store. + +The `ProgressTracker` component is used to report the progress of a flow throughout its business lifecycle, and is typically configured to report the start of a specific business workflow step (often before and after message send and receipt where other participants form part of a multi-staged business workflow). The progress tracking framework was designed to become a vital part of how exceptions, errors, and other faults are surfaced to human operators for investigation and resolution. It provides a means of exporting progress as a hierachy of steps in a way that’s both human readable and machine readable. + +In addition, in-house Corda networks at R3 use the following tools: + +* Standard [DataDog](https://docs.datadoghq.com/guides/overview/) probes are currently used to provide e-mail based alerting for running Corda nodes. [Telegraf](https://github.com/influxdata/telegraf) is used in conjunction with a [Jolokia agent](https://jolokia.org/agent.html) as a collector to parse emitted metric data and push these to DataDog. +* Investigation is underway to evaluate [ELK](https://logz.io/learn/complete-guide-elk-stack/) as a mechanism for parsing, indexing, storing, searching, and visualising log file data. + +## Scope + +#### Goals + +- Add new metrics at the level of a Corda node, individual CorDapps, and other supporting Corda components (float, bridge manager, doorman) +- Support liveness checking of the node, deployed flows and services +- Review logging groups and severities in the node. +- Separate application logging from node logging. +- Implement the audit framework that is currently only a stubbed out API +- Ensure that Corda can be used with third party systems for monitoring, log collection and audit + +#### Out of scope + +- Recommendation of a specific set of monitoring tools. +- Monitoring of network infrastructure like the network map service. +- Monitoring of liveness of peers. + +#### Reference(s) to similar work + +* [Flow Audit Logging and Management Design](https://r3-cev.atlassian.net/wiki/spaces/AR/pages/127180188/Flow+Audit+Logging+and+Management+Design) - this proposal from April 17 also includes a prototype specification of an [Audit API](https://github.com/corda/corda/pull/620). +* [Corda Support - Monitoring Requirements Guide](https://r3-cev.atlassian.net/wiki/spaces/CCD/pages/131398183/Support+Team+Monitoring+Requirements?preview=/131398183/131398332/monitoring_requirements.v1.0.docx) + +## Requirements + +Expanding on the first goal identified above, the following requirements have been identified: + +1. Node health + - Message queues: latency, number of queues/messages, backlog, bridging establishment and connectivity (success / failure) + - Database: connections (retries, errors), latency, query time + - RPC metrics, latency, authentication/authorisation checking (eg. number of successful / failed attempts). + - Signing performance (eg. signatures per sec). + - Deployed CorDapps + - Garbage collector and JVM statistics + +2. CorDapp health + - Number of flows broken down by type (including flow status and aging statistics: oldest, latest) + - Flow durations + - JDBC connections, latency/histograms + +3. Logging + - RPC logging + - Shell logging (user/command pairs) + - Message queue + - Traces + - Exception logging (including full stack traces) + - Crash dumps (full stack traces) + - Hardware Security Module (HSM) events. + - per CorDapp logging + +4. Auditing + + - Security: login authentication and authorisation + - Business Event flow progress tracking + - System events (particularly failures) + + Audit data should be stored in a secure, storage medium. + Audit data should include sufficient contextual information to enable optimal off-line analysis. + Auditing should apply to all Corda node processes (running CorDapps, notaries, oracles). + +#### Use Cases + +It is envisaged that operational management and support teams will use the metrics and information collated from this design, either directly or through an integrated enterprise-wide systems management platform, to perform the following: + +- Validate liveness and correctness of Corda nodes and deployed CorDapps, and the physical machine or VM they are hosted on. + +* Use logging to troubleshoot operational failures (in conjunction with other supporting failure information: eg. GC logs, stack traces) +* Use reported metrics to fine-tune and tweak operational systems parameters (including dynamic setting of logging modules and severity levels to enable detailed logging). + +## Design Decisions + +The following design decisions are to be confirmed: + +1. JMX for metric eventing and SLF4J for logging + Both above are widely adopted mechanisms that enable pluggability and seamless inteoperability with other 3rd party enterprise-wide system management solutions. +2. Continue or discontinue usage of Jolokia? (TBC - most likely yes, subject to read-only security lock-down) +3. Separation of Corda Node and CorDapp log outputs (TBC) + +## Proposed Solution + +There are a number of activities and parts to the solution proposal: + +1. Extend JMX metric reporting through the Corda Monitoring Service and associated jolokia conversion to REST/JSON) coverage (see implementation details) to include all Corda services (vault, key management, transaction storage, network map, attachment storage, identity, cordapp provision) & subsytems components (state machine) + +2. Review and extend Corda log4j2 coverage (see implementation details) to ensure + + - consistent use of severities according to situation + - consistent coverage across all modules and libraries + - consistent output format with all relevant contextual information (node identity, user/execution identity, flow session identity, version information) + - separation of Corda Node and CorDapp log outputs (TBC) + For consistent interleaving reasons, it may be desirable to continue using combined log output. + + Publication of a *code style guide* to define when to use different severity levels. + +3. Implement a CorDapp to perform sanity checking of flow framework, fundamental corda services (vault, identity), and dependent middleware infrastructure (message broker, database). + [TBC] + +4. Revisit and enhance as necessary the [Audit service API]( https://github.com/corda/corda/pull/620 ), and provide a persistent backed implementation, to include: + + - specification of Business Event Categories (eg. User authentication and authorisation, Flow-based triggering, Corda Service invocations, Oracle invocations, Flow-based send/receive calls, RPC invocations) + - auto-enabled with Progress Tracker as Business Event generator + - RDBMS backed persistent store (independent of Corda database), with adequate security controls (authenticated access and read-only permissioning). Captured information should be consistent with standard logging, and it may be desirable to define auditable loggers within log4j2 to automatically redirect certain types of log events to the audit service. + +5. Ensure 3rd party middleware drivers (JDBC for database, MQ for messaging) and the JVM are correctly configured to export JMX metrics. Ensure the [JVM Hotspot VM command-line parameters](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/clopts001.html) are tuned correctly to enable detailed troubleshooting upon failure. Many of these metrics are already automatically exposed to 3rd party profiling tools such as Yourkit. + + Apache Artemis has a comprehensive [management API](https://activemq.apache.org/artemis/docs/latest/management.html) that allows a user to modify a server configuration, create new resources (e.g. addresses and queues), inspect these resources (e.g. how many messages are currently held in a queue) and interact with it (e.g. to remove messages from a queue), and exposes key metrics using JMX (using role-based authentication using Artemis's JAAS plug-in support to ensure Artemis cannot be controlled via JMX).. + +##### Restrictions + +- As of Corda M11, Java serialisation in the Corda node has been restricted, meaning MBeans access via the JMX port will no longer work. +- Usage of Jolokia requires bundling an associated *jolokia-agent-war* file on the classpath, and associated configuration to export JMX monitoring statistics and data over the Jolokia REST/JSON interface. An associated *jolokia-access.xml* configuration file defines role based permissioning to HTTP operations. + +## Complementary solutions + +A number of 3rd party libraries and frameworks have been proposed which solve different parts of the end to end solution, albeit with most focusing on the Agent Collector (eg. collect metrics from systems then output them to some backend storage.), Event Storage and Search, and Visualization aspects of Systems Management and Monitoring. These include: + +| Solution | Type (OS/£) | Description | +| ---------------------------------------- | ----------- | ---------------------------------------- | +| [Splunk](https://www.splunk.com/en_us/products.html) | £ | General purpose enterprise-wide system management solution which performs collection and indexing of data, searching, correlation and analysis, visualization and reporting, monitoring and alerting. | +| [ELK](https://logz.io/learn/complete-guide-elk-stack/) | OS | The ELK stack is a collection of 3 open source products from Elastic which provide an end to end enterprise-wide system management solution:
Elasticsearch: NoSQL database based on Lucene search engine
Logstash: is a log pipeline tool that accepts inputs from various sources, executes different transformations, and exports the data to various targets. Kibana: is a visualization layer that works on top of Elasticsearch. | +| [ArcSight](https://software.microfocus.com/en-us/software/siem-security-information-event-management) | £ | Enterprise Security Manager | +| [Collectd](https://collectd.org/) | OS | Collector agent (written in C circa 2005). Data acquisition and storage handled by over 90 plugins. | +| [Telegraf](https://github.com/influxdata/telegraf) | OS | Collector agent (written in Go, active community) | +| [Graphite](https://graphiteapp.org/) | OS | Monitoring tool that stores, retrieves, shares, and visualizes time-series data. | +| [StatsD](https://github.com/etsy/statsd) | OS | Collector daemon that runs on the [Node.js](http://nodejs.org/) platform and listens for statistics, like counters and timers, sent over [UDP](http://en.wikipedia.org/wiki/User_Datagram_Protocol) or [TCP](http://en.wikipedia.org/wiki/Transmission_Control_Protocol) and sends aggregates to one or more pluggable backend services (e.g., [Graphite](http://graphite.readthedocs.org/)). | +| [fluentd](https://www.fluentd.org/) | OS | Collector daemon which collects data directly from logs and databases. Often used to analyze event logs, application logs, and clickstreams (a series of mouse clicks). | +| [Prometheus](https://prometheus.io/) | OS | End to end monitoring solution using time-series data (eg. metric name and a set of key-value pairs) and includes collection, storage, query and visualization. | +| [NewRelic](https://newrelic.com/) | £ | Full stack instrumentation for application monitoring and real-time analytics solution. | + +Most of the above solutions are not within the scope of this design proposal, but should be capable of ingesting the outputs (logging and metrics) defined by this design. + +TECHNICAL DESIGN +--- + +In general, the requirements outlined in this design are cross-cutting concerns which affect the Corda codebase holistically, both for logging and capture/export of JMX metrics. + +## Interfaces + +* Public APIs impacted + * No Public API's are impacted. + + +* Internal APIs impacted + * No identified internal API's are impacted. +* Services impacted: + * No change anticipated to following service: + * *Monitoring* + This service defines and used the *Codahale* `MetricsRegistry`, which is used by all other Corda services. + * Changes expected to: + * *AuditService* + This service has been specified but not implemented. + The following event types have been defined (and may need reviewing): + * `FlowAppAuditEvent`: used in `FlowStateMachine`, exposed on `FlowLogic` (but never called) + * `FlowPermissionAuditEvent`: (as above) + * `FlowStartEvent` (unused) + * `FlowProgressAuditEvent` (unused) + * `FlowErrorAuditEvent` (unused) + * `SystemAuditEvent` (unused) +* Modules impacted + * All modules packaged and shipped as part of a Corda distribution (as published to Artifactory / Maven): *core, node, node-api, node-driver, finance, confidential-identities, test-common, test-utils, verifier, webserver, jackson, jfx, mock, rpc* + +## Functional + +#### Health Checker + +The Health checker is a CorDapp which verifies the health and liveliness of the Corda node it is deployed and running within by performing the following activities: + +1. Corda network and middleware infrastructure connectivity checking: + + - Database connectivity + - Message broker connectivity + +2. Network Map participants summary (count, list) + + - Notary summary (type, [number of cluster members] + +3. Flow framework verification + + Implement a simple flow that performs a simple "in-node" (no external messaging to 3rd party processes) roundtrip, and by doing so, exercises: + + - flow checkpointing (including persistence to relational data store) + - message subsystem verification (creation of a send-to-self queue for purpose of routing) + - custom CordaService invocation (verify and validate behaviour of an installed CordaService) + - vault querying (verify and validate behaviour of vault query mechanism) + + [this CorDapp could perform a simple Issuance of a fictional Corda token, Spend Corda token to self, Corda token exit, plus a couple of Vault queries in between: one using the VaultQuery API and the other using a Custom Query via a registered @CordaService] + +4. RPC triggering + Autotriggering of above flow using RPC to exercise the following: + + - messaging subsystem verification (RPC queuing) + - authenticaton and permissing checking (against underlying configuration) + + +The Health checker may be deployed as part of a Corda distribution and automatically invoked upoin start-up and/or manually triggered via JMX or the nodes associated Crash shell (using the startFlow command) + +Please note that the Health checker application is not responsible for determining the healthiness of a Corda Network. This is the responsibility of the network operator, and may include verification checks such as: + +- correct functioning of Network Map Service (registration, discovery) +- correct functioning of configured Notary +- remote messaging subsytem (including bridge creation) + +#### Metrics augmentation within Corda Subsystems and Components + +*Codahale* provides the following types of reportable metrics: + +- Gauge: is an instantaneous measurement of a value. +- Counter: is a gauge for a numeric value (specifically of type `AtomicLong`) which can be incremented or decremented. +- Meter: measures mean throughtput (eg. the rate of events over time, e.g., “requests per second”). Also measures one-, five-, and fifteen-minute exponentially-weighted moving average throughputs. +- Histogram: measures the statistical distribution of values in a stream of data (minimum, maximum, mean, median, 75th, 90th, 95th, 98th, 99th, and 99.9th percentiles). +- Timer: measures both the rate that a particular piece of code is called and the distribution of its duration (eg. rate of requests in requests per second). +- Health checks: provides a means of centralizing service (database, message broker health checks). + +See Appendix B for summary of current JMX Metrics exported by the Corda codebase. + +The following table identifies additional metrics to report for a Corda node: + +| Component / Subsystem | Proposed Metric(s) | +| ---------------------------------------- | ---------------------------------------- | +| Database | Connectivity (health check) | +| Corda Persistence | Database configuration details:
Data source properties: JDBC driver, JDBC driver class name, URL
Database properties: isolation level, schema name, init database flag
Run-time metrics: total & in flight connection, session, transaction counts; committed / rolledback transaction (counter); transaction durations (metric) | +| Message Broker | Connectivity (health check) | +| Corda Messaging Client | | +| State Machine | Fiber thread pool queue size (counter), Live fibers (counter) , Fibers waiting for ledger commit (counter)
Flow Session Messages (counters): init, confirm, received, reject, normal end, error end, total received messages (for a given flow session, Id and state)
(in addition to existing metrics captured)
Flow error (count) | +| Flow State Machine | Initiated flows (counter)
For a given flow session (counters): initiated flows, send, sendAndReceive, receive, receiveAll, retries upon send
For flow messaging (timers) to determine roundtrip latencies between send/receive interactions with counterparties.
Flow suspension metrics (count, age, wait reason, cordapp) | +| RPC | For each RPC operation we should export metrics to report: calling user, roundtrip latency (timer), calling frequency (meter). Metric reporting should include the Corda RPC protocol version (should be the same as the node's Platform Version) in play.
Failed requests would be of particular interest for alerting. | +| Vault | Roundtrip latency of Vault Queries (timer)
Soft locking counters for reserve, release (counter), elapsed times soft locks are held for per flow id (timer, histogram), list of soft locked flow ids and associated stateRefs.
attempt to soft lock fungible states for spending (timer) | +| Transaction Verification
(InMemoryTransactionVerifierService) | worker pool size (counter), verify duration (timer), verify throughput (meter), success (counter), failure counter), in flight (counter) | +| Notarisation | Notary details (type, members in cluster)
Counters for success, failures, failure types (conflict, invalid time window, invalid transaction, wrong notary), elapsed time (timer)
Ideally provide breakdown of latency across notarisation steps: state ref notary validation, signature checking, from sending to remote notary to receiving response | +| RAFT Notary Service
(awaiting choice of new RAFT implementation) | should include similar metrics to previous RAFT (see appendix). | +| SimpleNotaryService | success/failure uniqueness checking
success/failure timewindow checking | +| ValidatingNotaryService | as above plus success/failure of transaction validation | +| RaftNonValidatingNotaryService | as `SimpleNotaryService`, plus timer for algorithmic execution latency | +| RaftValidatingNotaryService | as `ValidatingNotaryService`, plus timer for algorithmic execution latency | +| BFTNonValidatingNotaryService | as `RaftNonValidatingNotaryService` | +| CorDapps
(CordappProviderImpl, CordappImpl) | list of corDapps loaded in node, path used to load corDapp jars
Details per CorDapp: name, contract class names, initiated flows, rpc flows, service flows, schedulable flows, services, serialization whitelists, custom schemas, jar path | +| Doorman Server | TBC | +| KeyManagementService | signing requests (count), fresh key requests (count), fresh key and cert requests (count), number of loaded keys (count) | +| ContractUpgradeServiceImpl | number of authorisation upgrade requests (counter) | +| DBTransactionStorage | number of transactions in storage map (cache)
cache size (max. 1024), concurrency level (def. 8) | +| DBTransactionMappingStorage | as above | +| Network Map | TBC (following re-engineering) | +| Identity Service | number or parties, keys, principals (in cache)
Identity verification count & latency (count, metric) | +| Attachment Service | counters for open, import, checking requests
(in addition to exiting attachment count) | +| Schema Service | list of registered schemas; schemaOptions per schema; table prefix. | + +#### Logging augmentation within Corda Subsystems and Components + +Need to ensure that Log4J2 log messages within Corda code are correctly categorized according to defined severities (from most specific to least): + +- ERROR: an error in the application, possibly recoverable. +- WARNING: an event that might possible lead to an error. +- INFO: an event for informational purposes. +- DEBUG: a general debugging event. +- TRACE: a fine-grained debug message, typically capturing the flow through the application. + +A *logging style guide* will be published to answer questions such as what severity level should be used and why when: + +- A connection to a remote peer is unexpectedly terminated. +- A database connection timed out but was successfully re-established. +- A message was sent to a peer. + +It is also important that we capture the correct amount of contextual information to enable rapid identification and resolution of issues using log file output. Specifically, within Corda we should include the following information in logged messages: + +- Node identifier +- User name +- Flow id (runId, also referred to as `StateMachineRunId`), if logging within a flow +- Other contextual Flow information (eg. counterparty), if logging within a flow +- `FlowStackSnapshot` information for catastrophic flow failures. + Note: this information is not currently supposed to be used in production (???). +- Session id information for RPC calls +- CorDapp name, if logging from within a CorDapp + +See Appendix C for summary of current Logging and Progress Tracker Reporting coverage within the Corda codebase. + +##### Custom logging for enhanced visibility and troubleshooting: + +1. Database SQL logging is controlled via explicit configuration of the Hibernate log4j2 logger as follows: + +``` +
 + 
 + +``` + +2. Message broker (Apache Artemis) advanced logging is enabled by configuring log4j2 for each of the 6 available [loggers defined](https://activemq.apache.org/artemis/docs/latest/logging.html). In general, Artemis logging is highly chatty so default logging is actually toned down for one of the defined loggers: + +``` +
 + 
 + +``` + +3. Corda coin selection advanced logging - including display of prepared statement parameters (which are not displayed for certain database providers when enabling Hibernate debug logging): + +``` +
 + 
 + +``` + +#### Audit Service persistence implementation and enablement + +1. Implementation of the existing `AuditService` API to write to a (pluggable) secure destination (database, message queue, other) +2. Identification of Business Events that we should audit, and instrumentation of code to ensure the AuditService is called with the correct Event Type according to Business Event. + For Corda Flows it would be a good idea to use the `ProgressTracker` component as a means of sending Business audit events. Refer [here](https://docs.corda.net/head/flow-state-machines.html?highlight=progress%20tracker#progress-tracking) for a detailed description of the ProgressTracker API. +3. Identification of System Events that should be automatically audited. +4. Specification of a database schema and associated object relational mapping implementation. +5. Setup and configuration of separate database and user account. + +## Software Development Tools and Programming Standards to be adopted. + +* Design patterns + + [Michele] proposes the adoption of an [event-based propagation](https://r3-cev.atlassian.net/browse/ENT-1131) solution (and associated event-driven framework) based on separation of concerns (performance improvements through parallelisation, latency minimisation for mainline execution thread): mainstream flow logic, business audit event triggering, JMX metric reporting. This approach would continue to use the same libraries for JMX event triggering and file logging. + +* 3rd party libraries + + [Jolokia](https://jolokia.org/) is a JMX-HTTP bridge giving access to the raw data and operations without connecting to the JMX port directly. Jolokia defines the JSON and REST formats for accessing MBeans, and provides client libraries to work with that protocol as well. + + [Dropwizard Metrics](http://metrics.dropwizard.io/3.2.3/) (formerly Codahale) provides a toolkit of ways to measure the behavior of critical components in a production environment. + +* supporting tools + + [VisualVM](http://visualvm.github.io/) is a visual tool integrating commandline JDK tools and lightweight profiling capabilities. + +APPENDICES +--- + +### Appendix A - Corda exposed JMX Metrics + +The following metrics are exposed directly by a Corda Node at run-time: + +| Module | Metric | Desccription | +| ------------------------ | ---------------------------- | ---------------------------------------- | +| Attachment Service | Attachments | Counts number of attachments persisted in database. | +| Verification Service | VerificationsInFlight | Gauge of number of in flight verifications handled by the out of process verification service. | +| Verification Service | Verification.Duration | Timer | +| Verification Service | Verification.Success | Count | +| Verification Service | Verification.Failure | Count | +| RAFT Uniqueness Provider | RaftCluster.ThisServerStatus | Gauge | +| RAFT Uniqueness Provider | RaftCluster.MembersCount | Count | +| RAFT Uniqueness Provider | RaftCluster.Members | Gauge, containing a list of members (by server address) | +| State Machine Manager | Flows.InFlight | Gauge (number of instances of state machine manager) | +| State Machine Manager | Flows.CheckpointingRate | Meter | +| State Machine Manager | Flows.Started | Count | +| State Machine Manager | Flows.Finished | Count | +| Flow State Machine | FlowDuration | Timer | + +Additionally, JMX metrics are also generated within the Corda *node-driver* performance testing utilities. Specifically, the `startPublishingFixedRateInjector` defines and exposes `QueueSize` and `WorkDuration` metrics. + +### Appendix B - Corda Logging and Reporting coverage + +Primary node services exposed publically via ServiceHub (SH) or internally by ServiceHubInternal (SHI): + +| Service | Type | Implementation | Logging summary | +| ---------------------------------------- | ---- | ---------------------------------- | ---------------------------------------- | +| VaultService | SH | NodeVaultService | extensive coverage including Vault Query api calls using `HibernateQueryCriteriaParser` | +| KeyManagementService | SH | PersistentKeyManagementService | none | +| ContractUpgradeService | SH | ContractUpgradeServiceImpl | none | +| TransactionStorage | SH | DBTransactionStorage | none | +| NetworkMapCache | SH | NetworkMapCacheImpl | some logging (11x info, 1x warning) | +| TransactionVerifierService | SH | InMemoryTransactionVerifierService | | +| IdentityService | SH | PersistentIdentityService | some logging (error, debug) | +| AttachmentStorage | SH | NodeAttachmentService | minimal logging (info) | +| | | | | +| TransactionStorage | SHI | DBTransactionStorage | see SH | +| StateMachineRecordedTransactionMappingStorage | SHI | DBTransactionMappingStorage | none | +| MonitoringService | SHI | MonitoringService | none | +| SchemaService | SHI | NodeSchemaService | none | +| NetworkMapCacheInternal | SHI | PersistentNetworkMapCache | see SH | +| AuditService | SHI | | | +| MessagingService | SHI | NodeMessagingClient | Good coverage (error, warning, info, trace) | +| CordaPersistence | SHI | CordaPersistence | INFO coverage within `HibernateConfiguration` | +| CordappProviderInternal | SHI | CordappProviderImpl | none | +| VaultServiceInternal | SHI | NodeVaultService | see SH | +| | | | | + +Corda subsystem components: + +| Name | Implementation | Logging summary | +| -------------------------- | ---------------------------------------- | ---------------------------------------- | +| NotaryService | SimpleNotaryService | some logging (warn) via `TrustedAuthorityNotaryService` | +| NotaryService | ValidatingNotaryService | as above | +| NotaryService | RaftValidatingNotaryService | some coverage (info, debug) within `RaftUniquenessProvider` | +| NotaryService | RaftNonValidatingNotaryService | as above | +| NotaryService | BFTNonValidatingNotaryService | Logging coverage (info, debug) | +| Doorman | DoormanServer (Enterprise only) | Some logging (info, warn, error), and use of `println` | +| TransactionVerifierService | OutOfProcessTransactionVerifierService (Enterprise only) | some logging (info) | +| | | | + +Corda core flows: + +| Flow name | Logging | Exception handling | Progress Tracking | +| --------------------------------------- | ------------------- | ---------------------------------------- | ----------------------------- | +| FinalityFlow | none | NotaryException | NOTARISING, BROADCASTING | +| NotaryFlow | none | NotaryException (NotaryError types: TimeWindowInvalid, TransactionInvalid, WrongNotary), IllegalStateException, some via `check` assertions | REQUESTING, VALIDATING | +| NotaryChangeFlow | none | StateReplacementException | SIGNING, NOTARY | +| SendTransactionFlow | none | FetchDataFlow.HashNotFound (FlowException) | | +| ReceiveTransactionFlow | none | SignatureException, AttachmentResolutionException, TransactionResolutionException, TransactionVerificationException | | +| ResolveTransactionsFlow | none | FetchDataFlow.HashNotFound (FlowException), ExcessivelyLargeTransactionGraph (FlowException) | | +| FetchAttachmentsFlow | none | FetchDataFlow.HashNotFound | | +| FetchTransactionsFlow | none | FetchDataFlow.HashNotFound | | +| FetchDataFlow | some logging (info) | FetchDataFlow.HashNotFound | | +| AbstractStateReplacementFlow.Instigator | none | StateReplacementException | SIGNING, NOTARY | +| AbstractStateReplacementFlow.Acceptor | none | StateReplacementException | VERIFYING, APPROVING | +| CollectSignaturesFlow | none | IllegalArgumentException via `require` assertions | COLLECTING, VERIFYING | +| CollectSignatureFlow | none | as above | | +| SignTransactionFlow | none | FlowException, possibly other (general) Exception | RECEIVING, VERIFYING, SIGNING | +| ContractUpgradeFlow | none | FlowException | | +| | | | | + +Corda finance flows: + +| Flow name | Logging | Exception handling | Progress Tracking | +| -------------------------- | ------- | ---------------------------------------- | ---------------------------------------- | +| AbstractCashFlow | none | CashException (FlowException) | GENERATING_ID, GENERATING_TX, SIGNING_TX, FINALISING_TX | +| CashIssueFlow | none | CashException (via call to `FinalityFlow`) | GENERATING_TX, SIGNING_TX, FINALISING_TX | +| CashPaymentFlow | none | CashException (caused by `InsufficientBalanceException` or thrown by `FinalityFlow`), SwapIdentitiesException | GENERATING_ID, GENERATING_TX, SIGNING_TX, FINALISING_TX | +| CashExitFlow | none | CashException (caused by `InsufficientBalanceException` or thrown by `FinalityFlow`), | GENERATING_TX, SIGNING_TX, FINALISING_TX | +| CashIssueAndPaymentFlow | none | any thrown by `CashIssueFlow` and `CashPaymentFlow` | as `CashIssueFlow` and `CashPaymentFlow` | +| TwoPartyDealFlow.Primary | none | | GENERATING_ID, SENDING_PROPOSAL | +| TwoPartyDealFlow.Secondary | none | IllegalArgumentException via `require` assertions | RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING | +| TwoPartyTradeFlow.Seller | none | FlowException, IllegalArgumentException via `require` assertions | AWAITING_PROPOSAL, VERIFYING_AND_SIGNING | +| TwoPartyTradeFlow.Buyer | none | IllegalArgumentException via `require` assertions, IllegalStateException | RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING | + +Confidential identities flows: + +| Flow name | Logging | Exception handling | Progress Tracking | +| ------------------------ | ------- | ---------------------------------------- | ---------------------------------------- | +| SwapIdentitiesFlow | | | | +| IdentitySyncFlow.Send | none | IllegalArgumentException via `require` assertions, IllegalStateException | SYNCING_IDENTITIES | +| IdentitySyncFlow.Receive | none | CertificateExpiredException, CertificateNotYetValidException, InvalidAlgorithmParameterException | RECEIVING_IDENTITIES, RECEIVING_CERTIFICATES | + +#####Appendix C - Apache Artemis JMX Event types and Queuing Metrics. + +The following table contains a list of Notification Types and associated perceived importance to a Corda node at run-time: + +| Name | Code | Importance | +| --------------------------------- | :--: | ---------- | +| BINDING_ADDED | 0 | | +| BINDING_REMOVED | 1 | | +| CONSUMER_CREATED | 2 | Medium | +| CONSUMER_CLOSED | 3 | Medium | +| SECURITY_AUTHENTICATION_VIOLATION | 6 | Very high | +| SECURITY_PERMISSION_VIOLATION | 7 | Very high | +| DISCOVERY_GROUP_STARTED | 8 | | +| DISCOVERY_GROUP_STOPPED | 9 | | +| BROADCAST_GROUP_STARTED | 10 | N/A | +| BROADCAST_GROUP_STOPPED | 11 | N/A | +| BRIDGE_STARTED | 12 | High | +| BRIDGE_STOPPED | 13 | High | +| CLUSTER_CONNECTION_STARTED | 14 | Soon | +| CLUSTER_CONNECTION_STOPPED | 15 | Soon | +| ACCEPTOR_STARTED | 16 | | +| ACCEPTOR_STOPPED | 17 | | +| PROPOSAL | 18 | | +| PROPOSAL_RESPONSE | 19 | | +| CONSUMER_SLOW | 21 | High | + +The following table summarised the types of metrics associated with Message Queues: + +| Metric | Description | +| ----------------- | ---------------------------------------- | +| count | total number of messages added to a queue since the server started | +| countDelta | number of messages added to the queue *since the last message counter update* | +| messageCount | *current* number of messages in the queue | +| messageCountDelta | *overall* number of messages added/removed from the queue *since the last message counter update*. Positive value indicated more messages were added, negative viceversa. | +| lastAddTimestamp | timestamp of the last time a message was added to the queue | +| updateTimestamp | timestamp of the last message counter update | +