From 4a1d4659083d18063a785e51257cbae68550199e Mon Sep 17 00:00:00 2001 From: Tommaso Teofili Date: Mon, 31 Jul 2023 15:31:04 +0200 Subject: [PATCH 1/2] ADR-0006 - initial version --- ...ADR-0006-persistence-layer-requirements.md | 97 ++++++++++++++++++ adr/assets/ADR-0006-a.png | Bin 0 -> 40191 bytes 2 files changed, 97 insertions(+) create mode 100644 adr/ADR-0006-persistence-layer-requirements.md create mode 100644 adr/assets/ADR-0006-a.png diff --git a/adr/ADR-0006-persistence-layer-requirements.md b/adr/ADR-0006-persistence-layer-requirements.md new file mode 100644 index 0000000..d4631dd --- /dev/null +++ b/adr/ADR-0006-persistence-layer-requirements.md @@ -0,0 +1,97 @@ +--- +num: 6 # allocate an id when the draft is created +title: Persistence Layer Requirements +status: "Draft" # One of Draft, Accepted, Rejected +authors: + - "tteofili" # One item for each author, as github id or "firstname lastname" +tags: + - "core,service" # e.g. service, python, java, etc +--- + +## Title + +Persistence Layer Requirements for `trustyai-service` + +## Context and Problem Statement + +In order to generate explanations and calculate metrics, the `trustyai-service` requires access to (i) single predictions to be explained and (ii) batches of predictions to perform metrics' calculations. + +The current design and implementation of the persistence layer only makes it possible for directly reading and writing of binaries to a (pluggable) storage. While this enables usage of different storage options (e.g., PVC, MinIO, etc.), it has the limitation of requiring `trustyai-service` to reimplement everything else a persistence layer requires from scratch (e.g., concurrent writes, efficient data retrieval, etc.) + +As the `trustyai-service` aims to be used with multiple models served by different serving runtimes concurrently, the persistence layer would rather use a proper database solution for managing predictions consistently in a concurrent scenario. + + +When designing a persistence layer storage for a managed service, there are several requirements that should be considered to ensure that the storage system is scalable, reliable, and performs well. Here are some key requirements that you should consider: + +1. Scalability: The storage system must be able to scale horizontally to handle increasing amounts of data and user traffic. This means that the system should be able to add additional servers or nodes easily to handle the load. Additionally, the system should be able to handle a high number of read and write requests per second without experiencing performance degradation. + +2. Availability: The storage system should be highly available and fault-tolerant to ensure that data is always accessible. This means that the system should have mechanisms in place to detect and recover from failures quickly, and that data should be replicated across multiple nodes to ensure that it is available even if one node goes down. + +3. Data Consistency: The storage system should ensure that data is consistent across all nodes. This means that all nodes should have the same data at all times, and that data should be synchronized in real-time across all nodes. + +4. Security: The storage system should have robust security features to protect data from unauthorized access, modification, or deletion. This includes mechanisms for authentication, authorization, and encryption of data at rest and in transit. + +5. Performance: The storage system should be optimized for performance to ensure that data can be accessed and retrieved quickly. This means that the system should have mechanisms in place to optimize read and write operations, including caching, indexing, and compression. + +6. Data Backup and Recovery: The storage system should have a robust backup and recovery mechanism in place to ensure that data can be restored in the event of data loss or corruption. This includes mechanisms for data backup, disaster recovery, and point-in-time recovery. + +7. Compliance: The storage system should be designed to comply with relevant industry and regulatory standards, such as HIPAA, PCI-DSS, and GDPR. This includes mechanisms for data retention, auditing, and access control. + +In summary, when designing a persistence layer storage for a managed service, it is important to consider scalability, availability, data consistency, security, performance, data backup and recovery, and compliance. By considering these requirements, you can ensure that the storage system is robust, reliable, and meets the needs of your customers. + +## Goals + +Make the `trustyai-service` able to read and write data about predictions coming from `ModelMesh`, `Kserve`, etc. with high concurrency, low memory footprint, avoiding any data loss. + +## Non-goals + +Stick to a specific persistence layer implementation. While, for example, a database is likely to be a good candidate solution for this ADR's goal, other options can be considered as well. + +## Current situation + +In the current `trustyai-service` architecture, the persistence and retrieval of such predictions is managed by the service itself (e.g. there's not a "prediction provider" service) by accessing a `org.kie.trustyai.service.data.DataSource` interface. +Via a `DataSource` it is possible to: +* retrieve a `org.kie.trustyai.explainability.model.Dataframe` (similar to python's `pandas.DataFrame`) given the _id_ of a served model (and, optionally, a _batch_ size). The returned `Dataframe` contains one or more predictions (`org.kie.trustyai.explainability.model.Prediction`). +* persist a set of predictions (e.g., a `Dataframe`) for a given model _id_. + +The `DataSource` relies on a `org.kie.trustyai.service.data.storage.Storage` to access persisted data (both for reading and writing). Currently `trustyai-service` supports the following `Storage` implementations: +* `PVCStorage`: to suppprt Kubernetes' [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/), making it possible to leverage a file based storage "in the cloud". +* `MinioStorage`: to support [MinIO](https://min.io/) object storage. +* `MemoryStorage`: for in-memory support, mainly useful for dev/testing purposes. + +![](assets/ADR-0006-a.png) + +Such pluggable storage design is good for avoiding dependencies between the business logic (read/write predictions) and where the data is actually persisted. + +One big limitation of the current design is that it was implemented with a file-based storage in mind, e.g., read / write `Dataframes` in _CSV_ files. While this was good for an initial and single-tenant version of the `trustyai-service`, it brings some concerns with respect to concurrency, data consistency and performance. + +Practically, a few issues can be already identified: +1. data loss might occur if/when two different threads send predictions for the same model via `org.kie.trustyai.service.data.utils.InferencePayloadReconciler#save`; that in fact triggers `DataSource#saveDataframe` which has first to check whether binary data for that specific model exists in the `Storage` or not and, based on that, decide whether creating that same binary or append to it. +2. predictions for a given model are all stored in a single binary files (e.g., CSV files) within `PVC` (the default, but the same goes for `MinIO`) and therefore it is not possible to "select" a specific prediction by any filtering mechanism (e.g., by id, feature value, etc.), requiring the `trustyai-service` to load all the existing predictions for a single model whenever even just one it is needed. + +Having a service/solution that already allows for reading and writing structured data (e.g. a database) would allow us to avoid any concurrency issues when writing (e.g. "ghost updates"), retrieve only the appropriate data by using "filters". In general will require the `trustyai-service` not to re-implement tricky data management algorithms and services and focus on extending and enhancing its capabilities. + +## Proposal + +Make changes to the `DataSource` implementation such that it can (also?) delegate read/write operations to a persistence layer solution that provides allows concurrent read/writes, ACID transactions, etc. + +### Threat model + +N/A + +## Alternatives Considered / Rejected + +N/A + +## Challenges + +Identify/implement a persistence storage solution that works well both in a plain local as well as in a full distributed/cloud based scenario. + +## Dependencies + + +## Consequences if not completed + +When fetching data from `Storage` implementations will always load the entire set of persisted predictions into main memory; most of the time this is not necessary and actually suboptimal, especially in the case the service needs to explain a single prediction among all the retrieved ones. + +Possible data loss because of "ghost updates" when multiple serving instances send their data / predictions to the `trustyai-service` at the same time. \ No newline at end of file diff --git a/adr/assets/ADR-0006-a.png b/adr/assets/ADR-0006-a.png new file mode 100644 index 0000000000000000000000000000000000000000..89b217361a06c9c13139b7c2609c5a4162602be6 GIT binary patch literal 40191 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 literal 0 HcmV?d00001 From 5bc9e88371eae102791180d8d63ef34088c03e12 Mon Sep 17 00:00:00 2001 From: Tommaso Teofili Date: Mon, 31 Jul 2023 15:33:37 +0200 Subject: [PATCH 2/2] ADR-0006 - initial version --- ...ADR-0006-persistence-layer-requirements.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/adr/ADR-0006-persistence-layer-requirements.md b/adr/ADR-0006-persistence-layer-requirements.md index d4631dd..a74846e 100644 --- a/adr/ADR-0006-persistence-layer-requirements.md +++ b/adr/ADR-0006-persistence-layer-requirements.md @@ -20,25 +20,6 @@ The current design and implementation of the persistence layer only makes it pos As the `trustyai-service` aims to be used with multiple models served by different serving runtimes concurrently, the persistence layer would rather use a proper database solution for managing predictions consistently in a concurrent scenario. - -When designing a persistence layer storage for a managed service, there are several requirements that should be considered to ensure that the storage system is scalable, reliable, and performs well. Here are some key requirements that you should consider: - -1. Scalability: The storage system must be able to scale horizontally to handle increasing amounts of data and user traffic. This means that the system should be able to add additional servers or nodes easily to handle the load. Additionally, the system should be able to handle a high number of read and write requests per second without experiencing performance degradation. - -2. Availability: The storage system should be highly available and fault-tolerant to ensure that data is always accessible. This means that the system should have mechanisms in place to detect and recover from failures quickly, and that data should be replicated across multiple nodes to ensure that it is available even if one node goes down. - -3. Data Consistency: The storage system should ensure that data is consistent across all nodes. This means that all nodes should have the same data at all times, and that data should be synchronized in real-time across all nodes. - -4. Security: The storage system should have robust security features to protect data from unauthorized access, modification, or deletion. This includes mechanisms for authentication, authorization, and encryption of data at rest and in transit. - -5. Performance: The storage system should be optimized for performance to ensure that data can be accessed and retrieved quickly. This means that the system should have mechanisms in place to optimize read and write operations, including caching, indexing, and compression. - -6. Data Backup and Recovery: The storage system should have a robust backup and recovery mechanism in place to ensure that data can be restored in the event of data loss or corruption. This includes mechanisms for data backup, disaster recovery, and point-in-time recovery. - -7. Compliance: The storage system should be designed to comply with relevant industry and regulatory standards, such as HIPAA, PCI-DSS, and GDPR. This includes mechanisms for data retention, auditing, and access control. - -In summary, when designing a persistence layer storage for a managed service, it is important to consider scalability, availability, data consistency, security, performance, data backup and recovery, and compliance. By considering these requirements, you can ensure that the storage system is robust, reliable, and meets the needs of your customers. - ## Goals Make the `trustyai-service` able to read and write data about predictions coming from `ModelMesh`, `Kserve`, etc. with high concurrency, low memory footprint, avoiding any data loss.