From 5e5b88624717e342b9cab5ddf3a0003cb411ff1a Mon Sep 17 00:00:00 2001 From: katarinasupe Date: Thu, 13 Mar 2025 17:54:13 +0100 Subject: [PATCH 01/54] init --- pages/release-notes.mdx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 4d89dfa29..e2d446e23 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -57,6 +57,15 @@ updated. ## 🚀 Latest release +### Memgraph v3.2.0 - Apr 23rd, 2025 + +### MAGE v3.2.0 - Apr 23rd, 2025 + +### Lab v3.2.0 - Apr 23rd, 2025 + + +## Previous releases + ### Memgraph v3.1.0 - Mar 12th, 2025 {

⚠ Breaking changes

} @@ -311,8 +320,6 @@ updated. database in an HA cluster resulted in an "Unknown database name" error. -## Previous releases - ### Memgraph v3.0.0 - Jan 29th, 2025 {

⚠ Breaking changes

} From 46cfb1f3f7feb3899443a9f72cba6a69c7ed22d3 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:04:09 +0200 Subject: [PATCH 02/54] Add hardware sizing --- pages/_meta.ts | 3 +- pages/getting-started/install-memgraph.mdx | 23 +- pages/memgraph-in-production.mdx | 92 +++++ pages/memgraph-in-production/_meta.ts | 12 + .../general-suggestions.mdx | 148 ++++++++ .../memgraph-in-analytical-workloads.mdx | 315 ++++++++++++++++++ .../memgraph-in-cyber-security.mdx | 315 ++++++++++++++++++ .../memgraph-in-fraud-detection.mdx | 315 ++++++++++++++++++ .../memgraph-in-graphrag.mdx | 315 ++++++++++++++++++ .../memgraph-in-high-throughput-workloads.mdx | 315 ++++++++++++++++++ ...memgraph-in-mission-critical-workloads.mdx | 315 ++++++++++++++++++ .../memgraph-in-supply-chain.mdx | 315 ++++++++++++++++++ .../memgraph-in-transactional-workloads.mdx | 315 ++++++++++++++++++ pages/memgraph-in-production/overview.mdx | 315 ++++++++++++++++++ 14 files changed, 3102 insertions(+), 11 deletions(-) create mode 100644 pages/memgraph-in-production.mdx create mode 100644 pages/memgraph-in-production/_meta.ts create mode 100644 pages/memgraph-in-production/general-suggestions.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-cyber-security.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-fraud-detection.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-graphrag.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-supply-chain.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx create mode 100644 pages/memgraph-in-production/overview.mdx diff --git a/pages/_meta.ts b/pages/_meta.ts index 8df3472ce..67ec83c52 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -15,5 +15,6 @@ export default { "clustering": "Clustering", "data-streams": "Data streams", "help-center": "Help center", - "release-notes": "Release notes" + "release-notes": "Release notes", + "memgraph-in-production": "Memgraph in production" } \ No newline at end of file diff --git a/pages/getting-started/install-memgraph.mdx b/pages/getting-started/install-memgraph.mdx index 4dbd86dce..467fb533d 100644 --- a/pages/getting-started/install-memgraph.mdx +++ b/pages/getting-started/install-memgraph.mdx @@ -109,17 +109,20 @@ Before running Memgraph, please check the [system configuration guidelines](/dat Below are minimum and recommended system requirements for installing Memgraph. -| | Minimum | Recommended | -| ------- | -------- | ------------------------------ | +| | Minimum | Recommended | +| ------- | -------- | ------------------------------- | | CPU | Server or desktop processor:
Intel Xeon
AMD Opteron/Epyc
ARM machines or Apple M1
Amazon Graviton | Server processor:
Intel Xeon
AMD Opteron/Epyc
ARM machines or Apple M1
Amazon Graviton | -| RAM | 1 GB | ≄ 16 GB ECC | -| Disk | 1 GB | equally as RAM | -| Cores | 1 vCPU | ≄ 8 vCPUs (≄ 4 physical cores) | -| Network | 100 Mbps | ≄ 1 Gbps | - -The disk is used for storing database [durability -files](/configuration/data-durability-and-backup) - snapshots and write-ahead -logs. +| RAM | 1 GB | ≄ 16 GB ECC | +| Disk | 1 GB | at least 3x the amount of RAM (NVMe SSD) | +| Cores | 1 vCPU | ≄ 8 vCPUs (≄ 4 physical cores) | +| Network | 100 Mbps | ≄ 1 Gbps | + +The disk is used for storing database [durability files](/configuration/data-durability-and-backup) - snapshots and write-ahead +logs. By default, Memgraph stores 3 latest snapshots in the database (flag to adjust this is `--storage-snapshot-retention-count`). +Snapshots size is usually less than the amount of RAM that is needed to load the data into memory. + +The amount of CPU cores varies per use case. For horizontal scalability, you can further increase the amount of cores on your +system for additional scalability. Check out how the [storage memory](/fundamentals/storage-memory-usage) is used, and calculate memory requirements based on your data. diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx new file mode 100644 index 000000000..9c0a255cb --- /dev/null +++ b/pages/memgraph-in-production.mdx @@ -0,0 +1,92 @@ +--- +title: Memgraph in Production +description: Learn how to deploy Memgraph in production for your workload and consider all the advices directly from the Memgraph Team based on our multi-year experiences. +--- + +import {CommunityLinks} from '/components/social-card/CommunityLinks' + +# Memgraph in Production + +When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, scalability, and resilience. +This includes hardware considerations, the correct sizing of instances, configuring drivers, using appropriate flags when starting Memgraph, +importing data, and connecting to external sources. The guidelines in this section will help you make informed decisions for your +specific use case, ensuring that Memgraph performs effectively in your environment. + +## Prerequisites for Deployment + +Before deploying Memgraph, there are several essential considerations to be aware of: + +- **Hardware Considerations**: Memgraph is an in-memory graph database, so allocating sufficient memory (RAM) is crucial. Ensure that your system meets the memory requirements of your dataset and any indexes. A sufficient number of CPU cores and fast storage are also important for overall performance. +- **Sizing the Instance**: The size of your instance should depend on the expected dataset size, the number of concurrent queries, and the complexity of graph traversals. Larger datasets or high query volumes may require scaling horizontally across multiple nodes. +- **Driver Considerations**: Memgraph supports several client libraries, including Python, C++, and JavaScript. Choose the correct driver for your application based on language compatibility and performance requirements. +- **Flags for Starting Memgraph**: When starting Memgraph, certain flags can optimize performance. These may include memory limits, persistence settings, and indexing options. Always tailor these based on the type of workload. +- **Importing Data**: Memgraph supports various ways to import data, including batch loading from CSVs, using Cypher queries, or leveraging streaming data sources. +- **Connecting to External Sources**: In production, Memgraph often needs to interact with external systems like data pipelines, message brokers, and other databases. Ensure that Memgraph’s connection configurations (e.g., network settings, authentication) align with external sources. + +## Deployment Choices Based on Workload + +### General Recommendations + +For general deployments of Memgraph, focus on optimizing the following: + +- **Memory**: Ensure sufficient RAM for graph data and indices. +- **CPU**: Adequate core count to handle parallel queries. +- **Persistence**: Configure persistence according to the durability needs of your data. +- **Indexing**: Set up appropriate indexes for frequently queried nodes and relationships. +- **Replication**: For high availability, enable replication to safeguard against potential failures. + +### Memgraph in High Throughput Workloads + +In high throughput scenarios, such as real-time analytics or fast decision-making applications, you should focus on the following: + +- **High Availability**: Set up a replication cluster for fault tolerance and to ensure service continuity. +- **Memory Sizing**: Ensure that your system has enough memory to hold your graph in memory and avoid swapping to disk, which would slow down performance. +- **Query Optimization**: Optimize your queries for high performance, and enable result caching for frequent queries. +- **Load Balancing**: Distribute traffic across multiple instances to avoid overloading a single node. + +### Memgraph in Analytical Workloads + +For analytical workloads, such as running complex queries or machine learning graph algorithms, consider the following: + +- **Instance Size**: Use larger instances with more RAM and CPU to support the heavy lifting of complex queries. +- **Persistence**: You may want to enable persistence for long-term storage of analytical results. +- **Query Tuning**: Optimize graph traversal queries and ensure that indexing is set up to cover common analytical queries. +- **Batch Processing**: If your workload involves batch processing, ensure that Memgraph is configured to handle large imports efficiently without overloading the system. + +### Memgraph in Transactional Workloads + +For transactional workloads, where low-latency and high consistency are key, prioritize the following: + +- **Transaction Durability**: Ensure proper settings for transaction logs and snapshots for data durability. +- **Replication**: Set up a high-availability cluster with replication to ensure that transactions are available across nodes. +- **Network Configuration**: Minimize network latency between Memgraph instances to ensure consistent transaction processing. +- **Memory Allocation**: Tune memory allocation for optimal transaction throughput. + +### Memgraph in GraphRAG + +In GraphRAG (Graph-based Retrieval-Augmented Generation) applications, where the graph data is used to enrich AI model generation or queries, the following considerations are important: + +- **Data Import**: Use bulk data import methods or streaming data pipelines for continuous graph updates. +- **Embedding Integration**: Ensure that your graph data is linked to machine learning models and that embeddings are stored efficiently. +- **Latency**: Minimize query and response time, especially for real-time generation. +- **Model Integration**: Seamlessly integrate Memgraph with model inference pipelines to improve the accuracy of AI-driven responses. + +### Memgraph in Cybersecurity + +For cybersecurity workloads, which involve detecting threats, managing incident data, and analyzing network traffic, consider the following: + +- **Data Ingestion**: Configure Memgraph to handle high-speed log ingestion, including event data from firewalls, intrusion detection systems, and other sources. +- **Scalability**: Ensure that Memgraph can scale horizontally as the volume of data increases over time. +- **High Availability**: Set up high availability and replication to ensure that data is accessible at all times, especially in a high-velocity attack detection environment. +- **Real-time Analysis**: Optimize Memgraph for real-time graph traversal to detect anomalous behaviors quickly. + +### Memgraph in Fraud Detection + +For fraud detection workloads, which rely on analyzing transactions and behaviors to detect fraud patterns, focus on the following: + +- **Real-Time Processing**: Ensure Memgraph is optimized for low-latency queries to detect fraudulent activity in real time. +- **Data Import**: Configure Memgraph to efficiently ingest transaction data from external sources, including financial systems and databases. +- **Graph Algorithms**: Use graph algorithms, such as PageRank or community detection, to identify fraud patterns based on relational data. +- **Persistence and Backups**: Maintain robust backup strategies to ensure that transaction data is safely stored and retrievable. + + diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts new file mode 100644 index 000000000..238645804 --- /dev/null +++ b/pages/memgraph-in-production/_meta.ts @@ -0,0 +1,12 @@ +export default { + "overview": "Overview", + "general-suggestions": "General suggestions", + "memgraph-in-transactional-workloads": "Memgraph in transactional workloads", + "memgraph-in-analytical-workloads": "Memgraph in analytical workloads", + "memgraph-in-high-throughput-workloads": "Memgraph in high throughput workloads", + "memgraph-in-graphrag": "Memgraph in GraphRAG", + "memgraph-in-fraud-detection": "Memgraph in Fraud Detection", + "memgraph-in-cyber-security": "Memgraph in Cyber Security", + "memgraph-in-supply-chain": "Memgraph in Supply Chain", + "memgraph-in-mission-critical-workloads": "Memgraph in Mission Critical workloads", +} \ No newline at end of file diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx new file mode 100644 index 000000000..2bbdc075d --- /dev/null +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -0,0 +1,148 @@ +--- +title: General suggestions +description: General suggestions when working with Memgraph, from testing to production. +--- + +import { Callout } from 'nextra/components' + +# General Suggestions + +This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. + +## What is Covered? + +The general suggestions cover the following key areas: + +**1. [Hardware Requirements for running Memgraph](#1-hardware-requirements-for-running-memgraph)**
+Learn how to select the best machine for Memgraph based on your resources, whether on-prem, in a data center, or via cloud offerings (e.g., AWS, GCP, Azure). + +**2. [Hardware Sizing](#2-hardware-sizing)**
+Since Memgraph is an in-memory database, estimating RAM requirements is crucial. This section helps you allocate memory based on dataset size and expected workloads. + +**3. [Hardware Configuration](#3-hardware-configuration)**
+Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. + +**4. [Networking Options](#4-networking-options)**
+Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. + +**5. [Deployment Options](#5-deployment-options)**
+Understand the tradeoffs between running Memgraph natively on a host machine or in a containerized environment (Docker, K8s) to choose the best deployment method. + +**6. [Choosing the Right Memgraph Flag Set](#6-choosing-the-right-memgraph-flag-set)**
+Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. + +**7. [Importing Mechanisms](#7-importing-mechanisms)**
+Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. + +**8. [Enterprise Features You Might Require](#8-enterprise-features-you-might-require)**
+Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. +Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, +multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet +the demands of enterprise workloads. + +**9. [Queries That Best Suit Your Workload](#9-queries-that-best-suit-your-workload)**
+The type of queries you use can significantly affect performance, especially as the dataset grows or the workload +complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload +scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, +which will be covered in the specific use case sections. + +## Bringing Memgraph to Production + +## 1. Hardware Requirements for Running Memgraph + +To get started with Memgraph, we recommend checking the [system requirements](/getting-started/install-memgraph#system-requirements) listed +in our installation guide. These requirements are sufficient for setting up Memgraph on your own servers. + +If you're deploying Memgraph using a cloud provider, visit the [deployment section](/deployment) for guidance on choosing the appropriate +instance type for your specific cloud environment. + +## 2. Hardware Sizing + +The most critical factor in provisioning a server for Memgraph is **RAM**. Memgraph operates primarily in **in-memory mode**, meaning +the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently +and can scale with your workload. + +For a deeper dive into how memory usage is calculated, refer to our [Memory Storage Guide](/fundamentals/storage-memory-usage). Below is a +simplified, rule-of-thumb approach to help you estimate your memory needs. + +### Memory Components + +Memgraph’s memory usage consists of five main parts: + +1. **Node Memory** + Each node requires approximately **128 bytes**. + +2. **Relationship Memory** + Each relationship requires approximately **120 bytes**. + +3. **Property Storage** + Properties are stored in buffered arrays. The memory needed depends on property types and counts. + +4. **Indices** + Additional overhead is introduced by indexing node labels, edge types, and properties. + +5. **Query Execution Memory** + Temporary memory required to execute queries. This varies depending on the complexity of queries. + + +Items 1–4 are referred to as **memory at rest**, while query execution memory is often called **compute memory**. + + +### RAM Sizing Guidelines + +Use the following steps to estimate the RAM required for your workload: + +#### Step 1: Estimate Base Graph Storage +For datasets with minimal properties (3–5 small properties), use this formula: +``` +128 * N + 120 * M +``` +Where: +- `N` = number of nodes +- `M` = number of relationships +The result gives you the dataset size in **bytes**. + + +**Don’t know how many nodes or relationships you have?** +If your data is in a non-graph format (e.g., CSV), estimating the number of nodes and relationships isn’t always straightforward. +For example, one line in a CSV file could represent a node, a relationship, or a mix of both. In some cases, nodes and relationships are +split into separate CSV files. + +In such cases, we recommend importing a **sample of the dataset** (around 10%) into Memgraph and using the memory usage for that portion +to extrapolate the estimated requirements for the full dataset. This empirical method provides a more accurate and context-aware estimate +based on how your data is actually represented as a graph. + + +#### Step 2: Estimate Property Storage +If your dataset includes many or complex properties, refer to the [Memory Storage Guide](/fundamentals/storage-memory-usage) for detailed +calculations. Add this result to the base graph size from Step 1. + +#### Step 3: Add Index Overhead +- If you use fewer than 10 indices, this can be skipped. +- If you use many indices (e.g., 50+), add **~20% memory overhead** to the total from Steps 1 and 2. + +#### Step 4: Add Query Execution Memory +- For basic querying without graph algorithms: **1.5× multiplier** +- For analytical workloads with algorithms (e.g., PageRank, Community Detection, Betweenness Centrality): **2× multiplier** + +### Final Recommendation + +Once you've completed these steps: + +- Round up your estimate to match available server configurations. +- Example: If your estimate is **96GB**, choose a server with **128GB RAM** for safe headroom. + +### 3. Hardware Configuration +blablah + +### 4. Networking Options +blablah + +### 5. Deployment Options +blablah + +### 6. Choosing the right Memgraph flag set +blablah + +### 7. Importing Mechanisms +blablah diff --git a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx new file mode 100644 index 000000000..36ba4e626 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in analytical workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx new file mode 100644 index 000000000..61150e187 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in Cyber Security +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx new file mode 100644 index 000000000..b4723d541 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in Fraud Detection +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx new file mode 100644 index 000000000..fcacbad97 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in GraphRAG +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx new file mode 100644 index 000000000..79bdb1f40 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in high throughput workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx new file mode 100644 index 000000000..fdf2a594b --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in mission critical workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx new file mode 100644 index 000000000..25be5e450 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in supply chain +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx new file mode 100644 index 000000000..fcfaf59c4 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in transactional workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/overview.mdx b/pages/memgraph-in-production/overview.mdx new file mode 100644 index 000000000..2e61c61b7 --- /dev/null +++ b/pages/memgraph-in-production/overview.mdx @@ -0,0 +1,315 @@ +--- +title: Overview +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. From ee9a7bba1b6392a620e192542a5b405244390ce7 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:16:13 +0200 Subject: [PATCH 03/54] Add vm.max_map_count explanation --- .../general-suggestions.mdx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 2bbdc075d..fcef71e62 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -22,7 +22,7 @@ Since Memgraph is an in-memory database, estimating RAM requirements is crucial. **3. [Hardware Configuration](#3-hardware-configuration)**
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking Options](#4-networking-options)**
+**4. [Networking Configuration](#4-networking-options)**
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. **5. [Deployment Options](#5-deployment-options)**
@@ -132,17 +132,34 @@ Once you've completed these steps: - Round up your estimate to match available server configurations. - Example: If your estimate is **96GB**, choose a server with **128GB RAM** for safe headroom. -### 3. Hardware Configuration -blablah + +Still having problems with estimating the size of your instance? Try out our [official storage calculator](https://memgraph.com/storage-calculator), +or contact us on Discord! + + +## 3. Hardware Configuration + +One of the most important system settings when running Memgraph is configuring the kernel parameter `vm.max_map_count`. +This setting ensures that the system can allocate enough virtual memory areas, which is critical for avoiding memory-related +issues or unexpected crashes during Memgraph operations. + +You can find detailed setup instructions and context in our +[System Configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). + +If you're deploying Memgraph on Kubernetes, our Helm charts include an **init container** that automatically sets `vm.max_map_count` +during startup. However, this container requires **root privileges** to execute. If you're running in a restricted environment +or prefer not to use privileged containers, you’ll need to **manually configure** this parameter on the host machine. + +Properly configuring `vm.max_map_count` is a one-time setup but essential for a stable and performant Memgraph deployment. -### 4. Networking Options +## 4. Networking Configuration blablah -### 5. Deployment Options +## 5. Deployment Options blablah -### 6. Choosing the right Memgraph flag set +## 6. Choosing the right Memgraph flag set blablah -### 7. Importing Mechanisms +## 7. Importing Mechanisms blablah From de342fb50a060aba9f16350c302c0c99155d25ab Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:25:29 +0200 Subject: [PATCH 04/54] Add deployment options --- .../general-suggestions.mdx | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index fcef71e62..36fddd09e 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -152,11 +152,38 @@ or prefer not to use privileged containers, you’ll need to **manually configur Properly configuring `vm.max_map_count` is a one-time setup but essential for a stable and performant Memgraph deployment. +For system-specific configuration steps and installation guidelines, refer to the [Install Memgraph guide](/getting-started/install-memgraph). + ## 4. Networking Configuration -blablah + +To ensure Memgraph functions properly in your environment, make sure the following ports are open and accessible on your server: + +- **7687** – Used for the **Bolt protocol**, which handles all client-to-database communication. +- **3000** – Required if you're using **Memgraph Lab** as a visual interface. +- **7444** – Needed for **streaming logs** from Memgraph to Memgraph Lab. +- **9091** – Used for **system metrics**, an **Enterprise-only** feature. If you're using Prometheus or some other system for tracking system metrics, +this port needs to be enabled. + +In addition to enabling these ports, be sure to: + +- Review and adjust **firewall settings** to allow traffic on the listed ports. +- On **Red Hat-based systems**, even with the firewall properly configured, you might need to **disable SELinux**, as it can block +Memgraph’s access to system resources. ## 5. Deployment Options -blablah + +Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** +using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally +recommend using the **containerized version**. + +The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for +additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** +to enable the full range of MAGE functionalities—an often complex and error-prone process for most users. + +For most use cases, especially during prototyping and production readiness, the containerized image provides the best balance between +performance, simplicity, and feature completeness. + +More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set blablah From 0b741e88540f41a40e2fc9d14ed19136940fa8d9 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:55:06 +0200 Subject: [PATCH 05/54] Add flag set suggestions --- .../general-suggestions.mdx | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 36fddd09e..3f3404859 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -95,12 +95,12 @@ Use the following steps to estimate the RAM required for your workload: #### Step 1: Estimate Base Graph Storage For datasets with minimal properties (3–5 small properties), use this formula: ``` -128 * N + 120 * M +128 * N + 120 * R ``` Where: - `N` = number of nodes -- `M` = number of relationships -The result gives you the dataset size in **bytes**. +- `R` = number of relationships +The result gives you the dataset size in **bytes**. The size of properties is ommitted as it will not impact the sizing. **Don’t know how many nodes or relationships you have?** @@ -174,7 +174,7 @@ Memgraph’s access to system resources. Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally -recommend using the **containerized version**. +recommend using the **containerized version (Docker, K8s manifests, or Helm charts)**. The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** @@ -186,7 +186,38 @@ performance, simplicity, and feature completeness. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set -blablah +Got it! Here's the revised paragraph with a more neutral tone regarding log streaming: + +--- + +## 6. Choosing the Right Memgraph Flag Set + +Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: + +- `--log-level` + This flag controls the granularity of Memgraph's logs. In **production environments**, setting it to `INFO` or `DEBUG` + is typically sufficient. For **diagnostics or experimentation**, using `TRACE` provides highly detailed logs + that can help in troubleshooting—but keep in mind this will significantly increase **disk usage**. + Memgraph **does not automatically delete old logs**, so you’ll need to manage log retention manually. This flag can be + adjusted at runtime using the `SET DATABASE SETTING 'log.level' TO 'value'` command. + +- `--also-log-to-stderr=true` + Enables logging to **standard error** in addition to log files. + + + Optionally, users can manually stream the logs to another system such as **Splunk**, or using a containerized setup where + logs are collected from standard output streams. + + +- `--storage-parallel-schema-recovery=true` and `--storage-recovery-thread-count=x` + These flags enable **parallel recovery** of the schema during startup. Set `x` to the number of cores you want to dedicate + for recovery. This significantly speeds up the time it takes for Memgraph to become operational after a restart. + +- `--memory-limit=x` (in MiB) + This flag defines the **maximum memory** Memgraph can use. By default, it's set to **90% of available system memory**, but if + you're running other processes on the same machine (like Memgraph Lab or others), it's advisable to **manually lower this limit**. + On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower + than the actual available memory on the node. ## 7. Importing Mechanisms blablah From e8353d3b1eb19b7a54f7b40d54f5ceaec0547824 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:56:40 +0200 Subject: [PATCH 06/54] Indentation --- pages/memgraph-in-production/general-suggestions.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 3f3404859..cd7557963 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -192,7 +192,8 @@ Got it! Here's the revised paragraph with a more neutral tone regarding log stre ## 6. Choosing the Right Memgraph Flag Set -Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: +Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important +flags to consider: - `--log-level` This flag controls the granularity of Memgraph's logs. In **production environments**, setting it to `INFO` or `DEBUG` From b63517e062d156c9f4f399d83b94d46e6d1987c2 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:58:39 +0200 Subject: [PATCH 07/54] Remove unnecessary comment --- pages/memgraph-in-production/general-suggestions.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index cd7557963..c2ad0d0a6 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -186,11 +186,6 @@ performance, simplicity, and feature completeness. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set -Got it! Here's the revised paragraph with a more neutral tone regarding log streaming: - ---- - -## 6. Choosing the Right Memgraph Flag Set Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: From cf434bb857ec5a38b932b0c6ed6dc9caf441dd73 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 13:16:02 +0200 Subject: [PATCH 08/54] Add enterprise, queries and import sections --- .../general-suggestions.mdx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index c2ad0d0a6..490d9c000 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -216,4 +216,38 @@ flags to consider: than the actual available memory on the node. ## 7. Importing Mechanisms -blablah + +Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether +you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. + +We strongly encourage users to review the [Best Practices for Data Migration](/data-migration/best-practices), +which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. + +## 8. Enterprise Features You Might Require + +Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: + +- **High Availability (HA)** and **automatic failover** for fault tolerance +- **System metrics monitoring** for observability and performance tracking +- **Role-based and fine-grained access control** for secure, multi-user environments +- **Multi-tenancy** for isolating and managing separate workloads within the same infrastructure +- **Advanced security features** to meet compliance and operational requirements + +There are additional enterprise capabilities as well, tailored for advanced performance, scalability, and security needs. +We recommend exploring the other chapters in the **"Memgraph in Production"** guide series, as they highlight how these +features can be aligned with your specific use cases. + + +## 9. Queries That Best Suit Your Workload + +Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, +Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for +performance-critical use cases. You can learn more about these in our +[Deep Path Traversal guide](https://memgraph.com/docs/advanced-algorithms/deep-path-traversal). + +For advanced analytics and utility functions, Memgraph also ships with the +**[MAGE library](https://memgraph.com/docs/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built +**graph algorithms** and **procedures** for tasks like community detection, centrality scoring, node similarity, and more. + +We encourage users to explore the other guides in the **"Memgraph in Production"** series, where you'll find detailed examples and +recommendations on which types of queries are most effective based on your specific **workload and use case**. From bfc44eb52ed1314026adc88e383ac7018b6bccad Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 18:36:23 +0200 Subject: [PATCH 09/54] Finish general suggestions guide --- .../general-suggestions.mdx | 124 +++++++++++++++--- 1 file changed, 105 insertions(+), 19 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 490d9c000..f4b92ab8a 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -22,7 +22,7 @@ Since Memgraph is an in-memory database, estimating RAM requirements is crucial. **3. [Hardware Configuration](#3-hardware-configuration)**
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking Configuration](#4-networking-options)**
+**4. [Networking Configuration](#4-networking-configuration)**
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. **5. [Deployment Options](#5-deployment-options)**
@@ -31,16 +31,21 @@ Understand the tradeoffs between running Memgraph natively on a host machine or **6. [Choosing the Right Memgraph Flag Set](#6-choosing-the-right-memgraph-flag-set)**
Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. -**7. [Importing Mechanisms](#7-importing-mechanisms)**
+**7. [Choosing the Right Memgraph Storage Mode](#7-choosing-the-right-memgraph-storage-mode)**
+Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIONAL** and **IN_MEMORY_ANALYTICAL** - as well as one +disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode +based on your specific use case. + +**8. [Importing Mechanisms](#8-importing-mechanisms)**
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**8. [Enterprise Features You Might Require](#8-enterprise-features-you-might-require)**
+**9. [Enterprise Features You Might Require](#9-enterprise-features-you-might-require)**
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**9. [Queries That Best Suit Your Workload](#9-queries-that-best-suit-your-workload)**
+**10. [Queries That Best Suit Your Workload](#10-queries-that-best-suit-your-workload)**
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, @@ -62,7 +67,7 @@ The most critical factor in provisioning a server for Memgraph is **RAM**. Memgr the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently and can scale with your workload. -For a deeper dive into how memory usage is calculated, refer to our [Memory Storage Guide](/fundamentals/storage-memory-usage). Below is a +For a deeper dive into how memory usage is calculated, refer to our [memory storage documentation](/fundamentals/storage-memory-usage). Below is a simplified, rule-of-thumb approach to help you estimate your memory needs. ### Memory Components @@ -114,7 +119,7 @@ based on how your data is actually represented as a graph.
#### Step 2: Estimate Property Storage -If your dataset includes many or complex properties, refer to the [Memory Storage Guide](/fundamentals/storage-memory-usage) for detailed +If your dataset includes many or complex properties, refer to the [memory storage documentation](/fundamentals/storage-memory-usage) for detailed calculations. Add this result to the base graph size from Step 1. #### Step 3: Add Index Overhead @@ -144,7 +149,7 @@ This setting ensures that the system can allocate enough virtual memory areas, w issues or unexpected crashes during Memgraph operations. You can find detailed setup instructions and context in our -[System Configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). +[system configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). If you're deploying Memgraph on Kubernetes, our Helm charts include an **init container** that automatically sets `vm.max_map_count` during startup. However, this container requires **root privileges** to execute. If you're running in a restricted environment @@ -172,16 +177,21 @@ Memgraph’s access to system resources. ## 5. Deployment Options -Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** -using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally -recommend using the **containerized version (Docker, K8s manifests, or Helm charts)**. +Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, +or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, +we generally recommend using the **containerized version**—whether via **Docker**, **Kubernetes manifests**, or **Helm charts**. + +When deploying via Helm, users can choose between: +- The **Memgraph Standalone Helm chart** – ideal if you're working with a **single instance** setup. +- The **Memgraph High Availability Helm chart** – designed for **production deployments** that require **redundancy and failover** + with multiple instances. -The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for -additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** +The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for +additional setup. In contrast, native installations require users to **compile C++ modules** and **manually install Python packages** to enable the full range of MAGE functionalities—an often complex and error-prone process for most users. For most use cases, especially during prototyping and production readiness, the containerized image provides the best balance between -performance, simplicity, and feature completeness. +**performance**, **simplicity**, and **feature completeness**. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). @@ -215,15 +225,69 @@ flags to consider: On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower than the actual available memory on the node. -## 7. Importing Mechanisms + +For more information on how to configure Memgraph, as well as what are all the flags that Memgraph can be configured on, please +check out the [configuration documentation page](/database-management/configuration). + + +## 7. Choosing the Right Memgraph Storage Mode + +Memgraph currently supports two fully-featured and production-ready storage modes: +- `IN_MEMORY_TRANSACTIONAL` +- `IN_MEMORY_ANALYTICAL` + +A third mode, `ON_DISK_TRANSACTIONAL`, is still **experimental** and lacks many production-grade features. +While it's part of Memgraph’s long-term roadmap, it’s **not recommended for production** use at this stage. + +For all current production deployments, users are strongly encouraged to choose either the **in-memory transactional** or +**in-memory analytical** mode, based on the nature of their workload. + +You can set the storage mode in two ways: +- Via a **Cypher query**: + ```cypher + STORAGE MODE IN_MEMORY_TRANSACTIONAL; + -- or -- + STORAGE MODE IN_MEMORY_ANALYTICAL; + ``` +- Or by using a **configuration flag** at startup: + ```cypher + --storage-mode=IN_MEMORY_TRANSACTIONAL + --storage-mode=IN_MEMORY_ANALYTICAL + ``` + +### Which Mode Should You Choose? + +- ✅ **Transactional Mode (`IN_MEMORY_TRANSACTIONAL`)** + Ideal for **mission-critical workloads** requiring **ACID guarantees**, **replication**, and **high availability**. + This mode supports safe concurrent access, durability, and rollback on failure. + Keep in mind that **write-write conflicts** can occur and may need to be retried on the driver side. + +- 🚀 **Analytical Mode (`IN_MEMORY_ANALYTICAL`)** + Suited for **read-only** workloads or **on-demand graph analytics** where you need **multithreaded ingestion at scale**. + This mode supports extremely high write throughput, even reaching **millions of writes per second**, thanks to parallelized graph construction. + However, it **does not support aborting or rolling back transactions**, as it does not track changes via delta objects. + Once a write is made, it becomes part of the current state. + + +**Rule of thumb**: +Use **analytical mode** for high-speed, read-heavy, or bulk-ingest scenarios. +Use **transactional mode** for anything requiring **reliability, consistency, or fault tolerance**. + + + +For more information about the implications of Memgraph storage mode offerings, please check out the +[storage mode documentation](/fundamentals/storage-memory-usage). + + +## 8. Importing Mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. -We strongly encourage users to review the [Best Practices for Data Migration](/data-migration/best-practices), +We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 8. Enterprise Features You Might Require +## 9. Enterprise Features You Might Require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -237,16 +301,38 @@ There are additional enterprise capabilities as well, tailored for advanced perf We recommend exploring the other chapters in the **"Memgraph in Production"** guide series, as they highlight how these features can be aligned with your specific use cases. +Memgraph Enterprise License is enabled by issueing the following queries: +``` +SET DATABASE SETTING 'organization.name' TO 'Organization'; +SET DATABASE SETTING 'enterprise.license' TO 'License'; +``` + +However, the recommended way of specifying the Enterprise License would be through environment variables through Docker or making +it visible to the process (if you're deploying Memgraph natively). + +``` +MEMGRAPH_ORGANIZATION_NAME=Organization +MEMGRAPH_ENTERPRISE_LICENSE=License +``` + + +Reason for that is because environment variables always override any system settings that are set via queries. + + +For more information about what Enterprise features are included with Memgraph Enterprise License, please check out the +[section on Memgraph Enterprise enablement](/database-management/enabling-memgraph-enterprise). + + -## 9. Queries That Best Suit Your Workload +## 10. Queries That Best Suit Your Workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for performance-critical use cases. You can learn more about these in our -[Deep Path Traversal guide](https://memgraph.com/docs/advanced-algorithms/deep-path-traversal). +[Deep Path Traversal guide](/advanced-algorithms/deep-path-traversal). For advanced analytics and utility functions, Memgraph also ships with the -**[MAGE library](https://memgraph.com/docs/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built +**[MAGE library](/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built **graph algorithms** and **procedures** for tasks like community detection, centrality scoring, node similarity, and more. We encourage users to explore the other guides in the **"Memgraph in Production"** series, where you'll find detailed examples and From d3c71fe3fb384fa120e78f24f6e0db47dc75aa45 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 18:45:37 +0200 Subject: [PATCH 10/54] Make under construction notes --- .../general-suggestions.mdx | 1 - .../memgraph-in-analytical-workloads.mdx | 312 +---------------- .../memgraph-in-cyber-security.mdx | 312 +---------------- .../memgraph-in-fraud-detection.mdx | 312 +---------------- .../memgraph-in-graphrag.mdx | 314 +----------------- .../memgraph-in-high-throughput-workloads.mdx | 312 +---------------- ...memgraph-in-mission-critical-workloads.mdx | 312 +---------------- .../memgraph-in-supply-chain.mdx | 314 +----------------- .../memgraph-in-transactional-workloads.mdx | 312 +---------------- 9 files changed, 50 insertions(+), 2451 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index f4b92ab8a..9ff0bf0ae 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -315,7 +315,6 @@ MEMGRAPH_ORGANIZATION_NAME=Organization MEMGRAPH_ENTERPRISE_LICENSE=License ``` - Reason for that is because environment variables always override any system settings that are set via queries. diff --git a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx index 36ba4e626..df745caa8 100644 --- a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in analytical workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your analytical use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in analytical workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **analytical use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx index 61150e187..7936ad123 100644 --- a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx +++ b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in Cyber Security -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your cyber security use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Cyber Security - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **cyber security use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx index b4723d541..8b238defa 100644 --- a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx +++ b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in Fraud Detection -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your fraud detection use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Fraud Detection - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **fraud detection use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx index fcacbad97..e7d6f0cdb 100644 --- a/pages/memgraph-in-production/memgraph-in-graphrag.mdx +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -1,315 +1,15 @@ --- -title: Memgraph in GraphRAG -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +title: Memgraph in GraphRAG use cases +description: Understand the implications of getting your GraphRAG use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in GraphRAG use cases - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **GraphRAG use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index 79bdb1f40..c2fe4fee6 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in high throughput workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your high throughput use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in high throguhput workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **high throughput use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx index fdf2a594b..9416c4dc1 100644 --- a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in mission critical workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your mission critical use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in mission critical workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **mission critical use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx index 25be5e450..c1eef33a7 100644 --- a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx +++ b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx @@ -1,315 +1,15 @@ --- -title: Memgraph in supply chain -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +title: Memgraph in Supply Chain workloads +description: Understand the implications of getting your supply chain use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Supply Chain - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **supply chain use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx index fcfaf59c4..6bdc20178 100644 --- a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in transactional workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your transactional use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in transactional workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **transactional use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. From 1a5097f8b6d8e250d431d1b3ebcfe14900a51534 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 19:02:46 +0200 Subject: [PATCH 11/54] Add todo --- pages/fundamentals/storage-memory-usage.mdx | 7 ++++--- pages/memgraph-in-production/general-suggestions.mdx | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index 57421458d..e6f0ef1d0 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -32,7 +32,8 @@ Memgraph supports three different storage modes: ### Start Memgraph with a specific storage mode By default, an empty instance will start using in-memory transactional storage -mode. To start Memgraph in the ON_DISK_TRANSACTIONAL or IN_MEMORY_ANALYTICAL +mode. +To start Memgraph in the `ON_DISK_TRANSACTIONAL` or `IN_MEMORY_ANALYTICAL` storage node, change the `--storage-mode` [configuration flag](/database-management/configuration) accordingly. @@ -58,7 +59,7 @@ mode. When switching modes, Memgraph will wait until all other transactions are done. If some other transactions are running in your system, you will receive a -warning message, so be sure to [set the log level to +warning message, so be sure to [set the log level at least to `WARNING`](/database-management/logs). Switching from the in-memory storage mode to the on-disk storage mode is allowed @@ -191,7 +192,7 @@ built-in behavior is as follows: * [procedures](/advanced-algorithms/run-algorithms#run-procedures-from-mage-library): skip all records that contain any deleted value * [functions](/querying/functions): return a null value -Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior. +**Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior.** Users developing [custom query procedures and functions](/custom-query-modules) intended to work in the analytical storage mode should use API methods to check if Memgraph is running diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 9ff0bf0ae..aae7bba7a 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -9,6 +9,12 @@ import { Callout } from 'nextra/components' This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. + +**TODO** +- Add backup suggestions +- Add property compression inside hardwware sizing estimations + + ## What is Covered? The general suggestions cover the following key areas: From 5de8cac6b64bf25a002aff7a96c273ccd04d702b Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 14:33:16 +0200 Subject: [PATCH 12/54] Update property sizes --- pages/fundamentals/storage-memory-usage.mdx | 46 ++++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index e6f0ef1d0..73f28eb63 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -441,7 +441,7 @@ Estimating Memgraph's storage memory usage is not entirely straightforward because it depends on a lot of variables, but it is possible to do so quite accurately. -If you want to **estimate** memory usage in IN_MEMORY_TRANSACTIONAL storage mode, use the following formula: +If you want to **estimate** memory usage in `IN_MEMORY_TRANSACTIONAL` storage mode, use the following formula: $\texttt{StorageRAMUsage} = \texttt{NumberOfVertices} \times 212\text{B} + \texttt{NumberOfEdges} \times 162\text{B}$ @@ -476,7 +476,7 @@ that the formula is correct. ### The calculation in detail -Let's dive deeper into the IN_MEMORY_TRANSACTIONAL storage mode memory usage +Let's dive deeper into the `IN_MEMORY_TRANSACTIONAL` storage mode memory usage values. Because Memgraph works on the x86 architecture, calculations are based on the x86 Linux memory usage. @@ -558,19 +558,35 @@ value. So the layout of each property is: $\texttt{propertySize} = \texttt{basicMetadata} + \texttt{propertyID} + [\texttt{additionalMetadata}] + \texttt{value}.$ -| Value type | Size | Note -|-----------------------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -|`NULL` | 1B + 1B | The value is written in the first byte of the basic metadata. | -|`BOOL` | 1B + 1B | The value is written in the first byte of the basic metadata. | -|`INT` | 1B + 1B + 1B, 2B, 4B or 8B | Basic metadata, property ID and the value depending on the size of the integer. | -|`DOUBLE` | 1B + 1B + 8B | Basic metadata, property ID and the value | -|`STRING` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and lastly the value depending on the size of the string, where 1 ASCII character in the string takes up 1B. | -|`LIST` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the list. | -|`MAP` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the map. | -|`TEMPORAL_DATA` | 1B + 1B + 1B + min 1B + min 1B | Basic metadata, property ID, additional metadata, seconds, microseconds. Value of the seconds and microseconds is at least 1B, but probably 4B in most cases due to the large values they store. | -|`ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 1B + min 1B | Like `TEMPORAL_DATA`, but followed by timezone name length (1 byte) and string (1 byte per character). | -|`OFFSET_ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 2B | Like `TEMPORAL_DATA`, but followed by the offset from UTC (in minutes; always 2 bytes). | -|`ENUM` | 1B + 1B + 2B, 4B, 8B or 16B | Basic metadata, property ID and the value depending on required size representation required. | +| Value type | Sizing in detail | Total estimated size | Note +|-----------------------------|--------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|`NULL` | 0B | 0B | Memgraph treats null values same as if they're not present. Therefore, `NULL` values are not stored in the property store. | +|`BOOL` | 1B + 1B | 2B | The value is written in the first byte of the basic metadata. | +|`INT` | 1B + 1B + 1B, 2B, 4B or 8B | 3B - 10B | Basic metadata, property ID and the value depending on the size of the integer. | +|`DOUBLE` | 1B + 1B + 8B | 10B | Basic metadata, property ID and the value | +|`STRING` | 1B + 1B + 1B + min 1B | at least 4B | Basic metadata, property ID, additional metadata and lastly the value depending on the size of the string, where 1 ASCII character in the string takes up 1B. | +|`LIST` | 1B + 1B + 1B + min 1B | at least 4B (empty) | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the list. | +|`MAP` | 1B + 1B + 1B + min 1B | at least 4B (empty) | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the map. | +|`TEMPORAL_DATA` | 1B + 1B + 1B + min 1B + min 1B | 12B | Basic metadata, property ID, additional metadata, seconds, microseconds. Value of the seconds and microseconds is at least 1B, but probably 4B in most cases due to the large values they store. | +|`ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 1B + min 1B | at least 14B | Like `TEMPORAL_DATA`, but followed by timezone name length (1 byte) and string (1 byte per character). | +|`OFFSET_ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 2B | at least 14B | Like `TEMPORAL_DATA`, but followed by the offset from UTC (in minutes; always 2 bytes). | +|`ENUM` | 1B + 1B + 2B, 4B, 8B or 16B | 4B - 18B | Basic metadata, property ID and the value depending on required size representation required. | + +Users can additionally inspect the exact size of a property in bytes using the [propertySize()](/querying/functions#scalar-functions) function. +The query usage is the following: +```cypher +MATCH (n) +RETURN n.id AS id, propertySize(n, "id") AS prop_size_bytes; +``` + +Output: +```output ++-----------+-----------------+ +| id | prop_size_bytes | ++-----------+-----------------+ +| 1 | 3 | ++-----------+-----------------+ +``` ### Marvel dataset use case From d63d9d164d98a4079b9b6941c0642cf30ac894dc Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 14:52:05 +0200 Subject: [PATCH 13/54] Add backup considerations --- .../general-suggestions.mdx | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index aae7bba7a..9cf6e55d9 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -7,13 +7,10 @@ import { Callout } from 'nextra/components' # General Suggestions -This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. - - -**TODO** -- Add backup suggestions -- Add property compression inside hardwware sizing estimations - +This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. +It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. +Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the +*Memgraph in Production* series for tailored recommendations. ## What is Covered? @@ -42,16 +39,19 @@ Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIO disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode based on your specific use case. -**8. [Importing Mechanisms](#8-importing-mechanisms)**
+**8. [Backup considerations](#8-backup-considerations)**
+Learn about how to preserve your data in Memgraph to prevent any data loss. + +**9. [Importing Mechanisms](#9-importing-mechanisms)**
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**9. [Enterprise Features You Might Require](#9-enterprise-features-you-might-require)**
+**10. [Enterprise Features You Might Require](#10-enterprise-features-you-might-require)**
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**10. [Queries That Best Suit Your Workload](#10-queries-that-best-suit-your-workload)**
+**11. [Queries That Best Suit Your Workload](#11-queries-that-best-suit-your-workload)**
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, @@ -285,7 +285,32 @@ For more information about the implications of Memgraph storage mode offerings, [storage mode documentation](/fundamentals/storage-memory-usage).
-## 8. Importing Mechanisms +## 8. Backup Considerations + +Ensuring data durability and having a solid backup strategy is essential for any production deployment. +Memgraph provides built-in mechanisms for creating **snapshots** and **write-ahead logs (WALs)** to help +you recover from failures or restarts. + +By default, Memgraph retains the **latest 3 snapshots**, but this can be adjusted using the `--storage-snapshot-retention-count=x` +flag. Older snapshots beyond this limit are automatically deleted to manage storage space. + +During recovery, Memgraph first restores the **latest snapshot**, followed by a **replay of the WALs**: +- **Snapshot recovery** can be parallelized for faster startup using: + - `--storage-parallel-schema-recovery=true` + - `--storage-recovery-thread-count=x` (where `x` is the number of threads used) +- **WALs are recovered sequentially**, and this step cannot yet be parallelized. + + +🔒 Memgraph does **not yet support automatic backups** to third-party storage solutions like AWS S3 or GCP buckets. +For now, users are encouraged to implement **manual redundancy** by syncing snapshots to external locations using +tools such as [**rclone**](https://rclone.org/), which is already in use by several Memgraph customers for this purpose. + + +For more detailed information, refer to: +- [Data Durability Fundamentals](/fundamentals/data-durability) +- [Backup and Restore Documentation](/database-management/backup-and-restore) + +## 9. Importing Mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. @@ -293,7 +318,7 @@ you're working with CSV files, JSON streams, Kafka topics, or external data sour We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 9. Enterprise Features You Might Require +## 10. Enterprise Features You Might Require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -329,7 +354,7 @@ For more information about what Enterprise features are included with Memgraph E -## 10. Queries That Best Suit Your Workload +## 11. Queries That Best Suit Your Workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for From fd35d832cc4b699d8bba6b724c8aa4f0b2918052 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 15:01:14 +0200 Subject: [PATCH 14/54] Added overview page --- pages/memgraph-in-production.mdx | 95 ++----- pages/memgraph-in-production/overview.mdx | 315 ---------------------- 2 files changed, 20 insertions(+), 390 deletions(-) delete mode 100644 pages/memgraph-in-production/overview.mdx diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 9c0a255cb..55849a1b5 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -7,86 +7,31 @@ import {CommunityLinks} from '/components/social-card/CommunityLinks' # Memgraph in Production -When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, scalability, and resilience. -This includes hardware considerations, the correct sizing of instances, configuring drivers, using appropriate flags when starting Memgraph, -importing data, and connecting to external sources. The guidelines in this section will help you make informed decisions for your -specific use case, ensuring that Memgraph performs effectively in your environment. +When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, +scalability, and resilience. This includes hardware considerations, the correct sizing of instances, configuring drivers, using +appropriate flags when starting Memgraph, importing data, and connecting to external sources. The guidelines in this section will +help you make informed decisions for your specific use case, ensuring that Memgraph performs effectively in your environment. -## Prerequisites for Deployment +## How to use the Memgraph in Production guides -Before deploying Memgraph, there are several essential considerations to be aware of: +To get started, we recommend first reading the **General Suggestions** section, which outlines practices that are **agnostic to specific use cases**. +These are foundational principles that apply broadly across most production setups. Each separate guide in the **"Memgraph in Production"** +series focuses on a particular type of workload or deployment scenario. At the beginning of each guide, you’ll find information about +**when that use case is a good fit for your needs**. The specific recommendations in those guides **override** anything written in the +general suggestions when there's a conflict—so always defer to the targeted guide when applicable. -- **Hardware Considerations**: Memgraph is an in-memory graph database, so allocating sufficient memory (RAM) is crucial. Ensure that your system meets the memory requirements of your dataset and any indexes. A sufficient number of CPU cores and fast storage are also important for overall performance. -- **Sizing the Instance**: The size of your instance should depend on the expected dataset size, the number of concurrent queries, and the complexity of graph traversals. Larger datasets or high query volumes may require scaling horizontally across multiple nodes. -- **Driver Considerations**: Memgraph supports several client libraries, including Python, C++, and JavaScript. Choose the correct driver for your application based on language compatibility and performance requirements. -- **Flags for Starting Memgraph**: When starting Memgraph, certain flags can optimize performance. These may include memory limits, persistence settings, and indexing options. Always tailor these based on the type of workload. -- **Importing Data**: Memgraph supports various ways to import data, including batch loading from CSVs, using Cypher queries, or leveraging streaming data sources. -- **Connecting to External Sources**: In production, Memgraph often needs to interact with external systems like data pipelines, message brokers, and other databases. Ensure that Memgraph’s connection configurations (e.g., network settings, authentication) align with external sources. -## Deployment Choices Based on Workload +## 📚 Available Guides in the *Memgraph in Production* Series -### General Recommendations +- [General Suggestions](/memgraph-in-production/general-suggestions) +- [Memgraph in transactional workloads](/memgraph-in-production/memgraph-in-transactional-workloads) +- [Memgraph in analytical workloads](/memgraph-in-production/memgraph-in-analytical-workloads) +- [Memgraph in mission critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) +- [Memgraph in high throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) +- [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) +- [Memgraph in Supply Chain use cases](/memgraph-in-production/memgraph-in-supply-chain) +- [Memgraph in Cyber Security use cases](/memgraph-in-production/memgraph-in-cyber-security) +- [Memgraph in Fraud Detection use cases](/memgraph-in-production/memgraph-in-fraud-detection) -For general deployments of Memgraph, focus on optimizing the following: - -- **Memory**: Ensure sufficient RAM for graph data and indices. -- **CPU**: Adequate core count to handle parallel queries. -- **Persistence**: Configure persistence according to the durability needs of your data. -- **Indexing**: Set up appropriate indexes for frequently queried nodes and relationships. -- **Replication**: For high availability, enable replication to safeguard against potential failures. - -### Memgraph in High Throughput Workloads - -In high throughput scenarios, such as real-time analytics or fast decision-making applications, you should focus on the following: - -- **High Availability**: Set up a replication cluster for fault tolerance and to ensure service continuity. -- **Memory Sizing**: Ensure that your system has enough memory to hold your graph in memory and avoid swapping to disk, which would slow down performance. -- **Query Optimization**: Optimize your queries for high performance, and enable result caching for frequent queries. -- **Load Balancing**: Distribute traffic across multiple instances to avoid overloading a single node. - -### Memgraph in Analytical Workloads - -For analytical workloads, such as running complex queries or machine learning graph algorithms, consider the following: - -- **Instance Size**: Use larger instances with more RAM and CPU to support the heavy lifting of complex queries. -- **Persistence**: You may want to enable persistence for long-term storage of analytical results. -- **Query Tuning**: Optimize graph traversal queries and ensure that indexing is set up to cover common analytical queries. -- **Batch Processing**: If your workload involves batch processing, ensure that Memgraph is configured to handle large imports efficiently without overloading the system. - -### Memgraph in Transactional Workloads - -For transactional workloads, where low-latency and high consistency are key, prioritize the following: - -- **Transaction Durability**: Ensure proper settings for transaction logs and snapshots for data durability. -- **Replication**: Set up a high-availability cluster with replication to ensure that transactions are available across nodes. -- **Network Configuration**: Minimize network latency between Memgraph instances to ensure consistent transaction processing. -- **Memory Allocation**: Tune memory allocation for optimal transaction throughput. - -### Memgraph in GraphRAG - -In GraphRAG (Graph-based Retrieval-Augmented Generation) applications, where the graph data is used to enrich AI model generation or queries, the following considerations are important: - -- **Data Import**: Use bulk data import methods or streaming data pipelines for continuous graph updates. -- **Embedding Integration**: Ensure that your graph data is linked to machine learning models and that embeddings are stored efficiently. -- **Latency**: Minimize query and response time, especially for real-time generation. -- **Model Integration**: Seamlessly integrate Memgraph with model inference pipelines to improve the accuracy of AI-driven responses. - -### Memgraph in Cybersecurity - -For cybersecurity workloads, which involve detecting threats, managing incident data, and analyzing network traffic, consider the following: - -- **Data Ingestion**: Configure Memgraph to handle high-speed log ingestion, including event data from firewalls, intrusion detection systems, and other sources. -- **Scalability**: Ensure that Memgraph can scale horizontally as the volume of data increases over time. -- **High Availability**: Set up high availability and replication to ensure that data is accessible at all times, especially in a high-velocity attack detection environment. -- **Real-time Analysis**: Optimize Memgraph for real-time graph traversal to detect anomalous behaviors quickly. - -### Memgraph in Fraud Detection - -For fraud detection workloads, which rely on analyzing transactions and behaviors to detect fraud patterns, focus on the following: - -- **Real-Time Processing**: Ensure Memgraph is optimized for low-latency queries to detect fraudulent activity in real time. -- **Data Import**: Configure Memgraph to efficiently ingest transaction data from external sources, including financial systems and databases. -- **Graph Algorithms**: Use graph algorithms, such as PageRank or community detection, to identify fraud patterns based on relational data. -- **Persistence and Backups**: Maintain robust backup strategies to ensure that transaction data is safely stored and retrievable. diff --git a/pages/memgraph-in-production/overview.mdx b/pages/memgraph-in-production/overview.mdx deleted file mode 100644 index 2e61c61b7..000000000 --- a/pages/memgraph-in-production/overview.mdx +++ /dev/null @@ -1,315 +0,0 @@ ---- -title: Overview -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. ---- - -import { Callout } from 'nextra/components' - -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. - - - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - - - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. From 0042907f2827e50c47076622c9c0c1b894dbcf57 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:04:09 +0200 Subject: [PATCH 15/54] Add hardware sizing --- pages/_meta.ts | 3 +- pages/getting-started/install-memgraph.mdx | 23 +- pages/memgraph-in-production.mdx | 92 +++++ pages/memgraph-in-production/_meta.ts | 12 + .../general-suggestions.mdx | 148 ++++++++ .../memgraph-in-analytical-workloads.mdx | 315 ++++++++++++++++++ .../memgraph-in-cyber-security.mdx | 315 ++++++++++++++++++ .../memgraph-in-fraud-detection.mdx | 315 ++++++++++++++++++ .../memgraph-in-graphrag.mdx | 315 ++++++++++++++++++ .../memgraph-in-high-throughput-workloads.mdx | 315 ++++++++++++++++++ ...memgraph-in-mission-critical-workloads.mdx | 315 ++++++++++++++++++ .../memgraph-in-supply-chain.mdx | 315 ++++++++++++++++++ .../memgraph-in-transactional-workloads.mdx | 315 ++++++++++++++++++ pages/memgraph-in-production/overview.mdx | 315 ++++++++++++++++++ 14 files changed, 3102 insertions(+), 11 deletions(-) create mode 100644 pages/memgraph-in-production.mdx create mode 100644 pages/memgraph-in-production/_meta.ts create mode 100644 pages/memgraph-in-production/general-suggestions.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-cyber-security.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-fraud-detection.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-graphrag.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-supply-chain.mdx create mode 100644 pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx create mode 100644 pages/memgraph-in-production/overview.mdx diff --git a/pages/_meta.ts b/pages/_meta.ts index 8df3472ce..67ec83c52 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -15,5 +15,6 @@ export default { "clustering": "Clustering", "data-streams": "Data streams", "help-center": "Help center", - "release-notes": "Release notes" + "release-notes": "Release notes", + "memgraph-in-production": "Memgraph in production" } \ No newline at end of file diff --git a/pages/getting-started/install-memgraph.mdx b/pages/getting-started/install-memgraph.mdx index 4dbd86dce..467fb533d 100644 --- a/pages/getting-started/install-memgraph.mdx +++ b/pages/getting-started/install-memgraph.mdx @@ -109,17 +109,20 @@ Before running Memgraph, please check the [system configuration guidelines](/dat Below are minimum and recommended system requirements for installing Memgraph. -| | Minimum | Recommended | -| ------- | -------- | ------------------------------ | +| | Minimum | Recommended | +| ------- | -------- | ------------------------------- | | CPU | Server or desktop processor:
Intel Xeon
AMD Opteron/Epyc
ARM machines or Apple M1
Amazon Graviton | Server processor:
Intel Xeon
AMD Opteron/Epyc
ARM machines or Apple M1
Amazon Graviton | -| RAM | 1 GB | ≄ 16 GB ECC | -| Disk | 1 GB | equally as RAM | -| Cores | 1 vCPU | ≄ 8 vCPUs (≄ 4 physical cores) | -| Network | 100 Mbps | ≄ 1 Gbps | - -The disk is used for storing database [durability -files](/configuration/data-durability-and-backup) - snapshots and write-ahead -logs. +| RAM | 1 GB | ≄ 16 GB ECC | +| Disk | 1 GB | at least 3x the amount of RAM (NVMe SSD) | +| Cores | 1 vCPU | ≄ 8 vCPUs (≄ 4 physical cores) | +| Network | 100 Mbps | ≄ 1 Gbps | + +The disk is used for storing database [durability files](/configuration/data-durability-and-backup) - snapshots and write-ahead +logs. By default, Memgraph stores 3 latest snapshots in the database (flag to adjust this is `--storage-snapshot-retention-count`). +Snapshots size is usually less than the amount of RAM that is needed to load the data into memory. + +The amount of CPU cores varies per use case. For horizontal scalability, you can further increase the amount of cores on your +system for additional scalability. Check out how the [storage memory](/fundamentals/storage-memory-usage) is used, and calculate memory requirements based on your data. diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx new file mode 100644 index 000000000..9c0a255cb --- /dev/null +++ b/pages/memgraph-in-production.mdx @@ -0,0 +1,92 @@ +--- +title: Memgraph in Production +description: Learn how to deploy Memgraph in production for your workload and consider all the advices directly from the Memgraph Team based on our multi-year experiences. +--- + +import {CommunityLinks} from '/components/social-card/CommunityLinks' + +# Memgraph in Production + +When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, scalability, and resilience. +This includes hardware considerations, the correct sizing of instances, configuring drivers, using appropriate flags when starting Memgraph, +importing data, and connecting to external sources. The guidelines in this section will help you make informed decisions for your +specific use case, ensuring that Memgraph performs effectively in your environment. + +## Prerequisites for Deployment + +Before deploying Memgraph, there are several essential considerations to be aware of: + +- **Hardware Considerations**: Memgraph is an in-memory graph database, so allocating sufficient memory (RAM) is crucial. Ensure that your system meets the memory requirements of your dataset and any indexes. A sufficient number of CPU cores and fast storage are also important for overall performance. +- **Sizing the Instance**: The size of your instance should depend on the expected dataset size, the number of concurrent queries, and the complexity of graph traversals. Larger datasets or high query volumes may require scaling horizontally across multiple nodes. +- **Driver Considerations**: Memgraph supports several client libraries, including Python, C++, and JavaScript. Choose the correct driver for your application based on language compatibility and performance requirements. +- **Flags for Starting Memgraph**: When starting Memgraph, certain flags can optimize performance. These may include memory limits, persistence settings, and indexing options. Always tailor these based on the type of workload. +- **Importing Data**: Memgraph supports various ways to import data, including batch loading from CSVs, using Cypher queries, or leveraging streaming data sources. +- **Connecting to External Sources**: In production, Memgraph often needs to interact with external systems like data pipelines, message brokers, and other databases. Ensure that Memgraph’s connection configurations (e.g., network settings, authentication) align with external sources. + +## Deployment Choices Based on Workload + +### General Recommendations + +For general deployments of Memgraph, focus on optimizing the following: + +- **Memory**: Ensure sufficient RAM for graph data and indices. +- **CPU**: Adequate core count to handle parallel queries. +- **Persistence**: Configure persistence according to the durability needs of your data. +- **Indexing**: Set up appropriate indexes for frequently queried nodes and relationships. +- **Replication**: For high availability, enable replication to safeguard against potential failures. + +### Memgraph in High Throughput Workloads + +In high throughput scenarios, such as real-time analytics or fast decision-making applications, you should focus on the following: + +- **High Availability**: Set up a replication cluster for fault tolerance and to ensure service continuity. +- **Memory Sizing**: Ensure that your system has enough memory to hold your graph in memory and avoid swapping to disk, which would slow down performance. +- **Query Optimization**: Optimize your queries for high performance, and enable result caching for frequent queries. +- **Load Balancing**: Distribute traffic across multiple instances to avoid overloading a single node. + +### Memgraph in Analytical Workloads + +For analytical workloads, such as running complex queries or machine learning graph algorithms, consider the following: + +- **Instance Size**: Use larger instances with more RAM and CPU to support the heavy lifting of complex queries. +- **Persistence**: You may want to enable persistence for long-term storage of analytical results. +- **Query Tuning**: Optimize graph traversal queries and ensure that indexing is set up to cover common analytical queries. +- **Batch Processing**: If your workload involves batch processing, ensure that Memgraph is configured to handle large imports efficiently without overloading the system. + +### Memgraph in Transactional Workloads + +For transactional workloads, where low-latency and high consistency are key, prioritize the following: + +- **Transaction Durability**: Ensure proper settings for transaction logs and snapshots for data durability. +- **Replication**: Set up a high-availability cluster with replication to ensure that transactions are available across nodes. +- **Network Configuration**: Minimize network latency between Memgraph instances to ensure consistent transaction processing. +- **Memory Allocation**: Tune memory allocation for optimal transaction throughput. + +### Memgraph in GraphRAG + +In GraphRAG (Graph-based Retrieval-Augmented Generation) applications, where the graph data is used to enrich AI model generation or queries, the following considerations are important: + +- **Data Import**: Use bulk data import methods or streaming data pipelines for continuous graph updates. +- **Embedding Integration**: Ensure that your graph data is linked to machine learning models and that embeddings are stored efficiently. +- **Latency**: Minimize query and response time, especially for real-time generation. +- **Model Integration**: Seamlessly integrate Memgraph with model inference pipelines to improve the accuracy of AI-driven responses. + +### Memgraph in Cybersecurity + +For cybersecurity workloads, which involve detecting threats, managing incident data, and analyzing network traffic, consider the following: + +- **Data Ingestion**: Configure Memgraph to handle high-speed log ingestion, including event data from firewalls, intrusion detection systems, and other sources. +- **Scalability**: Ensure that Memgraph can scale horizontally as the volume of data increases over time. +- **High Availability**: Set up high availability and replication to ensure that data is accessible at all times, especially in a high-velocity attack detection environment. +- **Real-time Analysis**: Optimize Memgraph for real-time graph traversal to detect anomalous behaviors quickly. + +### Memgraph in Fraud Detection + +For fraud detection workloads, which rely on analyzing transactions and behaviors to detect fraud patterns, focus on the following: + +- **Real-Time Processing**: Ensure Memgraph is optimized for low-latency queries to detect fraudulent activity in real time. +- **Data Import**: Configure Memgraph to efficiently ingest transaction data from external sources, including financial systems and databases. +- **Graph Algorithms**: Use graph algorithms, such as PageRank or community detection, to identify fraud patterns based on relational data. +- **Persistence and Backups**: Maintain robust backup strategies to ensure that transaction data is safely stored and retrievable. + + diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts new file mode 100644 index 000000000..238645804 --- /dev/null +++ b/pages/memgraph-in-production/_meta.ts @@ -0,0 +1,12 @@ +export default { + "overview": "Overview", + "general-suggestions": "General suggestions", + "memgraph-in-transactional-workloads": "Memgraph in transactional workloads", + "memgraph-in-analytical-workloads": "Memgraph in analytical workloads", + "memgraph-in-high-throughput-workloads": "Memgraph in high throughput workloads", + "memgraph-in-graphrag": "Memgraph in GraphRAG", + "memgraph-in-fraud-detection": "Memgraph in Fraud Detection", + "memgraph-in-cyber-security": "Memgraph in Cyber Security", + "memgraph-in-supply-chain": "Memgraph in Supply Chain", + "memgraph-in-mission-critical-workloads": "Memgraph in Mission Critical workloads", +} \ No newline at end of file diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx new file mode 100644 index 000000000..2bbdc075d --- /dev/null +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -0,0 +1,148 @@ +--- +title: General suggestions +description: General suggestions when working with Memgraph, from testing to production. +--- + +import { Callout } from 'nextra/components' + +# General Suggestions + +This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. + +## What is Covered? + +The general suggestions cover the following key areas: + +**1. [Hardware Requirements for running Memgraph](#1-hardware-requirements-for-running-memgraph)**
+Learn how to select the best machine for Memgraph based on your resources, whether on-prem, in a data center, or via cloud offerings (e.g., AWS, GCP, Azure). + +**2. [Hardware Sizing](#2-hardware-sizing)**
+Since Memgraph is an in-memory database, estimating RAM requirements is crucial. This section helps you allocate memory based on dataset size and expected workloads. + +**3. [Hardware Configuration](#3-hardware-configuration)**
+Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. + +**4. [Networking Options](#4-networking-options)**
+Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. + +**5. [Deployment Options](#5-deployment-options)**
+Understand the tradeoffs between running Memgraph natively on a host machine or in a containerized environment (Docker, K8s) to choose the best deployment method. + +**6. [Choosing the Right Memgraph Flag Set](#6-choosing-the-right-memgraph-flag-set)**
+Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. + +**7. [Importing Mechanisms](#7-importing-mechanisms)**
+Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. + +**8. [Enterprise Features You Might Require](#8-enterprise-features-you-might-require)**
+Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. +Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, +multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet +the demands of enterprise workloads. + +**9. [Queries That Best Suit Your Workload](#9-queries-that-best-suit-your-workload)**
+The type of queries you use can significantly affect performance, especially as the dataset grows or the workload +complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload +scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, +which will be covered in the specific use case sections. + +## Bringing Memgraph to Production + +## 1. Hardware Requirements for Running Memgraph + +To get started with Memgraph, we recommend checking the [system requirements](/getting-started/install-memgraph#system-requirements) listed +in our installation guide. These requirements are sufficient for setting up Memgraph on your own servers. + +If you're deploying Memgraph using a cloud provider, visit the [deployment section](/deployment) for guidance on choosing the appropriate +instance type for your specific cloud environment. + +## 2. Hardware Sizing + +The most critical factor in provisioning a server for Memgraph is **RAM**. Memgraph operates primarily in **in-memory mode**, meaning +the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently +and can scale with your workload. + +For a deeper dive into how memory usage is calculated, refer to our [Memory Storage Guide](/fundamentals/storage-memory-usage). Below is a +simplified, rule-of-thumb approach to help you estimate your memory needs. + +### Memory Components + +Memgraph’s memory usage consists of five main parts: + +1. **Node Memory** + Each node requires approximately **128 bytes**. + +2. **Relationship Memory** + Each relationship requires approximately **120 bytes**. + +3. **Property Storage** + Properties are stored in buffered arrays. The memory needed depends on property types and counts. + +4. **Indices** + Additional overhead is introduced by indexing node labels, edge types, and properties. + +5. **Query Execution Memory** + Temporary memory required to execute queries. This varies depending on the complexity of queries. + + +Items 1–4 are referred to as **memory at rest**, while query execution memory is often called **compute memory**. + + +### RAM Sizing Guidelines + +Use the following steps to estimate the RAM required for your workload: + +#### Step 1: Estimate Base Graph Storage +For datasets with minimal properties (3–5 small properties), use this formula: +``` +128 * N + 120 * M +``` +Where: +- `N` = number of nodes +- `M` = number of relationships +The result gives you the dataset size in **bytes**. + + +**Don’t know how many nodes or relationships you have?** +If your data is in a non-graph format (e.g., CSV), estimating the number of nodes and relationships isn’t always straightforward. +For example, one line in a CSV file could represent a node, a relationship, or a mix of both. In some cases, nodes and relationships are +split into separate CSV files. + +In such cases, we recommend importing a **sample of the dataset** (around 10%) into Memgraph and using the memory usage for that portion +to extrapolate the estimated requirements for the full dataset. This empirical method provides a more accurate and context-aware estimate +based on how your data is actually represented as a graph. + + +#### Step 2: Estimate Property Storage +If your dataset includes many or complex properties, refer to the [Memory Storage Guide](/fundamentals/storage-memory-usage) for detailed +calculations. Add this result to the base graph size from Step 1. + +#### Step 3: Add Index Overhead +- If you use fewer than 10 indices, this can be skipped. +- If you use many indices (e.g., 50+), add **~20% memory overhead** to the total from Steps 1 and 2. + +#### Step 4: Add Query Execution Memory +- For basic querying without graph algorithms: **1.5× multiplier** +- For analytical workloads with algorithms (e.g., PageRank, Community Detection, Betweenness Centrality): **2× multiplier** + +### Final Recommendation + +Once you've completed these steps: + +- Round up your estimate to match available server configurations. +- Example: If your estimate is **96GB**, choose a server with **128GB RAM** for safe headroom. + +### 3. Hardware Configuration +blablah + +### 4. Networking Options +blablah + +### 5. Deployment Options +blablah + +### 6. Choosing the right Memgraph flag set +blablah + +### 7. Importing Mechanisms +blablah diff --git a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx new file mode 100644 index 000000000..36ba4e626 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in analytical workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx new file mode 100644 index 000000000..61150e187 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in Cyber Security +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx new file mode 100644 index 000000000..b4723d541 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in Fraud Detection +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx new file mode 100644 index 000000000..fcacbad97 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in GraphRAG +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx new file mode 100644 index 000000000..79bdb1f40 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in high throughput workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx new file mode 100644 index 000000000..fdf2a594b --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in mission critical workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx new file mode 100644 index 000000000..25be5e450 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in supply chain +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx new file mode 100644 index 000000000..fcfaf59c4 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx @@ -0,0 +1,315 @@ +--- +title: Memgraph in transactional workloads +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/overview.mdx b/pages/memgraph-in-production/overview.mdx new file mode 100644 index 000000000..2e61c61b7 --- /dev/null +++ b/pages/memgraph-in-production/overview.mdx @@ -0,0 +1,315 @@ +--- +title: Overview +description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +--- + +import { Callout } from 'nextra/components' + +# Constraints + +In modern database systems, ensuring data integrity and reducing redundancy is +paramount. Constraints play a pivotal role, ensuring that only valid data is +entered into the database upon commit. The following chapters delve deep into two +fundamental types of constraints, existence and uniqueness. + +The existence constraint ensures the presence of specific data within the +database, while the uniqueness constraint ensures that specific label-property +pairs remain unique across entries. + +## Existence constraint + +Existence constraint enforces that each node with a specific label must also +have a certain property. Only one label and property can be supplied at a time. + +This constraint can be enforced using the following language construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will +yield an error. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); +``` + +### Example + +If the database is used to hold basic employee information, each +employee should have a first name and a last name. You can enforce this by +running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| exists | Employee | first_name | +| exists | Employee | last_name | ++-----------------+-----------------+-----------------+ +``` + +To drop the created constraints use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); +DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Uniqueness constraint + +The uniqueness constraint enforces that each label-property pair is unique. You can +also, specify multiple properties when creating uniqueness constraints. + + + +Adding a uniqueness constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The uniqueness constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; +``` + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `Unable to commit due to unique constraint violation on +:Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; +``` + +### Example + +If the database is used to hold basic employee information, each employee should +have a unique id and email. You can enforce this by running the following query: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +``` + +The `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | ++-----------------+-----------------+-----------------+ +``` + +To specify multiple properties when creating uniqueness +constraints, list them one after the other: + +```cypher +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +At this point, `SHOW CONSTRAINT INFO;` yields the following result: + +``` ++-----------------+-----------------+-----------------+ +| constraint type | label | properties | ++-----------------+-----------------+-----------------+ +| unique | Employee | id | +| unique | Employee | email | +| unique | Employee | name, address | ++-----------------+-----------------+-----------------+ +``` + +This means that two employees could have the same name **or** the same address, +but they can not have the same name **and** the same address. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + +## Data type constraint + +The data type constraint enforces that each label-property pair is of a certain data type. + + + +Adding a data type constraint does not create a [label-property +index](/fundamentals/indexes), it needs to be added manually. + + + +The data type constraint can be enforced using the following language +construct: + +```cypher +CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +The supported data types are: + +| Data type | +| ------------------- | +| `NULL` | +| `STRING` | +| `BOOLEAN` | +| `INTEGER` | +| `FLOAT` | +| `LIST` | +| `MAP` | +| `DURATION` | +| `DATE` | +| `LOCALTIME` | +| `LOCALDATETIME` | +| `ZONEDDATETIME` | +| `ENUM` | +| `POINT` | + +To confirm that the constraint was successfully created use the following query: + +```cypher +SHOW CONSTRAINT INFO; +``` + +Trying to modify the database in a way that violates the constraint will yield +an error `IS TYPED DATA_TYPE violation on Label(property)`. + +Constraints are dropped using the `DROP` clause: + +```cypher +DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; +``` + +You can only have one data type constraint on a given label-property pair. +Attempting to create a second data type constraint on a given label-property pair will yield an error +`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. +Attempting to drop a data type constraint which doesn't exist will yield an error +`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. + + + +Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. + + + + +### Example + +If the database is used to hold basic information about a person like their name and age +you can enforce the name to be a string and the age to be an integer by running the following queries: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Then `SHOW CONSTRAINT INFO;` should return the following result: + +``` ++-----------------+--------------+-----------------+-------------+ +| constraint type | label | properties | data_type | ++-----------------+--------------+-----------------+-------------| +| data_type | Person | name | STRING | +| data_type | Person | age | INTEGER | ++-----------------+--------------+-----------------+-------------| +``` + +Creating a person with +```cypher +CREATE (:Person {age:22}); +``` +and trying to violate the data type constraints by setting the age of a person to a string with: +```cypher +MATCH (n) SET n.age = 'age'; +``` +will yield the error `IS TYPED INTEGER violation on Person(age)`. + +To drop the created constraints, use the following queries: + +```cypher +DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; +DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; +``` + +Now, `SHOW CONSTRAINT INFO;` returns an empty set. + + +## Schema-related procedures + +You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). + +### Delete all constraints + +To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: +- `indices_map` = map of key-value pairs of all indexes in the database +- `unique_constraints` = `{}` +- `existence_constraints` = `{}` +- `drop_existing` = `true` + +Here is an example of indexes and constraints set in the database: + +```cypher +CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); +CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; +CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; +CREATE INDEX ON :Student(id); +CREATE INDEX ON :Student; +``` + +There are three uniqueness and one existence constraint. Additionally, there are +two indexes - one label and one label-property index. To delete all constraints, run: + +```cypher +CALL schema.assert({Student: ["", "id"]}, {}, {}, true) +YIELD action, key, keys, label, unique +RETURN action, key, keys, label, unique; +``` + +The above query removes all existing constraints because the empty +`unique_constraints` and `existence_constraints` maps indicate that no +constraints should be asserted as existing, while the `drop_existing` set to +`true` specifies that all existing constraints should be dropped. + +Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also +useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). + +## Recovery + +Existence and unique [constraints](/fundamentals/constraints), and indexes can be +recovered in parallel. To enable this behavior, set the +[`storage-parallel-schema-recovery` +configuration](/configuration/configuration-settings#storage) flag to `true`. From 6de5e652cd62ab8acd9f9a74e0a8664fe2151ac7 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:16:13 +0200 Subject: [PATCH 16/54] Add vm.max_map_count explanation --- .../general-suggestions.mdx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 2bbdc075d..fcef71e62 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -22,7 +22,7 @@ Since Memgraph is an in-memory database, estimating RAM requirements is crucial. **3. [Hardware Configuration](#3-hardware-configuration)**
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking Options](#4-networking-options)**
+**4. [Networking Configuration](#4-networking-options)**
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. **5. [Deployment Options](#5-deployment-options)**
@@ -132,17 +132,34 @@ Once you've completed these steps: - Round up your estimate to match available server configurations. - Example: If your estimate is **96GB**, choose a server with **128GB RAM** for safe headroom. -### 3. Hardware Configuration -blablah + +Still having problems with estimating the size of your instance? Try out our [official storage calculator](https://memgraph.com/storage-calculator), +or contact us on Discord! + + +## 3. Hardware Configuration + +One of the most important system settings when running Memgraph is configuring the kernel parameter `vm.max_map_count`. +This setting ensures that the system can allocate enough virtual memory areas, which is critical for avoiding memory-related +issues or unexpected crashes during Memgraph operations. + +You can find detailed setup instructions and context in our +[System Configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). + +If you're deploying Memgraph on Kubernetes, our Helm charts include an **init container** that automatically sets `vm.max_map_count` +during startup. However, this container requires **root privileges** to execute. If you're running in a restricted environment +or prefer not to use privileged containers, you’ll need to **manually configure** this parameter on the host machine. + +Properly configuring `vm.max_map_count` is a one-time setup but essential for a stable and performant Memgraph deployment. -### 4. Networking Options +## 4. Networking Configuration blablah -### 5. Deployment Options +## 5. Deployment Options blablah -### 6. Choosing the right Memgraph flag set +## 6. Choosing the right Memgraph flag set blablah -### 7. Importing Mechanisms +## 7. Importing Mechanisms blablah From 60f1f25dcff36ca605906d47912db61a55d06c95 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:25:29 +0200 Subject: [PATCH 17/54] Add deployment options --- .../general-suggestions.mdx | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index fcef71e62..36fddd09e 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -152,11 +152,38 @@ or prefer not to use privileged containers, you’ll need to **manually configur Properly configuring `vm.max_map_count` is a one-time setup but essential for a stable and performant Memgraph deployment. +For system-specific configuration steps and installation guidelines, refer to the [Install Memgraph guide](/getting-started/install-memgraph). + ## 4. Networking Configuration -blablah + +To ensure Memgraph functions properly in your environment, make sure the following ports are open and accessible on your server: + +- **7687** – Used for the **Bolt protocol**, which handles all client-to-database communication. +- **3000** – Required if you're using **Memgraph Lab** as a visual interface. +- **7444** – Needed for **streaming logs** from Memgraph to Memgraph Lab. +- **9091** – Used for **system metrics**, an **Enterprise-only** feature. If you're using Prometheus or some other system for tracking system metrics, +this port needs to be enabled. + +In addition to enabling these ports, be sure to: + +- Review and adjust **firewall settings** to allow traffic on the listed ports. +- On **Red Hat-based systems**, even with the firewall properly configured, you might need to **disable SELinux**, as it can block +Memgraph’s access to system resources. ## 5. Deployment Options -blablah + +Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** +using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally +recommend using the **containerized version**. + +The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for +additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** +to enable the full range of MAGE functionalities—an often complex and error-prone process for most users. + +For most use cases, especially during prototyping and production readiness, the containerized image provides the best balance between +performance, simplicity, and feature completeness. + +More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set blablah From 01f350eeaa5da625d09268734139569ad05b97d3 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:55:06 +0200 Subject: [PATCH 18/54] Add flag set suggestions --- .../general-suggestions.mdx | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 36fddd09e..3f3404859 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -95,12 +95,12 @@ Use the following steps to estimate the RAM required for your workload: #### Step 1: Estimate Base Graph Storage For datasets with minimal properties (3–5 small properties), use this formula: ``` -128 * N + 120 * M +128 * N + 120 * R ``` Where: - `N` = number of nodes -- `M` = number of relationships -The result gives you the dataset size in **bytes**. +- `R` = number of relationships +The result gives you the dataset size in **bytes**. The size of properties is ommitted as it will not impact the sizing. **Don’t know how many nodes or relationships you have?** @@ -174,7 +174,7 @@ Memgraph’s access to system resources. Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally -recommend using the **containerized version**. +recommend using the **containerized version (Docker, K8s manifests, or Helm charts)**. The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** @@ -186,7 +186,38 @@ performance, simplicity, and feature completeness. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set -blablah +Got it! Here's the revised paragraph with a more neutral tone regarding log streaming: + +--- + +## 6. Choosing the Right Memgraph Flag Set + +Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: + +- `--log-level` + This flag controls the granularity of Memgraph's logs. In **production environments**, setting it to `INFO` or `DEBUG` + is typically sufficient. For **diagnostics or experimentation**, using `TRACE` provides highly detailed logs + that can help in troubleshooting—but keep in mind this will significantly increase **disk usage**. + Memgraph **does not automatically delete old logs**, so you’ll need to manage log retention manually. This flag can be + adjusted at runtime using the `SET DATABASE SETTING 'log.level' TO 'value'` command. + +- `--also-log-to-stderr=true` + Enables logging to **standard error** in addition to log files. + + + Optionally, users can manually stream the logs to another system such as **Splunk**, or using a containerized setup where + logs are collected from standard output streams. + + +- `--storage-parallel-schema-recovery=true` and `--storage-recovery-thread-count=x` + These flags enable **parallel recovery** of the schema during startup. Set `x` to the number of cores you want to dedicate + for recovery. This significantly speeds up the time it takes for Memgraph to become operational after a restart. + +- `--memory-limit=x` (in MiB) + This flag defines the **maximum memory** Memgraph can use. By default, it's set to **90% of available system memory**, but if + you're running other processes on the same machine (like Memgraph Lab or others), it's advisable to **manually lower this limit**. + On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower + than the actual available memory on the node. ## 7. Importing Mechanisms blablah From 74715a8fcb58d7dfb7a16f0000a313664a712772 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:56:40 +0200 Subject: [PATCH 19/54] Indentation --- pages/memgraph-in-production/general-suggestions.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 3f3404859..cd7557963 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -192,7 +192,8 @@ Got it! Here's the revised paragraph with a more neutral tone regarding log stre ## 6. Choosing the Right Memgraph Flag Set -Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: +Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important +flags to consider: - `--log-level` This flag controls the granularity of Memgraph's logs. In **production environments**, setting it to `INFO` or `DEBUG` From a97f41a842f3a9a3f466877b5fe40de9f0a150fa Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 12:58:39 +0200 Subject: [PATCH 20/54] Remove unnecessary comment --- pages/memgraph-in-production/general-suggestions.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index cd7557963..c2ad0d0a6 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -186,11 +186,6 @@ performance, simplicity, and feature completeness. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). ## 6. Choosing the right Memgraph flag set -Got it! Here's the revised paragraph with a more neutral tone regarding log streaming: - ---- - -## 6. Choosing the Right Memgraph Flag Set Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: From e8753c1c6374de01b5418ea1a4e43523e59a74ac Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 13:16:02 +0200 Subject: [PATCH 21/54] Add enterprise, queries and import sections --- .../general-suggestions.mdx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index c2ad0d0a6..490d9c000 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -216,4 +216,38 @@ flags to consider: than the actual available memory on the node. ## 7. Importing Mechanisms -blablah + +Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether +you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. + +We strongly encourage users to review the [Best Practices for Data Migration](/data-migration/best-practices), +which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. + +## 8. Enterprise Features You Might Require + +Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: + +- **High Availability (HA)** and **automatic failover** for fault tolerance +- **System metrics monitoring** for observability and performance tracking +- **Role-based and fine-grained access control** for secure, multi-user environments +- **Multi-tenancy** for isolating and managing separate workloads within the same infrastructure +- **Advanced security features** to meet compliance and operational requirements + +There are additional enterprise capabilities as well, tailored for advanced performance, scalability, and security needs. +We recommend exploring the other chapters in the **"Memgraph in Production"** guide series, as they highlight how these +features can be aligned with your specific use cases. + + +## 9. Queries That Best Suit Your Workload + +Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, +Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for +performance-critical use cases. You can learn more about these in our +[Deep Path Traversal guide](https://memgraph.com/docs/advanced-algorithms/deep-path-traversal). + +For advanced analytics and utility functions, Memgraph also ships with the +**[MAGE library](https://memgraph.com/docs/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built +**graph algorithms** and **procedures** for tasks like community detection, centrality scoring, node similarity, and more. + +We encourage users to explore the other guides in the **"Memgraph in Production"** series, where you'll find detailed examples and +recommendations on which types of queries are most effective based on your specific **workload and use case**. From bcf7a0c2c7569effa24734b810f1f15ba140aef1 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 18:36:23 +0200 Subject: [PATCH 22/54] Finish general suggestions guide --- .../general-suggestions.mdx | 124 +++++++++++++++--- 1 file changed, 105 insertions(+), 19 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 490d9c000..f4b92ab8a 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -22,7 +22,7 @@ Since Memgraph is an in-memory database, estimating RAM requirements is crucial. **3. [Hardware Configuration](#3-hardware-configuration)**
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking Configuration](#4-networking-options)**
+**4. [Networking Configuration](#4-networking-configuration)**
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. **5. [Deployment Options](#5-deployment-options)**
@@ -31,16 +31,21 @@ Understand the tradeoffs between running Memgraph natively on a host machine or **6. [Choosing the Right Memgraph Flag Set](#6-choosing-the-right-memgraph-flag-set)**
Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. -**7. [Importing Mechanisms](#7-importing-mechanisms)**
+**7. [Choosing the Right Memgraph Storage Mode](#7-choosing-the-right-memgraph-storage-mode)**
+Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIONAL** and **IN_MEMORY_ANALYTICAL** - as well as one +disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode +based on your specific use case. + +**8. [Importing Mechanisms](#8-importing-mechanisms)**
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**8. [Enterprise Features You Might Require](#8-enterprise-features-you-might-require)**
+**9. [Enterprise Features You Might Require](#9-enterprise-features-you-might-require)**
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**9. [Queries That Best Suit Your Workload](#9-queries-that-best-suit-your-workload)**
+**10. [Queries That Best Suit Your Workload](#10-queries-that-best-suit-your-workload)**
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, @@ -62,7 +67,7 @@ The most critical factor in provisioning a server for Memgraph is **RAM**. Memgr the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently and can scale with your workload. -For a deeper dive into how memory usage is calculated, refer to our [Memory Storage Guide](/fundamentals/storage-memory-usage). Below is a +For a deeper dive into how memory usage is calculated, refer to our [memory storage documentation](/fundamentals/storage-memory-usage). Below is a simplified, rule-of-thumb approach to help you estimate your memory needs. ### Memory Components @@ -114,7 +119,7 @@ based on how your data is actually represented as a graph.
#### Step 2: Estimate Property Storage -If your dataset includes many or complex properties, refer to the [Memory Storage Guide](/fundamentals/storage-memory-usage) for detailed +If your dataset includes many or complex properties, refer to the [memory storage documentation](/fundamentals/storage-memory-usage) for detailed calculations. Add this result to the base graph size from Step 1. #### Step 3: Add Index Overhead @@ -144,7 +149,7 @@ This setting ensures that the system can allocate enough virtual memory areas, w issues or unexpected crashes during Memgraph operations. You can find detailed setup instructions and context in our -[System Configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). +[system configuration documentation](https://memgraph.com/docs/database-management/system-configuration#recommended-values-for-the-vmmax_map_count-parameter). If you're deploying Memgraph on Kubernetes, our Helm charts include an **init container** that automatically sets `vm.max_map_count` during startup. However, this container requires **root privileges** to execute. If you're running in a restricted environment @@ -172,16 +177,21 @@ Memgraph’s access to system resources. ## 5. Deployment Options -Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** -using Docker on any operating system. While native installation can offer up to **10% better performance**, we generally -recommend using the **containerized version (Docker, K8s manifests, or Helm charts)**. +Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, +or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, +we generally recommend using the **containerized version**—whether via **Docker**, **Kubernetes manifests**, or **Helm charts**. + +When deploying via Helm, users can choose between: +- The **Memgraph Standalone Helm chart** – ideal if you're working with a **single instance** setup. +- The **Memgraph High Availability Helm chart** – designed for **production deployments** that require **redundancy and failover** + with multiple instances. -The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for -additional setup. On the other hand, native installations require users to **compile C++ modules** and **manually install Python packages** +The containerized deployment comes pre-packaged with all **MAGE algorithms** and utility tools, eliminating the need for +additional setup. In contrast, native installations require users to **compile C++ modules** and **manually install Python packages** to enable the full range of MAGE functionalities—an often complex and error-prone process for most users. For most use cases, especially during prototyping and production readiness, the containerized image provides the best balance between -performance, simplicity, and feature completeness. +**performance**, **simplicity**, and **feature completeness**. More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). @@ -215,15 +225,69 @@ flags to consider: On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower than the actual available memory on the node. -## 7. Importing Mechanisms + +For more information on how to configure Memgraph, as well as what are all the flags that Memgraph can be configured on, please +check out the [configuration documentation page](/database-management/configuration). + + +## 7. Choosing the Right Memgraph Storage Mode + +Memgraph currently supports two fully-featured and production-ready storage modes: +- `IN_MEMORY_TRANSACTIONAL` +- `IN_MEMORY_ANALYTICAL` + +A third mode, `ON_DISK_TRANSACTIONAL`, is still **experimental** and lacks many production-grade features. +While it's part of Memgraph’s long-term roadmap, it’s **not recommended for production** use at this stage. + +For all current production deployments, users are strongly encouraged to choose either the **in-memory transactional** or +**in-memory analytical** mode, based on the nature of their workload. + +You can set the storage mode in two ways: +- Via a **Cypher query**: + ```cypher + STORAGE MODE IN_MEMORY_TRANSACTIONAL; + -- or -- + STORAGE MODE IN_MEMORY_ANALYTICAL; + ``` +- Or by using a **configuration flag** at startup: + ```cypher + --storage-mode=IN_MEMORY_TRANSACTIONAL + --storage-mode=IN_MEMORY_ANALYTICAL + ``` + +### Which Mode Should You Choose? + +- ✅ **Transactional Mode (`IN_MEMORY_TRANSACTIONAL`)** + Ideal for **mission-critical workloads** requiring **ACID guarantees**, **replication**, and **high availability**. + This mode supports safe concurrent access, durability, and rollback on failure. + Keep in mind that **write-write conflicts** can occur and may need to be retried on the driver side. + +- 🚀 **Analytical Mode (`IN_MEMORY_ANALYTICAL`)** + Suited for **read-only** workloads or **on-demand graph analytics** where you need **multithreaded ingestion at scale**. + This mode supports extremely high write throughput, even reaching **millions of writes per second**, thanks to parallelized graph construction. + However, it **does not support aborting or rolling back transactions**, as it does not track changes via delta objects. + Once a write is made, it becomes part of the current state. + + +**Rule of thumb**: +Use **analytical mode** for high-speed, read-heavy, or bulk-ingest scenarios. +Use **transactional mode** for anything requiring **reliability, consistency, or fault tolerance**. + + + +For more information about the implications of Memgraph storage mode offerings, please check out the +[storage mode documentation](/fundamentals/storage-memory-usage). + + +## 8. Importing Mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. -We strongly encourage users to review the [Best Practices for Data Migration](/data-migration/best-practices), +We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 8. Enterprise Features You Might Require +## 9. Enterprise Features You Might Require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -237,16 +301,38 @@ There are additional enterprise capabilities as well, tailored for advanced perf We recommend exploring the other chapters in the **"Memgraph in Production"** guide series, as they highlight how these features can be aligned with your specific use cases. +Memgraph Enterprise License is enabled by issueing the following queries: +``` +SET DATABASE SETTING 'organization.name' TO 'Organization'; +SET DATABASE SETTING 'enterprise.license' TO 'License'; +``` + +However, the recommended way of specifying the Enterprise License would be through environment variables through Docker or making +it visible to the process (if you're deploying Memgraph natively). + +``` +MEMGRAPH_ORGANIZATION_NAME=Organization +MEMGRAPH_ENTERPRISE_LICENSE=License +``` + + +Reason for that is because environment variables always override any system settings that are set via queries. + + +For more information about what Enterprise features are included with Memgraph Enterprise License, please check out the +[section on Memgraph Enterprise enablement](/database-management/enabling-memgraph-enterprise). + + -## 9. Queries That Best Suit Your Workload +## 10. Queries That Best Suit Your Workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for performance-critical use cases. You can learn more about these in our -[Deep Path Traversal guide](https://memgraph.com/docs/advanced-algorithms/deep-path-traversal). +[Deep Path Traversal guide](/advanced-algorithms/deep-path-traversal). For advanced analytics and utility functions, Memgraph also ships with the -**[MAGE library](https://memgraph.com/docs/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built +**[MAGE library](/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built **graph algorithms** and **procedures** for tasks like community detection, centrality scoring, node similarity, and more. We encourage users to explore the other guides in the **"Memgraph in Production"** series, where you'll find detailed examples and From 89ce823d857781932d54720ca96120eca1cfe210 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 18:45:37 +0200 Subject: [PATCH 23/54] Make under construction notes --- .../general-suggestions.mdx | 1 - .../memgraph-in-analytical-workloads.mdx | 312 +---------------- .../memgraph-in-cyber-security.mdx | 312 +---------------- .../memgraph-in-fraud-detection.mdx | 312 +---------------- .../memgraph-in-graphrag.mdx | 314 +----------------- .../memgraph-in-high-throughput-workloads.mdx | 312 +---------------- ...memgraph-in-mission-critical-workloads.mdx | 312 +---------------- .../memgraph-in-supply-chain.mdx | 314 +----------------- .../memgraph-in-transactional-workloads.mdx | 312 +---------------- 9 files changed, 50 insertions(+), 2451 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index f4b92ab8a..9ff0bf0ae 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -315,7 +315,6 @@ MEMGRAPH_ORGANIZATION_NAME=Organization MEMGRAPH_ENTERPRISE_LICENSE=License ``` - Reason for that is because environment variables always override any system settings that are set via queries. diff --git a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx index 36ba4e626..df745caa8 100644 --- a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in analytical workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your analytical use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in analytical workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **analytical use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx index 61150e187..7936ad123 100644 --- a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx +++ b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in Cyber Security -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your cyber security use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Cyber Security - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **cyber security use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx index b4723d541..8b238defa 100644 --- a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx +++ b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in Fraud Detection -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your fraud detection use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Fraud Detection - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **fraud detection use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx index fcacbad97..e7d6f0cdb 100644 --- a/pages/memgraph-in-production/memgraph-in-graphrag.mdx +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -1,315 +1,15 @@ --- -title: Memgraph in GraphRAG -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +title: Memgraph in GraphRAG use cases +description: Understand the implications of getting your GraphRAG use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in GraphRAG use cases - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **GraphRAG use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index 79bdb1f40..c2fe4fee6 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in high throughput workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your high throughput use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in high throguhput workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **high throughput use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx index fdf2a594b..9416c4dc1 100644 --- a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in mission critical workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your mission critical use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in mission critical workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **mission critical use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx index 25be5e450..c1eef33a7 100644 --- a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx +++ b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx @@ -1,315 +1,15 @@ --- -title: Memgraph in supply chain -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +title: Memgraph in Supply Chain workloads +description: Understand the implications of getting your supply chain use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in Supply Chain - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **supply chain use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. diff --git a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx index fcfaf59c4..6bdc20178 100644 --- a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx @@ -1,315 +1,15 @@ --- title: Memgraph in transactional workloads -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. +description: Understand the implications of getting your transactional use case to production with Memgraph. --- import { Callout } from 'nextra/components' -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. +# Memgraph in transactional workloads - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - +🚧 **This guide is still under construction!** +We're working on a full walkthrough for deploying your **transactional use case** in production with Memgraph. If you'd like to help +us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. From 6c5e47f65695885581093c85eede82406df4746f Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Tue, 15 Apr 2025 19:02:46 +0200 Subject: [PATCH 24/54] Add todo --- pages/fundamentals/storage-memory-usage.mdx | 7 ++++--- pages/memgraph-in-production/general-suggestions.mdx | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index 57421458d..e6f0ef1d0 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -32,7 +32,8 @@ Memgraph supports three different storage modes: ### Start Memgraph with a specific storage mode By default, an empty instance will start using in-memory transactional storage -mode. To start Memgraph in the ON_DISK_TRANSACTIONAL or IN_MEMORY_ANALYTICAL +mode. +To start Memgraph in the `ON_DISK_TRANSACTIONAL` or `IN_MEMORY_ANALYTICAL` storage node, change the `--storage-mode` [configuration flag](/database-management/configuration) accordingly. @@ -58,7 +59,7 @@ mode. When switching modes, Memgraph will wait until all other transactions are done. If some other transactions are running in your system, you will receive a -warning message, so be sure to [set the log level to +warning message, so be sure to [set the log level at least to `WARNING`](/database-management/logs). Switching from the in-memory storage mode to the on-disk storage mode is allowed @@ -191,7 +192,7 @@ built-in behavior is as follows: * [procedures](/advanced-algorithms/run-algorithms#run-procedures-from-mage-library): skip all records that contain any deleted value * [functions](/querying/functions): return a null value -Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior. +**Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior.** Users developing [custom query procedures and functions](/custom-query-modules) intended to work in the analytical storage mode should use API methods to check if Memgraph is running diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 9ff0bf0ae..aae7bba7a 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -9,6 +9,12 @@ import { Callout } from 'nextra/components' This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. + +**TODO** +- Add backup suggestions +- Add property compression inside hardwware sizing estimations + + ## What is Covered? The general suggestions cover the following key areas: From 84f4d0c441ae405736d72872b9706c9ecb1564d6 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 14:33:16 +0200 Subject: [PATCH 25/54] Update property sizes --- pages/fundamentals/storage-memory-usage.mdx | 46 ++++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index e6f0ef1d0..73f28eb63 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -441,7 +441,7 @@ Estimating Memgraph's storage memory usage is not entirely straightforward because it depends on a lot of variables, but it is possible to do so quite accurately. -If you want to **estimate** memory usage in IN_MEMORY_TRANSACTIONAL storage mode, use the following formula: +If you want to **estimate** memory usage in `IN_MEMORY_TRANSACTIONAL` storage mode, use the following formula: $\texttt{StorageRAMUsage} = \texttt{NumberOfVertices} \times 212\text{B} + \texttt{NumberOfEdges} \times 162\text{B}$ @@ -476,7 +476,7 @@ that the formula is correct. ### The calculation in detail -Let's dive deeper into the IN_MEMORY_TRANSACTIONAL storage mode memory usage +Let's dive deeper into the `IN_MEMORY_TRANSACTIONAL` storage mode memory usage values. Because Memgraph works on the x86 architecture, calculations are based on the x86 Linux memory usage. @@ -558,19 +558,35 @@ value. So the layout of each property is: $\texttt{propertySize} = \texttt{basicMetadata} + \texttt{propertyID} + [\texttt{additionalMetadata}] + \texttt{value}.$ -| Value type | Size | Note -|-----------------------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -|`NULL` | 1B + 1B | The value is written in the first byte of the basic metadata. | -|`BOOL` | 1B + 1B | The value is written in the first byte of the basic metadata. | -|`INT` | 1B + 1B + 1B, 2B, 4B or 8B | Basic metadata, property ID and the value depending on the size of the integer. | -|`DOUBLE` | 1B + 1B + 8B | Basic metadata, property ID and the value | -|`STRING` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and lastly the value depending on the size of the string, where 1 ASCII character in the string takes up 1B. | -|`LIST` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the list. | -|`MAP` | 1B + 1B + 1B + min 1B | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the map. | -|`TEMPORAL_DATA` | 1B + 1B + 1B + min 1B + min 1B | Basic metadata, property ID, additional metadata, seconds, microseconds. Value of the seconds and microseconds is at least 1B, but probably 4B in most cases due to the large values they store. | -|`ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 1B + min 1B | Like `TEMPORAL_DATA`, but followed by timezone name length (1 byte) and string (1 byte per character). | -|`OFFSET_ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 2B | Like `TEMPORAL_DATA`, but followed by the offset from UTC (in minutes; always 2 bytes). | -|`ENUM` | 1B + 1B + 2B, 4B, 8B or 16B | Basic metadata, property ID and the value depending on required size representation required. | +| Value type | Sizing in detail | Total estimated size | Note +|-----------------------------|--------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|`NULL` | 0B | 0B | Memgraph treats null values same as if they're not present. Therefore, `NULL` values are not stored in the property store. | +|`BOOL` | 1B + 1B | 2B | The value is written in the first byte of the basic metadata. | +|`INT` | 1B + 1B + 1B, 2B, 4B or 8B | 3B - 10B | Basic metadata, property ID and the value depending on the size of the integer. | +|`DOUBLE` | 1B + 1B + 8B | 10B | Basic metadata, property ID and the value | +|`STRING` | 1B + 1B + 1B + min 1B | at least 4B | Basic metadata, property ID, additional metadata and lastly the value depending on the size of the string, where 1 ASCII character in the string takes up 1B. | +|`LIST` | 1B + 1B + 1B + min 1B | at least 4B (empty) | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the list. | +|`MAP` | 1B + 1B + 1B + min 1B | at least 4B (empty) | Basic metadata, property ID, additional metadata and the total size depends on the number and size of the values in the map. | +|`TEMPORAL_DATA` | 1B + 1B + 1B + min 1B + min 1B | 12B | Basic metadata, property ID, additional metadata, seconds, microseconds. Value of the seconds and microseconds is at least 1B, but probably 4B in most cases due to the large values they store. | +|`ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 1B + min 1B | at least 14B | Like `TEMPORAL_DATA`, but followed by timezone name length (1 byte) and string (1 byte per character). | +|`OFFSET_ZONED_TEMPORAL_DATA` | `TEMPORAL_DATA` + 2B | at least 14B | Like `TEMPORAL_DATA`, but followed by the offset from UTC (in minutes; always 2 bytes). | +|`ENUM` | 1B + 1B + 2B, 4B, 8B or 16B | 4B - 18B | Basic metadata, property ID and the value depending on required size representation required. | + +Users can additionally inspect the exact size of a property in bytes using the [propertySize()](/querying/functions#scalar-functions) function. +The query usage is the following: +```cypher +MATCH (n) +RETURN n.id AS id, propertySize(n, "id") AS prop_size_bytes; +``` + +Output: +```output ++-----------+-----------------+ +| id | prop_size_bytes | ++-----------+-----------------+ +| 1 | 3 | ++-----------+-----------------+ +``` ### Marvel dataset use case From 51f07a45e99c9d568bae00d69095b3babf4eafe2 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 14:52:05 +0200 Subject: [PATCH 26/54] Add backup considerations --- .../general-suggestions.mdx | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index aae7bba7a..9cf6e55d9 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -7,13 +7,10 @@ import { Callout } from 'nextra/components' # General Suggestions -This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. - - -**TODO** -- Add backup suggestions -- Add property compression inside hardwware sizing estimations - +This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. +It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. +Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the +*Memgraph in Production* series for tailored recommendations. ## What is Covered? @@ -42,16 +39,19 @@ Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIO disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode based on your specific use case. -**8. [Importing Mechanisms](#8-importing-mechanisms)**
+**8. [Backup considerations](#8-backup-considerations)**
+Learn about how to preserve your data in Memgraph to prevent any data loss. + +**9. [Importing Mechanisms](#9-importing-mechanisms)**
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**9. [Enterprise Features You Might Require](#9-enterprise-features-you-might-require)**
+**10. [Enterprise Features You Might Require](#10-enterprise-features-you-might-require)**
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**10. [Queries That Best Suit Your Workload](#10-queries-that-best-suit-your-workload)**
+**11. [Queries That Best Suit Your Workload](#11-queries-that-best-suit-your-workload)**
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, @@ -285,7 +285,32 @@ For more information about the implications of Memgraph storage mode offerings, [storage mode documentation](/fundamentals/storage-memory-usage).
-## 8. Importing Mechanisms +## 8. Backup Considerations + +Ensuring data durability and having a solid backup strategy is essential for any production deployment. +Memgraph provides built-in mechanisms for creating **snapshots** and **write-ahead logs (WALs)** to help +you recover from failures or restarts. + +By default, Memgraph retains the **latest 3 snapshots**, but this can be adjusted using the `--storage-snapshot-retention-count=x` +flag. Older snapshots beyond this limit are automatically deleted to manage storage space. + +During recovery, Memgraph first restores the **latest snapshot**, followed by a **replay of the WALs**: +- **Snapshot recovery** can be parallelized for faster startup using: + - `--storage-parallel-schema-recovery=true` + - `--storage-recovery-thread-count=x` (where `x` is the number of threads used) +- **WALs are recovered sequentially**, and this step cannot yet be parallelized. + + +🔒 Memgraph does **not yet support automatic backups** to third-party storage solutions like AWS S3 or GCP buckets. +For now, users are encouraged to implement **manual redundancy** by syncing snapshots to external locations using +tools such as [**rclone**](https://rclone.org/), which is already in use by several Memgraph customers for this purpose. + + +For more detailed information, refer to: +- [Data Durability Fundamentals](/fundamentals/data-durability) +- [Backup and Restore Documentation](/database-management/backup-and-restore) + +## 9. Importing Mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. @@ -293,7 +318,7 @@ you're working with CSV files, JSON streams, Kafka topics, or external data sour We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 9. Enterprise Features You Might Require +## 10. Enterprise Features You Might Require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -329,7 +354,7 @@ For more information about what Enterprise features are included with Memgraph E -## 10. Queries That Best Suit Your Workload +## 11. Queries That Best Suit Your Workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for From 2d481aadf827975546b23ebaae586c1f142f8209 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 15:01:14 +0200 Subject: [PATCH 27/54] Added overview page --- pages/memgraph-in-production.mdx | 95 ++----- pages/memgraph-in-production/overview.mdx | 315 ---------------------- 2 files changed, 20 insertions(+), 390 deletions(-) delete mode 100644 pages/memgraph-in-production/overview.mdx diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 9c0a255cb..55849a1b5 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -7,86 +7,31 @@ import {CommunityLinks} from '/components/social-card/CommunityLinks' # Memgraph in Production -When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, scalability, and resilience. -This includes hardware considerations, the correct sizing of instances, configuring drivers, using appropriate flags when starting Memgraph, -importing data, and connecting to external sources. The guidelines in this section will help you make informed decisions for your -specific use case, ensuring that Memgraph performs effectively in your environment. +When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, +scalability, and resilience. This includes hardware considerations, the correct sizing of instances, configuring drivers, using +appropriate flags when starting Memgraph, importing data, and connecting to external sources. The guidelines in this section will +help you make informed decisions for your specific use case, ensuring that Memgraph performs effectively in your environment. -## Prerequisites for Deployment +## How to use the Memgraph in Production guides -Before deploying Memgraph, there are several essential considerations to be aware of: +To get started, we recommend first reading the **General Suggestions** section, which outlines practices that are **agnostic to specific use cases**. +These are foundational principles that apply broadly across most production setups. Each separate guide in the **"Memgraph in Production"** +series focuses on a particular type of workload or deployment scenario. At the beginning of each guide, you’ll find information about +**when that use case is a good fit for your needs**. The specific recommendations in those guides **override** anything written in the +general suggestions when there's a conflict—so always defer to the targeted guide when applicable. -- **Hardware Considerations**: Memgraph is an in-memory graph database, so allocating sufficient memory (RAM) is crucial. Ensure that your system meets the memory requirements of your dataset and any indexes. A sufficient number of CPU cores and fast storage are also important for overall performance. -- **Sizing the Instance**: The size of your instance should depend on the expected dataset size, the number of concurrent queries, and the complexity of graph traversals. Larger datasets or high query volumes may require scaling horizontally across multiple nodes. -- **Driver Considerations**: Memgraph supports several client libraries, including Python, C++, and JavaScript. Choose the correct driver for your application based on language compatibility and performance requirements. -- **Flags for Starting Memgraph**: When starting Memgraph, certain flags can optimize performance. These may include memory limits, persistence settings, and indexing options. Always tailor these based on the type of workload. -- **Importing Data**: Memgraph supports various ways to import data, including batch loading from CSVs, using Cypher queries, or leveraging streaming data sources. -- **Connecting to External Sources**: In production, Memgraph often needs to interact with external systems like data pipelines, message brokers, and other databases. Ensure that Memgraph’s connection configurations (e.g., network settings, authentication) align with external sources. -## Deployment Choices Based on Workload +## 📚 Available Guides in the *Memgraph in Production* Series -### General Recommendations +- [General Suggestions](/memgraph-in-production/general-suggestions) +- [Memgraph in transactional workloads](/memgraph-in-production/memgraph-in-transactional-workloads) +- [Memgraph in analytical workloads](/memgraph-in-production/memgraph-in-analytical-workloads) +- [Memgraph in mission critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) +- [Memgraph in high throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) +- [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) +- [Memgraph in Supply Chain use cases](/memgraph-in-production/memgraph-in-supply-chain) +- [Memgraph in Cyber Security use cases](/memgraph-in-production/memgraph-in-cyber-security) +- [Memgraph in Fraud Detection use cases](/memgraph-in-production/memgraph-in-fraud-detection) -For general deployments of Memgraph, focus on optimizing the following: - -- **Memory**: Ensure sufficient RAM for graph data and indices. -- **CPU**: Adequate core count to handle parallel queries. -- **Persistence**: Configure persistence according to the durability needs of your data. -- **Indexing**: Set up appropriate indexes for frequently queried nodes and relationships. -- **Replication**: For high availability, enable replication to safeguard against potential failures. - -### Memgraph in High Throughput Workloads - -In high throughput scenarios, such as real-time analytics or fast decision-making applications, you should focus on the following: - -- **High Availability**: Set up a replication cluster for fault tolerance and to ensure service continuity. -- **Memory Sizing**: Ensure that your system has enough memory to hold your graph in memory and avoid swapping to disk, which would slow down performance. -- **Query Optimization**: Optimize your queries for high performance, and enable result caching for frequent queries. -- **Load Balancing**: Distribute traffic across multiple instances to avoid overloading a single node. - -### Memgraph in Analytical Workloads - -For analytical workloads, such as running complex queries or machine learning graph algorithms, consider the following: - -- **Instance Size**: Use larger instances with more RAM and CPU to support the heavy lifting of complex queries. -- **Persistence**: You may want to enable persistence for long-term storage of analytical results. -- **Query Tuning**: Optimize graph traversal queries and ensure that indexing is set up to cover common analytical queries. -- **Batch Processing**: If your workload involves batch processing, ensure that Memgraph is configured to handle large imports efficiently without overloading the system. - -### Memgraph in Transactional Workloads - -For transactional workloads, where low-latency and high consistency are key, prioritize the following: - -- **Transaction Durability**: Ensure proper settings for transaction logs and snapshots for data durability. -- **Replication**: Set up a high-availability cluster with replication to ensure that transactions are available across nodes. -- **Network Configuration**: Minimize network latency between Memgraph instances to ensure consistent transaction processing. -- **Memory Allocation**: Tune memory allocation for optimal transaction throughput. - -### Memgraph in GraphRAG - -In GraphRAG (Graph-based Retrieval-Augmented Generation) applications, where the graph data is used to enrich AI model generation or queries, the following considerations are important: - -- **Data Import**: Use bulk data import methods or streaming data pipelines for continuous graph updates. -- **Embedding Integration**: Ensure that your graph data is linked to machine learning models and that embeddings are stored efficiently. -- **Latency**: Minimize query and response time, especially for real-time generation. -- **Model Integration**: Seamlessly integrate Memgraph with model inference pipelines to improve the accuracy of AI-driven responses. - -### Memgraph in Cybersecurity - -For cybersecurity workloads, which involve detecting threats, managing incident data, and analyzing network traffic, consider the following: - -- **Data Ingestion**: Configure Memgraph to handle high-speed log ingestion, including event data from firewalls, intrusion detection systems, and other sources. -- **Scalability**: Ensure that Memgraph can scale horizontally as the volume of data increases over time. -- **High Availability**: Set up high availability and replication to ensure that data is accessible at all times, especially in a high-velocity attack detection environment. -- **Real-time Analysis**: Optimize Memgraph for real-time graph traversal to detect anomalous behaviors quickly. - -### Memgraph in Fraud Detection - -For fraud detection workloads, which rely on analyzing transactions and behaviors to detect fraud patterns, focus on the following: - -- **Real-Time Processing**: Ensure Memgraph is optimized for low-latency queries to detect fraudulent activity in real time. -- **Data Import**: Configure Memgraph to efficiently ingest transaction data from external sources, including financial systems and databases. -- **Graph Algorithms**: Use graph algorithms, such as PageRank or community detection, to identify fraud patterns based on relational data. -- **Persistence and Backups**: Maintain robust backup strategies to ensure that transaction data is safely stored and retrievable. diff --git a/pages/memgraph-in-production/overview.mdx b/pages/memgraph-in-production/overview.mdx deleted file mode 100644 index 2e61c61b7..000000000 --- a/pages/memgraph-in-production/overview.mdx +++ /dev/null @@ -1,315 +0,0 @@ ---- -title: Overview -description: Understand the constraints in Memgraph's operations. Our guide breaks it down, streamlining your approach to graph use cases. ---- - -import { Callout } from 'nextra/components' - -# Constraints - -In modern database systems, ensuring data integrity and reducing redundancy is -paramount. Constraints play a pivotal role, ensuring that only valid data is -entered into the database upon commit. The following chapters delve deep into two -fundamental types of constraints, existence and uniqueness. - -The existence constraint ensures the presence of specific data within the -database, while the uniqueness constraint ensures that specific label-property -pairs remain unique across entries. - -## Existence constraint - -Existence constraint enforces that each node with a specific label must also -have a certain property. Only one label and property can be supplied at a time. - -This constraint can be enforced using the following language construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will -yield an error. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT EXISTS (n.property); -``` - -### Example - -If the database is used to hold basic employee information, each -employee should have a first name and a last name. You can enforce this by -running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -CREATE CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| exists | Employee | first_name | -| exists | Employee | last_name | -+-----------------+-----------------+-----------------+ -``` - -To drop the created constraints use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.first_name); -DROP CONSTRAINT ON (n:Employee) ASSERT EXISTS (n.last_name); -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Uniqueness constraint - -The uniqueness constraint enforces that each label-property pair is unique. You can -also, specify multiple properties when creating uniqueness constraints. - - - -Adding a uniqueness constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The uniqueness constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property1, n.property2, ..., IS UNIQUE; -``` - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `Unable to commit due to unique constraint violation on -:Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE; -``` - -### Example - -If the database is used to hold basic employee information, each employee should -have a unique id and email. You can enforce this by running the following query: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -``` - -The `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -+-----------------+-----------------+-----------------+ -``` - -To specify multiple properties when creating uniqueness -constraints, list them one after the other: - -```cypher -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -At this point, `SHOW CONSTRAINT INFO;` yields the following result: - -``` -+-----------------+-----------------+-----------------+ -| constraint type | label | properties | -+-----------------+-----------------+-----------------+ -| unique | Employee | id | -| unique | Employee | email | -| unique | Employee | name, address | -+-----------------+-----------------+-----------------+ -``` - -This means that two employees could have the same name **or** the same address, -but they can not have the same name **and** the same address. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -DROP CONSTRAINT ON (n:Employee) ASSERT n.name, n.address IS UNIQUE; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - -## Data type constraint - -The data type constraint enforces that each label-property pair is of a certain data type. - - - -Adding a data type constraint does not create a [label-property -index](/fundamentals/indexes), it needs to be added manually. - - - -The data type constraint can be enforced using the following language -construct: - -```cypher -CREATE CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -The supported data types are: - -| Data type | -| ------------------- | -| `NULL` | -| `STRING` | -| `BOOLEAN` | -| `INTEGER` | -| `FLOAT` | -| `LIST` | -| `MAP` | -| `DURATION` | -| `DATE` | -| `LOCALTIME` | -| `LOCALDATETIME` | -| `ZONEDDATETIME` | -| `ENUM` | -| `POINT` | - -To confirm that the constraint was successfully created use the following query: - -```cypher -SHOW CONSTRAINT INFO; -``` - -Trying to modify the database in a way that violates the constraint will yield -an error `IS TYPED DATA_TYPE violation on Label(property)`. - -Constraints are dropped using the `DROP` clause: - -```cypher -DROP CONSTRAINT ON (n:label) ASSERT n.property IS TYPED DATA_TYPE; -``` - -You can only have one data type constraint on a given label-property pair. -Attempting to create a second data type constraint on a given label-property pair will yield an error -`Constraint IS TYPED DATA_TYPE on :Label(property) already exists`. -Attempting to drop a data type constraint which doesn't exist will yield an error -`Constraint IS TYPED DATA_TYPE on :Node(prop) doesn't exist`. - - - -Data type constraints are not yet supported in the [`schema.assert()`](/querying/schema#assert) procedure. - - - - -### Example - -If the database is used to hold basic information about a person like their name and age -you can enforce the name to be a string and the age to be an integer by running the following queries: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -CREATE CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Then `SHOW CONSTRAINT INFO;` should return the following result: - -``` -+-----------------+--------------+-----------------+-------------+ -| constraint type | label | properties | data_type | -+-----------------+--------------+-----------------+-------------| -| data_type | Person | name | STRING | -| data_type | Person | age | INTEGER | -+-----------------+--------------+-----------------+-------------| -``` - -Creating a person with -```cypher -CREATE (:Person {age:22}); -``` -and trying to violate the data type constraints by setting the age of a person to a string with: -```cypher -MATCH (n) SET n.age = 'age'; -``` -will yield the error `IS TYPED INTEGER violation on Person(age)`. - -To drop the created constraints, use the following queries: - -```cypher -DROP CONSTRAINT ON (n:Person) ASSERT n.name IS TYPED STRING; -DROP CONSTRAINT ON (n:Person) ASSERT n.age IS TYPED INTEGER; -``` - -Now, `SHOW CONSTRAINT INFO;` returns an empty set. - - -## Schema-related procedures - -You can also modify the constraints using the [`schema.assert()` procedure](/querying/schema#assert). - -### Delete all constraints - -To delete all constraints, use the [`schema.assert()`](/querying/schema#assert) procedure with the following parameters: -- `indices_map` = map of key-value pairs of all indexes in the database -- `unique_constraints` = `{}` -- `existence_constraints` = `{}` -- `drop_existing` = `true` - -Here is an example of indexes and constraints set in the database: - -```cypher -CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name); -CREATE CONSTRAINT ON (n:Employee) ASSERT n.id IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.email IS UNIQUE; -CREATE CONSTRAINT ON (n:Employee) ASSERT n.name, n.surname IS UNIQUE; -CREATE INDEX ON :Student(id); -CREATE INDEX ON :Student; -``` - -There are three uniqueness and one existence constraint. Additionally, there are -two indexes - one label and one label-property index. To delete all constraints, run: - -```cypher -CALL schema.assert({Student: ["", "id"]}, {}, {}, true) -YIELD action, key, keys, label, unique -RETURN action, key, keys, label, unique; -``` - -The above query removes all existing constraints because the empty -`unique_constraints` and `existence_constraints` maps indicate that no -constraints should be asserted as existing, while the `drop_existing` set to -`true` specifies that all existing constraints should be dropped. - -Primarily, the [`assert()`](querying/schema#assert) procedure is used to define a schema, but it's also -useful if you need to [delete all node indexes](/fundamentals/indexes#delete-all-node-indexes) or [delete all node indexes and constraints](/querying/schema#delete-all-indexes-and-constraints). - -## Recovery - -Existence and unique [constraints](/fundamentals/constraints), and indexes can be -recovered in parallel. To enable this behavior, set the -[`storage-parallel-schema-recovery` -configuration](/configuration/configuration-settings#storage) flag to `true`. From 81d7788831693786a91fc1a53d62f3e869cce9ad Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 16:43:34 +0200 Subject: [PATCH 28/54] Main --- pages/release-notes.mdx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 7927f576f..e3a4a8429 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -60,14 +60,6 @@ updated. ## 🚀 Latest release -### Memgraph v3.2.0 - Apr 23rd, 2025 - -### MAGE v3.2.0 - Apr 23rd, 2025 - -### Lab v3.2.0 - Apr 23rd, 2025 - -## Previous releases - ### Memgraph v3.1.1 - Mar 28th, 2025 {

✹ New features

} From 72f664915427aa0bed4f953231a7ae3648d85a7f Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 16:44:01 +0200 Subject: [PATCH 29/54] Main --- pages/release-notes.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index e3a4a8429..733ae89a5 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -156,6 +156,8 @@ updated. by predefining the UTF-8 encoding in the export utility to support special characters [#554](https://github.com/memgraph/mage/pull/554) +## Previous releases + ### Memgraph v3.1.0 - Mar 12th, 2025 {

⚠ Breaking changes

} From c27b99dfaf13a0aa07aac8fe9a1f932ba1a29ef8 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Wed, 16 Apr 2025 16:44:25 +0200 Subject: [PATCH 30/54] Newline --- pages/_meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/_meta.ts b/pages/_meta.ts index 67ec83c52..8a6698c6a 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -17,4 +17,4 @@ export default { "help-center": "Help center", "release-notes": "Release notes", "memgraph-in-production": "Memgraph in production" -} \ No newline at end of file +} From c58d7c532d64ca0478b8a91f3221a9755cabbd90 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 11:14:47 +0200 Subject: [PATCH 31/54] Add section for query timeout --- .../general-suggestions.mdx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 9cf6e55d9..1659c58d6 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -206,6 +206,8 @@ More details and installation instructions can be found in the [Install Memgraph Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: +### Logging configuration + - `--log-level` This flag controls the granularity of Memgraph's logs. In **production environments**, setting it to `INFO` or `DEBUG` is typically sufficient. For **diagnostics or experimentation**, using `TRACE` provides highly detailed logs @@ -221,16 +223,35 @@ flags to consider: logs are collected from standard output streams. +### Data Recovery Configuration + - `--storage-parallel-schema-recovery=true` and `--storage-recovery-thread-count=x` These flags enable **parallel recovery** of the schema during startup. Set `x` to the number of cores you want to dedicate for recovery. This significantly speeds up the time it takes for Memgraph to become operational after a restart. +### Memory Limit Configuration + - `--memory-limit=x` (in MiB) This flag defines the **maximum memory** Memgraph can use. By default, it's set to **90% of available system memory**, but if you're running other processes on the same machine (like Memgraph Lab or others), it's advisable to **manually lower this limit**. On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower than the actual available memory on the node. +### Query Timeout Configuration + +- `--query-execution-timeout-sec=0` + This flag disables query timeouts entirely. By default, Memgraph limits query execution time to **600 seconds**. If you're performing **large data imports**—such as using `LOAD CSV` or the `migrate()` function—it can be helpful to temporarily set this flag to `0` to avoid interruptions. + + Once the import is complete, you can restart Memgraph **without the flag** if your regular workloads don’t require extended execution times (e.g., during daily batch jobs). + +- **Runtime alternative (Cypher)** + You can also configure the query timeout dynamically at runtime using: + ```cypher + SET DATABASE SETTING "query.timeout" TO "0"; + ``` + + This is particularly useful in environments where you don’t want to restart Memgraph but still need to adjust timeout behavior for specific workloads. + For more information on how to configure Memgraph, as well as what are all the flags that Memgraph can be configured on, please check out the [configuration documentation page](/database-management/configuration). From 6742f6132a5a1992254c2ed1a8d67ec27016f1f5 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 14:01:34 +0200 Subject: [PATCH 32/54] Set up lab features --- pages/memgraph-in-production/general-suggestions.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index 1659c58d6..c79e2b90d 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -369,6 +369,9 @@ MEMGRAPH_ENTERPRISE_LICENSE=License Reason for that is because environment variables always override any system settings that are set via queries. +Additionally, please check out [how to set up the Memgraph Lab Enterprise license](/data-visualization/user-manual/remote-storage#how-to-set-it-up) +if you require any Memgraph Lab Enterprise features. + For more information about what Enterprise features are included with Memgraph Enterprise License, please check out the [section on Memgraph Enterprise enablement](/database-management/enabling-memgraph-enterprise). From 5cedea691ef8ef8d1833026ec77c6465d86da41a Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 14:05:57 +0200 Subject: [PATCH 33/54] Sentence case --- .../general-suggestions.mdx | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index c79e2b90d..aed77c5de 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -5,36 +5,36 @@ description: General suggestions when working with Memgraph, from testing to pro import { Callout } from 'nextra/components' -# General Suggestions +# General suggestions This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the *Memgraph in Production* series for tailored recommendations. -## What is Covered? +## What is covered? The general suggestions cover the following key areas: -**1. [Hardware Requirements for running Memgraph](#1-hardware-requirements-for-running-memgraph)**
+**1. [Hardware requirements for running Memgraph](#1-hardware-requirements-for-running-memgraph)**
Learn how to select the best machine for Memgraph based on your resources, whether on-prem, in a data center, or via cloud offerings (e.g., AWS, GCP, Azure). -**2. [Hardware Sizing](#2-hardware-sizing)**
+**2. [Hardware sizing](#2-hardware-sizing)**
Since Memgraph is an in-memory database, estimating RAM requirements is crucial. This section helps you allocate memory based on dataset size and expected workloads. -**3. [Hardware Configuration](#3-hardware-configuration)**
+**3. [Hardware configuration](#3-hardware-configuration)**
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking Configuration](#4-networking-configuration)**
+**4. [Networking configuration](#4-networking-configuration)**
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. -**5. [Deployment Options](#5-deployment-options)**
+**5. [Deployment options](#5-deployment-options)**
Understand the tradeoffs between running Memgraph natively on a host machine or in a containerized environment (Docker, K8s) to choose the best deployment method. -**6. [Choosing the Right Memgraph Flag Set](#6-choosing-the-right-memgraph-flag-set)**
+**6. [Choosing the right Memgraph flag set](#6-choosing-the-right-memgraph-flag-set)**
Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. -**7. [Choosing the Right Memgraph Storage Mode](#7-choosing-the-right-memgraph-storage-mode)**
+**7. [Choosing the right Memgraph storage mode](#7-choosing-the-right-memgraph-storage-mode)**
Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIONAL** and **IN_MEMORY_ANALYTICAL** - as well as one disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode based on your specific use case. @@ -42,24 +42,24 @@ based on your specific use case. **8. [Backup considerations](#8-backup-considerations)**
Learn about how to preserve your data in Memgraph to prevent any data loss. -**9. [Importing Mechanisms](#9-importing-mechanisms)**
+**9. [Importing mechanisms](#9-importing-mechanisms)**
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**10. [Enterprise Features You Might Require](#10-enterprise-features-you-might-require)**
+**10. [Enterprise features you might require](#10-enterprise-features-you-might-require)**
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**11. [Queries That Best Suit Your Workload](#11-queries-that-best-suit-your-workload)**
+**11. [Queries that best suit your workload](#11-queries-that-best-suit-your-workload)**
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, which will be covered in the specific use case sections. -## Bringing Memgraph to Production +## Bringing Memgraph to production -## 1. Hardware Requirements for Running Memgraph +## 1. Hardware requirements for running Memgraph To get started with Memgraph, we recommend checking the [system requirements](/getting-started/install-memgraph#system-requirements) listed in our installation guide. These requirements are sufficient for setting up Memgraph on your own servers. @@ -67,7 +67,7 @@ in our installation guide. These requirements are sufficient for setting up Memg If you're deploying Memgraph using a cloud provider, visit the [deployment section](/deployment) for guidance on choosing the appropriate instance type for your specific cloud environment. -## 2. Hardware Sizing +## 2. Hardware sizing The most critical factor in provisioning a server for Memgraph is **RAM**. Memgraph operates primarily in **in-memory mode**, meaning the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently @@ -76,34 +76,34 @@ and can scale with your workload. For a deeper dive into how memory usage is calculated, refer to our [memory storage documentation](/fundamentals/storage-memory-usage). Below is a simplified, rule-of-thumb approach to help you estimate your memory needs. -### Memory Components +### Memory components Memgraph’s memory usage consists of five main parts: -1. **Node Memory** +1. **Node memory** Each node requires approximately **128 bytes**. -2. **Relationship Memory** +2. **Relationship memory** Each relationship requires approximately **120 bytes**. -3. **Property Storage** +3. **Property storage** Properties are stored in buffered arrays. The memory needed depends on property types and counts. 4. **Indices** Additional overhead is introduced by indexing node labels, edge types, and properties. -5. **Query Execution Memory** +5. **Query execution memory** Temporary memory required to execute queries. This varies depending on the complexity of queries. Items 1–4 are referred to as **memory at rest**, while query execution memory is often called **compute memory**. -### RAM Sizing Guidelines +### RAM sizing guidelines Use the following steps to estimate the RAM required for your workload: -#### Step 1: Estimate Base Graph Storage +#### Step 1: Estimate base graph storage For datasets with minimal properties (3–5 small properties), use this formula: ``` 128 * N + 120 * R @@ -124,19 +124,19 @@ to extrapolate the estimated requirements for the full dataset. This empirical m based on how your data is actually represented as a graph.
-#### Step 2: Estimate Property Storage +#### Step 2: Estimate property storage If your dataset includes many or complex properties, refer to the [memory storage documentation](/fundamentals/storage-memory-usage) for detailed calculations. Add this result to the base graph size from Step 1. -#### Step 3: Add Index Overhead +#### Step 3: Add index overhead - If you use fewer than 10 indices, this can be skipped. - If you use many indices (e.g., 50+), add **~20% memory overhead** to the total from Steps 1 and 2. -#### Step 4: Add Query Execution Memory +#### Step 4: Add query execution memory - For basic querying without graph algorithms: **1.5× multiplier** - For analytical workloads with algorithms (e.g., PageRank, Community Detection, Betweenness Centrality): **2× multiplier** -### Final Recommendation +### Final recommendation Once you've completed these steps: @@ -148,7 +148,7 @@ Still having problems with estimating the size of your instance? Try out our [of or contact us on Discord!
-## 3. Hardware Configuration +## 3. Hardware configuration One of the most important system settings when running Memgraph is configuring the kernel parameter `vm.max_map_count`. This setting ensures that the system can allocate enough virtual memory areas, which is critical for avoiding memory-related @@ -165,7 +165,7 @@ Properly configuring `vm.max_map_count` is a one-time setup but essential for a For system-specific configuration steps and installation guidelines, refer to the [Install Memgraph guide](/getting-started/install-memgraph). -## 4. Networking Configuration +## 4. Networking configuration To ensure Memgraph functions properly in your environment, make sure the following ports are open and accessible on your server: @@ -181,7 +181,7 @@ In addition to enabling these ports, be sure to: - On **Red Hat-based systems**, even with the firewall properly configured, you might need to **disable SELinux**, as it can block Memgraph’s access to system resources. -## 5. Deployment Options +## 5. Deployment options Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, @@ -223,13 +223,13 @@ flags to consider: logs are collected from standard output streams. -### Data Recovery Configuration +### Data recovery configuration - `--storage-parallel-schema-recovery=true` and `--storage-recovery-thread-count=x` These flags enable **parallel recovery** of the schema during startup. Set `x` to the number of cores you want to dedicate for recovery. This significantly speeds up the time it takes for Memgraph to become operational after a restart. -### Memory Limit Configuration +### Memory limit configuration - `--memory-limit=x` (in MiB) This flag defines the **maximum memory** Memgraph can use. By default, it's set to **90% of available system memory**, but if @@ -237,7 +237,7 @@ flags to consider: On Kubernetes, node memory is often shared with system components like **kubelets**, so the flag should be set to a value lower than the actual available memory on the node. -### Query Timeout Configuration +### Query timeout configuration - `--query-execution-timeout-sec=0` This flag disables query timeouts entirely. By default, Memgraph limits query execution time to **600 seconds**. If you're performing **large data imports**—such as using `LOAD CSV` or the `migrate()` function—it can be helpful to temporarily set this flag to `0` to avoid interruptions. @@ -257,7 +257,7 @@ For more information on how to configure Memgraph, as well as what are all the f check out the [configuration documentation page](/database-management/configuration). -## 7. Choosing the Right Memgraph Storage Mode +## 7. Choosing the right Memgraph storage mode Memgraph currently supports two fully-featured and production-ready storage modes: - `IN_MEMORY_TRANSACTIONAL` @@ -282,7 +282,7 @@ You can set the storage mode in two ways: --storage-mode=IN_MEMORY_ANALYTICAL ``` -### Which Mode Should You Choose? +### Which mode should you choose? - ✅ **Transactional Mode (`IN_MEMORY_TRANSACTIONAL`)** Ideal for **mission-critical workloads** requiring **ACID guarantees**, **replication**, and **high availability**. @@ -306,7 +306,7 @@ For more information about the implications of Memgraph storage mode offerings, [storage mode documentation](/fundamentals/storage-memory-usage). -## 8. Backup Considerations +## 8. Backup considerations Ensuring data durability and having a solid backup strategy is essential for any production deployment. Memgraph provides built-in mechanisms for creating **snapshots** and **write-ahead logs (WALs)** to help @@ -331,7 +331,7 @@ For more detailed information, refer to: - [Data Durability Fundamentals](/fundamentals/data-durability) - [Backup and Restore Documentation](/database-management/backup-and-restore) -## 9. Importing Mechanisms +## 9. Importing mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. @@ -339,7 +339,7 @@ you're working with CSV files, JSON streams, Kafka topics, or external data sour We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 10. Enterprise Features You Might Require +## 10. Enterprise features you might require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -378,7 +378,7 @@ For more information about what Enterprise features are included with Memgraph E -## 11. Queries That Best Suit Your Workload +## 11. Queries that best suit your workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for From 2a3aa4e0d4aa2461a9413c16fc353e638ef9b17d Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 14:21:21 +0200 Subject: [PATCH 34/54] Remove empty construction pages --- pages/memgraph-in-production.mdx | 30 +++++++++++-------- pages/memgraph-in-production/_meta.ts | 9 ------ .../memgraph-in-analytical-workloads.mdx | 15 ---------- .../memgraph-in-cyber-security.mdx | 15 ---------- .../memgraph-in-fraud-detection.mdx | 15 ---------- .../memgraph-in-graphrag.mdx | 15 ---------- .../memgraph-in-high-throughput-workloads.mdx | 15 ---------- ...memgraph-in-mission-critical-workloads.mdx | 15 ---------- .../memgraph-in-supply-chain.mdx | 15 ---------- .../memgraph-in-transactional-workloads.mdx | 15 ---------- 10 files changed, 18 insertions(+), 141 deletions(-) delete mode 100644 pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-cyber-security.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-fraud-detection.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-graphrag.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-supply-chain.mdx delete mode 100644 pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 55849a1b5..8e483555c 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -1,18 +1,18 @@ --- -title: Memgraph in Production +title: Memgraph in production description: Learn how to deploy Memgraph in production for your workload and consider all the advices directly from the Memgraph Team based on our multi-year experiences. --- import {CommunityLinks} from '/components/social-card/CommunityLinks' -# Memgraph in Production +# Memgraph in production When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, scalability, and resilience. This includes hardware considerations, the correct sizing of instances, configuring drivers, using appropriate flags when starting Memgraph, importing data, and connecting to external sources. The guidelines in this section will help you make informed decisions for your specific use case, ensuring that Memgraph performs effectively in your environment. -## How to use the Memgraph in Production guides +## How to use the Memgraph in production guides To get started, we recommend first reading the **General Suggestions** section, which outlines practices that are **agnostic to specific use cases**. These are foundational principles that apply broadly across most production setups. Each separate guide in the **"Memgraph in Production"** @@ -21,17 +21,23 @@ series focuses on a particular type of workload or deployment scenario. At the b general suggestions when there's a conflict—so always defer to the targeted guide when applicable. -## 📚 Available Guides in the *Memgraph in Production* Series +## 📚 Available guides in the *Memgraph in production* series - [General Suggestions](/memgraph-in-production/general-suggestions) -- [Memgraph in transactional workloads](/memgraph-in-production/memgraph-in-transactional-workloads) -- [Memgraph in analytical workloads](/memgraph-in-production/memgraph-in-analytical-workloads) -- [Memgraph in mission critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) -- [Memgraph in high throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) -- [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) -- [Memgraph in Supply Chain use cases](/memgraph-in-production/memgraph-in-supply-chain) -- [Memgraph in Cyber Security use cases](/memgraph-in-production/memgraph-in-cyber-security) -- [Memgraph in Fraud Detection use cases](/memgraph-in-production/memgraph-in-fraud-detection) +## 🚧 Guides in construction +- Memgraph in transactional workloads +- Memgraph in analytical workloads +- Memgraph in mission critical workloads +- Memgraph in high throughput workloads +- Memgraph in GraphRAG use cases +- Memgraph in supply chain use cases +- Memgraph in cyber security use cases +- Memgraph in fraud detection use cases + + +If you'd like to help us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 + diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index 238645804..e6a27c5f9 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,12 +1,3 @@ export default { - "overview": "Overview", "general-suggestions": "General suggestions", - "memgraph-in-transactional-workloads": "Memgraph in transactional workloads", - "memgraph-in-analytical-workloads": "Memgraph in analytical workloads", - "memgraph-in-high-throughput-workloads": "Memgraph in high throughput workloads", - "memgraph-in-graphrag": "Memgraph in GraphRAG", - "memgraph-in-fraud-detection": "Memgraph in Fraud Detection", - "memgraph-in-cyber-security": "Memgraph in Cyber Security", - "memgraph-in-supply-chain": "Memgraph in Supply Chain", - "memgraph-in-mission-critical-workloads": "Memgraph in Mission Critical workloads", } \ No newline at end of file diff --git a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx deleted file mode 100644 index df745caa8..000000000 --- a/pages/memgraph-in-production/memgraph-in-analytical-workloads.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in analytical workloads -description: Understand the implications of getting your analytical use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in analytical workloads - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **analytical use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx b/pages/memgraph-in-production/memgraph-in-cyber-security.mdx deleted file mode 100644 index 7936ad123..000000000 --- a/pages/memgraph-in-production/memgraph-in-cyber-security.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in Cyber Security -description: Understand the implications of getting your cyber security use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in Cyber Security - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **cyber security use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx b/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx deleted file mode 100644 index 8b238defa..000000000 --- a/pages/memgraph-in-production/memgraph-in-fraud-detection.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in Fraud Detection -description: Understand the implications of getting your fraud detection use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in Fraud Detection - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **fraud detection use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx deleted file mode 100644 index e7d6f0cdb..000000000 --- a/pages/memgraph-in-production/memgraph-in-graphrag.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in GraphRAG use cases -description: Understand the implications of getting your GraphRAG use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in GraphRAG use cases - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **GraphRAG use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx deleted file mode 100644 index c2fe4fee6..000000000 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in high throughput workloads -description: Understand the implications of getting your high throughput use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in high throguhput workloads - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **high throughput use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx deleted file mode 100644 index 9416c4dc1..000000000 --- a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in mission critical workloads -description: Understand the implications of getting your mission critical use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in mission critical workloads - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **mission critical use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx b/pages/memgraph-in-production/memgraph-in-supply-chain.mdx deleted file mode 100644 index c1eef33a7..000000000 --- a/pages/memgraph-in-production/memgraph-in-supply-chain.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in Supply Chain workloads -description: Understand the implications of getting your supply chain use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in Supply Chain - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **supply chain use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - diff --git a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx b/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx deleted file mode 100644 index 6bdc20178..000000000 --- a/pages/memgraph-in-production/memgraph-in-transactional-workloads.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Memgraph in transactional workloads -description: Understand the implications of getting your transactional use case to production with Memgraph. ---- - -import { Callout } from 'nextra/components' - -# Memgraph in transactional workloads - - -🚧 **This guide is still under construction!** -We're working on a full walkthrough for deploying your **transactional use case** in production with Memgraph. If you'd like to help -us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - From 313e61b60cfd38a98b9082fdb63523119c67b84c Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 14:22:10 +0200 Subject: [PATCH 35/54] Add callout --- pages/memgraph-in-production.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 8e483555c..1bfa0b98f 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -3,6 +3,7 @@ title: Memgraph in production description: Learn how to deploy Memgraph in production for your workload and consider all the advices directly from the Memgraph Team based on our multi-year experiences. --- +import { Callout } from 'nextra/components' import {CommunityLinks} from '/components/social-card/CommunityLinks' # Memgraph in production From 1b5c5f342bb9630358d0cb736fbe2a54b313bf33 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 17 Apr 2025 16:37:13 +0200 Subject: [PATCH 36/54] Update GraphRAG use case --- pages/memgraph-in-production/_meta.ts | 1 + .../memgraph-in-graphrag.mdx | 152 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 pages/memgraph-in-production/memgraph-in-graphrag.mdx diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index e6a27c5f9..ab285dfac 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,3 +1,4 @@ export default { "general-suggestions": "General suggestions", + "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", } \ No newline at end of file diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx new file mode 100644 index 000000000..25408a030 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -0,0 +1,152 @@ +--- +title: Memgraph in GraphRAG use cases +description: Suggestions on how to bring your Memgraph to production in GraphRAG use cases. +--- + +import { Callout } from 'nextra/components' +import { CommunityLinks } from '/components/social-card/CommunityLinks' + +# Memgraph in GraphRAG use cases + + +👉 **Start here first** +Before diving into this guide, we recommend starting with the [**General suggestions**](/memgraph-in-production/general-suggestions) +page. It provides **foundational, use-case-agnostic advice** for deploying Memgraph in production. + +This guide builds on that foundation, offering **additional recommendations tailored to specific workloads**. +In cases where guidance overlaps, the information in this chapter should be seen as **complementary or overriding**, depending +on the unique needs of your use case. + + +## When to use this guide + +This guide is for you if you're exploring or building **GraphRAG (Graph-Augmented Retrieval-Augmented Generation)** systems. +Consider diving into this guide when: + +- 💬 You want to **query your graph using natural language** through a proxy **LLM** interface. +- 🔄 You need to **seamlessly extract knowledge graphs from multiple source systems**, especially when graph representation + naturally suits the data structure. +- 🏱 You're building a system where **business stakeholders need fast insights** without relying on engineers to expose data through APIs. +- 🌙 Your engineers are **deep in the trenches at midnight**, and you'd rather they write **simple questions** instead of Cypher queries. +- 🧠 You have **embeddings** and need to perform **vector search** across your graph, along with structured Cypher query language expressiveness. + +If any of these resonate with your project, this guide will walk you through the best practices and configurations to bring +GraphRAG to life using Memgraph. + +## Why should you choose Memgraph for GraphRAG use cases + +Choosing Memgraph for your GraphRAG use case means prioritizing **performance**, **scalability**, and a **smooth end-user experience**. +Memgraph is currently the **most performant and scalable graph database** on the market—your business stakeholders won’t be happy +waiting unreasonable amounts of time for answers, and with Memgraph, they won’t have to. You can explore our performance results in +the [**Benchgraph benchmarks**](https://memgraph.com/benchgraph). + +Thanks to its **in-memory architecture**, Memgraph delivers **predictable response times**, avoiding the inconsistency of LRU cache +strategies that sometimes hit disk and degrade performance. + +With built-in **vector search powered by [usearch](https://github.com/unum-cloud/usearch)**, Memgraph supports **high-performance retrieval** +across all your GraphRAG workloads. It also acts as a **unified analytical engine**, connecting to **legacy systems** and supporting a wide range +of **data ingestion sources** to populate your knowledge graph with ease. + +Memgraph makes **schema metadata accessible in constant time**, allowing your LLM to construct Cypher queries **efficiently and without overhead**. +To ensure a secure environment, Memgraph also provides **role-based and fine-grained access controls**, including **read-only access**, so your +GraphRAG queries never risk modifying your data. + +## What is covered? + +The suggestions for GraphRAG use cases **complement** several key sections in the +[**general suggestions guide**](/memgraph-in-production/general-suggestions). These sections offer important context and +additional best practices tailored for performance, stability, and scalability in GraphRAG systems: + +- **[Hardware sizing](#hardware-sizing)** + If you're using embeddings for vector search, be aware that they can significantly impact **memory consumption**. + +- **[Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set)** + Configure runtime flags to enable **constant-time schema retrieval** for the LLM. This reduces overhead during `text2Cypher` construction. + +- **[Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode)** + Guidance on selecting the optimal **storage mode** for GraphRAG use cases, depending on whether your focus is analytical speed or transactional safety. + +- **[Enterprise features you might require](#enterprise-features-you-might-require)** + Understand which **enterprise features** — such as security, access controls, and dynamic graph algorithms are + essential for production-ready GraphRAG deployments. + +- **[Queries that best suit your workload](#queries-that-best-suit-your-workload)** + Learn how to use **deep path traversals**, **vector search**, and **dynamic MAGE algorithms** to efficiently retrieve contextual data and + handle **high-velocity graphs**. + +- **[Memgraph ecosystem](#memgraph-ecosystem)** + Explore the tools and integrations within the **Memgraph ecosystem** that can accelerate the development and operation of your GraphRAG pipeline. + +## Hardware sizing + +If your GraphRAG use case involves **vector embeddings**, it’s important to account for their **impact on memory usage**. Currently, +Memgraph stores embeddings in two places: + +1. In its **proprietary in-memory property storage** using 8 bytes per float. +2. In the **vector index (usearch)** using 4 bytes per float (float32). + +This results in a total **12-byte overhead per float value** when storing embeddings. + +To estimate the additional memory required for embeddings, use the following formula: + +``` +number of nodes with embeddings × embedding dimension × 12B +``` + +This will give you a more accurate view of your memory needs when planning hardware resources. + +### Upcoming optimizations + +Memgraph is actively working on two short-term optimizations to reduce this memory overhead: + +- 🧭 **Reference-based indexing**: Embeddings will be stored only in the vector index, with the property storage holding just a reference. + Since embeddings are used exclusively for vector search, this eliminates duplication. + +- ⚙ **Support for float16 in usearch**: Users will be able to store embeddings as 2-byte floats (float16), commonly used in neural networks. + This reduces memory usage significantly while maintaining accuracy within a **\<1% margin**, making it a strong tradeoff for most applications. + +### Best practices + +- Consider the **appropriate embedding dimension** for your use case. While models like OpenAI use 1536 or 3072 dimensions, **lower-dimensional vectors + (e.g., 512 or 768)** often result in only a **5–6% drop in accuracy** and drastically reduce memory consumption. + +- If in-memory embedding storage is too demanding, you can **offload embeddings to a third-party vector database** and still integrate it + into your GraphRAG pipeline alongside Memgraph. + +- If you're also storing **document context** in Memgraph, keep in mind that these long strings are currently stored in memory as well. A short-term + roadmap item will enable **offloading static text content to disk**, since these strings rarely change. This will further + **increase your memory efficiency and scalability**. + +## Choosing the right Memgraph flag set + +For GraphRAG use cases, it's essential to run Memgraph with the following additional flag: + +```bash +--schema-info-enabled=true +``` + +This enables **constant-time schema retrieval**, which drastically reduces the time needed to supply the **LLM with schema information** +required for generating Cypher queries. Without this flag, schema discovery can introduce unnecessary latency, negatively affecting query +responsiveness and the overall user experience. + +## Choosing the right Memgraph storage mode + +Schema retrieval is fully supported in both `IN_MEMORY_TRANSACTIONAL` and `IN_MEMORY_ANALYTICAL` storage modes. This means you can choose the +mode that best fits your workload without compromising the ability to serve schema metadata to the LLM. + +- Use `IN_MEMORY_TRANSACTIONAL` if your use case requires **ACID guarantees**, **replication**, or **high availability**. +- Use `IN_MEMORY_ANALYTICAL` if you're focused on **read-only**, **high-ingestion**, or **analytics-heavy** workloads where + **maximum write throughput** is a priority. + +Both modes are compatible with GraphRAG workflows — choose based on your performance and reliability needs. + +## Enterprise features you might require +blahblah + +## Queries that best suit your workload +blahblah + +## Memgraph ecosystem +blahblah + + From 05e8b71c5f3322fab254161dd107a525ff7882e8 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 24 Apr 2025 13:38:49 +0200 Subject: [PATCH 37/54] Finish memgraph in production for graphrag --- .../memgraph-in-graphrag.mdx | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx index 25408a030..9a3223992 100644 --- a/pages/memgraph-in-production/memgraph-in-graphrag.mdx +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -141,12 +141,60 @@ mode that best fits your workload without compromising the ability to serve sche Both modes are compatible with GraphRAG workflows — choose based on your performance and reliability needs. ## Enterprise features you might require -blahblah + +To ensure your GraphRAG setup is secure, scalable, and suitable for multi-user environments, Memgraph offers several +enterprise features that are especially relevant: + +- 🔐 **Role-based access control** + GraphRAG systems often generate queries automatically via LLMs, so it’s critical to enforce strict **read-only permissions** + to prevent accidental data modification. Memgraph supports + [role-based access control](/database-management/authentication-and-authorization/role-based-access-control), + allowing you to limit access to **read-only roles** for GraphRAG users and services. + +- đŸ·ïž **Label-based access control** + For more granular permissions, Memgraph supports + [label-based access control](/database-management/authentication-and-authorization/role-based-access-control#label-based-access-control). + This enables defining access rules at the **label and edge type level**, so users can only view and query the parts of the + graph that are relevant to them—ensuring **data isolation and privacy** across teams or tenants. + +- 🔐 **SSO support for GraphChat** + If you're using **GraphChat** inside **Memgraph Lab** to power LLM-based querying, + [single sign-on (SSO)](/database-management/authentication-and-authorization/auth-system-integrations#single-sign-on) makes it easy + to integrate with your existing identity provider and streamline user access across your organization. + +- 🔄 **Dynamic graph algorithms** + In scenarios where the graph is **changing in real-time** and you need **continuous summarization or insights**, Memgraph + provides enterprise-only [dynamic graph algorithms](/advanced-algorithms/available-algorithms#dynamic-graph-algorithms-enterprise), + such as **online community detection**. These allow your GraphRAG pipeline to stay up-to-date with evolving data without expensive + full re-computation. + ## Queries that best suit your workload -blahblah +When integrating Memgraph into open-source GraphRAG frameworks like **LlamaIndex** or **LangGraph**, +it’s essential to incorporate this query in your retrieval pipeline: + +```cypher +SHOW SCHEMA INFO; +``` + +This command retrieves the graph schema in **constant time**, enabling the LLM to construct valid Cypher queries with +minimal overhead. It’s a key part of the retrieval pipeline that helps bridge natural language questions and +structured graph queries. + +While there's no single query pattern that fits all GraphRAG use cases. Since LLMs generate a wide variety of +questions, **multi-hop queries** (i.e., traversals across multiple relationships) stand to benefit significantly from +Memgraph’s **in-memory architecture**, offering fast and consistent response times even under complex traversal logic. ## Memgraph ecosystem -blahblah + +To accelerate your GraphRAG journey, we recommend exploring the [**AI ecosystem**](/ai-ecosystem) page. There, +you’ll find detailed information about: + +- **GraphChat**, Memgraph’s natural language interface powered by LLMs +- **Open-source GraphRAG integrations** with frameworks like LlamaIndex and LangGraph +- **Practical examples** and usage patterns to help you get started quickly + +Whether you're building a custom retrieval pipeline or experimenting with LLM-driven interfaces, this ecosystem +is designed to give you a head start and reduce development time. From 52485acb22c6ff918f85ece54caa61e9c6e954fa Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 24 Apr 2025 13:56:07 +0200 Subject: [PATCH 38/54] Update initial page --- pages/memgraph-in-production.mdx | 16 ++++++++++++++++ pages/memgraph-in-production/_meta.ts | 1 + 2 files changed, 17 insertions(+) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 1bfa0b98f..55710325e 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -36,6 +36,22 @@ general suggestions when there's a conflict—so always defer to the targeted gu - Memgraph in cyber security use cases - Memgraph in fraud detection use cases + +## 👀 Additional guides to consider when bringing your app to production + +In addition to the core *Memgraph in Production* series, there are a number of complementary guides designed to +help you better prepare your environment, validate Memgraph’s fit for your workload, and ensure a +smooth transition to production. + +These guides focus on areas like performance benchmarking, testing, and operational readiness—offering additional tools +and frameworks that can help you get the most out of your Memgraph deployment. + +- [**📊 Evaluating Memgraph**](/memgraph-in-production/evaluating-memgraph) + Learn how to properly **test Memgraph for performance and scalability**. + This guide walks you through performance and stress testing scenarios, benchmarking with real-world data, + and identifying key metrics that can help validate Memgraph’s fit for your application needs. + + If you'd like to help us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 Your feedback helps us build what matters most. 🙌 diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index ab285dfac..fdba04057 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,4 +1,5 @@ export default { "general-suggestions": "General suggestions", "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", + "evaluating-memgraph": "Evaluating Memgraph", } \ No newline at end of file From 7f2fcb27176f648326dd62439abcbb316cc781a3 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 24 Apr 2025 13:57:00 +0200 Subject: [PATCH 39/54] Add graphrag link --- pages/memgraph-in-production.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 1bfa0b98f..e1366c433 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -25,13 +25,13 @@ general suggestions when there's a conflict—so always defer to the targeted gu ## 📚 Available guides in the *Memgraph in production* series - [General Suggestions](/memgraph-in-production/general-suggestions) +- [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-ing-graphrag) ## 🚧 Guides in construction - Memgraph in transactional workloads - Memgraph in analytical workloads - Memgraph in mission critical workloads - Memgraph in high throughput workloads -- Memgraph in GraphRAG use cases - Memgraph in supply chain use cases - Memgraph in cyber security use cases - Memgraph in fraud detection use cases From f3b0c5c0751aa8e47f02db0f7c24cad4131f3aea Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Thu, 24 Apr 2025 20:55:16 +0200 Subject: [PATCH 40/54] Add page for evaluating memgraph -> mgbench --- pages/memgraph-in-production/_meta.ts | 2 +- .../benchmarking-memgraph.mdx | 78 ++++++++++++++++++ .../benchgraph-snippet.png | Bin 0 -> 138848 bytes 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 pages/memgraph-in-production/benchmarking-memgraph.mdx create mode 100644 public/pages/memgraph-in-production/benchmarking-memgraph/benchgraph-snippet.png diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index fdba04057..2fb4d4858 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,5 +1,5 @@ export default { "general-suggestions": "General suggestions", "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", - "evaluating-memgraph": "Evaluating Memgraph", + "benchmarking-memgraph": "Benchmarking Memgraph", } \ No newline at end of file diff --git a/pages/memgraph-in-production/benchmarking-memgraph.mdx b/pages/memgraph-in-production/benchmarking-memgraph.mdx new file mode 100644 index 000000000..3d9c7ee46 --- /dev/null +++ b/pages/memgraph-in-production/benchmarking-memgraph.mdx @@ -0,0 +1,78 @@ +--- +title: Benchmarking Memgraph +description: Frameworks you can use from the Memgraph's ecosystem that will allow you to test Memgraph's performance, scale, and stability +--- + +import { Callout } from 'nextra/components' +import { CommunityLinks } from '/components/social-card/CommunityLinks' + + +# Benchmarking Memgraph against others + +## mgbench - framework for performance testing + +At Memgraph, we believe we are building the **fastest graph database on the market**—and much of that performance comes +from our **in-memory architecture**. + +Many disk-based graph databases must write data to disk during every transaction and retrieve it back during reads. +While these databases often rely on caching layers to speed up reads, **cache invalidation becomes a bottleneck** in +**high-throughput write scenarios**. Every new write risks invalidating parts of the cache, leading to more disk reads +and slower performance. + +In **read-only workloads**, the performance gap between Memgraph and other databases may appear smaller at first glance. +However, even in these scenarios, **on-disk graph databases can suffer from unpredictable latency** due to +**cache invalidation**, especially if the system has recently handled write operations. This can lead to a +**poor user experience**, with inconsistent query response times depending on whether data is fetched from cache or disk. + +Memgraph, on the other hand, **consistently performs better** by keeping the entire dataset **in memory**, +ensuring **predictable latency and fast response times** regardless of previous operations or system state. + +To support fair and realistic comparisons, we built **[mgbench](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md)**—a benchmarking +framework located within our core Memgraph repository. Unlike simplistic benchmarks that run a query once and record latency, +mgbench focuses on **repeatable, controlled testing** across different conditions and vendors. + +We designed mgbench to: + +- Eliminate noise from unrelated system activity +- Account for whether a database is **“hot” or “cold”** +- Detect how **query repetition, data freshness**, or **system-level caching** affect results +- Support **adding new vendors and workloads** easily +- Provide both **latency and throughput metrics at scale** + +Many teams make the mistake of testing queries in isolation. But in real production workloads, especially ones with +**high write volume**, read metrics can be greatly affected. This is where Memgraph's in-memory model really shines, offering +**consistent performance even under pressure**, where disk-based solutions tend to fall behind. For this reason, *mgbench* is designed +to support both [**realistic workloads**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads), +which simulate production-like distributions of reads, writes, +and analytical queries, and [**mixed workloads**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads), +where you can analyze how the combination of different query types affects individual performance metrics. + + +Ready to test for yourself? Try out [**mgbench**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md) with +your own data and workloads, and see how Memgraph performs under realistic production conditions. 🚀 + + +## Visualizing results with Benchgraph + +While `mgbench` gives you full control over benchmarking and produces raw results in JSON format, we know that +analyzing performance metrics at scale can be time-consuming. That’s why we created +[**Benchgraph**](https://github.com/memgraph/benchgraph)—a companion visualization tool for `mgbench`. + +![](/pages/memgraph-in-production/benchmarking-memgraph/benchgraph-snippet.png) + +Benchgraph is not required to use `mgbench`, but it offers a **convenient way to visualize and explore** +benchmark results across a variety of run conditions: + +- **Cold vs. hot cache** scenarios +- **Isolated query performance** vs. **realistic workload distributions** +- **Latency and throughput trends** across vendors and dataset sizes +- **Per query comparison** across the whole workload + +By turning JSON output into interactive dashboards, Benchgraph helps you identify trends and outliers quickly, +spot regressions, and better understand how each database behaves under pressure. + + +For looking into the published bechmarks, check out [our official BenchGraph site](https://memgraph.com/benchgraph)! + + + diff --git a/public/pages/memgraph-in-production/benchmarking-memgraph/benchgraph-snippet.png b/public/pages/memgraph-in-production/benchmarking-memgraph/benchgraph-snippet.png new file mode 100644 index 0000000000000000000000000000000000000000..dcea11919e907cae26885e5be31ad0f663f8dbcd GIT binary patch literal 138848 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�qi0)H~BF<}f#V<3$UWKzklhRtc?o~K@@Xskm z5iw&zT-SojA)GpbBlfv_PwzA{#^hD3X(jVXvnm2ufj?ImV^XeUk=>1k9(&*|F|E&$ z$Fs9>SnsEM+683s!RZx%X(QYM7vi9InEl?}+}ggN^MW3%pCQw2aidFpfR>yklJ)o6 z=!^U;Rdx)@9|(gypz>B!9LXDkO?RSfBAUr1KP+nfd#z| zJ=PqGXI`GsM}EImCU1~sc~%UF_f@pNC3XE_$?BfuMH z3~q4klZD;(LziIuS$Qg}U%BU}MH2|~8qmp)i8mm3tzK@-4`xVXHYyBkQ2HX~+mESU zb6L5QmAB)&`%Y0X6>dJi4j?2iuT-&M#7`|s*lm}Vx7x>PW(zj?b4yxQLBVVhAoVR< zdP1BnFd(2e&rQzl+1`95KI8LKoG{{Y?|b5-=7t zjGXC-8Q1DN2CO6V@;DQO4onw@>kM{C2p{Om=?gFEu2Q#YYdCjRn>1V|`*v3s3JMoD z7yQeuR+ZMY$b#wS^KeRsGBqcnU)^rYDiLn0k}lGwd_dUh+rKczdg7h~3tx4Ef`bub zQ753Y?hdoVAm_hb^OUk9ynP)O*89skTG;9L^NzX~sUlX6+2AFr)UGO5-ytmOx@xNo z8GLvk%;WurFF{%^AeeM+rS|&7_yqWk{fvUisjUfZw~}RC(YQDL5H%{P;zj@g%qvqT z{8<5+J`WrK>i)J$lGJ?$+5XJ3Zgab0N=nL%km=U08K(@%+Mt1HHbqX(?+M~Nmy7gJ zs`l`t)p}g{j_p5D3XAC7!9Whm|`PEsE1yYlpJ zS)(W~G)8@WQ1SSYN#9Y(KZd+~Fn7%p>%_e+uwtfV$k&D78$^Ap^b%!v@51VfH}-t_ z^)-{-Tv94vQk75@m@*ycU&qJC+pJ^q9?{y^+vtE$>|>k?Vj1KLr0VpthZNMg`10yI z8v+=xG{IWKCJ)V0*N>J2ZGY{!+YeF+Y}8rz=%-sgr3ITSz|SxKxgj?+!;@JKA9{uh1k#VX`gSVG#@@58u&O!5WOa3oeXB2D_Rb5S+`Itw z`-8(!8X00F9(E_Q#q;;RSuyjHy7(WJIDLA*hxy18nF!E^fHZ=>Yj(49f=fmVegeQY zn&({~Kp-1J*e&Qg$iCC3C+!*@`zy(=5xspMbrWF!`=@U{WbJh9f3TZ_u92OQN&Ja5 zE&#zrEDo9~uA#7DM|dGU@73JHRQ1Cf|LH^2d3`296S09DqQnpZ+&vdezfLGwsIL)q z2RBsa4XVXH4sY2=pxoy@%VT3jDoR=o%k&LdqJUlX?-c-_D{N<}z+RLQ9Bz%9Teq>Z zTfmn+Nin|*4GHSe!VYA~1VP_2*x<$*5?h-8ciaq!Zr`(CBhuuM;AFP4>ODu)q<%2> z_OTt6Zn+H3Vopvr#j|lg`U=H`%qhwVu5gGB@%4nKK)2``HLqTOABfl;W^kME-@Myr zT0@W5PcFd_ym!3ySKjvVTpoG=2fe^?0L?a8Z&_&AL$J60p62j`pUIE)?BRF9?;kJz zPo+Eif1Xt)5ctP|aQN0IoW!Fy{`0!`KgIHg|JNZ-ntxmZf8Y4#|2p#Re=qf)@jLRr z1Nh&e`2Q4v%TzjGSb;QI$M=`DJJQj9{(l@P_rF2=-+?{yzf1AIWAVRX@!z5NeZvybfzKq ztLIK%m5@+!A90P(Zhr$(YnQV7f2QZg0c>Sn{}e<(`R<7NPN4C^roX<`@trZcG4tYV z!Xw)J!or?5Cdpe&wjc$;ky-oV2N4n;Ki`e`OsHtb#4AU^CN3=_Biw|@fAiz0N3Ai@ zBArFI9F8jkSrrNP1-jYr23x_N2~n`Qy9jJ34(0na}~M z3Tq6a7?rK96R=}c1CB=PEiov8Jr>+xb#@6S9;>$l2M10%bHagdCs^gJhI)&P>WW9F zb?=x5f~X8EQu^B3T8D{EQUP@3$r7$go%iq4zj@1vshx0m1m`Dc=BR$3UgzSKHi854 zfUFf&(uo3xi0kh!X9gNR3tM&k3l?$L0%Ok2KCT?q6s_BjGf$Gzc33m5ySj4m^2WB> z>+0(#fK3#$E`H+JQ6LDwx~~I79oMBxX+x+!Y;Rsox*;7MU3S}lCe8|+u~*?(J279p zfxcI(Mkuq8JoFCmg}}J^8Zo1v!qf#}O|f4TqkpY>Y8z@5E*MnKZI+tRi`(=}GwUK} zBEYfFrbs-FB#4x*E32b(!41)9;4m90E8As3?)vy~rbSyq7G|;9z^p0Ej?mzu_9$(! z8t=`;&D|Xf2J2J%^#@s%BOY@6^_x*j%F1$h@b$T_izvI|7b>}hgf!v2IQ?=jREoD- zHTcCkv)843KOWgz-rqs4cV#7?d;zh6&@JiA@ZTwbGq6}BM!>`cr!JuJix@UV+3hh3 zP|l0Y-AB@PSUmnk*427%%#AGhs^zGq_BOgq%>)xhOL9^?ZY>q*XZ7~#fIfo{_unsP zS9{Bdwt%2D_SrvCySgz1#O3Woudc3c*J=0q0tZ8XBh15oShp+Nc&}v+S#4~q8oD{KAMQ|2?DIT@;D9)0_Z|K7*zBjr>vP$uyi z_56LPzuyFGDEq)-rK(iP5<<>lT*9lCjyTI#%EiFESb8%&(WXw8b{3_NSr8@n-D125 z?0OjSZam%@uDQTb4I-CRxlac#aBRHOB?BD`%*@B4B#8n zPVpmC#KuUzH4erd1=ucT%g@TqY{#$llA;*si2~=SWH_fbfM0L_@qLZEcROJZV1QBHt?XwznN0&9chYt zg!a2(eoNrcmA!=rGP^Fd*xr#MudQnUJ6S$@%q{QJK-}p?&)(g;@=PjKATmXB!W!7L z6s&x}hC&T5YxNuCz4`kxOs#=`e670rfj^vg672vOCkX>4s!chxL{y{v4)QZ-NMKpvH{KC`$qoEF>90<%_r z{la_9peX@*UBEKbTB=zMW&nCo>zt8s@TEdG;TRI=|e0TlYu06$8 z%MXHB#Z^E{2Xwds#C$fBb@J~mZzV4GNehJ}4DAS=J9pFUYp}MvO3A;qwM-lwJdmuM z#{$R%L7yy;NC3!^->~@ZNcn=!#NITREzb5i7k?}iO~PTA9+mb~pT;)R7@CY9?8!aHH9 zH~jX`tngvmUxuDXCgdSv_B}61bA?1h06+o66@@??;>yO+{$W&_>hNC2onolsHN zq*_Z=X8E~F#QSy-vdox&ZwIStiigEAu2GtW5!NS!u^Mz!X{3o-dwpbz_}`KrMh@=X zy@HW$6ui(5!Lb;08PQ0F8Ll=6jJ{+oJ}Cm9VxZ*vK^;`Agjb1>kPyUAscJbG2POEr zmjiCWjjC8%XpC-G~8D-!_gbSMf|Wy1Oo!$wqbpS^y`Ji`!` z>NQZcLvq{>gq7N~k)1{SgnK0{3}%gwKcvo2G0uc33fdoUHS(EYB6lz@A?liLaGVzS z2UCFRs&>BzZVJ4mydt~ZK}MH6f%Q#$15;iJSohL_#ZDbr>M|a8T7v9(Qc&+hqV$?t zW$-6RRS>A+va=4oNG;{BG};*S!k$a{z=;CU&c;TTbRhOq>#;uk(M@R^I(eNR>H>i^ z-8+hd8euKv08Lwb(9JAvxd16dd?<C;EOm~AOTf7P^?&y7g@e_fL5& zzn38QL#`X9SVB0m>5KPpyZWqq!JSW%TOZjzN{cp1pTlppsD0lAtK#%N<~DVRDV$kr z-4}sn;D)Kb6L@m2-**dBN#zAq4!3%v5d!Q{bD_*FTdn4z_*W>~Z1Hj+H_+{|L^G~* z>Ox56F+~wRCFj@28^S?fa(kQcH;zsx;B@N+Lx+7+6q0OUJy4(4T$FtQ+YTG$q_4Kv zrlnQeZ|%ty-hD!_fVhS(pEiFUJgs|;9SgIlbmh48`FT6k=k}$|WwV7IcG$Z2I(&o+ zWmX?@WZz?B)%xTVz|&jVkf{y4A5h%_6F#>V2a0VycZw`fuvpvZw)QQw-Kw)qK}kvD z@#DtetUfK!PFyuSL92Vcm-o&nL?==2_;;_)y=7!tvx+YMg-a)TkZd^QQ2RQNHHHFAdU zA3X4*C;(Ho+%TT_r|6Ut2lqzE+z0*N_#D-B-w(nWs2EI3+4#}~YG6ijrbD~j%SgVb~S#n}*I z*K)bH3=|KjFqphmfUH9|Rd&-fde%iu$~@C6Irg*<-UN+9ho%1f=|q2*IzR zAp$8O)CF~DaEf%Xk+HGaWo1|4$7H3|LB-f(T>Yn7SmUWJDnd8em(GrGJiZIW$7*V88-gf%P?y!wx82slRbH+98{N- z%#R*jbhbJrmqFP@ek|iDb%+yx7Y^TU?{AC_rw)cr_c~uScKi} zrg6Z{=$yXtMtebZ+U+);iH$99>j*OC17bfQumRz=14|8uARVXh|5dI6n&VS=8?dUO4Xou`c4;R@Dl|&&&A*h|M*(hJd)s zudnHg@R*jcCfI7PjpxP#o1+JK2dfQ)1u8Nb`D(YmI1YC>LRtp}8`Rdmef{NT0kDCX z3<6Qv<>e@^BYv`{c$D5HZ9jV^5HARNBQQJ(dzPYrt(xTp&8X7S#-+cZ0fG@$7|qh; zPVHsMA=W~k_ZKd9NIBxqL*jzKPn|5qYx~^r!5XQCv9PjE*c~calb4ro7`-aX7XSMq z+c_XD%q`cm>joBat?7H}mDARv@w`$z^3cC}-(zcK{+jc_L=o}}+{TAR18!uuEa)e5 z(6&9u+X8QKEZ*rUsPR_13BW6ttar;Cr#}HK^0G~*jFfx$OE7^JeK@VwH!oIhD8R(V zdlxV3&;P83p&LpP>@-5EZOI0sA<5k6{5LSA$84R~gl(ef!$kAb5?80_4O zpl7x8@{A~A1$}l|LTOhG7;Iud+q;W>)H-7eg4!Vqz*`Ys-@@ z(6KH!<-r)r%1Z0gG;KKig=r?x5op(cCe+TG&gNzn!Y3hU$NTyu0@+1!=y(^f+f!QN zCS1xP-a;XkbWpx+p6Sr<8%~XZ#8K&pPZ7=0RSbidD;tTTrNcgzaFK9Dh#d09H!=Oq zmGf}Eqrv6Cx+@(OmYtIWWjzlFq4Y7EJPj|3LxTJEzV*FjfYa3ODp!9_F2KL)OZ%OYA zx~5l%^PddV=+a<1&NeSHSY#OjT$cVksYl8|9oiX*blb?*1nAFw3DYR?Q$Vs}~Uc$APfvupIngQ2w+R@e{LwfT{XvT?>)t$tO;_}1<(A6jg`!mgTQDBzJX zm3u*uh#&UFFV<|Z8+o^g7V72kUb@uH3QO6gy7^iVB6D?e^2>g_HXe~Z_WNv*{Q!@W zrcIkkV#Z%`&e%esJtJ)K4G4i2rN0`6bY)f?^y?s1sx4T(8CFB7t~|lcJ`9z9>0eOk z7u~yr;2VgJO^7V{ZkUMm3g^Vc6ngG41lS%yUn(DUt$rsRVvNniYbMa>y%S(tR8&M7 zOWcloVQJ}6=~D#7&!nJXlGGwOpSB$!3!GY8M9#O;0?re3>=NbWzzzDCHOIxtJsZ3< zob}6wv>C(rZacE#}H=YZGvomhvoHgHUy>ItZhWeqCCr)I74k9CU z{)Y5g(E+KdMc6{kHgKraW(cU&qW+0TBQWHwu68lE%AExl1UG7s_yy=min-ybls=x4 z>c>0cc^jKZKR`4b?mielq+g0(sPxB*e9hSna{l3>zMmfh`~)jX3ae9sYIb&ZptB3z zExKPs8n>-CRALvi|KU-qQf@vz#gWP>c4^ACD@Y0IC+<97yqGS zlQn(jvsyVa%S!Y<-%af!R0~K6fnY0jCRDx{zypy}&- zQy^Lqlnu!}{3AN>Rf!JgR_(!=>{Qsf$02R`w8>8P>y_85CYM`}G|g(;zPY1#avja~ zXvH|6 zgP#bVhQmcQ1hHwPu0>KGtk}!?{=7k*Vf1bPe)iTlNN!?gBL*Ejtf<@jRuP8-M7Y_M z@aMw6Hvz^qJq54)Nq*y8kQt*+VRvr%S9;`t%?QZC+V{X6n2A~%%pQmu$_EW!ne6|F z1;7t@49v6LZ=n=ts{&mE<{ETsSyhzb`MG0HYU(%s)1_TiWjMi1LLa)G zc!-g4=_mRRsT}#O|Ch|mdnpxkblxfOm_oK^RAI9;>0dSm(BTosSuLN_voC3rZoPaI zsl1yKJ#`>&AbS4FBNciK0b|#+!4g#^gGTEe>n|}6d{|&L`r{6gb)Xz&*FXM~f^!&> zlwQe@g$`9^WM`C71O(glE=9@Yp80*-1(bkb*AIGEhdq{bpf&?BbGJ6e8Z5l!>uBrE zGtYys%TACUK}UCkX>phz1uQ+n$N5n(LZS?g0l8A!JQ7fE1TKd5R7vjBRSvF=GH8Er zwmnu`!flx#z#JA4otOcna*Oz`nQjBenk?AqMIiuQ3P|06``(YL-}Vk5_L&WG%hJ>@ z;h4D=@mu(!$W%CRO$#n0Q}gM=@P@qQWD^kR5muERnmn-OpAYSX;>MZ3PiGaF)x+Cx z0YD7MJnE0`^?GLi$#?>GP$Tp06x+!qTA%I_WLb&t$0Bjmub^88f*qdS>f7SUWn3Gg21WSpGM!fJwzflH4RX5`+rk#>E6dzRbBJG=Jku547@zgM(GM_ ztJJ|WJ0BpP;;;B6kMDp2soU!2GKh^uaq=6lRqNy#diKZoYNJ8xT6*RCt=ZwUKskDj zWc6D4%&v(A^u*ibSW&aDr-_=yeRg1N;lKFKEl$#7K510fFpE{rB*%EDBFDB~rNXzd z8X6+`NQypqD`D~58u*35tWTe=D_4^v#bkOMCSsfE)hMS6rDGA1@mT zdG)HJB+q~g6<_Jr0hI?J;sU+SzqYAqUsaS(fVk^yCoWFT*fj{Gj$K?_MCMXXOJdfh z^-Aqa2Sj~`uPL9Ay%C7tF=p`Udn+Iyrh~27ex2YwYt5#riv1WgpKDhw<6W_sD0Ih` zn0t{Z=irdlz@Wk{1V-fXZ4UG;lRuCGTFr%^aQl2u@Ll&fCWwVlr7oZ6d0coUEEA zTc8#Tg9MUv){p5MImp*Kcjn;ru<Am| zm^@Y4tuJh1CH6!WZwg8C-lKQx3qu)^P0@THNe`pFSXv_=>^tj3=Ns`Z=-Pm!cgwr1 zv6&g0zU;`agCZ($)GfvD|Nh)R#mz6D>Q0eY4LaqClSz=4mmmAFHo3uH3L1-QMTY%a+ZiVZeB8?Jdajj3(&&wph(9HtclUVym*tN4zthKi>=V zUMZXIvQW@PZ6qO#KlHVi%E`sUW!={%YYXTyW^LK`;>*6bzWUuK_I|ezVIfp{aDXj}0`T!8)AWQb zcx7a28tqRoFknJQp$&G3#I>kXdy_ntB)yRgu0Uou4z!1?;?`dXy3sikY$r~nRxZWluCw(khWQMXYr?CN^|5&tW_P3oqDd1> zOyLu8d4I_LEtx|19{2Lw!6BCcPC};oEs4H9@^(>ML~Pgxz~-z!S8c(a23v#fym0P| z|NN8GTmyb4jjfGgoOd{0_EeR-BGzqh-~MGJ`nb&DjIbQM`k{S>LcR{4K?@9WTx4{# z$LEma+O=#RbmsTq4tsT7#aVaBX`tS2OiY<%e*E}Rfs&DyW(}`cSZW1&4xaQbCV_3x zh$N&~3i=25BQNqs{?H;vV8r+d`#z{pPe8A`YtMl)-+QzRK%mtPI(633(Gi}iZP$w+ z^MvQ2sc**WMZG4Kz{oAfdF-qC>~C>YI=a?ikOLXX+Owt5bzC6DTB#FDchmOK7I^U4k)vI?u z<7`l28e!o>syI$AE?!Yl?XVN<#=2*dlOsz^;f$2n8xSP>RP5|>E7?#Gsr1Y4KZT!$ z;1|wgjykIv=-NLxy=5Dbs4E_V*Mn_9IIEbQArI16XIfb5qBLfDHuStXulAv(i0$p& zOia_u33SZkT1PR}8xapt>WzoSz36{sd}wa2m*6 zE>(&E1uhB>PKHhALTUNZ5KY|~es-W@)S%5svY(uZS6)?BRiiy96Q}v?*?-|$?!#wI zFra`FjA0Mnod8QhYRR3d8FxF^7Y5b{(I%06M*6W$6B_EK9eT?49=!{NeXqGtb#-+v z33j#h#Hly7g0M2^>FHIu!GGbxp-V-Dg(6-(m)N8*TBKP*7k{{ed8p~{i@aJ7i43zb zlOh&PHI~D9wXxfDO%^I_TscKA6koM5H#Id?Q*RJjF*=&ftZot(5fhWOwpQ%g?fPDk zNib|b9j~XP`M+TGR5z8NYiyh_>9dh1={E1wuLi;)-|k<&3-p^0-6^_Q1)|v{{>vBI zjpPW#dLq@#tf((l_~ARQr{)dEuk24zwjy4QrqI-=E|AQtOP972$PcKWRWU#`;C@Z2~Ehdi$s7hNF4__vYo@ZLD%hKscK8C=+MMH)rSdcaFfBFlh!IPbRCNuNfevG_LKUKlJ#)O$M9gIJkZk893^bDEl>LB~25983Yw)QLHL zn!vy|*OQae-Y%)2sz~feaaKU72oybXvIgt{(rSeBb{XeI7?D4H`V=IjAOjVE572i3 zf_@BSXOMq>$N5X4Ei_ap=8a8HRg9AMyR<(rpRX=N;8+Q>7r|#_Kpp&pfn%le#fAnX zeTq5sCOP71a-}1l0bYfT7a>w0*$vs70R`Aiv*m1qQV0LUP%tHm7=MTgOT;JI=p0jl z@sNY$6JeX4;w?2mpP|E+U7CaXRe$U zJ|`~u-rPc{C*Q~b+#`{h-wEGK#!wzhn_K|;58H>Fs>xYR?n;RQDiQ&Tsyr8GbMU<$=m4J zZiK>5H1SE}j}wo!t%X!N46}8gj&mrDE{m4#3=_PW%x_0Lo6l)q$G&Da;QW=fTU9P~;&f>Nso|R&4coH}&H~R5|J&c=Y7$ zuC|Eqcz%fcPyG&LS+vA5&!199!+V#VYH?i|spm!Qx_=fAfYLE(x=&Ml@R1)xrC>Jc z?0R;e+ao@!L7<$A2=l>mSM|h!D$HNL-JK)N<_OLNope!y--UEXfSGK_SZfYfjU4C!{nF;L0+5ifi;0Cy zI9x$08Gte@&#{K44|OV%;Gm9H&ku^-wF?3GMCY?}HVb}d+9#{cGfvY1T#ChZdMG_Y zRG$9Kiz{Z0mr|G@KtM@p)3zuL@VIld-okK707&MhhV=#O(sShh`|KvtxfIig(S@fynCipvLH*^Dh-d%`OWnsYXrPEe{Xy%`{b6ltoT6PfEMUal{9@(h_(j$DtCAs~^dILC+E~knbJ9fXwJX#=R5#+8~1Rk{Hc( zz}_Suzr4SC{VS`D$JT^UOp`z&40lrDXOk;*8OWZ+OuQY~3`3#>%z70?BVXiz9eF+}-=H?^# zQ4KaE9e4*py40(n(qDr&j_$UnHXZYGIMDGQFB1+Ok*cIBbBj6*-UDWnOuh7sn*t8*XKD>NNz~#RTi_O)4 zV2SxKNYb-Op2~v>RHcb( zNzH%%o&WvT|K5%Nd2Igwb~k9;$BrEn`IWJI@A{ER#ALq%g6`4(IsZR+!gHF%4_Uctmisq89MXXgSiP#I-7vnxpi zvkC`I>-u&j1zkk({r!m3ss}QIREWh*FMGG5?t^&Y@!;ofI2ZiZ;wE;=hJzA+T zgmKd~y^(x#?s-C&+Cg7OPzolG>`)Vd`*7mSiAyH3Bd+~2Q-Z&q;1tJB=l&;(-LOl7 z11}H2`Gg%1_gT~0{cIACH+Qcc{bZsC`m-S0anR1G6@0@8Sd*Ri2ZGa_f8TxD<*vW! z1kS?8mz!kK3G?hmNq(FFRUf|fu>hFmIc3;i;KHq!X?Wz<;BYSdcHgB zl#LQ6xH}?iB;^w2uV3bFJL>%ngEl}vz(O~Bd;1z(CGz~MUi@td{&*1)5vlLz?fi*( zIiEg#QYZzMn>O%sZMrj)KT4Vs7Cm4@bL_`)P88ItgyEfTiI{{a_x>8=3(PAOQK@ip z*`!Vi0(eWvF;TT}CGCW7+_pf79_HXW1g^PBNl7*bw5NW==Dr5^Ixsw*eT1oi*~!hD zw-g&+#wty17q!;)N}gfh;^xfkwa|R{a9nT4_{7PRyb>4nJU!t9ywL*ir!6~PSORU# z@=)#e?dM99vq50i92yMrVRi+XK2)-$ch#?Fz1;^aflpjttS)EYst+y%t$CTh@>*EM zc_+X9jk6gd7gN}J@g`9ChjRkppaR4_?Za%~Lnf9xlC)F3`~tQxW_^Ypie)LsHM!Nt zd)TzU{XF5IkO7xnQ$`U$ zelk4xff9$V_h8$1cIG0;X|n*02H_&?+q+k*e+r%(zv0L0CO+%sfNAPZR<}!{-6lTr zq32Rj*&KhXJB+~1suo({YDo}P^zre*EY>lD_VL={fFqv(Q1V192JfMlhHt4#FF!9V zDk4-UvsRN@2jFI1A|eYwA&SwnCL!0dVdjcn=uvj8+QRcje~DDs{iH6Cd2Urzmf$Gm z>xy>($O2keBi9A1pqj;st`{nBdH`J$p#~APb9C$xtU22pp?18f;L-T#;~)iC<=uX;(^z6Z(0Kpyc1>b!vY-q_Nl?aQMZ6ps zi|CzOj5?=Ye);mnYQ@OoBKdkTz@uyHUQ1awYWIgJ{QERIU4_8oDx(7`vtWKy@z|TX zHY~MdRZAv;|FBD`t0!e08NiBIIWlN zTL&0t7k0c96{9W*6mi?uzMvPi7`zIKZ6%!r;V_ivgwax^{x)?(GBjq!ry9;sh4uC$ zq-ifs^p%9iw+db}!e9aa?AdM*ZoaWY-oOvt==ZP5@9OG8??iavOsN#z2sO6?gr-_JK*M3KsSV#ARu_mC5y)@$f{uBb!(%9o z_3PlHe!A)k;zJjW!gQsCc^ZQZmnhdAvwXa97}D8W3*vM6uq-<1I}NA?%-qg!_jdUz z42|wt?C9fcW_kv=t+&6tBKSP|ZWv@g(`yEjx<$37|GLqjHH3qc!<$qoB(JA$4kzBy<#Rh{yI7GMLsM~H2IMAII4CM zU^u!Uvjflr%nWl8u$oiaZHEfdKSZ{jGEGLA?aNO&k$3jHAkvTvoK=a|#Mu&c+Kj82 zy#LV$$QsrNgY=7EX1iI){Yp26S>4@0Ue%#LbyW>$kCxlHaJs8E)}>-aT`gk%k>f4D zSOxK2dhOF57>;aAmq+ zt?A_CDbCg#bjLAl7y!$u)4b-PKxl~_9XS0ae+;gaOy~3RVu7`O> z)pD{sYntV*-%LHM+7^dkj|{7!sY&{Ni?Ol3bbh`XbOc*suiSF1-twK;9ifSAU`_Ao zz|6*2BFJrPlTv;sb!$D&5m|Qx{#c}!Z~giLW}0?D1Wqdxp)f7Mb0zCAmJQZh(`2`k z#_GFBD>+!+!W&!PI{+#jeYOwdIV-{jrd@-Sf`S5!^{m#yFiufbX0;f>c^%@ue7P;X z{`0&(-odDR1Cjt~rPx}(*eSWQt20}2ujRr`W1UZV zQBQi7Yh{CP?m$>s!keTgr>>k8*m6PKSu-jj{3IMiRx*CBPvcRnd+^P9UJh+UZl&fIJ~kdI+k)8vlQ>a zyB&P@?%np>)51tihoRLm!>lr->qT>91FVQKC;H|);yCN1EJnLIA61y%VWsfcN|XkGngZSZMj*73c_Z{*31ZoJ>+7W2?Yd@4_l-DduJBF z&lg5$?U=Y02qMUX_9&ZNSjc0S_R6-z8bit3U*#FDBVdx8n|o;ihyVzc+St>+VwQ+C zaet$lAF2meCz_-96=dc$V9!b$k~pCd&F9~9NE^ZvF@C?v6)o6~0qg8`!6u)cs1^gDC?byl*+z`7baP&sa{>v` z(VNv2chozz936hr3{f}Q#HW0>-!r$%lKQfjj6Z;<58@z+RZYzQwm;Oywa;9e1zQvf zkB8%=pn9G@&TRor%rc@hG5z&dQf&Z(ZNrZ`gdhjA)3w$C$EKzx10Eq^roN=7`KP7z zmE*(+uX2y~7g`tj7*=QjN4LMiJg0^>Ydhq%<87|+0xBWzjh0f(vi}sg3JOAm9X&6X zXV-_ zK6n&rUdq~Bwl<6n+dNsZodVrx@5X`@c{I!o{QU#6e0O$L)T} z!ATF!h6jh2f=gj~_0MNM16y%0{pk0YPFj?%$U$`h$lTJ^kFzcQ5j}yh$2+-XGhy^F z(HfJz^3YDP>D#wKrkBIuV6DAQkY6i%<29@&p@3Wa!!p& zab~~wFQoc=8*CXPc`id!%%DL3_MI%KP!ZY?62(g#NAiJv6IEG&bSBoCs6#raS`jMC)0X+fbKmVSv2wR`-yEXEd6xrF;1q3TlQ%;ZK+wcHC zth%pX?}o*eS|z8q#A-_}rUA8@THzAubFb>Q25P1`Bhg#Oo3cTX-g{a?Lp=C&3bOJK zEEobdM+c$g%=FJ;qdTmwIhy@|BIy8!&porRLHx&^1}{&_Nw1D;!a(msxak}Ndb+n` zXatIFYV*6h+OBC%UIW#3*6wq>3XWClv7m4d z06=QfhVWpFVdWMao^~t)%$;=tbd_@VP^kxp-jEYgyS-!HP#v>kUIAXTm>Lg*C z4*9kT2eWMYg7S>+*WKGkIwaJJ%H= ztG@Qs%9~WjjG14q=aaTn;ekBTR{gUIvp!3r>=8bn8cKZAN!45=K-NV}&mi@pozN_!7<~`rYDQjzP-qZ=*#)bxU zh+V+L(Hsoy;)xP9(-~@q9bCrA9QGCWxYc6;2fC0Z*`JSR?q~#SF!YzLdbHRauF5; z-`)HGcF$h6gZu_1N*xD2Xvd7|hO~ox9N#6QfvP)4?%*!Pm>y?l&4GoxCya%@Km8dj5{q&S}W`H9Y~5AYe9tQl7RMMPTzR0O823NdQ) z-BUJ-g zUYiEo%AMx+13H_u8Kj^$tQn-f{Tivu(DlxFtkM6KsBKSo%W>{Z*Z>bpMY;u1uC)F{ zWb+dkY?<W5=ub<;K|1=~j|T<~E#LEv2&t&!s7_e$K5^VNb+Cl8vCsu;Y@8^2LC1`! zJcWOm44fyD$39P60bGKAnCNbgoW8?~V+g-+&;Fxs2NEki76eL5;t+TC9QV6-{p}y= zRRQ^p=-pL{jsszo)rgbFj{PuR;+MrIHA{vAOhTZAd)HKXd!grhvpQXT><4g~i8bC_ zs0I`YG15H4AYZ-!t0DR&Hrg|957Lw31*kho}ZszmE>L! zz#Hl$t`xn7@jjf7A@e&$Di0$iIN(jl?FlPrZf(_pJz9VJqo*ac20WuCoESC@dAA zOIygNpwKhXe>tv&r|D`B07l*UBkmUk#dVQgopk9+#m4lWLr1!hw^&-xmXM#8l$7m1 zkWhvkSYS9rCLG}oM*)QxeE^R_y+V=|DDL8Hk+PRW@)(&765r9azA!gtrX3W^m8cG- z_qN8a>GCq}9DrfI5s7Wmwc=OK7X0gs7|42Y_$0wc>R%sFp})YirW3$pW}puMC`^is zXJSJ#!sEgb&3I@sGl9m%a`n;M$2)%=O+3baa?QT^3%Jr@Z@_6yEe&-7Q@MhmIrH}wMsj7hJglOt6ofS=wq=`9{dCg;@b z-qgg4>yD0>SQwd1Fv~+1}kCCMFR=D^%Q45F7MkX{2u{2%G{wq4!3Q_Q~!ks+(s} zgI7Q)GQv}o<)~1M!mz7Eyo;nSo&nh1gTl4v&YwSFT;VErj!o(y3^GWmp;$R0(VYMb1BNM*%r+ zFp-$4o}$1J_H&s;P{h`Jfb*nZYHb#SGiy6^tHC4Y#(oI>5@%o$0GuL#nTgoY3=9}R zeBoIty0Gwz$^Zwz7C~#jTJS|RO@Pg)I?#I8hQvpZ<p%)7VXX{Ok- zY6~>H3bzGrc%p!3KQQ2W8kWQPm!x*ja_xp}d%%*fTAB}3=m{lub6cR978w=AT{$EG zmAs^>DO0PlL>;6JhoOoU5=k2}I@F|~us=RFrn%7O01b10aW%4D{yOO5#R7e-k2hRD zvJg-Ye+|rRiY&3bZM|@xAmxWHj90|rYy!$G2eb)wXTlpFd3o9X$Sb`d#x2JeC8!HV zc2G@e3o6HFb1TPY8icUV4wPac_ZsJ~O_=n(0w6xoL}=ByWma^yGubS1o$t~mw*yLj zeSN%F`{b;wtkQ%((OTiuXqOtJl5AmpIy~G+pt85wblLOIb8v%Za_0(#;ksN#MP3N$tjsWX>#$>rb}Z z#*SL5UOmHLZV?e7vA(jRaWDR3QEcBY9wR)Pox?CSc{5}Ytup-69(dwj%2-e)vZbsq zfYu5qXhTp=c@P{CVYtwjlkx9dRC9?l93zX{QIk%Rtr+g0AqT0cB9rWYCtM=myi!ou zMJ8`1EOS?tmX=)q z#MUV1<60&KrYvz7T}t{o-1M$26MPMg;_EmYv$HJ^tm#D}BZo{bFvo6tz#91rM%Hvd za3()APfHSk#0UZu6B?vFES0rq2O0wQz>6?6;gAQf+Ve9^E9)h|ng(WU)O*>q7t)Yf zx{M#IYtkgjmA+5I1y1tE<^Zt@Y5ga9{<>Dz1-BjQj{Hg*re$l~EU3x?h}jAjyue?c z0oYt*p)RY!W||2D(pKp4yIY<8Q~fhv*9|`o%)2C9tLHzdh^rA4Uw?Pp;J=$cO! zRpl{7OZ<{S{;pA{1eQb3fl_DvDd{Ofj6_h9twNLT3{?||hBMJ7QCR>10fL+fG<)sX z7$A2y<;o(s6V9P)^3BsoK$;-`wazq`WkWPAOxQMzZkk}QQK`2mDtB~Z4fid3z2h^k`Pi7dD;fOTtWC&KuV8$TThO%VC=|ZoVRM9E|D_ zFkD>q6c(U959=m~4Hvm2^qdQv$;9&^HVd<&$QJMk78Z;#**W=EoZw_WkPEg z)#k+P-C$h$tLZ+qNg+oApRr;o;o_p`RmIh{*lVmX6U-YZ?yz;NN(QhV0VJUjB9hUz zNY4qu9O-w3Zcdfw83$Ss1$DqWn0~Y?EQ%)IipO4_gIgS|fE3|;OlowQQ5q)-1 ziUR0`VP%g&=M0L)&qVNuQ?s(lgwg_Wa0&|p>qYe$B~A!nZ^lk9hDGCSh+)yhzBZj# zFEgQ8l#JZ*940#Fso+X*1$16Z?Bd(63@K)>+Ot26G< z1UTmFrmODwt}jZ-WIs6aebZ49vlPi$%=!FS#lf3&q1?Skp2;wMdG_GO+N-sUw5$WXAx5MGeV&(%BGgEWav()=)Z5y)gmP@ViFUfuGXk2)00LSA`jC{Ib(Q!=_*U7(nB;PQotGCdGqFDwe zA6Qyi9-6eq_2-Cn>7{At<&S`t8NB;fVw7~NzU2LN^WfR%@L$fjw`7Bx2bUSA#>Q%s zKKrR4k;IBK7YKl-L&(**<>KGW_}J9N8qGs7M&_ zcDtcM|L2=~K{Djv;lm#Q)1w!tJ9Xm3^U_lB`wZ`35ZgVVgC)8GI0wFpt;t_*Y3b^g zZoV^Yekc^`ynYGbQA=WL{P@GEZkrUHoN}LUe;GfvmUM7%I29zvFh8PdZGG`R!!~Hz z=?j(f=J4`!$zv^mUi!!rv-@pW*iCg))APZ9Kgo#RIt7^S_+&Rb-d8F}u69NaDb7MDeH?t_Vl!+Xu?J*^kt)E!5WTy87s6 zlDT73#rWi8*k6fHI;_655P}3cjN&31veYhs8%;Vou7@tyq%s(qExL9nRP}L(9e!S+ z?%DlvzJ1q|;x#uFZXTkSjCEF)QoN8qv_vjo{~Qa#wW+o*3? zT^RwB&3A3NaN;m%bNpOc*`1VK*m=xJ{lW=QlDzjU7z!~`lvuZpmYOyi{ZL_xyw#EK z?M0{2`rZ`A3(-fTdb+#S^z^Jru^G`k_WdRM{yBf%ugu<=wAiGtZv0foKyS*+*K&W# z-|_j2TVgRv@2~p24{vCo+q-wK{_a5T@K>|5Hb=j@QubxrX9`~A7Zi_vw!JMmVjFB* zSW{z?`!VT>?#{;HjUcxAh>V16k2X1GOXmK7IAYZ2`_~PU^Ga2tJ9`W4Ra#qH-Ij*g z;1r`!hd`4d(<2C|?TB;yBCAwzmS(z>m~?d?MUw;m#>0o4ptC?XfA=n2Yf5VgTtsK5 z(3|v}BCXs%FI&f{V{RvIzVv+xLt&Q|Q6in1n%Z?|^(zW#VYEH_zjOT6o@D0#!vD(3 zSM@0&9^tK@)QyJb=G>x%7-7pto5`<~&d5GZJ&sbA3HiFRvTiY+$y!ianvq)e4kpWQ z){6TXKZvr>uTHg?jSN@Qtt}8kT8$*dwScOthI82A}`)niFJz2*6 z`Al)4l&)NH1?XwGDCe0ydW7jLiI@2HJ}m6tLZGL^Y+k3buuupV5e~m=!tNIp+EY+q zHt@dNM)b;_1IIdfzf^sV7MyBN{-VDCmkV|T=L8&B>A>J`?>^D(05;{00!^L|t&s)F z3JO0e(?&Er^F>IWaw(x@_5+*lGo6xt8SsA~BFa2klAiQgyHX#*Mo}aSAR!uw(;|`c zGZ&u!wX=Jjfx*zX_Aeg;{P4>6-g;v6yQgp^1=j5aa48-m!Y}*|Fnr_^i-I4OfO#Mk zo`?yvC9i$Hf7uD#&2*?_K0Iw3-qrok+`-2=6mrlpv^r0Vm#J3%&Hbjydk@4NEnuAb z#`n7qm@`;1^jwuJv?Mx22^xG&7^>WLkp9TT=K$_|yG@l%ZEY2SdcS2lp`h?OaMwX= zrkm0E9YHLN{x@$le|q}nltdU)Q}l5T=C&hkvTfrT37i$Sx*%H8hj-*K@uBU57S;gQ`jMwL@-^tS?jER5Zg6usfm`x!++ZT@;pRcpx^sDYEp$tb`A`ttz|C4xT=6Kwc&OTEmL@Ncj7R!Fj6V zCI6eB(Nv1C#k_RX{poJY1k=vrs+aA)IGtf*LrH}H3mm0;G%5(o2z@LnIxlfCW{$ie zpQe$l#f#*Y|HIyUM>V;2-=Zi2Dk652YDcNkr36$|RGNx_bd(a1PUxX2Dkx1sdJ_?l zE?sIsdJRYky+i1shtP6Y+~5B0IcJRf&;9d^A7d~E+nxHp&$FJj=A3ITXjZC7VqgJ2 zar8soRe_Bm8_Ju6Ji2FmpLW<|o9)Agd}5>* zyP~6_qG&Y9LADGqJJF10qNR-#YLJ(gC#|gVx6RnW{%HrP3k)l^v9%pr6fo#1K{JBD zif7OGc*^w@shcmPpwISOad9M~#Bp`Ea9AUA5bB3^@nZP|0>+=Me+rC(Dt~P+Qlj|x z#H8ilDC}yd5cY=2()ll3r#Y3PM@p{9lP2+T?K5_pTU)ln9(!IT-rJ{Vl-)|p%Fanz zvdSIhNJ>xlvpT~!VeTT4V9D&CoXjaAB2sP9U(3j?)*UtZN|?F#^QQ{fv2ASVY zrxB`?>`p&h3l`bgrB>&2kDNW$@r_ZTthCf=w8qpVU(;b>;GyO#W-E+{SK)BvlQaux z=5U%^$IT2Bkp#CS?%q4j&fW?gIaJd_VDP!;k3DE>V>23oOn$8Uq_tSY>_edMC279Q z)&lTlc+n@%f1LTut92Loz=n6MF|5w6D<>x=rhYn)Uxg$p0x_p!jCCn&-AK3}Xxy_j zsurOI6V&|599aQCyd)$PoI5kqyzOL}fpwVg{P5<@n>1sBEEXbj$?C4@UE&;BGJm1CVZG{8%p0rvZc{sMmpU9&rxU%#GbcbgIQXK!~`R|qmmx+x#GvD-vmgyevhu_)G?1O26l z!~L$R>S<`l*9S00Z2`Wc39fwC$cUZ$lYYX#*${^AdKMSNz!SQYv$t1!i@n%6Or5 z5`JlYJ!8E*D~=;jDYE)^jqkLh4(5Ky#q>Z}#bsFngt(dcc?E!Be#)rd&(3z;-3b># zi)LZguU6X6C!9HcuF@k}aeO<3d%8Cb8N)cNWvjq9g(qO^R;QNR2Z|tH^N7*-1-419 z!j61d<$J}d8jY*aIo6;hg!~g9Fe3NB!vQQghK#5`P*GGwCq5YOdlYugVFUIb^ z@iGfLW)ItEkB&osxlq0+(}R4UZa7k+prxU*{c|0svhf~-jxxDGO=#dEEstz2r|*zB z7faJ7_ZEBl`c~SiKQy}&5Ph-zR_k{_T2F7^-lweW9t2$Zv17+721s$o z2D?}vXQZcJ=H>N=&jGvWpxGdE$SjunF(i;XrBVP!w9T#e^!J}+Ki>W$zvGJ;P(IdM zou&|lsFv2&zCqd!*Ry*&A}HvDU~BBXg!Yly*+&rippG%O#*8-lS)ky08X6mZiir3C z9|l+-r|nH`dk;rD2{y3f2L<4P8-#n{#rd(pWXufnnjqNg@xs}rgy%b7+(~u+?dSADQ(5y zzem3ym%CA|&3CcHN#q=kx|5-Xix|Wm^H>kxIS0=G`72!e-PKyACn>6VppR+gNx)Di zKKQ*Q5Zy!_ez#vwMh3QImiRc-@R>{XkKQLXodH=rg+XHN{M0}=<-+)SLV{y>ywiQ(c#5;%-NpgX)yaVEJJ=H%xO__Kf zFT1?9HW5>Zv;|{gfQqhhapkt#oSSNl)Ih1V0`4TYH*pnku5_rA4aoOB*oee7NR2RDIbA@^jb+}&&D z{R>&mVvhfQ&;OoQ*Nx;IZ~gn=|NQI0W#UyzN&oME{`1$};0J9_{?A9YU;bxd{BMsH z{_yuJ{_l@f{EHI&U-Xi*p^KjIb!=iA48MK<^)`Bd^@s~%Z{I#A7ng6(8m$Z=2RKSw z_L?_48+>;d6WV)p68thwM-YLi})6^>cSVazVawdl^ zM*e%9Y`TKJ!olKm1}PZPQ)EC#NgJUPK~tBwzCKx1#Lap1%Kgm_1S z*}+2$q^8>Ql9IkzOnm1#CFwjtIw*Ml>iswW-Imo?{;q(#zF@I%xw>u@JYTig7h{oT zNN+DMS^9N_gBR|~gU^EgtdNRNpf7cWOhp_~X2%D1K)yBhzf0r^#~}&WwkA(NzT&#v ziwyNNnCs3cTHomRnLqX}IhhV1BePL zgA5fcNgiK2b%2)Ec6|B?1+&Q)I(q~@85HZ%M`$m>$y|NJFJ9hm4a29R_Mhcgt*89= z8)Ed2nv4Su^cuQ!_ayw85T)*~u|H?VI*7 zEQ|>ZCcY;p?xUPuCeNoaf+Gh%MFlr~%fei^bcxy%+Wl#d_@xLs zadGjVLTo*ZsP*rs!1(^%BY9^b78@I1g{k&bc7*mAwF?=V@F5Of(2N9-kx^#r;Ks%V zR1KDAU8o`dFq^T|QB}CABJX(n-`(}|pMAw!Ya;wK^SWFXj(TKr(m%uBf}Z$@az9kK zLXP-+cyUWXLEiD+zfU?A_KnD`$)-n)>H6kK9r|sUpefyj(;d%sfVq&79kyY}_R zPoELBN)H!SugY5v4x*7cg8TPVd#796{?`}4#?djWP)Apnl8V4B8zJBW6M_xAwvsa1 zY&YgLfX83jSO%N732Hp=b1Q7ush;TWQRUf3$hH)S;i`?94CS6=->FVCM>`vZ@Q1}x1 z#Z*NYp?PAbHhRJ8)f`fx)pS(CeBt5iAB8e0m8Yn6cpn-U zu5*QQ5i*#XmO(ZT$R@+4(=#*SYDIngXbT0DHF3$L(g*kLmt>sQ3j>*ba z9Sl4OGjXKz4j&?Q7{HawHrRFuU>Co0|2{3ez1?)%>)0}v4(Za}xBzu{Q@RZT5)u-? zTJL1xItpMX+kHsFiIh=Jy(MQ;?!0u3+{oM+Bmm`<^LjR^opP040q_Tq(MxRRAgiRy zmv3Vi@5om9eEj%LdDv`jN5SBW`ZH_uRpX-^64fvd2{O=c;J|T&RoDk4sCwOHMHf~> zK7X$4>oRLI5k576SJBoscjo8j_T`r$vm%j9kb*2PtzAa&GL-3JhY%&a3y_>eiCJ9c z=V#2l6C|Z@&UBYa9)9<2 z$bk6>Ej+rxn}ET>SPq`7v`#MYK4`A?<}j{rV|j(pFZ;!N$HZrqaE1>yX8 zALNMdkFwOF$h(y9upZz6ya6Snk|A$i@@C|Kov%5BvbG+{6mVRf{{9erSf9JK8VW9h zC~u`vo?j&)Z8YM#%2^Q?VNfpWuV&2*e{@S%4L*>t2<-?+S zZxzNf`grU@8tQ|O&q#N_)p&vA;xEHvx!H3m7XW$M-0LSiFEREvV*IrVxvkSgyIhf3 z^I-mdZ>G#cVq#*(7&5!A?vSgfhqR4NKSWSoH-EqRiXZI>MaY5Y(J?V|^WF`HV@vT> z&AXeIPK=H5&fQIUcA_i$QlOH{5hOB9LZ<&1oSO_R?ob>K;h{vzA0jyF$RtS-S#^r- zZpS~?7iK1bafQ>|x>$NQUj=d4y$*iqDm=r_4sDOPrRhh)8`O_p;?c zDnBtO5(y(vK3$Nt`XS`wdTsewFsI^0z(Dp12uLA}4(sthudtfKsp#nF;PZ!iNTtx! zCqB%(8=5E|`pIS^ebOGVjQ2;%UdwTy{lEajxD0WUMT=z!P-1Yd*1?x$|NQwWi;2e& z&RG+nc;{kcW8pmi_#sfrwP9`=n88QJ-OUl^AN(^`-QcCzT{d4_m0Eio^LiRM0apnR z={a0xT0URv9N2D4ZERlN+L%-4ehl+x8dCM#C!4S`_-5!>av!Sd@B-YT3hX)_&&Aqb zpPXcN*DeYIk3Om?XTnfIi7HjQ{*9jo+efFcQFVZj%t-~r#ION!I`X-W-RzHhB~(*} zJ06Q!>yBn%0q!bFRsk!p7WxjoJZ+S{>9ng+G@|KuR{2F48H`&0&W`IDkKBWvBWf8w zK0f*#wf^?(V$yuEo4=f>t|~-7Aed!lM>f@FCd|+ClG>9*+WBKUd<% zj%1}}XqtPHl5yY`fK~v|b8zT&XX%)&FYUbq!i0kRR(!qHWM-OTs<2If*)5fK()5R% z;TXQmE>#cGx@iDErOV6J2tX4Sa9*a2xBK$w3IO=7;Pj?u0P;E{ZFTimAf*C*#~;7E z_$cv2hFTMdA0Iq=^jrVNb2c&jKseaBU+3q?r*Ouex71?Q%;Dt+E1_lN+Bfh$n#a4UKiQDX)Epu7_-PO`EyL+-OFxLph z1Hk3x7Abpv+bCVHFaTJ-GzX)^L{=%MPb!?jGbjtaXq{Jd?Lap;FNCO*AeMnP!)!hO z>sL?UrC1+H-6gUktcgF^oXi_tidkWssbcmTRRU!yK^cCXmp7v;>D#wAZ{IpWMS*bH zsP5hfJW)>T_4@6m(@}OQAI*%~nwob-9B$RGsp$aTRdZ{>HW@(X`Ak){BTXGEL2ZsE z&QJCtV=pXM+)lH9DHu`T)MQXazywDI-BD9J17GZCcg8u0QgMw(PsBuYU~4w0rT7twBdRR$!Ty@6LaU`AjX08_AYoYKB1t>CX$I$_-2R^gs*9&T@cAb*$$SOFd;9kpXXq()W!E+dCR}eU_R3l+9mTE9pnA_$!Ahm9fOG|vXi0Fc&?PI@hx%VhR&OfxN1*oA z?_0$NKUJ<+buIaRk`v5$sVH}|sedk~T!vFJ3Pd1Jb#;%1XO+nBwlp?2U$&f6gD-4y z`ovQ+HWA};kiS2Nj<5{O6(*EDs;a+#J80Zm7naU+KQ?-%r_t$pB+q15%v``}j)d8b&(zd{;^MG? z8Fz49etVQn;<=lyR=m+hx}!!;eKcYecz#g5D?O_VfVGv4kbVtKU5|BiTEOe|zP0s@ z1h9N={euP>%6v~bh5GcazXt5r&;0x=H*bbJEc7q$$#Hz^Qp*9EaQU{wby(?9Rz>f$ ztS}6K*+4uTJ$^iN{4~>PAyn-V(A4b@&sB$jD~FlOtOEU}cB=)72Qu8~n>KI6^gO&Y zP{ufyHfL!cbsZf=S8Z1P)~&feiS)n{+D)G|az2Y5{RP0&8R%oN#Ec&Wkh7+x#UFxD zu0snmP&^Oq?M336qr5XR*sj#nynXxL7Od9WfeU~1NS?xoU^4WS0a*sktpO=#Qi`;u zqITO8nkV;&f0|fj9FBOxidya&4+apI2^QgeUv@AU)xHJHI`~*zg}R=5!zzF2V}k5X zoX5@~&yf**OIE~35LtW)387rNo|dST%uHa+5axZqUZZRMb0h0MempGgLr_eb^RF=)GOKpqQ<4mSD_c` z8e}E|*3>|NDMJ+n?p8fZ+XPqWaX7_vI`?I-?Zr!y)}Vy$)M(6wxB`Xcf%6g)ku2{S zFMU@%2y345c|hFwQkYpI_tj}xZ-zv5!jM`VwxM-2fl+$Iytqg>9;6svS(sR9oUU)B z$w*t}-Gb^`O(V4EmrDJkjV*D+Lx2Z^G)O>S|A+<^nn2foqXCFDTLHRMa5C?)7%tBZ~F74=19^}Lb({7l@;MWmM|%S+7iyfY;i$Z zoZ~VGwU3sT7Q{iGbANz;;ZRHej>7W|*fq6;a%j7*uH*7shihx!dsf_n!yzR#HK$bf z@#6~EejrqRbl30zL^?J}hm{Ux84aVC)F6;dc$QLMGVOgBO7ld;eo@Fs##^4MiHnGY zSe@a?J5bt+a&V}v>IjDEP)D|8-lWt8uulRc>$Dvcp~&ZJhD6Hg>z{`jPb~sNfgfe? z2x!STcxd-hR9W_FmvkutVl|+3K5AN(P>S&24dEFj8w=U_`87Cf*vN<)gvpa-LlwZW zrZsPXqG^YE|Kl`O*gVe{vlVsZ{w*4+YyK7u_gtlUPvnDeGLRlDCd##Sq zcTfO8NW?eluCTsFlbPF!)W$dn*NB6<8Vt@oJT7ip-dUV7R01+U{rGF zjZEP{D=^}QURIjs+f5V#OTdeE?>Nww#*6Gi@57RnIWI`Rw$Lhsm`fON$H!a?Y2?m{ zN`9GLg%>X_z;S%&B*)l?hG1SaXK_i%$ur%|AOJINb&7&i2GjzvEbouzGY8T` z3xNQ9oLaxKIxe7#WdMCApvw zDqtOt%{F)_@wpm>m2QIGN&Y!j{-kyjN@Ri~egtU8{#;`LAdu72Qy~@QNK%e69*W{c z#I!_-9`B8(E_Zi_6zc`R8C+b#S)^A>tHd-RYKl51(Kqa|ni@b4Y>Z0b71eU=wF__N z`{dZseph_YELWll3Bcfh!*UxeXo)P+Dy3*6>$>H4ONE7ni8Z+BoK^EW*} z_`wvaeA@~mFcpi=G`W=oDKV7izh5!H!t|Qq$J_;y`qW_>7!_2y+pT3j^s8Yd0<0b1 z?FmqW#_6KSl@M9Fle}?YGCTTihCeb_dtsqn%|xSM9xd%crj~cmtL@E&O!2|?(guL@ zAZ$}=pg|uAV70%b5{GWhAr5~SuZs{jpgpvF$dklg2rx$N04lJnEL)%z`7T-Ku}dMe zDVOdva@)-fF?1Du#g_)+S9XcR8vE^`SiN}V$~#N{BNF%T-(UH?HnUcqBvw#RFum@g z#-rCNDKu1YKqin4(HPpp>sLb1ea594JT*5+oLZV;ii_HIpS}S_Th4(B5YCgPgPrpm zw?Klmv?!VTe6vg4Q~Z4+#^l`A(}NUg#!xD>V9Yv z-o1Zs+aKfjyjBEiAgDakyJhY3HCrGRHpP`AsGOZiYDUv*w1}(0pEC!GZFR_desMSA zTSv6uxSMIODkbW`wu`{C-@+wo5f`PlHot4`6V2CD9CpWzI%WpKF{F0o8pyFAmA#$N zu5VyaMLk}pMaPOY1#|%Z9EVG1WOM}vB>((kW3jmYEDX4m=;#=VH~|n4!)65WInCP& zDaT#9>SJfl(1ufU-n;kCp0&jsB9{$b$FYmcadvjL(et&y&6_vB;^rPc6gNy4)h`s#gdt>67@1X#=hx{!-62;Q&T@eNzuuL?;8LZ?e6?kOQ;83;j5aw8U7Q4`DsSHd6t8b zGyG-d_AP8O4$VRBYMPpNNHphMfbIt|hym&K`-I?2Fh~<{70bdr&_-X7lw=8Rf2*6t zNq+OEJU}U6;D+6g#PlC`;=jV_q>`kRfB56+(`R&Y?#@htK>XAM)8mBgapw8{EdF+# z1YY8GfU4L%Yy}bMJ%PH(NZYg{74Jb+h6HG9s(25lA=ugVN~CbWg_|-ZHZcZ}DOlX0 z=M7QKxDe|rTa$2mnt(Ff^k)C&=5$k9TKca2ypOMZ2QmBfe-7DY;`xZba52br)d^Jy z&=fUSoCV+O%Jxfg^{uTJ5s1O3`xgyu1tEK+YP(WQ556*~D6AZej-q8Oj|C0{HW1h= z{~npk%2MUS4i3gFEiD;0Y>I;#m$P*}1GKek)9tyfw@n``%A__8<1cT&& z{QbrNgWLIhEKV<5Gq*5bW<2;K_j2;nl*<(|RFL*S8h41E9;gs5CUqt$EDcp!D!8(4 z8pQEIR>vMA{6UUWvDi=#v}f$Gv7X{3nd#|AfC$Aq-L>~iE$mEyO4Nvhk`kwVg}USn zX8;>rGkRyeBvUU2pZWOO!9bt$!k6CyT_V5qr=cOG7f>Ql7dfM9{bVfgBl>~9X{6#G znVI*~G9;;xR#wV(xXd2O&Q?FwYo~w>dI=gd*h7g|5+TerZ6lT9F6?S|s~Q^8L8oV7 zYkt-k*MSzU4HFkV?%fx?p!qEEiyPby%K52oU`Oae&hnWFB}|zURIDA~a?G(8`Jf{PXk(BuR;z$DH__ zz=Pzjt|cGXd@<~`Ha&rQf2JcjV}naUSsn?RS=?+KTztb7j));BgMs3CDHwV1km)^` zq;%zh1I*%HWDyI{EptKbGRRKj2oj*9k_XhH`E{)?h52M-YoR2fuovNi0y|d|shS3r z)(x<8pB~Dk1D?*0O)Rsw?l$1j=gxfq0uD-4D)av0kSCYq1mQ4%e#T}I+{T6T=RLvX z*1PkS78 z08o%OB_y6|Xgma=jzrAjM_RTh%Ay3s+jV2c^`)3aW}(7l$d0@JeJ>@eSQUONjx@g? zO_KF`C9$CL$c_@DD4l^LEq}kBe=gi_3UOkc7QvN&7{p1Psobl2JPl9T% zNqXD;b(fh|q4^6KlIO9d0h%SF#4`yEK4FDoj)QJuAUDqaA}KP>Fh7v(1@a`Sks>21F6(lH_O5KZ<(Hv+x_(bnzRTMQ8xYv)Anx&^YemEA zkFuQ=pS^zu%r34t0r!~Wu+H3_sY`te#D|Up&YXqAHgdtl&!wm2I60*dwR;OGheP zU6VkalMi|HF2QR&PP(};H&@n;PbKrImh(NYZ!Hg#JC+9vhgT}xh}rH75>3&tUfZFP zEq0i@52uM5V`0`anO0;rNUTJD+SS9XotZ3`<&45-q449wok9Qxsd?(y}@+kg6JE;p_WRquB6J;jk?|7;Z2-GtG}4 zoI;$`^O#e#EME_mAl5)*Hmj7iCq6EwQ3obYOrg-RqD&yDXOEMLXi%1Zmz#@g%| z`0Ap$WwFgEU}_#D7K~o;=l6b^lBXF=>qTOJp3ouE$PNU~DFRf5 za&AFwqja9P=0GPNDXAIK0`#x(Kh$3P={euBn6fKvKX5ZFJJzHu zO=Va#u4VfBpwFcCt5_R|>#s_$+`z39tn!mV9?Gta5QuTQ98-*zbu!_=OwDEQUN|H}N4$@;0%6GJ-I&>ogva+na0zlAY6(Jj= z6gXr(qyM?V0m1-vsH zjQcs0i>bAkataGMzsXLV?(a|nMS13>3fvEal$O_Zd%N4KfCuJm;Z&og>~p$x698}H z+1g_Hc-YrzG9Q=zh>nhiW_dAM+^>YF3!CjXh?nbIS~BdhpsuL)vcisRU675m za0U8L(TMKhzghrFJ>^$Eb2XGt<9Bz@b5IecZ)nkgj9?_Da2T?wk$7(oOQ(&EIDl5v zZcDxU?aJ_@Yt}d_YpOGP?!muehF_m3Qi?IXyUDmG04PDz5L`=OBAcr8)Ya7?>AR|+ zjk4gls^t2sbv^_Wc_+RC&@#~U!NO>0X{pk`(HZ3i`kHH8Ja^E12{ldq^jcaan4Bhm zX!>r%%Iw_*CIO({9YUA-Sy9)`)AYKXa`BHxrYC&12Fkwx63=i{g6q^rj3EL{1acf& zG>^DYLpM_dC~+a_Z`ooCyZ55p9oiQFIRrlK2(4_w#tHK!b?#FjWwYsDC`7?;0&cGN z1WS{j*ziRJuRn}gJ_*1Qz`R{wZ~5l>3c>qReF+KucD>`4{_X-KtZ*dLHQzRKbPS}; z8icAw5-rKf+H7RhBr^FWG(>cI6)%3XM@vu)fj|_IU(-QPoJD7LWU#-#zv@y4Ooibrj^4i+0GU?ANV+YYZ3zS|9m|jJxr}7_V=>8%a z04gLG$ogWi&wgiZJP_m;)KeA1LHB$qlo?>>{*fPL zKc8+2{Tvy2INZZ8$F}AX)a7m9QDMBh2VGF|;xBKI4}#`%`D3#>>X+vBm46F$0%2Bp z7b0!DIZ#pnb?&dIY=2$x^*oSq(2oR4PssN`@*1u1C@Hl#^&Q#_RO2Qc1-zhhrvm8Q z&sNd73+BN6^!9!jOYlWP%4(1O^ke^UJVM{PkRgS3VR!n?)`~>rx@gl@2<*S@3 zPjuVohe4r2!5jg8%!Qo`3GA!vzTqWn_y{Hs(6m$IDmq**Y zjf$1FpADLPN`LrpeSJMuMSop?4#q$h1SiGC#V1akI&jP8p}_TItZ-^`T2x8NBrD|k zH_@VHm36;PP?YGWPrM`*7Tjs7q^4tj_D40L{l<7p;jg(3$zQ7l{mzDCcR_5jKisRw zeQ$5T1|Q?PGJ_m&ZQ>2Zf-9Z%(#Sq&a_ouyR6$J~tcxieYz>zrK>Mb2{H%6T8US7Z zxas$;F-5E|mQdsc0XDOp*FgniF?)j&k5rTurdsBAhpty z&`jPDl5iwlst5;00a{d}%7jhab=hI=N2o_hQPJqa!XVG8{T1zu-4p+l1^<}d~<`!@_ddg9o3&d9pVwu z+61TM#3Vuc##59|y53ylC%U?OVddA_V0VY^)k=7sOVHQ{R3^vfS^sf zO=|1Sr3*(nq@Ur#K}-hrl+5{Yk?kgiK!mQ2cq}3Z!qLxjoNQtYlAySxr>|;iqW4T~ zY?OD`)7i~){Fw~umN95W0RaNcE6;75DE?HbuMd8WhOzV#4n7}$W3gGc@!i_1rdCL0 z?;l5hbONiW2^;l(LVIU#0i45%2s{*Pcf=*m&cdB9xOT;6x~LCFm6eKIcQw%Q+5CIBkIi%O5D&_bS!w#}AXO@{hC$ zuO}u9q3xBT(D4~$)xx_82=n-Xz(#8?12@L3q^Qm+|8(a-_k=EyVMf?FlrQpm|K$0393i2e4&eyZZ z#Xn7}Z*HEbA`Wv33FWpD#8Fd67-fV%e;DWdWOs3W;j4&HqVyvaqxwOi&Jb*Fn0EFOT-1kX#~B0 zc+{2sCr_T_H9Ey_x6>mq0_gt`$j$^nn5eW~8j#lUdav?fqfr26e(7C5fcb!#D6;7^k?+P&zRDy$OHtMC z&*Ip(Z_1MhjZT}pg~K(p`RBnjHUAebZTLhV;Z5G zEu&QhLK(8VglA`#`;7bgX=R1TyHNVGva!w#B<4=modvYOwb1Z7X8=dxRwl4?l%_+j zp4quM-@f~bbD|l%=Mt%)3Cx(3V-#vGcY|&m=-%HDhgGDji*>s$-YZ@T){pZ;`AxP~-7|JOYgLS{ zq#AsvnQVqyhsP}sXnad*#W{nXQVk7_WswrOdm)b!RloMYzI*&wf1UVUe0v>9(R_UP zP`WnetAO6a0C7{;E2Q{uKeaZ(Q2B2^wMNlPnwl#$QtN9SAngtciD-a*%A--*^Heh* zoP3}qF+i~dbWo7HlH4|$J<23*wqo;FV79B^V{-HMQ z1xjDtv6EHcsc?;(+n}*SS;(ntz4{2N55QIs2JnUJbG)|=ZieM2v@TzibbRh@Iyq~|Oum*3sv$-5m zjMF8JbL@SB2tQdya90kH*qWY4SZH>4XS59OxgZyR?QS^St}nJ{NJ*R9Qp*}Fam3mX zT#<`Ab1@N;NTt0MybS(Jpt~@^6}Nm&`sH0sEv?mQ8IA@Kb)6V6@U~i(LKDgy18%O= zYol94G=Ayd-dp_=I61-Se%_nS-HG;@IzA;iwvk|23c|jwtQ?p)_DoH6>Z{jqTX*ax z4J`4Q-drRAKel+iy-4n?P1B7&bk^G9pd>U#DQIYDz?8M1lfxcQ!Adm*fUXHMJ<=m~ z*OAw(yWNF`fryQI7}<pWfV&eIs;q7p1h&j=pa;*m$xNpbQH6sJP5ncA z1=@RM1n|7QijPGpvG3v&#Fr|lJIcPHK@=Usl8KQn9CDFQzA#2#>&F%q7;F2el{6OC zr%QfAL%*_kawxdcix#;YR8zEV<vLuVUrc8&Ft8nBgu*i0fJvM{AGG_(~+M^y(lt zmo0YC5ubfds4r71$IwPCOE)IyPY|p1$ZOIhzk9nH+vetYAfBy`HL5IGu?b9%a_oBl z?Ci#(Ra909kIgDP_w`zlsX_h{3$tjAFw`5^aq?j?TZK0@)-*Rg#qk$Kup;ku8lM2yRxj0c=sJsq7l%uFl>L*}=pCAJaDIOPB%?phFJ&J+(CMKCbzGcX8;lEQ|6xP+LSooRIW-O2I}1zZ!fMRO_y7>)uPVJ=Z!m=jlp-4v29B zGj8Bg7X>DoLUin#TNXJ)ee&M!YA@D<59p(oGU7lmNx20pR=%YPC99ymiGrAdhpYY&+_BE^ zYJRD$tu4^Ab_|I{pr_V^un!b16x}^K2(SmpeQGaAPYr7Q8t~;y@BCQ1q!oJl;*zD^ zwx=$U2?-J6s~?&@Ub(AfXoi?&mV=>?n|;aZ{Q%~OZ>J_5(((TN`vEtcj)ljA zrI(DF>wnCU(x)j)>Ka$h5G)ihs1cgMaIOj>nn4Zy67(_7n%dzy5x(>>IXgSl- zvSar|T>wj1Q@Mw;w0(E7ko8E#(K@=L%wNdF?7&G*b)AUajp3{&*~APBuo)zE7h+<9 zWi~6HspVTK_hx7nvf8h;lSTtQqKO_O)S%RP6x8fGw#@sgtEgyBoUpTsTEvz2&G651 zE=mZ*kp}Q9xq7>T9mI&C8U9a&8+g;k&J@z3Q5byUfNARm`U_Qz9xK7D?i0r!b{CdL z3yf#;_NmyYK>SB7#d}X|lXr(9Anr=AatInepnTv}7(HBOfob9ejl_s6F{jD4Au-ID zOnuY@XS+?QVw>MTVi`(5=ZwkY#gt*QMArtj5?h64>pG^8(3T$@d zFA?5e+$QYqNo>160oy&t(z%$TvD7r%&UQIRr%*7bix4z=)VkZJzB<^%XGXODGw|n+ znvwbwjfhlsRjsAF5|qKdj2>$Vq^-j8qK0D@J8usaO$QMsz<1wto0*rFw`G~&>{w39 zSQRCmo9@rDDtBt_6<^Qm=%ZPStkKS!J|Kgn`vZA#2)8T$@4x}IL;uh)bputLJ=`HFvbJQ}_; zoW-9o9xqaeK6DI7sN-{X-&dv&Y?CnTlY>fb@ z5PG#X9j0>}*V7Z8%!G)Ej1&Qpn>B=Z^2dX#aC}e}zeoP3^v1S$Sv>)>xx;DdS*@EyV!9f!*_ zEq4_q#n9T@tQ&Cex6mjzcZPai5H__RGlFY-zQig?DJJrx|9o~Y#Hu_93_@G^t%y{E;#GWLeyzOI1a|pak$=q*r*_HCQr!?1|5bG=tc1> zB}axQjmYyFS|y@lgLVdI_@0M%cXzMNc5%y&U0an1IKAu6^RHL zOGQpLh34n;xb^o3nieXryg#+&p5ba~f9`og`M5o)_17Eryqj<-6C%kf62tGFs)>5EUSi zI*6OuDia|`=y(CZh=mTCtyP7&km3Z zw9YX~TqCRMOPtho+{cN>0OQ)aG$-#~F51 z@Kxp35Ouu}WUTX5NFY^0C{rAlQem7am40$N938Hk2^~yNt7*EqmgI*;ta2L z&thq%EG-^VsAjF9t!_{S+mj?{8Q7z5d(kL;376(u)XL!=)Rp;T7WSmk8c#_;2+5&EMY9um0B3 zdGZ>37o}*qJI)JvMepn?RN)g| z9D9%V#_hBEXHkFW4*WELd5rU4fg;G!hkC-AyxVVB4vDj~LrreKAs^-Ss2q4;)VXJd zIm}Ipkny;!sYTWa(SG(U`tv0@!=+Cb`fZFTk7^U43^LbxB9B73J9a1*ZT|NC1DX+0 zU8v}kV{Kf9JXRXIvaV16s|C2ucfB`&`v5%C^Y>{h0uALg;73w41QL@Ukj_SL?h)}& zAS#%d&E3alWMyGhWy%B{hwar%mO(jCO86d}L*BQY3~W;Z@fuVHfIf*jv;TdSEa^LP zkbgjT4;bW!JW053u`-YkNi-3&t>aC3wjTg?Shz{|UUYaWhGC@K;cigUUsglugFkU^y@ zLGHfaS?Ik^{*J`PMwJ4{5#l`Cz_~h6$_d_Z+<(BO{0Ot~wUdthB7j%)j`lSa2#XqJ0^q|$w9}u5Grei*$#`KgF+5$Ehstd1kEU%m+^BrEDVqr2dO%Go?mxT>hmM*HqWkVi6 z(K8;<)NbV_4|!vDJ8qz_FLKU(yFVJ;8m6MBj$7=ZlU|E=9a~ej6n09>$Y=?}*g061 zj=pyorvL^dCY^BHUY&=z-5zW!ZDQ2$RG(R90}SnyL?El{KZ`H25$Xl%*rF&IYWXIK z{K&$>qJOy@V8K%QR;>Fr0n=Wj+ZJg!by9hvPG;+gWy!ko!s7Z%S?{AQoJukXx$+r* z7FSvpm#jL!bHVa_CBijVt^h)n*`g>m@CgqM7aeGY2ni8_^e94n$LG=?nAyURi5@Jr zFB<8`<5T1I@&=s%3M=2~%9>ELa$mh)1+HahCW1N0cVT)^w!=7Dq3;rtjxEeFNN?a& z+IE8a?q?wj3wP!b%Q8d0GN!gYDu}}&{ zHI<`H*?*B=@8Fm)pI}&|<%Sf%h@=fMOn&7plx%l~Rv1fWiS%&M=C}0c)qw+l93T`#8Hp!R!t zyt^-6yTrl5eWi+Hatm%K7^{l8EJ9HojP+Q|b)Jr=L-lb=f0lOJGT)Hmj&&1da@~e0 z4MS+Lv#-iWM!ne^s~8bq%(BX_#%*U|k=W}h&!j1Xk}E`CD#JHyZVcf@O`et+G#uG{ zN!ih@dt+fa)}z0}L~u3TEqC4MjXHg^yvIPucw#NkLrQ&Tf-@y+c;zcJ z&KDF+m3!=5Lb~ppj5@-qXWkHWrD!{|sP7kn1sbd@E%!3qD*?$ zFQ9N1MaOofsZDP5TlQ1lJy2@L8s0}7`v0bk_5M~Xr(hIIg@Ys6jW~%XZ|A0K6igJl~^vYe+TK~RFdU6>Tl~#-A?7j#I`@c~kO)aZ2x)&vAHr4Ea-Y6T% zlq56*>m&yu493-%m|C`Ske9p%?8g0Bq>yc2Z}%Fke1FFHqZO#0)gHfd&U+-hWHb)& znK&J^1;7u5lnT8+-yRS3t8JHZRx?08k_cS@jGrXPA&CeF<$J`BQtO>6&J&~Fg_`;j z#?9gL(x!oHi(QgUp}b8e17(Ix*I&VwvcxPDly6mJ!ARs0c`xPH2&mAT=r@Jp>3OK!Bi>0HH(Z z<*d#8=KX&;U(TmOS07IdlNcnCGT;-r|D~(+4K~GxDlJartP#o^iTr4MS z%Xy$TXoi;6)5dy|=JnZ$8(zCU- zz05oz>!4e^z`{CCS{o|==Nrfq6~CiriE!kM{(Vh-W6CQau!9^geebISQP#ilb?|f| zy|ES>pcjrHc?-+7gDIUQoxxjTHZU0s)yHQe9eNoY>-H-cCtFo*g1rWKu^HYq;FyHz z`@TLLuZl+qg)IZlDic3ig~92&V44I{*gWEkq}T3kw?~N8qG5WB0-sOUf&2`ZCKQ8^ zE|t&&-Mt!Q(K>PBT86`8(-m-u4F=zApkvkN`78(nua$QK9mD70o`T2E2()!!3>DVR zm(}MUo!m)T$;>R&trtVgvVR(bsX8o>g0?C32%q<1X9p&A zE*_10H}bKLlk~9SZll)$fCk+5oRSVYcgz1aY4_c^;7Y%(#b?Ql<`7$z!IZMZK(3M@4Rbn&f_q`g1KQJ zxy?S)5a&4c$&*{z*EM|JDBr~K+Y1vvQ~W|jOuVYs@;aJV;RB2hKBF1YxcbX*XQ@fU zmOQZ7vW}Fbf?)Siilr#Bx0TNVs!Hq6{;96$Biyx0&G-bzFp%ib9re$fS4nM17Tdo% z&zGmbWHJG9e_Q5$9cOI=qQ)prjk{;yVV#G zEd#K!h37Zq_NM-VB4O$s4-ZetL;Hpo1u*doLHdatYA;{DgwM6V^XruHWlhRCyYyA7 zo~_dM^73+VQNlNfOG%Mn4unFlxsRiVDj@x%?xArxdbp?aSOZ(G|Zjy_Ao~7z&d$%5rFa__cevQ z%(AynKJXW>$-B0LXP6X<+hY5?P5y+mv4KHJuY|3i!#9m@e0io8<`$jp?W~|1Ge0A` zjP)=g>RDwVE1|%;#-X&$Ef;b$IXN&^`0A?;4EnhDqoEiK%7J!T%C8jU1>~wY@p3R%6(0OOE*rEo3l-nC|;X<@rcnly7g(IoM`dd3m<}oga%% zp->97gn^exvIaB=v&;wuO9|(Z4w^Gsj4ba!NbUjBzl%z!miBQZ_xC5oh?xK}gJwBv zX*I2rzBc41ridZykZd^d^XHowK(n8+an49vmu3q?KP;zm2E88Y8^?~X8fMgXwRSN{$+n@; zXBTJ&&SuUB4xeaG`&v>|B)OOxC?i2j8(@lQTt%Y+Rqwi2T>$fr*H>s`Mi`nEs^%;nEaWb#18bT?HV%Bw*MLxnRCb6n;l;v^ zf6A%yIxLKsf3N@oH>9}UKc5p56$N5v&iAshvzGGn|{vC`V;Fb%#Q%T75 zafQl(KXKlaLh1HtQc+H~Z;3@D4wKS@5MmL!Xu#B2tKndGEQK;%qRF6iCQw{m&$K|A zC0S^0fe(vK$srQ-S6TGY_WT zfUM`q+G3Sm3ZEV{i3h6fu}V#@Z5Bw%eL(*~U7lra+&A{g4F=+-dD1AqvohZ3Bi0)% z{+t-?fyfq+BH|$qT9XtCZaTEDtN$HHom?qfI@&sYYCFg+14wc=N6ulY=M}F4zHSw+ zwOEzt-e-q{K7M*38=-`zQZZxii+IMnbG`O)R(%mh#>UVc-nOc{!izBm<+Oqlb;_(1 zITqu#lyn@%DoJuHk}FkTX-6|r{_}#Gu3}{o|u9PLP2r{DiM%)^hKnsOqG(x z{2+rRt|ktS!|U}l%tA$LF$dNsA{H-vvy~zz^ZE5z9tMB5c=gFL}Dn666~yw z5fy_7pv-P!1LT@$Ru)t2G+l zk(#fM&!Wb~s7;lT)S9Ruh2%D2z&Kl6EQNAob38Nz5y3H%jj!_0t?!d{TVLXM-17PH zQ<=cl_pOQ4S|1b|jh3uf{ZU|Lk?6-C8Vpdy8zG%oyFqAmK}!y8Arf}1Xeoh-8kqx3iOYuncT^yvuzC}K$$m9vw zu_cR~3jN?j9yVik;vpr^f@hE*660Z{W6^38SH*TYK@VPLIBF$;SPMnd!>VG~N(f8^ zkz=x5+Z&J?#f?KYts-p~Rl`p{bTTI=NuP7iT*a%y~~DM0+|w=)QN94FThwlp!I45KP&cUkvgKwIFZ z*n8+>Lj>%;6GS;nm5et3c3_S~3f7`-n z3ylxKZ#X3hD(as$7eFR%KazV}>n#A3t{+*GtG^t1xWK0NIwg6Et2aZ_Xjy&e(dKlU zvU1s40es9)vhpImV*H0>fK^=>drOtRzy4TjN^)P{a-k;J!9&0bh%qLcWjbSlS)W!? zg1n&C%mzc*$HvjxnWdv(?%4z0@1)mMG2mCjL2`EE?YgOIs$PuNi3hGwFRm?dJNouk z!iX;!7jSJ8WDyvcS^J zOu>O2T&gWB(e+m+-&Ep!pww?=KM)DZ6cAe6%HjGO*{aon&e(&WZ^Cr-vxrP!(63X= z?Yp+43ktPcox9e*f{y@s-kd{H5W(?vFchc_Yv zTP*PFme>nWLIx}}i752`So%wWzt>sAr9|zO^4KR^P<-STmNv$nIRDTNDy2_)V}Zr# zKhh8L%UlF3k5|!oYtb3DR{FD-3++!i9lP_|EP92tpxlPX`j8)oOn3auaT8#Un^tbHU(_07h1h|Np|a}d%_&B#f53&3`w4-r~Z%0OMnF$~?r5?Skb+`vlD8Hnrj0p8`#sbyMb=@kbfI8`T%d+wjq)@NQOQ!qx6k3 zz~}8Qu^z?M_*nvA4})&v7=WxvNcD-p7=!JJ?tH2+cDAF`aXCpw2*f6$hh~f zzI(U5p=o&gEzGC41NoXWT$*>5Qe$y|<75xz*urSRMtdDc3nD;3#0|TON}5|Jk_IRx zDMR*s@?p=t0CFPzT~oA3&Zm*l{Tjm}uSiivt6B)~QSrT!iEKCMTa`Vah#)$ko>rWi ztMh`FJAw^e(*b`$De5fnpw~|HdJU7xOlGYWr;iAbPJwpTW@nPRHcV7-1SFhqEI}S^ z4x;D?P!C{ROlpJOW5uoeXObZ8zH_Gxd$^z=w{a$DDg{n-k3B1|%sm?~?Mc;}nJ|Hv zIQh7{7d2Z$oAxw=iZ|_QxP#YV4Ya>H>D6DXxH|OItByrwMAAG+89Tvc1B_E<^z~%` zWW4}mzQ1^?MyXfe2;wB(W{yWG&^rTrehYV}YPc3e9N*)r z2NUjPlj=QlV0cE;Mb{nL4*$SJLsvgO@1`$R9=z!}+6NyBG>L|Vc?FFPjZ$R3c0}zOnQbQO3sFM0pfx;w} zE#2@3tSltKbd9?QEQ-H&%Vr@DvM#)1<(hk{^T0`D!KW-#=gvV^4iScG;^g}VN+`%Q z&=pfvbYi&L!kcP$!d9PoCdUcsKlUB)t~L3dr07)&Ksm=SBAkadaB+nmC`2pv;qp@% zr}htp4|kB2gS50RoRcoPqE8`&6%W_jL$z(;7Re4RJ4xf`Ga!YZgKA1RKgA}d5{Dvx zbhkoEw8Cl?sGp$+KO(=9cM=@=&RTH z(MuhIP1OO55649GNfA_f`#nP4aYX6H7H5+J=zdXMF~V&;q6)l+JyTfej@-;$212j< zN<+rGHti7-T{TmgKd_2m^`#*2w)CCIQXG1;4^i{V!djGN%OMK&S@(4-AO|wYK{KI4 zdy$=G2wPCX8UbSWe2fNC@H0m?2$RDF0_*_&T~=9`tK*9#fz%0+I}vN?AkcsKLu*z) zieI(zK@9S#&-u@n^Y(n)#{;^n+6T*VGM+#FelkN2{ro5?WQ@BP9W_wpw96EJ&VK_c z0N-2D!g|d@aaIW#gAcu-F{1S{t7^7bQ?hnMIiT(f8!CTy2Jz8rK}LIe9cN zZq|!^R0l*)*}@kJ4qG*rOU&4gHQ9|RC}{nBc^{%{LUi}gP3aV{JG8+TcOVwe0;(pm zGi#fZiNRF3RYRzZ7SX?XyLb$Fe?YVv{~y&8^EALF67PAb{uS0QQrnKOq1CkFAgdUM^X{5X{*6e%Z ziuDs&(p~YsA5Bd1;%HQC$h-24cQgI7OZwi||8XzATtY~V19(YwrDEk+!ivex&OY%Q zg1>jf5jM0_LpGIh7jTW9N#FQ?pED?SgDy4wRF6;=CNg*%C!7)eQdMyG9a*2C-{(aB zB{kSH{yqeT$#e{T{%~dO)YkXZ&Bxj;I>)OCrsCPdn);K2$r}r7n+N~%l>4N_u|($t zCEt-x?$%Z3u+;pzS^mzk!bH0h_4hfaWA2{_gHr=stp_KU3zgv`N)J{aZH8=fBXYn! z{#$cct3Ceg-|n9usPWN9ZmY5JulvRC4|6vYiAH7`@$w!6xm@|ijZBv0@-12S`@vU4 z2#Q(Dcn-M&EOiE5b+q;L;k`0V7xb)Dg0xm<`{PIqa#}g3%U1n?pD>3Xp48-=QEQJk zWW0;v>UvzaVN_b+P#pd&K<4n(e5P@euCUL@%Zy5&ExdM%HqN=_`pj|Uwmzf6VLw`* z!Micrh{{Snn`P;A=kH^Uw=bSFhW~U7Up6#(|CuGm` z*XDPlOi<1~-S;^U`MX_=%wSvLpX`f4Jsfc&QzBe?E)BhRE;hAPz|ys`F##*fsz#E9 z????hwR@5&o&Rb92rh@~+9z?SuI}DKrisV+=ENaU39(U+-Hb$6v1pUxZ&fEaOZ@rT6yRw_1kq2l^_}hYC;I>>|4x^YizuW@1Y+e&FuThuZDQ_ewNnuEbXptxct{7Prj;dQV5Fl#0f_W0H0j zNJk^CaW!+oB8V@)enbs}dE=ogkHZdxkFo-B&P-vhV_cHUAFXxNQuWz5W~mfDixJlP zyQ>#j&0^xBU6o-tCBOA*C1s`VzW%&*cGgQ(al{bz$b^X$@0sKI>)L5pof2UY+qaov z?5sd$O`Ysdnp&a~Qr#QsYj5(h`zj+MU%^}E)fSGXGvC_?66n|`Uw`^Z^N%{=dXc5M z?W>BB8J#5tg&2#izB^ikf-7miJ|0GW9iDe0_H~P%Z8VW_o}8Hunjp}2uUpOG&vR%G z_VF@>gXO!T{r1I+mt|fn9Q&VsQ7o4_E8bqD>T&jcOkei=y^Qt14MJS8HpT^0T=h2TQ{~Q(61sfIr*Bl zl0_2@zd!%;z&hInwc3yKf7abIp*C46DOX;QQ4y_^F1z2oU>4dlcOi1v$<=!*-qs7Q zVmmxJGuhETH1}kt#ZCtE4o|%xR(vLTI9!>c< zKc~uMz>u2eoFFw)>Pn+D&i#cvh_3h)o7hW56aDDtp0KiD%BD^bLkz{TzjlNo30t?MG#?cgxzzpsx#+`JF32j*2&L@!Woc2vkRxPA%Um>XoW@*K=?%QN)+ysQ}6LBGXy3BIj`_u?&`%gHoYn1{(qA=d$7 zd3z?tgclthvcVUwl62E1sLl^C1gLl zmbUpCJYr^{GG0ZldU|4}V?INqySqbf829z-Pt(dDO;Eg)wyGZs^YcoP8Baai4hXyE69W%x4}v+8odV~zk-~ZMNhe@h!U6SkQu~x-c zM*IdN=6Qg{IBqHP@%CP;+6Nk(v+4=P6HLxkoa>s*bPo>Qr+S`RY+2!N6v3I8oy;56 zkWsRL{J;w8pVrD_^l0j2o}3s993@PtSn(cij&Qzb`^Sk}Zf+62GrgEB!+YOc>l^BQ zmb(;Ht9P8tZ^R`gLI*j#Q+Gm)esYwfmav{rnNQ;dJ$rjcOg7$u;$@eR9T^g!TkhU( z{Ktv*S@de{Pmdq9rKn+xA|-qA$%FWUYTc^k+BA5Sk$HbG#dIBp6!ZI*=8JDBKMS!t zk2Z5DZO)tmw2`QIy``k9zo$U#I5sc8G^h_fy|{L#s^B`b;5F#(Y&Ih?uN3(;cy@t9 zFl->@5@u<8nxqfM#KyQ#QFf!xtx~#yfkX9lN`GRi{;s9piFg%5mT+ zc8FzeLe4Tun4MYmo#f1}0QBsZ-_e)fyf??2oQL0LR+Z0!*Qk1%nb7BgUgZo4tyg++ zNHzS;gKGji3*}%^Fv+=-O<>y;-hZX%R`&1nf2#0DanztMS@^l?r7EP7a`GlcRX?rV zDmK+EBo^isS*<^{#_mR$xplyjADLKQ;OV}*^GNWVn6gewELDwKH+|RmmRyTV7o|^< zhVQ0Rv3931m!#*e)GNv>Wn&2!^J2#++*q^NEXtTXc#?@^fto@b%g5`0YBHZyr5__D zT{3S8w(@kMpZAZRCT`A&tUHIA$RcgIu=!`VSChHB!GVo;Jk?ZM_|~DAzh6_cVQDSL z#^u4?8&&jvAp%`}$5RQPkZA5b@zwsB$X8kGsO#y7{|0Bon%VFWQ}A2PD{Wi8iC*jd zE#h!gtG@()6q6D`1Mfp&6R5?$5aRNRY?$tMbQ$co)`4qn53oT3lW5lPMpv+V_2X?= zionI8T8tbko9(Aq`-ys7q7r(uwZO<9@0O1(*G~;ycm@|MV-QsrHW=IVA}7H-TN^&Z z;ks;Ytk92NVGN-~Mq;4aS7&;fuhjT&_9f2oQhG?y_q2P6tH>uswOYrF-yrHq zyttmRUXvOkjhZ_FA+hVZEF>|MS3_Fb&-~%1lao^vW%UpXCJ#kT?p8<&Qxz6q3gcDj zySDy{?s%E6N12JP>hY3`(~S@V(;t~9PfpCdNii6A zh1@UpofudSSmeNf>WhXS%}vCnk$byg|KnLuT?}y@Px(1Vpc9%PlgzqTtitX=5@Twe zYU2Bq^7rbWKc251`i0N%aib$9yja`lTLHZXINvMJ>V89vRu%rFUOgC*7UHwk9#q4t zG00m-X^sB=>`DM!D!|2u7=5y#1Du@C=J+pWv5?5*%P}cVy(K&WXloxo*7!_ekhY+~ zW&MzlT8?U5Xy0~KRiO7&qbI}zNF9u@~+Z#N@>qi@v#$H-9@?sn3uL?Wmt@Xnj^6UiD2{Lk3%f5{o$D{mc%&l}{-! z(9np#uGs*7F8#~nFI~l!)d@xNNldoAs)o~Vwn7NT)ItMdNkUhWk1zB4Q%&u^k0iW* z-_AlQ7}aJ5o99OB3i^C+edaynbRsM-FTd;|>!7`(V?LqWMuJ$#!_N~^w0I$IzSvVw z*XS-dv2)5Bz0AYrv02&m4*N@=m{UI*8|jF}#3UD{Jw635>{3RCZHb%3@iyysRHo7C zH@A@cR*=svE4X0vY+w*GR__3AV{lN-iV$eH!8x7iI{^Rxk|8-UhD)57tcU1QXkKHm zw*M6P6%3}>49p6$oCbngT2j(U35(mxjGk1V8j;_B;oDjN(|zER#5q<*6pNSV&TX?( zvhjCxyb=@m<(lwr5Y2?pg$^$4t&oP4ijcDQHEcc!mawU?(P<1OxlYoZmKIonm;NZQZCpUxtX^{xkeWPYedhACl#aNkSBC$=lVtS zf|^p%^XI?(Q&)dRJw~b*Png-n7Yt41&;9DZ-7YPnpt(2n5+p&zromrOUaAq!36+5p zd4Yijj~ny4#lUaQ>K@WmaJ9w`+oZOGZ@zF|EsbeXlwUSAZI*8jVFN$ipI5W7(#kXy zU^1u4S%>1F?08~!xn^r(C1(Dkg--~XB!sKLsiOmeRN0de92 z>-B*2;hJK{oaO3@AoKTSTM^Sr=pV0&EUQJy7kqYR(1qru+P%H{9*>oHVV5d!&XDoR z7CA<=>0W{b{iI@%a`{ZN=gEO8FB?cNau=S*roXvyjnQU#OYrwM4Z|IgRy3R=-F5FJF*LjN)!-g?+ImA-Qjl z|Ab(4wyUyH)DO=8jNv5Tkr>epiR*@U&0<|q4mwxL53CGs%;2%}9L$%zTYo#S6 zWwz8v@7JBZ9a$Z3I?Pr~iZid!{M;>W4LK!bgR{v1N5~zPMzm1CxW_IhB3l^{1^7-= zlNP`)%_Rj(YM|{96yf?gHa4a$*Lra+xbLH9%|Z_47B3%nVvYTXmuW>{mb}}*#o=lP zdwb{EZBKHPY9GZ$MBb5b-jsAG_9&OR`#2If9#?*=HrhDRb{T9|C629X?`>@ z3!Od^GBePIn0Qf}yN|xne_~@|TSv`xDc`XP#eW4HNWuNDZp`HIdGj(T5Ty?_komEg+spz}N z8+CVbzO2 z&j4Iwfp@2b5qTZ@Wuq9Y^QvSj4Lg<{>&#SBn{cKET|7x)iD6i`A*4awrJRvYVX|Dn zTFq-^eE8To+4HMPH!zEG0^c79*38(4T&`WXA}e;HMXcG##72bMp!0%UySg-r*~_?H z?jC`6rK@C=D1ygC-1S=w?$7YBux=W8gNF&}d;wHSA~*!f?BnD%tvRS@|O@xLv+i;bHktmP9- z+b^a92$m#!gJU%O4m>3|kdcd#fF9Th>+OHCS14KwvY0H3#v+udGNana$Rw|<`p!V| z01r10WMZoYM)B@rgVWXaKg>(Sbs&?+ys*A%Ug2V|G_+X+#bcJ7PIrITX<29F86rz9 zpI^Hop(g>q3Wy7nf=w)aBKPahPIcMrF3v6;BX+s~AHjV>i+cI>z&!^HDRHw$!vUi~U{yj=laX{aoA0 zT8?K6Wex)giu*bs@XNX8Qim?(RhHD?tXS+!hd<{#$=H?0+n3q(aQPB821N<>-KFlp z2W55p$vfGfA68W5Wb7m^lnEm z6*Rqy_BRHQ?e|sh&)>NdRQe2Vdv9IxPz~)@9=SjGT1jby(@sA&nvYMKn zjcPd4QUcX)$&^iJ1bEdJIdoZnNq?tTy<=GcpCxLO5zP~ym(<$a zoFIEPxBxQ!XNE>`5XJ!-BB@Y%>wmDaBfCA5oL4mbPbVGRC&YF1s4l81K`U}PGyfcW z-7V=8jW;g+nD@`ZY^?^@=A=Y5U=<_JFrF)(;N8HVmVYN;Fn*5N#}l@T<1}{xmZf)d zfJSQZJju>}IE!}MQ2;G_^&#NQ8Do_q}S zHDonaw{7-|edh%-r+$sKM`gE5wkbZ#=_1zxq_QOSwJT6Ne)FnX@%;P-RyA5C8nYY= zdtqIDCn|K&a#mJPcHkIU`fy<(mVAl+sK}<~6l6!keR>0*d=1Td&GImXl!cfUTp%%z z+fCW#&;Dx5{rmQQ6Sf85abhw4l}P7hXaCa$JDURAvy8q}TV?IqM-_g`ND(34V(gly z-9EdLb#-*@uZANN+I0m}L$cI~CAQ6G5+{P{L3#-Hw7A`x^ZeWW36zqQlpp0qjV_F# zOK~~xdg3i(zfr2h-*y)gYSkC71T3tjZWGEe0!Qt{c%F;$zdZL_luqEb&oRCR0LpKM zQmt!(^-QE}f{Iq^kNjfBeJ~Cvy0V@2#PtO~8)t8C-F;j8);FW20PRK4cUKJkC-=S82abdEyY60hgeO~xBi?g&6GU`-YoO42nn{ozciyqnXmR!1N`n zSL@l*;apgFyVRW$HhPlB~>;xS4n5<=iv#jq4~hhn2c=ubMZ;&WMV}RzGg^U zp`|3-$hi`zi|e5aIicw-$0eCSza+f63F$DzaVNm|xP`7GjH$v{)_>I!+hzZU`zd6Fljo7bEMh zHxLTn9vnUQ>tQ=@*RY1@ke{akL$7p?xw1woIt%Zh8`sTk)6&9ZWeZdu=Q{5Et$CDW zG!UPhTv%N_A;Ak+19-*^ITY0ikgLe$n-Etpg*Ypq7_seiYh1lpJ@=zpz-_5N(IUp>Tm6GO8~kIM z?$#*>4<5Pz*jSI9F4$LIf%!t~i(}-nu7U34z#ASn;Lr+p=!t2T|It=$TYa_ug(0L}^}s8yqiR$3oDkAVJgzamk| zo%(9)s-q)qgk(nP-?sDKdyf9RO70E<0%rTtBq`X~7wPCx=05tVQ>;(F>@ZE?qWfO~GdEqP*=XJ)KAotD$E=(hj0y~Ttad904Hg30}K zOU8iHlx>}jfg7{S9%Y1grjh36{z`x2NFw`bWJJ4oxS625@(-|uITVUp7?`CApy!GX zzC0+T1+(8F`SVg(H3PaY?Ms?>OWUoG+9!|i{FRWLoLuyuSJ%^dxcP~z3{p64*Nj&- zj4aKQ1{>OkriT5+PjciJW?8vOO!Elv#=XnqL7yDF4uO*AMDV2J^{;O{=!wyVn@=1~ zPLVXvs@s?u-kSnsOjuZ0rgEp@aNR9;_aaE{A?1UHw>sqexDV1c6^4(u#~X9&8&cKS zAO(K`f*PnHbEl@(aO(1huWEeL01UyfP?wFs{<>BKDn$a;1~AV_r&(PreLeG%^aq9I zsjf%`e2es9sK(ls+xDa5)L+`0@ti#I*4_-Wc1~2+BvxwKlIT#}06qgaWX!Q2VVa5+ zic%Vnb}8gX7Q0RcwzYXh;IoB83qo3uOji1BY#Z+5hLFaf@I^#sKFNjD%G^M%W-Eup z)G^V8EEIq@$ALIE8X$WP?0>+MiRQ;P&Mra-ms54oHbE1Ge$%i1D1GMWLCmMJYX+j; zZf?#&laBG(iWw1hHXf-Vi~0IXF9};Qk!T;?;-+}c4WV;V>iYW)8Gqy5gpM4tXd&Sl z?(y+|iFEaK#)h=pSl<>-RW}+q@$-Q@W@eDM9F1yVjep6=!T;@jQuFy?tiq~bIl_Sf zx9H@|GT6Vlw-;Yn6j`Jg)eA@xA^0j`m1Z+(61sxImHXV#-X8~*A3%o)8B7n7W)y=> z;3>XxH+?ym8RgZhHM%N*}H)-8P)u*}Bcd0m_X zzYbQQvghIALRncLM$q~G>4N(32>s-z*_uRcVLd1;v$|srX+lxbMcX3^$OH}sLFktdWuoU4xQ8Cx&@ ztAf89nP}!@c5(g(sO?U5^MHF^to~3D2fF)t5|D2^0{UAgzAzW_KIT69r(wmMIpW@^ zm$aOpPg1ebu-U|Q0E#3R@|HWI=iJR=-K_{fT^GT7dv@g%R7+>9Bv|j&hfL=0Xqd>n zx#MNK`05}x@GGkywSf0e^>PJH0f=M?t;var?h4uC4(x#^8*Y0UzT6(eh89VMu5`=p z%X-$>+}2r7WR7yK0{S7DRFey~X;z+`^lGA!VZ-#;URTG=-?LS!2E_ITcD84rJ0r-? zk<-87dILGl9dvRJ7mpp2ms7yy+Qy#m!x!>WJ@O`QmkOSPVz0$STt|I;1q_z@QNE^@ z0hE=`##pjhgG)lTq)l~t=#IzsQ^}O7r7IL>%il;LlxJ>^dC`1O%K)X5h`ei5E)DQq z3k45&Xdv%8nU;`=f_fp>PPE$H%{|e}+QOpGd%j-?QbEW&)>$lW4>SyJ$30{?Z5YF=3 znUz{v*&#`F(N~u4G6U9v)r`c?>6l1^P!&xff%6B1U8e9(qq*s=*Y00bq$0gHD13l` zwtcbzrGSZ~^=)7|1Tkf0*L3nkJF$)~;EArPrAnbK=lz4*zOQFf-o48-6;E;BSkL^X z68_x(ifsm_{k(#L$ukWp6DDf6v^mzi;TXCe6@qtRHEZx)6AoQt-=lsF;)4CCJ-AhJ z``x^6R$hjs3A`2vp$Eyz^XDHEcoSKyI8E}Y=OFkw^@w1JkXix< zVWh%OwevRELhnN&3Ruf~4h%RY6bd%=xq1hW4PfYPp(<|M2X6|lE4%NlCt^ar*F2Ntfi&1zv<^@RTx2b`b^`Q%wcCBv8Y9sR1<=7I$$(HzyAV6bbel? zHassJhk1C?V%m)l9sr33ynKf4?vcSEg!=26KFoSIxSi7jW5q?aHv#N#Vxnv5>nZ`& z4xNB&=fl3%Zq^>@iB#%S@*W#8v#JVKjP>O@WA_@TrRZ4owho!m|78|mZeyEa`=a59i;Jrc!|<$6&S8WxoEL zQL#9^jhUc9CP3U=amNsX#MX8=;Y2F@W)4t$&lVQ#OZm34c)7z@dCOh9fJ`7y|H59^UQgsj9y~t|3rx^Jg^vjcmy%hF zqOo_J8hB|VD^gSX?zcVP{Fwg~DWIALVz<^xBEpc=&m_z8n*)Ri2`NsZcAYKD9FemHFqr5G`nv0WLNQ-21L5F^jsD=kJbX)LEr`O z?sSK!}f_?1R)I{unf@GHl21E+vApq`& zP^JYXoFojj4m#{N!j&2Dx$yz7&`npE`#M8jTL4_S#xD-`<3I)41ZsM!qzx6pL2s>e`{UR4+I>MdnWySYVjwnxnyAwe z_+n7mAS$PPQeyNXJ=gbn5%8B|j+=+%Vu0U6jru3k7$CY?sBe{8jq!Uip)h zZsL2YZfgwu3OLNAp~0)*_Mm7PkG2UF1*!)Km@XhL$f*_v1_jN{LM{n;?dssLGrLxr zHA6SkuB6?nua~ol9L_DTLTYo+Q^ZPOjsAGud(i2h2yzJ2$JN(C(!4|o3I~5@4fXJC zaz?gdev?E?Cj@dM5O&1qh&c`j|N8UzkOkWaYt4J3e%g5hED=Nx2(RVx!Q&D=7`=&M z-k?ZgyF=%p)JDW3UuxK1gg8bt-U_*aVlsiYa+_fLDy1!bqRV2I<fCltMMoHE44E z43x=bmD-?nOT=|H3CuCumDzBC(=OnPtt5yN+QQl8f<$hU0L{U^>gV}9RIJMe zFAZ`6d)2^Ir#{B&R*2g`t%k=`ucG5E020f5HjYVP3fiBQ9`B}fs@0(LPJzPSWq6Fe z?_)sSA@pi8CcrC5BI|3<0g$GEHcl4s)nwmov|1`+dv40*phY4Ud;xxg+-9YWh@E6b zNO`3hD07-to%9_etE_3|a+mG|qlxx)4Up=S1vck35n^tJj+P$wr%Oi7saz1wMO>lvIrpo$v!6lj={kwK3A_3(kG=EZ@}d}sR99C10kM%_m1 z;i20$lVdCkaC1eVp zQ`3nDA2RZKTK={KUc)l6+`SVhywvp-VL*>T-qSfZ*Z7J#r11)+27%u^k!DB3LmdYI zi_#hD>0OJG+Bcjp6?t)XdpsFZlGEqCRE76E3IcrT_sFv+BO4*oM<*hxGd|^qDgzHC z!Ow-$6*GOk`p56gDMWv z`NnIeP%#0IJl1`s3-aW;dl6BQEf@4g$3`)qGMz5F-4L396+miy@M{r?3ggoPHA1PR zxb38_YAtYlhYntVx;P>#qKUDX|3Nym<4T4uF_DHk_xp1vr@N+aGr(x~Kij{^U$?sY z=k=Vg5lQ|18p%{3cfNo5?#J7P_W!k0+P~0Kd#|AQQy2i(I)8FJmD14#)A|^UId7cCk zV+htf0*SZ&7&mb?>#Y_GIId&@5B=b-G|o8x-dRvRd;d*1Q$pu=F&&hXIHBSyx=LFe69 z{>bDv{lE4Mu_5{s^{qNiGOAlKGTJ41Qe~rNPe_l83#!c5DjCEiz>SYtDjfUl4H-8m zVgz9q5$+{7i!~n)y0QD0?2g(W*)z8fbPrjG78;DT#7zgqq}I;)F!ofhZ-%4-!&Cqo z=H2A!nOGo*xdSxcaJnj)eZxmki4XXUT`yTA>Jqv|rBE zY3RGiBL?kXPt8ZrEK=Tge(}*fTj6yU_N}jTO7W- zikIr)I0KxM7&I<2PeX64G!M^@H)rKn2CgNX4Cg0)js`H{nb&b2=DNiGpZ4+vqX@Ca zCKAgZcc0GxloR&gp3gej_q8fuzn*G=wgQ0|75lTqSTg0Rq_xowty z^^O179B|(%qT-@=zP)`oJwPxKGnQbe?Lv{&_*dB4i$AJ2GW?GD{QCUb{oCpSaFs)j z3E3+AoO>0e50NhjX{iYiR(5%{>cQ1GDfqeNDc!we^;^4tVl{1u0X@|I{j!9XUjP1k z&(J{#4FCNy0o(cS|9rXvqWpiqK6CwhBmwzO5gfGp_a}QEJo~5K-TlTRF#t9G`}Mz< z`tMTw?*jgJDZn`YA6OvvBZXoN-5?~FVQlC>U+NG3*U0`|i~rrF|E|QI|Mflp-*(>L z%6s-T{(0$-k3CoSJkl857zic6zyA3OkyZ(vBSdNERgyCeX;+qMwpO~ zkkMd6)^ywoJlZEm#g^1JU#6bpkCOVIXb*SkKe~D>XwT=DpcaV?*}{cv3+!A6mUtmh zRq4UphixWd6NbCn6HzwbADHe?3ER}l;E58DUjTNsO$*(O!Ud7|xx?n4{5NtwqFRK8 z5^6U$4_RFdwT9EJMt&x7u@W{#>+6BjgQc3YTdSI)l9Jnl%TJ%3^8On|^gPL0Sz$Jh zwoDwvfBABmFN7)4t#F|gWwSCfNNyl(o1$xK-exBCTe5R-I0XMs)6zYAHh(qE&eB(3 zyS76nY{pH_G83204Q(?Lcz9V8BB|rpodH6(`c5xtX>5CI!QVlCzO^f4h$HPx$OAYC zDZ{Zgyc>GCcXmmAd&&{OmT1A6fV+f2^sH)3 zY-}FrkQt<8vO+^72A5@|zQNeJvcue2#+JElEFUis_@p`hm%vH#1s1W`c6}}*HLtpQ zvtke=P5Ce~sb=WSzlJ_n*ABhH2;1d5(2K|x`l5A)u9$>o5ms9Ww|sBj3{2da)ZaQAwVt}4`X=SxR&VM~hV$h#K>+x+R<~AgMYYy%y@%`BapgGsVD-ks zS2Q>8t_G{g71&4twlqKQiS}B0f4{g#macIAeA;kB?b=Rn6>08lOIgG)qs;rKUtYid zQ#kEEnK!VVFC$#`Uz>O_ZccH7<^dEdpXq1mKc%16`1Q@{vmc-l$&Ftmk}pjP8qzuM zQmVC_^zWK3?4Xk>7=cAi;UW#tg}|nK%D3vu)vMh_=#4{_MYK`q3-#7_rUwJF6$4aK zUTz&kjt&L~S4W;y6OU^#8mokUN7K-^smgce4xHTXcVF3WmGklAErO8XIL!ZesFZws z0P7uiVfyjbh7SxwD_!=S9MPKYt1z60V>EDPS7Rp`;8laShDQmGQ8$y7e2U$4-rjUb zxdRvZ{y)9__dMEpST&&JJ@*>!uE03o9Oqv6`t@rAsxe1l0cz6f%*2w{NLg3eL+sOq zFaAb%P6$V96`GgFwu~Bltwr~JyjR1)r;;m7wGN_?rfF3sFarFIpd6v-nF}?qkex|Q zT@2j6f4}*syN?Hh7*>3$fo0>Y&z~>(%yeoT;ZMpPBPPO$Dz1(GWjET=(4TDYl9CGG z2>y7?EsJO6V%KZFoU3DzY?t8g`g6UTn*CrMhWPG8pZqiw;^~Ez$XV09u zIy}RRZQu{B&K-=LR$gk~$2q<_KRnr2n8+V6bzU!3$w*z?EFCVTaqaq~`fPQEyc_MP z)nZSvZGtNEki_;()$at+=YPHGLa)%V$}^RQFyH;#;9_7+f`VU>uqvwzRzJmm{T$AX zc2gi^uGU!Gq6nr?rsJUh2#@c|12_u(qZ;1~gGycM8M0+}2UYCt?2gu0=li;`CI@sf z>R;W4vuldX|M9q=yC>n&ftwwgIOkzuvqEE47}Cn498z6C`B7}>>x)M&c`Rh3P|$g< z@aj@)DM|F=Mbq+ukSZmg8Nt%tg23Y95>xRk>lQotCI{86lYP_kXXz05;o`gi?xn*tYfVO|E|GoFC%!7Tvo>kq+bXnNzHOQS=eJ< zhwZO0oia((e@Z_;%;Ux@Zn6WDUg+$TLQh5Y2*y`+!9<*P=j`dmK(oy)7UKm1p%ABb z%M!Ew0)wGyizr%w(`aZBb+52&Te1;`C;ol`o7#r!YnWzi-6q{g=WatumsQoa{R1=i z{rY9OrR3D)d9S%xWgX!+t0bIRb^hZk4rZ&-R_yy;#L)Lnd08O5CB+_flDQ|~YUkdc z(_Av#ax{h-j}D~dOTBlxK~TRoD8I;C(vrPK#MILgtS#r_;*w$3B`1%DmLI%s+_HfJ zCr%M4DI4CHgZ;EP_StD^@lpIZIDDSRFw%BeqC-A7R<<`&v1M6^hDX&C=H0=Xr(Rzp z7kKrwJ58$lWZa{hG51)WBhfTO^-rZZR*vDrhYv9uf=|Ys9vl&IaDd3|&9Eiru>8gChg1Z@1pdO5f*;quriYIX;)gt&@dIu z#liLxB4@Omo{sK0&JJbAt}pRY$@}xObF&MD3vrqD!^Ng_4sW6*88GKn5v?q#1SDbo?{(8foASL6dI?V#=BsrpRKv|?Jp?axO?N8&4< zgIy_5m&ReX`0J!3-6oi}`RC6*7PCH6*tkrvzNB2{J)=iZbNwxgW}Ew?=0S9OUETR~*f!6_xu=edXk5U?UX1;+n<=X?azV3k3BA~l zG()=fGmFW=kM_h1A75TAsa%{`{(TSmDR^S=IedKO5q;UnnZvc4V8@FsM>DIB4?_1u z-Zf6slYC5OZd{lz1aJlueQy>7Ni-)x);|4)WCW$edW@=Cl&p<*R=PmjUF22 zeBC5pR!Ca?Pdv<7R!<)ZyJf7G%IZ{FYi#7?D&b7$NL+N9LuYd^$m?< zTsm*HJkcC&kv#Iu;S%G_drHtff_}#=N(c^0x$V5B%W``laYY}w<5&g=jhY*ui`}fimpA>~5?;Pl>h=PmGj1n~{|4 zkAApQkwjB~qHBc3W))YS|2QbTYbAcgjXr6L)Xy&=_~8c27Um)79Osd3VG2 zLO|ETgB-IGOA>A;>xl8=KwVv3_SNc*lmIFw_&Mw6c^F_oYE>@gh)1qQ$bIrt7dd~B zGYW!iO|h>0q-UWK&WWu{=!NcQn49-$i{Z(We(R5y{%Qf9aCm$_HXY0irKabh7hCGj z4j!Im&h}F)HcdICbc%mk>g+;nKr9lwf6s2_$2F+ciaQScvng20WiT?_thLl8Qz0V= z4jIREVS8v~P$v1XqwyG*?d+>Eg$gNhe>lR5nF>5+*_wQgrbh$$`ljo zcLPatYd?K5CG3CBQhgLOL1t-@Pt~dE!RwzaGi(zr<_^EsHMeNk!({_Q&2M4zlBJb{*dxwm|D5m zlj6IxI*JIH(Z|>qJQ7Yio8ZfrFDpaaB#G>6*RLl+YdMLax9{HdR*Gz0Pl%$9U^49e zn0ZIY)3Z#28OZ78*;h@5g-zh``g3BM`E7VRmOAp2B9HF;IJ%4Ih0AE#4F9}v|JZ81 zV*cx``kpM+MOScD-M%bUc75#~M%&8Bgr=PcV%1DBueXZD^Uxog3eW@~d(U#Mj;?9U#^T>IK) zh@S71KgPMz0j+Yu8H!@An>Rj12YCP4wYA)eXCv9Ek`h9?%Ym>*NQ)LF14Bb1R{?73 zr!Y-UL1VgM&VEMGx%ZpbUGQd8*1S-cM5vnd0meqBj#fC(FYMW5YgMV)*!Uad$Bxm= zTXS!BXDW(INXWl@84o=uvpYIu23H))ns4PU(%o11UVR>x>%s+`rJ>aRu-ord?-Riq zNN`3;dU<*Ay3OSATS|AL+r0w^D`(AtD*Y0ZR3+ zUESTG(b373BO{k7{7sQ6`h_uqm%FANT9!666omdPn>EKJT>LJ9yKB8LcbkxsGIw{( zIc>h`r)Ifv$LBk9=;aDHF(ho3(U52k4UWl%rbcEkjvL~DUYs>lZk)$~SZ?Kx=_kCW z;hFf%WC?c}2-CXn0A7n~gTkGPrKsFFIvyTXp=^x|=$*q@(6C?=4=!J=(8Zq`jwHG# z#~ml4%@9=fJeXy(D+`H@Wfp60Z|T+}5K3&M@R+GQXl`i>RwgFSyglhqVZYg$D*#?l z0l;3%&Se5XS)oEF>48E=&(%4ts+0RMh6+NM(Ae0VBex3N%a>Ue(0|@F{+|qDQp6WM z(WQN{b_Ny}=`PQ}Nn8Q~p-4VaQeQTtJ3(48lxK+vH|WF`VSspIqI8@M zY)lJ3LC5T?7~5rIRd}G;jc-4BN`|xDT&t;@ObeUF$4?71xS)S7i*#(3H^o<#`_@-3 zU6QR8_3tTAWaO~udvRvAXsOE-d2ZLa4hIjfP)xKdf85?%cU^y=x7m(C6O$ zXS?csL!@PQ>U?2%Fq~lA9ezv4h$Ow1;M&QK?F@O2t*zUy$~4N0P2~M6tSZYjJl*r( zy@%Pt+w1QLOr}5JeaKyS#p8QcC#sD$DpVblnZW!BA%=xfgbgLGKkd@w~M>`=&}<*5k>RkL78ER6vFXNyo(iV!#Ve8I~6pR~wp zAF4ZdgyYusJ8=Kdf>dCnvBpyv~~;8qEC{ zyuHB48|YOsFcg8^MLcV#08c2+>!Bf(qml!tgi%016QZ`o!HRGO#Y{Ub0w@~tayqT=tC1t&EKf@tr2!0m4^-zi9xJckf0qti2WU^|enba9gNJ#~-o)Rts zAcKJx#SBXdVbt`1G{4=s6TTh!@9Ja8$U;%;QgEPP7X<7Eg@f!mQ%~`#?LShRjIy8> z5rf)cPOH%%Mn7!ump?A=PnT>_Qaj#iH>&!O)NyAm;%@WVvquJ;Q7uUPW*kpR1!Ek= zuhz7(qxrxjUk%1nr+sGowwPGmf#k)@tg><0NA;!_a4M#z zq%z#CT1JgimTCNCZLe#E3Pu*Q{$|UP7E>eu+UT=KEpu7_rV_ER(tnItmQ8TRD0V8l_sOL&%~U zR0Ph+9irss!&-;UagcUpYZfX!!M@V76&$N5Dk{3~7U63Rp53%7`4hFtc(r?^fNj|F z{(gO*6MXN4FpOp0p> z;3~A&@6W*jpRc}jBP7o%4xfTQy*<@4({K9<#(LvYYQ&WB8@P!NEvDi^u>noU|fC zXbMj|SNI)vd5(bu)A1zEaoFeh3UB;wrsG3|;!;)&s1+~$SxNLZf|6{-#wTXa`i{aUOL@l}B8Fxc=YtFqakI7+~kzyI7625GF| z{=DArSdQJgg9@|auyNq1xpq+^W_?+GJ_aqAIMlJ+Ss^S_GWl?c88o3+t2CDFEL>O>aG4)6iy}hHX#LBOOhU8= zLH*-?PK!)!&wUY0xNOE@-m{*#2p&ZN12JPgro_m(tslXh*q%hM%MzN<;U94?)IxIXNt{ z8W$v&dI9~Q!wi3ZO^9GKBm@DLrDntnxv7MwgIimn(RLaXfoVN9CD6^Zxv8nMu>D0B zd@-QQxkl`gL<4R!d3t(2IsRSvg@yGB_XyeY?IWW%%U+TQl-0TJ5ID#hipE z==G#P20HHa0aq}*4G*<$b}U56wQC80y(1(f%YuRM8C?;!_$(n!`f}jlIouoOFaXH( zX)j%V>MDmly5=3h;+85Ku%xaL&AbEatQ%Wf-O!_O`v7&CJs@|sV@a*J$L!Qa({aT4 zS?8+sU7Y3O(pBE7<5j-J;aqJP4XiUciTOyu2u?-!Lv%1T)Z&3{5qQNdPMJSc?CvUg zY*!Ank^*$-WZuy-HcqE-*^_qB+0>6)q5Um0fYx^iv7=alNI`>4U_}9|+qeI4RfJCa z*O$~vOp?^CDkQ=9LTG$2|Be9GNNwBW#S1?z&uKh(jEcpMe7#R|^#DZ$Tz9+fb*C<7 zh_%z*=g;c#Xhz`sJ!H}>XdO-i#XqO4(r0T(Cmc0r+?eS4(caSEne=QnRJpUW6BiE; zZ7*Q}9PiNSuobWUVhDAHDD+P4$+!zL(LkXS2x6DUgOZ<7xGGFHj2q3nJq01kb`d+d zpqmO+@EQhl9wpUpT_7$DUY zSvYszas`JdlZRwyX#{R$))-tRB-<9U4T}dDFik5dUTMivwDf#x!TJFmw+gr_R&_s% zFwTeWWJhh7F;Cy``a}>f!yPVIVZxR z!Xfe)tvpm^7Ve>1p>*l+dPs0^5-?MlIvRJ8YaPPy)D{2yv|*2UVLDQpzZ&&Gm^oa& zA8AgH@EX4T_v5O+%e!ESV3>Uk#~934t~yUzgJdLz+N!qvV-2vdp0gg7|9yD)(bM7o z)STS><|EW3o@SmEK2X%`}*v;>1C)e#SV@=58QO2w-5%~-T=e74U{dt~n{Ic^h zXZij^(DJ|i;jp5AUkdB>o&VGtT=`Gs!pn>QshxT9zxy64?xR>(<3Czkdn&9@*;N}G zn_Igu!U#_>uG(F{Smoyd4_q)zG;}-8o!PrK0r30cs;lS^2ryV-z!{W?Vp!xNol#z} zY-`o-7+(p#=4SRr=_tT{uQB&S3&l*rOd)~IyrCispxkF_fBsqViA7+w(=a?}8!6X9 z->{WDgv-^Im*$`$N_u=WzW3B_of$y&pybvY5#_YSu_T=4PJQhV*QKPTU?N7S8tLIc z=X~j(t|DU!=K~Z9%{)ySjN<5lQV#0LlYIgiCfy}W9qY>zH{iEFQ@f*_@*_E{a$diF z-7?1ZXJIw`Auuks9L)3@XYMFSX$gse2_U_h8II$E)s}d3pP*elHGtyt?wrT+$H$*M z>8Ppe`d2iFfYv`SKS&dS0WxRLp6ji2w$0Y85(Nt=8O0eK*X9sW{ujg}f`JErbBwb+ z`rS+IXw;F{VfkyW`r9+Goj&(93!VbH#sGyD^Xl?IH{|fjfo{VK2o-IABH`1fS1Y97 zSyd^5@zv4M2~YT&rY?2iA;Yga+!>DKHSif>cI;e22}PZp-dM1nttgkjfJICV%K)^! z{8Yk|YRlOmh$hvJyW0nJ59B|CAezcl}3 zV%qcMi4~|Y1tr5liQL$qgHVp8|Ddd_O#JzCna7X6i}W++5zkM2 zdk0WF9Pa!qS__bAV*q*gwn=vgg!*AX6%`L#g~y>zMSB0{)HO=GNd^942U-(z@gv!G zN&QQADHH?KnS-19M<;b3Xx&V&@j%&ldwH4E#J^E1olpmoWwFA`x2IT>4_z^dU8|== zusohiFy^W)5$2|C?&}8G1^dPr$N3$;HU6dBL16u0@Iz^^W+z=Z|_Nq-lCbXvx|G;rWj<4kJQVkF$gpD~#+Ane3W zPEH~pY@E?93Y2&s6Y%K5&Y`Gqa8t6w3ZL=B#5Z7YF<_=hI(7cX7nr{|Jp0Pzq|&+E zu}gtFCW#4>^Ma1|Xxh=(%l$Lnd}^PAvMiQ!(k(gDK{GM#;if|4~W92R^d z3Kd?mluoEol&Hf9mR{d>f!x*`_Xw|EjlWYemKmQC$5}0i=vMjB-uF68*hDIzRBEsbu*$Hu-k6uU2(1cUh3n24=cppwi~n z)=e#Brj`r(roidcWy~I1mhT%L&suqQlb$+Dz{r;nSZl}2#HRf}W9m(p$B^paiK!q> zXWEM}!MqRvKMtL#b0qxwA7h;s#oYJX$neV0N@8iJZ`{|%T*alOS&;YAx-&4FAxX-G z$!`Lnfg+>6s&%11L#nYJoq2PHgx9Ks%x0vn%%TCDkJb5{|)y#4Y({#K4Gkf>hc9tarq*QARyp3h4F zr|X#SNsGyY4;OW=IF8-%?M>X?wmUv(VG-dYxr~cjv{$ia2S3fm@d0uA`b@!~d3_G# z8&WS3NGY@^G0}+e6p^OZDK<6K?|f?o>&d z82nAc_JhY4F3fQmasbT{#c3HrE%K}}{aJe~&G*M*KxTkB^G!5;iYXAqo`|WnHdH%Nx+=_9h0?oD-m;TrR5;yrAu&F84^KB zVX|}&tYSTmXZyEE3M1|K#bBgYkQB}bu+7aHq!bjX>zRr)Vj1-Gus1=}H23b&Gcid9 z0acd0c2qn~xXNy|EOG4GUFAUAH?L)HxSh zSgJ8e&s52X#L`kxpDfwwthlyjUK-bG&7kd;A>?9L!nCtyVSJv9mlr;`W(@tklI8dD zqs*g6HL60SL=-uI17ZFXJs%@tK&Rf-VXcq2uv$h(C&6WRAqjFTpFICQqD(+ehO+cs zuq@$PPWkiu=5}^PK%FGP2T(eCw|K<_V2Ps}wbPz=L9g=d*<}_9Kj!1R08qZa{k}DH zZU3}PlNnNnVnMZ&pXXlq*TmP@##R{@4$jQ2X>Y4m9NWE&fTl1D-Kh)nU+((Awf@nW z1a{8hTh(6-*lfrdWe=%!)1hUQo&ul4YI3S%)P5eLhUK!|d6`tR#olg#BvDgH_|pN` zYm172c46u<912B)=78XXl3^r<{bE;2>(;H)ba9o?#D|%}Z-fh?FOkGZrP&2s*|K}N z5JDZ%YN(n`GJO zU@x7#LL`z*A!Q9Ehrl9nd5ewYxH8SC`GR&&9 zaxoSi+zYiBFg9{vJoNKb>?EsPOd&tgnci0r{o_Xm#F;c=0HHD!D~j(E6$1ZC)#AL| z^%tc1Fx=x)mrr}q`RyDIEDpPA>#da3WMhoUM_I=Fazi5<0w|S>R54a3^&EE^HhO7k zO!h9ic_ut0E-n|wOtbcjK7Gni?BPz%?<51fRTs3+0^QDCTY5do6t+38a)EjWRwWRb zfT8J%9j&lmo_}~;WEu*>2p0Pnh*8V_$hhA9Ifa)ch;e_e`CQM_inC`=h69IJ1pDLu3f>sV0*A9<%!P4%!E8`62zX5A z*L8U9H5{w;Liir=`B!{0N$vxadb+K4HE__3l`QV!hD7dqg<^y4R!jWAZ&b! zZ(*#18TPGoa!}>d|;34KX;t+))l498=wH`fsjnPE>`0*S}x;w|AgvC&4 z{!=^v5YD!9rk&rtf|3vLNYc%AR}HYE|D561n}xvcxbs?+V_YZ&MGgj1eEfK^*Q(>U zHHXiXlb1(tcvI4IaP;=COkIb$@YY(78NC66jer2nur(UBnIi|t#OpHi z@nmODn|*F67s{?^AYOS7sD1C`u-u~KC5UW@G2h`Ku_tL6n3gUDn1cOf$0H!U!8|Q| z5faGi*FMC=0f{g;0<$f#d&A@CMF|#YyRQB1+qZI~nW{|n-Q|A^8Q?AaT7EY7VSMI# z%^9&SrgH}f_d|TJ10Xjl-$;#uC%Y%s)qqFJK8MJiwdQskWMzKXfH=!-W#d21?pAjZ zqfE$*kB@J)2jYUn^Yj>qkI4P~EATibUGmaq@D;9NhE%if&r2vb8|ctwt2#bD=74i) zU}Uhglv^$5pcIC&%z%4B(yswPf!x+LOyZk0YM<$kN5#?*2U{`DqLnq2A)bxTGW#N#Rhw{5|0Kw2=W4AJew5en!>ncD9v_B!Xv)v$c5s z{Nu?{IBLGFj z4$`+T^`^2eF69`@k@CqESi2`u7;QF3?;V{)0nPVdAMJP*y4P%`9`S<%? z%lau_cx}7x#1AnMVCyeU`ax|C*wT++(gqcl*i&JJTgrdG_v9ml^gr)#a3})x97a88 z&IypWN&nZ*Uw*e{4Sp$B$`8mixUN+qvek>}AWsa^GqIq|XHV&hJ+c7G2J-!}TUZm# z!cZKOVvS+V)ZapptlD-Y2uohSIj4vcK1b>Bdv*i-@|sZ0oCNV}6O{4AD?=c2k-=04 zg0&ncJ;;D)2oPJfxoq(^?RW2zi-j-Kim0#&vyRLUj4t^tKavbh6^wUD=RBT zfCS>D`E)u(T*z>#*C@7w1{M?8NJxgZ%qsbld;#KrIwLNkS!kN zbo$6!vKIgC9QNYQohR)26I2$1mGDavqCtcvu)ph>E>1+yw zBd5RdLQRS&ARpy7%gbb-7m&Qi$_g(xW5+G7S;8f#RcP2s=Q1(!nx^*9cnjAo(c z5|n2#pyBg6DLqXy)($3IW=*ZhBZ9j1D9rA)(r` z#b|TIVa0!(5dyIJd1DTXAqB(7jf{^$vT%a_MJg^P1_a;Nw{K5Mo&~y8Y0=P-D?@IN zIs;~LXDTAX0xKSieE<7K`2W5UKLv;K^Lr_dJ z<12{nfnkN-XF*LMgPZ{nvDk%$NT<+UEdU)}%q92qrNIqSp8c zD%f>-&fye1eGpZ6Xde1X!IHzXLueP1sjzT5P_ed0mx+8cjbajW;7aBSx+?zq(d+XW z*UO(PKh+k_N`I}gs+bpUcJ5|{TxCDvpNns)7{ujeT?i6(ubvfqo||kTR$S?6KF|_` z_Zd5E?vRqts69G@jjhEfmcM={q z8NH4coTQ*}n3?_6#m-JBB!t2yRqpLdu}pIbs7>6GlW_|v7LP(! zwta_%lA|Iw9!N+?jR%^KMn2SQv_Aa$(5Z~>%-7I}reOKhSMw9XktN38mOVWmD<~)+ z9f*9rSWtU!c?ylrt=ENQy!gs~&C$`3R_Nejn|cvdr7AZ>p0)>C!XAR{{Aj|BiYb$^ z#0yqc0`FFai?>#SXn-c2oSgi2nWd_3z{ZiATwX``3=BlpZu)4t zce7ew{PNhifXdpBfTO@8NPvqw1^F|hR&u@l_Gsz7g+|Ix#$(Z{wuaq;yaxr1PBZH| zse&ixAu}Qs=ee_x<_2G{-L(WGISPk3RN{C__p0OA_wPV@z9m|5+N zwSQ1-NJZ8k;oP}i+FRH~prPd;69a{R(`#s2z@8wd-Q z`}b#}M0|PjPO+?hIxYE}RYF1@t6ANuvI(@)8kX&CZ3e)eQP(xHO^-T$Tlg8I22Bumdl37l;dh< zF%t_*VEgHz_Rh+vZ!0s1w*x)Xv4n0taiaCDJ}DpCmGJRZ zJ!rdLY%%EL%9Oj{tEi;3IlVdCdbEBw?e(75@b1cJ%j6SpIGeRE+~)7Fll0G3CwzTD zOXPvR8Fz9hbjx2f%>vF6)J?qF>$sE1XIB&7&$X!X)9jAyyD4-p)n zboKF(7d;^^aJr42LZ@W!gxeFnv1ez>ChHiaxv)p{2J4IZigktp4 z<;&Bp5p2}4+``QfY0^7RwLX_4?t`Rv#-}-ZIYwKCI>o`+_t}YSAuX1kK{QM z1H;s@v8@sI6|(2u9jRnO4$+dNoS$Q3rF3-eP*(0zfFOArrlH&nw;f)zF)A@M<>#&6=(;ZyR~ zf&S%*r6n_o|A*4Cs)eC!(#f{(O+TA;YY*_2#h%x|V}XW5t;qP>k0`5Gw~cuy&CSQF z1>6tNDU!p*GrwjE>|2O1m!#Kp7S^~mg2R`~ur*z2ij$EoI6T(#&FIDk)>EXfZVyw^ zr;i`0P*ic_9-1wydrq5MoBMmUffos^L>r~ZNs9;&_1{^{JPh{C{UM}RV$0$@CoghiU%2qlc>Sd=U!$CcMr-W*^A6_2T62e~#UxXa z+_|Z)B1yfRKi1;@#R!itZYbXqes<^mo-gRbsHq-YefO=#3YBvKKKNQMvyYaI4RR5o zT4;zl;yx@S_yq($`7X`=sC-D`ceFmbOC861q2_bK z6X#!LM&QRrHjX!T$J@d3ecc&a(l;{5-%Pt%S6BDopx8_~mN`?CuzTFCTXt&eF}TXs z0$JIG%CVl0Q^)=T(y@6~QR)-*O zWVqG|F$O+tdp4HZM2m}0A(gdsPOFcpm&e-rFB3gLXQV|JBtoTGGaS>G57jST!JTPJ zi~IFUHi?Xe)ap>kX`3=|W72i4@W`7I?%D+Ebe|ymAN6a5gm#1ZhDHV(J5g4=TFV3! ztncr|IhXgDv1mQdP+;8&;B%U4%8)vfIoueaa}uu2YSd@`g7(JY?#gtS`ZK<_QJhi! zdwZH-Td!lWu&`+D^-GMfx)jMTGUz$3|&CfKi)JZQw1y7m7s^ySeSVy?i33`O1BFA z46tvKT;r|X?SfCbJ7k_kL8VsV>aKm|LwDW_`}_91JB+ z*9m@dU*Ofp9~o17O&7XL2w;)HUtf1L9{r$vAoj>}RqGI~ovI5_}J*$+h@kT4R zak$ajQ*eVD1RS|U9p>G|O!BdAKA%5d#p28vugOTJZghYjYd7ortaVeP927FQ>|2Zq6`L}=6 z_Oed(m~z-ux!xcnyD}h=9otTH?!j$I^P#*>*@9O+^Xj)A>gppO#iLL{yV6d1L-s?r zNX+od?N*vKcSkgrUf+ErB60!JF)GCKkNxO}zjB9q~;XQvy1rf-y3 ztgKeag8_0-!Io&&ZbQi_Q82 zp23w)dwqAHaLTIG=Bi#pfW@{Dp~*+HsmI~v%8Q4G_x^-WkB`WocgSA6Q+HFIj#xL7p9C0ruE~yoN^s zaw)nU@xlQdqd=T2{0OKVa&vb#=`Qwf37->9`T_tYv45#^!R`vF;D~`k_~QgBU>))U zrWU%>V*0Ki0&g@120MMP4$zup?dNGKih`6E zhl*`Sv4uLk%o00oirm7*8gKyxtZd}{tzIA~uU*vk_%+bx)u9g}T(a!4NQmGX{uV*blJ?(U7sj^KSY-3a1TqPZ9j$qG%s=yr!w2U492!fwcVgF8!1{H}HIkdZNX zMC0)5<0rKjGn=p4iDW2TCna6FxRa8cEF-6}T#zkn^>TGi8v_hq3vi=1&2o9O^#)WF zOhczh3f1lpFqe94zOrpXFdPqLsXq1p=7Y${{}nGdGCKNcW#o2@)H4GEnnJeZ_s z&8_C^4}od<92WVZ-ds4Tw=2~mNy<5N+=S2Z=Ive7La zLP%Wb>d2pDRdJJ&l5%r%i`#AQ!Ak-PJCH(bB#!8_%BtLa)M1P~e*s5xlcqdMO%j54 zcwVSMk(2qn#>zIU*0qEtRKDQyGd0ELFuO#vbN9X>n@bf%RGU(6mn#t>6h!!~hHUUN zATy13$d{UM#`Ss&JdqtQx#+t$Z{NBBqz9BOqoVRLpW(&>ZtlH;rO*5Ohtq3o(uRh# zH1+jxw<^p_99GmAUeGpPK3Q_r^i(_>5H7DK=hOKTi;9X0Yn0|KQu$q*p;-}6IzftD z)5M)8&&Bzh`Y#j9X=(lZo=cPmtipNh3tb3hGMlRDd7+nBXVCXUFHiI)e@dFM1RIV0 zuBtM#tRU#9lE21d9K#&*!TE!Gl{@0)iq1$Hx4_&ZuiCiHd!yI$szV zNH7+zx~qWb?kvivYF+SZPx%0~4CDq0qobpju3V{1O?d+M#0-)1-!6$hFYcZ9={LO| z3wsBULlHh6)~7~k*g&5)!@CPJVvTAT!I`yMZo6szOU~iK#gQJW)nbU4w}OU8N6(dH2F18ky=fLd z+y&w*PpRsC|6);i+Za@6)mAb1Hd2tWmYW-~-->4SQOZ_nT0v=u5z-lT;+vo>gS zroD-m2}#SNQ*!>-K#^duF&mA#)Y>pDkAp!eOVrw&PZYaeqv&x%ZfyX9M9YA zP{YD$!3_2&@_thbMTLQPV|bn3_2#LGiD&b0;(Po<7L;-8oEraX0aR5NoQg|boisR; zWHJ;Iw~E5EIn?h1{^B^IG#M}t*auWwal}^ng`>``Pf64B^sle^h`QW_;VNX!KryoI zvs4ced{bN7EU?8z7Nqqj?uQt})ZZVjWm9DhbeY?;m0uscy#>C$M5>X0DO!+@czvkNd-`o$Cy7Ph&7I(-7B zntawa_W~CeS7W}ilG4eY0%KBJ{6&duCEMC~(o&rcylA-T$F95AC{exu&V%eWslka& zcYB_CTc{Ur4sDyjwak|~%vZbr^~KIah8h~RS;`RFY~x0f9hOl0ftU4=StLcSE5(ef zhe7e(wvJHy&Z%G&Q<&)ClZyldc$TX>dR$R$Dw$_zIPwS0zxwUGj+aX?4k<{(O}U<3 zyy&cl)J19y3!OFjxS!=)e)J!t5bXIvZ3TUq{;o;A|ua4HC1ExE^J$dau zS`FPmqN{DI$tVXTCT|iEY86zb*PGI?u*5Ytivx&)Jqmc;O6jGPB%D@gL=0t(wrhKb z#$$=~io+8sGV^a(aLPA2YCT3?9_3}9W`R8r__*bdMfEXuWqEt?5380tI`Wla| zlU~D>w=DUVI(l6Tc1{WxF8JX3J|OXDH|d&*^t)i(!((h*U0pq5Jxyf^AP0gEz!F=l z!)tkbg0WDR&*u8;K1BZbAXZ1N4!>lV(=ZpJ1-QU3uwt92gxJ_A4!>pZ=s1@eWtFOZ zdV*Z6xO32g{mcG>{iji9vKYp^3@ESb#Gm|QWNQT_C+qXZ3hZAGn3!qHPGOglKzE5;H zO^Qb6!7Z7Lm2q@BL;Gza3Q=q8i&!Qm88iAu{R4qu#h5rkzrQ_OL!q*bM%xb-OQWhG zLc3{2X+`0&eGTC8*&67kq58l|utAjJk_!(codV3*fg6Zkjy7WrxXb}Y=b5bRH3$oC zpv;KVq~mgwg5(lpu8t<0gJuB?7D)MO|ECcEY2ApExKHz0@vbrFwvsheH)r zVjLis@l;#uCp4!~GrGLT;r>v`e%0x{8UO~-^Y zbHQJ}oO$%`?Uq27RI^4^+>sz~NbLgtf&|UdV1-peVuZfA5SxSsR?Ud_( zUe(wA=NSRznRuWIo)%lK4E$PM4KPwvQdV{5f!v4Yj!wP`ouIN`{RlTpqJ1nc1WfUA z2)g_C^`X2!{7RxK6H#+Z3n}~aP9UhEp6GH-!aE#bULW}%&q2g!dx#IeN^ndNaCl-S z?hH0IHsE1ptgiOdLg(&Ol5@D&@7{o6kyBIiQOi!#?b1KjqWiF)dmIuO!2Li#d$t3{fE{JBE-5hFBn7|AEXv0W+zEZN|Mb`1RPLkQrkds?ieN>5{8$0aB2pKCQTkJ zxh8a)(*H~R(lgQ95B8bne+JIFH8WoCaDK->NH|L&11x8fp486rn52iILkeTK%PMCb zTjiZ!g*m4G8NO)#4qu+e&?z!t0;V77O-=bmJ|ZF_osjPdAOB?99{`<~&V2s*^~d*p zZCN$7V-XiMQwCeZHrjjlaFA*k%7yH<7nWLfn9`SQEIN8-!{Op=fcj~ISwxrN_4J`J z0$ON*B)!-413sy^e)6%C^3mQ6gmUhy2n%^x*>6Es)suZu&o&iEmx4S-*C- z!FV#!s0X?1A{Jr(6$BTJi?T5aH`8*PZp@AtZC|T5IbI=&g2U z9gV(A$Nj^=voVm|XeHMSaX}u^ej61I`SVL6fEp9K7v@Jx0+T{A9w)@B@2=L>*Yh;6 zTCIk|<|+?+LvT75K<-!Je#NSK1N(yFoAeL!PqLM|8JPo8@9fwXICs->W@&3PeO82( zavW#YYb;)2Zm*rC3l*PoX}m^0O7WU0inGwPS3G>q0-6c9fy@=SZRqH5`{AqP=}L|w z@KU8Yz5?p|HE+(f#&M0$?n}IQp+D<$lLNbr^w0JFZ^jm1UlORktM+|~2A82D5}7b_ zsG3M_saAkJiW#_(+m8J)7^WtzxswPD`s5WmBlmU z9@-7a0Bc?MOEPKzk|8r<1U7S;j$P2j3L+>t8GDlzYmgpL3`i;O^G zTb9|gYkNElhYd{?eCRdY=%=&(`P+Lgvj$LEs1%xVKt;u6!@}Fr($bhL3X^(q<anN|BrAZl#zysyW zUol1cfP}qTz1Z&w4uKZZM$?}s-5(hI2EVFzGc%4*Trju7H9oc1sW;+qc-on0Ac4EN zg<*}xnN`J2f$PBVGektc3WR|6X$=(o2ZB8u3b)U%YaU6_(ryk9jblhRXbf{^zO(S& z@!_tcT3kXbb|-MAvPsXj>gVF%{x?T;r`pbR=(y~zK($Una_ReCgi^lE^amFMRW3Zd z+1SI?X@xHO#=_Z&lXD~Ni-wR}p}H#&V~@(6hN_dabTfHR_7%R|eio{U*gKS1TIXKEBi~kSK2>)Ma^b;;{)#0{I z?FYPDc-U{xzJuHDour?}2(^`S^`6JRkc60C9xMbh-}-zlb04%-({hK zsbH0YJyJ}r1vU&ib=jIu&#p)w&d-bDkVe+VRd)2To6Zg&hG|7*L@p&xv;8XS5Y zA8yt4Wb~f(Ku=O$tto65^8WVnrK20aNk&HH?#~L$SMN} z++S^a%{})>sjs@Gfa>5YD_U&x6-2P}SApqiJ#aEaIzTr2mLzTC!6*L()3$G+a!MCI zv9WgTn~u2w&>UyhY%Y-qFE<|tzF@U{&JxmMTLYj?(59p$YCZM2u!VXxnCfoF4us}g z&qt4t7UX;mGia_B|?qA#Vt;cLPv?etvck zQi%b4@Fix_-AELX8b@#L(8AS~7ZYPXtcKZg-yl$>`@!;Zr!@lO++2DYo_$#I0u<;t zW=|YU>7LwRJARcA_X2F?J~khcDXFJ&n3|gdpd{<>&d~$jEay{s zddq)HQ_L!2_?U?Q)!vs!L*4&xD@mnNDOti@$WD}fYY`>c*U3_LBfGK=l}aU)lI&ZI zeX>l*R)i3PF~$a=Q+Q>pYuG=ANM);nWLG{`hLG&*LA(FOFNzG zw8WeD8Asfvl9NR!Vg~8Dt%d%I!%gwEMY1zW$s~PyBGCAzUQ^-T1Is*W9) zc+x9G@fY6h9ckHb(Vu3vIe$NsT##4j>0ssH2F z4Nn?9FSZZ_oJ+mC6ysYovqkZulzFgC9OFN_?`ZJRAF?yZy$63MHOx3)`MUSXWaYjG zg`paF33xo@C>w4Yl!|y$v57pW!+dAwROHqcbx3(9NoaA&KCx#YCt&DF3}~1UIJ(b! zak381)zi6hSCoVUD*JlS8zHaN7L~|C=N$mJX1!S=AHu9sC=25gxP`W}gda0AW>ZqT zd!4EChrd}5UYAVNCd=;`nx1ZwU+HNWF>6XO+x8=^Yp3fiP7?zo-K-ZT=?DV@=8h~; z%E`A{2LnAPVhc?a;$djO^G?@<+?k&ju=|&Se%WNjBO#ez9%PE)@ymp1}c%^F`M|IUdnVfsmqnSnwyI!9(wj8W@a3KilJj^41xI? z8zdTLGhVE!v}HNc$~)0bQidwrC^FKv09ARhDhItgzQvYnO;^myDg{t1_`BJa72F+( z7?Sx(Zt?Ka@5TU#?LD~nBp26U*!_o!`5PO)O)(0=By@F8PXJ8bK#O)6$|aqxH7Vdy z5eBe5M#HuxuUe0p$l-$2tH4`WT3ecnMef;mFv)9SSUhCx>(44R%*I|&M~y+v!qK$R zahOR2BuGL+$I5!oFAN`kc+LSeaqopGjz?aeExJ+J(AE~8;BT>_1+t>nIxc>GHQuB3 zG90x-9k(SwF?1!9e}wrElP4)&=y)b*oYva-U<`1FZR?jHsM(r5J>NV3yr|&4O$`O< zw&zB?+)h~WKX5E*p7CjTa;0>GIW)cm5;1R*7X8r#MN9a z;{@8E5d3ax>VVn#<&S8Kby@Cr*wby#%{bj>f%rRu1um900Axhq}1Y?7}Kb($Qt`U5rq2s8H=OtqX5Ig+`3}qJl+Ta*~CpIA2=qz>N zq>9CrgfK?%X7YaF2qu}|f|3Y)znM z@M4;}*e`VPDu|FkZNm&rTMb|@(N4_VpPA;cVK3eXhAOUt*y02LS0(*sgYWQN$j>^GA@z#;i|=}f3I_;_+A7vD>9dsmPS+$V7mA&CDnEMyEGMS zMi*djWc598_9Tl=ciIQI^ucC+TanNY=Q?!jpW?icJU9E7tZetoo;^QcPf6bPe11?^ z^(q|y`hOgMk=)-uaXR^>g-U4H+qa{uf;k>er?1C7SI3q-=441(hC0#<-a}{%!ycJo z9>D`QHdu$AGB>wCNZYaYGFjCW-PGg#GgtinMX4HosU@BW48s-*d>A3fq^Pm;hYI&EAQQE)eo{uo%i;ZLB+ml&+b0NzQ zKzV9FT3T9BD&_be%>ECU))w2g=O6O#pAUn6;rV}rAHV7ErBev^{f7$w{eMbjzxt;C z{!~lk7i#q1f7|wd+gGOHmZ*N6x6%NBYKV+SC|VM{$Ta!G5dzHryuU6#3C|w)?*&^8 zdV%O?_MBAW%%Je1W-(}r)7U=jdlLZsHls6 zrf~!Lz?(x@U5-if9LqI1mj?$XB&F)h?!u}7Y@$A07wi7rFvESkRh#&}^ESX;qP~NB zDyyn+Eni_b`uk^e⁣_xpnXNk+j{vn0OPWLV*LOOa6Rb)A@{Q z$~=nx+QtREeP0~31-Z*)GZz5&no4F)Ehe4+@Xxt`WYqfiV?O@5ww9KIBi>$q{~YZI z)U`kXH^P6I?7&n1z8Ff$zi!Vq^ii1H;`E6M z{E|)lG;rjF2||VQ@}v>Kwg8dtXBCb4_I$tJ_LQV{)4N09rU0yQ%B9+ayhrz8XB-U+ zVd8fHPV54f*aAcuAUtE86YDZ24bq*Ri2k#>nmfcbvq~kzS$Z4bo)aDZ_oyi=E@E?B z08D&s&xY~=n(XO2AL)#6K=~04cr=I@-&I<=LaxErTU+uY3}DH>$ByLz>GnwxMUH?A8fevwWD_GhW<}p^ zHgIZC0cgc`HW)Trl9%xa=#G)b9>BI-!>o{Upn8lb4I(F`eRpN+H#~XCIi~1L#UDQY zj8OK^Q}Q=6+As12oqLFbgTRw-lkeZ>S33f#wGcL0QLusBeW1L>uBYJn^ZjxD!$t^Q zipn0Ilh+tUQVR;& z=88dz09Wm1<-4m7O-^;d;5VZFL9}@eneyjdObrd0U>_Ko_L<>6b*ggAM>*gnSg;0g zlvCKDqwjq?$5{24-eceC*h0Ws;(UgUKvcyf>dHmz^M3V(fyWbFuM6B!Y!`b+MLg>| zJyil&oP66_;el&&E58%r6xIAOEWqsfOqWI2#Q7hjrw>I_1pY@kvHnyE++yF=En45Q zn>%_H6iO*uKA_m1t+t6ri0WFF`*58=>2P*F_2qZazyo9tt&uS+qz(&ipzjZqDxGr* zL8bJAx{%Z*ylK4`RM9_3WV2*Yqaj61yx+#*pib@VY~@U=Nj8r&Yd`CDwH=aD18F80 z+r9uBkz06F><0{hoj|349$RcK@#;h40tIs;BW46W>y`JRvTc307kqPu!7d@B93B`T z?!;SKrnJ%!J`S`bHMBEj8m)D&glmDEu6ad}YXsm9easVzr{M`2y1J&O-3j35yg1cI z;2X6)$7EFIeQaUGZ)|>JCkWO>K@L^4vNb2Z+ZgvHHJEkHVG0H7LyUqMr2z-$5FM=& z-2XIP$CiZ!%6`1O!M-2xIQwq(ZOil!M`G3F>gtqy6hX&TWqZaMj7QK@&y_hd^|N-J zhZhW%%&rd(6GemxnLuF05&4r7qtNcj6(YbxS7f|eT|##Nze3FPIDyJ;x9a<|P{ZP8 zgDh@ODxT!!iIu}>3ZoyMib%t(M8%l@2o{t1jeNp9*!|6adPv)1J89$s9TkYvU!KKk z=;-Qxq3_#1Z#>frif0$!J~TehWk4A)sa@wSC)yDuIrd)ie@lRaaqr*1fFm0Q3a?ap zK&VSa#lYU;4AUw!?8vV_SGK@Uft*|L?e;O&cBdcUH4Zw>5wWyYm9YO&DV$hSE@*vL z_QTB0&6x^_W$}aKnQ3tci2spi zQZ?lPpN7I%5&O>#y!$<}8D=RDBZw+`0YZ@l;N`yDoi-j=f+>a?5ZR(98_aXx&tu~~ zw|iEkJf@9^l76<>` z)B8nK9)y5M@i?f{BG+0}LIh(!C7efeMToA~_FSG@?ZNh~d4bcfo`NC45q9>b>&cGF zK)nJe+*3ji-Ek#Q=D-~l3)BE}P0tIxW~%5jf|GN)lrICyV+8wQQ=B{^&j)Wr?44){x%JRm zsFGANNv(28YsXfM;L=e55ZEK-?djPJI5BMdKN9C|g2wW8Z$TtrXrL|kvZ?9*=DETK zg^0;AO!qW_u8|7L>IT4Hp4hTquvXp;j}r*F%ivW81FXkzBB=o3E-8ve{1}=F4lR^K zh6wKGJ=pLC?2&EzuA3X@Il<>iNvB|Wj9E9NOrQYGSB4IcEk0Hn6D5h&z%e>6uB_Dd zR0T|C31SH_H1+v8pqQ~MeaT?6j#oGc0xR&4SOdG}*av#5DPe$kF76H>&LPUfq2q3L z9BkLI>aplDMFUD`A3i8tF*}XXdH== z)-qNnr@KocisLa&3nFEstJKuU?+D#MV{*FiP8!Zv_L!7-R9T+WdJ#|P%(r2iLgZC2 zEW#fi_Cd=b{fr)5yo9`@FrB(OKn`pDP?weahlU=hV>$milVCc4PLl@4{ZC2%Yi&@J zhhKmdi;bJMTSaA|=7v*Cu8xWdvS1}$i^zdxor0VaOrd}Vng{B;oe3@IB9P^ERCsWJ zIQQe+qc$q46XS?4!0<|+pT(z7JNAQC6t-zURC!FYN+{Ktn?I-!P23Po=Y{{R+7+u- za;qwE0fIjevQ~cY-Yg1+&7!vFr=jGuuJ#d_QhDBDtAr)a5qQ{<8t!iWJywqqso`%? zjgmpfUjftJ=U)N^vrl{abrWcFrFWXa<+R1i)QK;}bW{ zY+RQz^uBGo#&LdfU;KJlUvL|yaJG4-r>BK$vqA7BJADx34i^>}8|xYxtI34WZ&`v0 z+cP#t8IMvepC`e>U_Jgy;|L2Q=mxoOtvFhjuD#(r6crU#*=6Q;lAk}LX4547*|UB9 z9#gLS-W=eNe!vs&{mfd=>LG(h%nw9OZ>#}Z1<3Y9?|zp88`X(|K6tr?=U^uzrUq|* zk9aLLjayhKvTCq)1e-5!(>{iKi;Iggv6+2-o8h%swOe1C6QR1-&oTU5KsNQ*#Xt*X z9jub!=+0u0u*xkg5<&AwbI<>tB2dFo7IGu8@;)C--A12jr(43X#CL_e`&-y#fOUVM zo$hXQ(deJ2_u@S$@y|mWwh3yt+0DEeULm3ChVEz(STeE6$V7~~!_d;OmL$g?4PL_cn~nl?&{t?K~pstb5l9UVO<@ncuVvKB0;C+QTtR|TRSJ8 zNveM%bmK}#8S6a=wQ?q4R`lrDpRTs{@EWuj5wtHnreT-=l|sQI&@}X=CAOEuao1oIgBGqdz!A&JXH@i0_sBrGkCO zLMa;?j$W$}waB1RfW~1IG>|OWj4%=h`p{#?NaUtW9C*~2Zji4b8}zPofHERrFH8pn z>vY5GYK7hdP@?Ou*ueHK8o;qS>A2vbh^%<^8tZ1M6{e zOW)2u)X$bjb9jiv`TX<(sD*9n$Xy;OD*`FJ^Y&>GNy3~{ta@#504-7QRHe%|0J7nH zb+JpDLHp?VN^bVFb&a;-=?E2zrvQozv2TTo_w*GTpd2zk3sRm755(J&{^ze1DXodfOkM{ZZ6c9^!MOf zmS?&{N#^P8>d7sKx{LE01g|{0cuPbP6@JISH|MRYTNN$a=Emk6{T}BvubVfUw`(#; ztz|Z$=!G=xbWr6yB8t9#{pKd#5biZJU!V8}a2geU!h~2A9zdWwbsnCQ z6CU^cfrA^!2tP$)-9On^LD16IO(uz<+k|$}(CAGhgC-pMFt+Wcx~9{zC0pcp)%2vR zuTF>fNAs&e+f;-)pKH~iFSj^bCATq}tOm9{H6g5lg+V@3s!Di!jhp9h;tB9@GD(;h zai%$l-nb&?vNPcGY@n1u&bgW*7-$*o#)*K$BUsG(&&~uT>nT1QKNPU$`azE%UawZh z3!WG|ZTxaqH83jIqaPb&iTAPF|{e&ZdCrIpsug0$&W^D<7yPf#jBZpKtE^)LmQ4H!(r@YK{+!j|hdg9A+h9 z9zf+xGa|BtmRBbBAiz2oE+~YDAG0l3GRvGNe6c*I?R??<*GtJK!I!(n^^GH19G;2< z{Y-e`Vbq~1RyGjN__-Z!nyTIW^hU##;< zgw9#`+I`cIYBb?Cby^lm-$4di@*%#>Ar>h~Ub=S&&Lvfdwi zxVB^83Nc~_mYqFtA0*Nk9_Guy87d?E&pAD* zh*n&=VFG6rt-~L#KBPrip0F_+m=@b140j01j~2U3T(m9Tu%pz4A&{Q!uI~rTgal+1 zd|E2=*0aZHXKLwMCDFi`7I-SV8@zY~?8y^!Uz3*Of-jVlxl;A^TSh=lpJVZP4@RCQ zT2#5el$z{H+UdHyA}^WO3?Nr&xS&0`8eQgov(Y*kbm8g2UDr)Avo7ILCfSzG*&Vl+ zn%w1J3iUa(D-p6YAEUYZ1g$kH*?}|4=%MFf(3l4Y$g@+yW59$qADil~xg6;m@pba- z4MBCIW_0*Iyy~MNdiNaRwnJ`)(UXS%ocx}jQ3&YRuc4N%iL?`_?3%~H?D9Thw_3k= zm)+A)RW%1cThQj!+91p|%yON&QFxk{_iK9DFSR7C2soV;du(9NIkhC|Qx=_9&L`*M zy0MZ><1uAxDv7$hK$cZDHXe9Y2Lt!Qj6o?iVu_$%Osc%N-2j~-+jcj3B& z)#V2ki_YZZ-5oIc=K?-`em5ur0)87$VGetTCUg?e8xmm`0^?_qZrp>QratbS{*W-j zBwVyDjuLX5o=(s__q7;3dL;9JXkg8@(~NpmhTw{P$aXuGpwgkkpLb|R7U&)oz@DhD zZ+<+bs~%3WYR|p8@UFz9b{;L(Sf54ICq8!5)0dD9PqpQK%aBGR^0{xzPRI3bUBO9OD9pi*doCsnH zw6HAegoV6TZ6V|L=Zk%ozY+)V~`&OcRqqo zZ*xr|x>Ky4^1QOr%${xHZb^8dqy}p%5pT6%9scHZ-0Fj?y%`!24cwGEzn8}YI?c6i z-_Cjvps~vqhF>5U33sOyTtKFKN-PVzA?m6~^2h5#L%2%od_Dz*&#X=Cihjn-%s1@i zxAL+IbOGe*CEWjT9?Z!;wmTP|P7G$@gYZVm8pt&2*QF=uGe0bO%BgBxBX*Qbx2OH& zbnpJE6{YBrvoeBs($$%SEsehB^S-<8{z!7ucf$iX^*2jhElB2B05qI8DMxj=~mVBq}54D{~LXyC_PD|@0gop|SmM#AvPPwMT z3&?Y5|Gs_NBZE!gqs`AJ-2UO)b5tlvP(*-yO{6@nWSS|tLQ$rZ)fFYk_-I$)m9UH$ zJsHPebMS?C<1Xu z$|r-{MEUAETweNXgO_BijE6QPKCE;BI+Y+_vZBj6G3WU@GBSNq=VEQzs4d=sJYCvr zb|qPP+_lrHSK-;g5|eA?>#DFSWw>ngmidU=RVslMG38DxU9+WF-A`v*P%YGVeAI(6 z!1gGTy{@b3C{JwKy~#Gdt8;UEd+h;oQ#e*PVqv5H;UI^!@szihQDnPs2@00(UVAuf zaEs0&mdTJk+}EW-vEC_z)qa7$9e1R_{hexS&>V%=t_K%kxJuyiZjUR^fZdU$79yua zSp54iEdDc?u41BO%r~&fmUSvaa##ff1=T#hODn^h{7mCYGnl)Ai>LeSj|+|QvKqAX z?2ElNcob=Sed}3p@GCCNT;~WXZ!nYPQWdn_nAt4~i~GR!^$L&PrN^(}nt=dC3;bun z8D7C?!l5YR&hB57~^(F9iH^ke(L|UeC*RTI4~Qu}qi7e!!_!iH~~sSL^)rq9Q}OKI4Q93?(BNW~zy$l3VFeL0t(0 z1!f}jTyZPDHahN$X}uTzbCCqbr=^!6f1##IB@{ z#9@>P@|(|}fr|`o4c!W|TD;s^Aa&RxF@z1ZF=rm1VFk}NSWT~G@ul6m6n}5o7qC=4 zg5pBf^3aeM_-wuzkKd*|E{7P~8T|Xx*V`8A9CYeIRycF$>O{3F=p!R z*xJrn-)#J;dDSIXV|G=gyecDuKO0r7{fcL^(7OCgmCg3=XJBi5bPV1_S2%;h{S|tV z%*?35yVuw`1hK7_wEG~PJSUi z(hWLBeh zzOg4+a-`7!Jar$i?B+Wus&#->LTf-;SgyD@QOQj&x_jccd!>(9vOE?RxzKD_ST)LSc!wfPrtF^I8H3Y0natz z%bpBo!ROXLbQj3GMN8TmLxK_bYPwI|A@iL&brEs7g`bMolvX$i=|}YC2^MzScV_Hy zO>@hE-(S4Rx$$_v9z=f2+*Q>E2@SFa&>lSTf~z=27!qo}YSD(f3ZV)#Lj`cU%XRm% zGBQ+ggvewO1&Ug136}sY=G}!3nflc+-?@z-N9DMj(|D9P7@~u6FiVgz6Oc(51RFd% zJnm;8kgmfsGxD(mxo%&rUqTw)vc9fgp<}Eghd2rfI-nL>-_?cki-~E&V8*j&A@faU zrlvXARCQ7%4V@+4(7oZw&yRo~9KdB>&}=qZX#j(82XqFz|0D;$fV@kV8{)P~h|$Xa z+u-h#*Qz{z+EjuV`HHXQf$oa$S3Y&t>-*G$ ztBr}rZNBN+U404-L?%Gk?M06P^&L&z%ivon4vA;C3VV}UTaT1UJCy@hwe+=$0bQyn z#ic~A5t9Z%POB#>nAq4-;6bomUmy;>?+(v}P?islOT}a;08E5S)L@s!>&r?wem5-h zUO}9G2VFldj`dfsyU&?lR#qNQ>C6xR&RtwXQ9s`P?kRh$m=VP4C^~i|V7$DdL^o*~ z-)>#R=Q?y1v@mF>>roD}HH~#XZu>l!TY+t=t7F;zK^-M-kt(@MKtzNLxWE3uDx`SI zZz*cyCRwJb*?`{?hU|A~e!WzNiLy(G>9!KMl?ZmwM zLoZb`B#aWKd&1No`oz83WP;3P_s*e+i3UQ&?DAzHVPSn2ms~JUcfV9UCu2k|UaY)R z(gVwkel_2w5whX}<@zUElr;A9T6-I@UyXn;_e-1 zoCu$?vTh?O9j+ZyP=vHP6!xXIXQT9d{9Gf1A&B>hpnlLn4uvduoym#B5F<93+i!b) z?Gr&l7x3VcZzhtID07;7tj>ph8|a<&qp*Fp`3H00q#*>^;{EZ%hZAL|Pb6RF z@PBu*>aqTWkL?G;bGBa(U5^vH{PTL1eT@I`wfu7~t(^Pp+S1y~z$jt8`^V>T@=O$} zHYT&9NUG7#A>_2W_q&>#z=2;JfV8BAK2#di>-s)UcTzI)RHt95^giAd_;q_H|4u?+ z?Lay{aH!KzknsSEoMFCMbn>CVO;e8+3aB}WoLssGW`F)Q9CAHkuo&$x| zWWNb<3$kvmNRcZR35hUgk%Y)$VrA2US1-7^>zbOT?gk@lV37Sge2k)_GWbH(_2IRi zW|!5czwX)LBR3s+Vs*(t=*CCgloa@mU$Id2pLU>|6Ydj=pi)8h%My>NH{DbEHxsUt zmu7GsMMY==QU^sj88rNKh7hHR*5!zd89f8is-LQ=>S80=%-&=i#U{WX)(y_YTr{+S zqcF17Do!@xiOybMA%R5#-n`Qlf(~HKH|X5?Df(1o`<}Ls=zw&vIU( zS)-Zv{ElW|&Ac07oCNGY-~i*L<+Sqbp?Q8=M)o@7aj=Si1qpEL#v}vKIdtV>O4?I$ zNI51Yy4uEB(0q)0G~&nOT{ormv{-sFAwnBcFi%-+ZsdO#j)46Msty5h&(TBiYiy9o z=eHTGFKCtnOqfxYKHjkhb;*f2HRS45($0|ulL+WTAe9{pb+c>n_g;B)UwIiJ9}N22 zQ&Yz$Ahh({sf$%A`wi3%_`mz@N<3?z1g*h&`4M*+Jea_gN!Wxuc))_L2|7!{I`svW z3I6RBxwNM?b0?K1VM?R}7v7fzHDT+JfpHG}@Nu83fB(%mGx+vVjf+B&4zeRYa2AKU zuWicXxt6W%Xtn9zd06+nYrJ!h!$0v;ge(SXr&2A4Zv3lS&T5`71{40Ye}9)Ugh{~9 z!AxB>50~+z)!!ed)k>NENpt=G7RrBCYvkhm9}>Ft|D3t%Ha&-t%!?*Dn!k{{aP_jf KQqIMj5B?WZMwvkX literal 0 HcmV?d00001 From 53e1526fd8d38c736231f8e0d8673f8e6bee54cf Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 12:42:57 +0200 Subject: [PATCH 41/54] Address PR comments --- pages/fundamentals/storage-memory-usage.mdx | 4 +- pages/getting-started/install-memgraph.mdx | 8 +- pages/memgraph-in-production.mdx | 44 ++++++--- pages/memgraph-in-production/_meta.ts | 2 +- .../general-suggestions.mdx | 97 +++++++++---------- .../memgraph-in-graphrag.mdx | 68 ++++++------- 6 files changed, 118 insertions(+), 105 deletions(-) diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index 73f28eb63..4f592b74b 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -192,7 +192,9 @@ built-in behavior is as follows: * [procedures](/advanced-algorithms/run-algorithms#run-procedures-from-mage-library): skip all records that contain any deleted value * [functions](/querying/functions): return a null value -**Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior.** + +Please note that deleting same part of the graph from parallel transaction will lead to undefined behavior. + Users developing [custom query procedures and functions](/custom-query-modules) intended to work in the analytical storage mode should use API methods to check if Memgraph is running diff --git a/pages/getting-started/install-memgraph.mdx b/pages/getting-started/install-memgraph.mdx index 467fb533d..cbff11f1e 100644 --- a/pages/getting-started/install-memgraph.mdx +++ b/pages/getting-started/install-memgraph.mdx @@ -117,12 +117,12 @@ Below are minimum and recommended system requirements for installing Memgraph. | Cores | 1 vCPU | ≄ 8 vCPUs (≄ 4 physical cores) | | Network | 100 Mbps | ≄ 1 Gbps | -The disk is used for storing database [durability files](/configuration/data-durability-and-backup) - snapshots and write-ahead -logs. By default, Memgraph stores 3 latest snapshots in the database (flag to adjust this is `--storage-snapshot-retention-count`). +The disk is used for storing database [durability files](/configuration/data-durability-and-backup), including snapshots and write-ahead +logs. By default, Memgraph stores the **three most recent snapshots** in the database (this can be configured using the `--storage-snapshot-retention-count` flag). Snapshots size is usually less than the amount of RAM that is needed to load the data into memory. -The amount of CPU cores varies per use case. For horizontal scalability, you can further increase the amount of cores on your -system for additional scalability. +The number of CPU cores required depends on your specific use case. For horizontal scalability, you can increase the number of +available cores on your system for additional scalability. Check out how the [storage memory](/fundamentals/storage-memory-usage) is used, and calculate memory requirements based on your data. diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index e1366c433..5ec40f3c3 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -8,24 +8,44 @@ import {CommunityLinks} from '/components/social-card/CommunityLinks' # Memgraph in production -When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal performance, -scalability, and resilience. This includes hardware considerations, the correct sizing of instances, configuring drivers, using -appropriate flags when starting Memgraph, importing data, and connecting to external sources. The guidelines in this section will -help you make informed decisions for your specific use case, ensuring that Memgraph performs effectively in your environment. +When deploying Memgraph in production, it is essential to consider a set of prerequisites to ensure optimal **performance**, **scalability** +and **resilience**. That includes decisions about hardware configurations and integration strategies. -## How to use the Memgraph in production guides +This guide is your starting point to production-readiness with Memgraph. -To get started, we recommend first reading the **General Suggestions** section, which outlines practices that are **agnostic to specific use cases**. -These are foundational principles that apply broadly across most production setups. Each separate guide in the **"Memgraph in Production"** -series focuses on a particular type of workload or deployment scenario. At the beginning of each guide, you’ll find information about -**when that use case is a good fit for your needs**. The specific recommendations in those guides **override** anything written in the -general suggestions when there's a conflict—so always defer to the targeted guide when applicable. +## ✅ What you'll need to consider + +Before you dive into specific setups, it's important to think about: +- **Hardware requirements** and **instance sizing** +- **Driver configuration** +- **Flags** when starting Memgraph +- **Data import** best practices +- Connecting to **external sources** + +These factors ensure Memgraph performs effectively in your environment, no matter the use case. + +## 📖 How to use these guides + +Start with the [General suggestions](/memgraph-in-production/general-suggestions). They cover **best practices** that apply to most +production deployments, regardless of your workload type and are **agnostic to specific use cases**. +Each separate guide in the *Memgraph in Production* series focuses on a particular type of workload or deployment scenario. +At the beginning of each guide, you’ll find information about: +- When that **use case** is a good fit for your needs. +- Specific tailored **recommendations** + +⚠ Recommendations in those guides **override** anything written in the +general suggestions when there's a conflict, so always defer to the targeted guide when applicable. ## 📚 Available guides in the *Memgraph in production* series -- [General Suggestions](/memgraph-in-production/general-suggestions) -- [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-ing-graphrag) +Here are the currently available guides to help you deploy Memgraph effectively: + +### [General suggestions](/memgraph-in-production/general-suggestions) +A foundational guide covering universal best practices for any production deployment - recommended reading before anything else. + +### [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) +Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems using graph data. ## 🚧 Guides in construction - Memgraph in transactional workloads diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index ab285dfac..8a143b406 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,4 +1,4 @@ export default { "general-suggestions": "General suggestions", "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", -} \ No newline at end of file +} diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index aed77c5de..c0b2dd365 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -4,62 +4,62 @@ description: General suggestions when working with Memgraph, from testing to pro --- import { Callout } from 'nextra/components' +import {CommunityLinks} from '/components/social-card/CommunityLinks' +import { Steps } from 'nextra/components' # General suggestions -This section provides guidance for getting started with Memgraph, regardless of the specific workload you want to test it on. -It's ideal for those who are either testing Memgraph for the first time or working with a simple dataset. -Once you determine which workload best suits your data and use case, you can refer to the more specific guides in the -*Memgraph in Production* series for tailored recommendations. +This section provides guidance for getting started with Memgraph, regardless of the specific workload you plan to test. +It's ideal for those evaluating Memgraph for the first time or working with a simple dataset. +Once you determine the workload that best fits your data and use case, you can explore the more targeted guides in the +[*Memgraph in Production*](/memgraph-in-production) series for tailored recommendations. ## What is covered? The general suggestions cover the following key areas: -**1. [Hardware requirements for running Memgraph](#1-hardware-requirements-for-running-memgraph)**
+1. [Hardware requirements for running Memgraph](#hardware-requirements)
Learn how to select the best machine for Memgraph based on your resources, whether on-prem, in a data center, or via cloud offerings (e.g., AWS, GCP, Azure). -**2. [Hardware sizing](#2-hardware-sizing)**
+2. [Hardware sizing](#hardware-sizing)
Since Memgraph is an in-memory database, estimating RAM requirements is crucial. This section helps you allocate memory based on dataset size and expected workloads. -**3. [Hardware configuration](#3-hardware-configuration)**
+3. [Hardware configuration](#hardware-configuration)
Optimize your host machine configuration for Memgraph to run smoothly, including key parameters for effective operation. -**4. [Networking configuration](#4-networking-configuration)**
+4. [Networking configuration](#networking-configuration)
Learn the best ways to configure networking to enable Memgraph’s interaction with the outside world and ensure smooth communication with external systems or users. -**5. [Deployment options](#5-deployment-options)**
+5. [Deployment options](#deployment-options)
Understand the tradeoffs between running Memgraph natively on a host machine or in a containerized environment (Docker, K8s) to choose the best deployment method. -**6. [Choosing the right Memgraph flag set](#6-choosing-the-right-memgraph-flag-set)**
+6. [Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set)
Memgraph offers a variety of configuration flags for performance, persistence, and other features. This section guides you on setting the right flags based on your use case. -**7. [Choosing the right Memgraph storage mode](#7-choosing-the-right-memgraph-storage-mode)**
+7. [Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode)
Memgraph currently supports two in-memory storage modes - **IN_MEMORY_TRANSACTIONAL** and **IN_MEMORY_ANALYTICAL** - as well as one disk-based storage mode: **ON_DISK_TRANSACTIONAL**. In this section, you'll find guidance on choosing the most suitable storage mode based on your specific use case. -**8. [Backup considerations](#8-backup-considerations)**
+8. [Backup considerations](#backup-considerations)
Learn about how to preserve your data in Memgraph to prevent any data loss. -**9. [Importing mechanisms](#9-importing-mechanisms)**
+9. [Importing mechanisms](#importing-mechanisms)
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. -**10. [Enterprise features you might require](#10-enterprise-features-you-might-require)**
+10. [Enterprise features you might require](#enterprise-features-you-might-require)
Memgraph offers a suite of enterprise-grade features that enhance scalability, security, and manageability. Key features include role-based access control (RBAC), advanced monitoring tools high availability cluster setups, multitenancy, and more. These features ensure that your data is secure, available, and that Memgraph can scale to meet the demands of enterprise workloads. -**11. [Queries that best suit your workload](#11-queries-that-best-suit-your-workload)**
+11. [Queries that best suit your workload](#queries-that-best-suit-your-workload)
The type of queries you use can significantly affect performance, especially as the dataset grows or the workload complexity increases. For general use cases, simple Cypher queries are sufficient, but as your workload scales, more advanced query optimization techniques are necessary. Also, a different set of queries is needed based on the use case, which will be covered in the specific use case sections. -## Bringing Memgraph to production - -## 1. Hardware requirements for running Memgraph +## Hardware requirements To get started with Memgraph, we recommend checking the [system requirements](/getting-started/install-memgraph#system-requirements) listed in our installation guide. These requirements are sufficient for setting up Memgraph on your own servers. @@ -67,7 +67,7 @@ in our installation guide. These requirements are sufficient for setting up Memg If you're deploying Memgraph using a cloud provider, visit the [deployment section](/deployment) for guidance on choosing the appropriate instance type for your specific cloud environment. -## 2. Hardware sizing +## Hardware sizing The most critical factor in provisioning a server for Memgraph is **RAM**. Memgraph operates primarily in **in-memory mode**, meaning the entire dataset is loaded into RAM for optimal performance. Properly sizing your RAM is essential to ensuring your system runs efficiently @@ -103,7 +103,8 @@ Items 1–4 are referred to as **memory at rest**, while query execution memory Use the following steps to estimate the RAM required for your workload: -#### Step 1: Estimate base graph storage + +{

Estimate base graph storage

} For datasets with minimal properties (3–5 small properties), use this formula: ``` 128 * N + 120 * R @@ -111,7 +112,7 @@ For datasets with minimal properties (3–5 small properties), use this formula: Where: - `N` = number of nodes - `R` = number of relationships -The result gives you the dataset size in **bytes**. The size of properties is ommitted as it will not impact the sizing. +The result gives you the dataset size in **bytes**. The size of properties is omitted as it will not impact the sizing. **Don’t know how many nodes or relationships you have?** @@ -124,17 +125,18 @@ to extrapolate the estimated requirements for the full dataset. This empirical m based on how your data is actually represented as a graph. -#### Step 2: Estimate property storage +{

Estimate property storage

} If your dataset includes many or complex properties, refer to the [memory storage documentation](/fundamentals/storage-memory-usage) for detailed calculations. Add this result to the base graph size from Step 1. -#### Step 3: Add index overhead +{

Add index overhead

} - If you use fewer than 10 indices, this can be skipped. - If you use many indices (e.g., 50+), add **~20% memory overhead** to the total from Steps 1 and 2. -#### Step 4: Add query execution memory +{

Add query execution memory

} - For basic querying without graph algorithms: **1.5× multiplier** - For analytical workloads with algorithms (e.g., PageRank, Community Detection, Betweenness Centrality): **2× multiplier** +
### Final recommendation @@ -148,7 +150,7 @@ Still having problems with estimating the size of your instance? Try out our [of or contact us on Discord!
-## 3. Hardware configuration +## Hardware configuration One of the most important system settings when running Memgraph is configuring the kernel parameter `vm.max_map_count`. This setting ensures that the system can allocate enough virtual memory areas, which is critical for avoiding memory-related @@ -165,7 +167,7 @@ Properly configuring `vm.max_map_count` is a one-time setup but essential for a For system-specific configuration steps and installation guidelines, refer to the [Install Memgraph guide](/getting-started/install-memgraph). -## 4. Networking configuration +## Networking configuration To ensure Memgraph functions properly in your environment, make sure the following ports are open and accessible on your server: @@ -181,7 +183,7 @@ In addition to enabling these ports, be sure to: - On **Red Hat-based systems**, even with the firewall properly configured, you might need to **disable SELinux**, as it can block Memgraph’s access to system resources. -## 5. Deployment options +## Deployment options Memgraph can be deployed in two main ways: **natively** as a `.deb` or `.rpm` package on various Linux distributions, or **containerized** using Docker on any operating system. While native installation can offer up to **10% better performance**, @@ -199,9 +201,9 @@ to enable the full range of MAGE functionalities—an often complex and error-pr For most use cases, especially during prototyping and production readiness, the containerized image provides the best balance between **performance**, **simplicity**, and **feature completeness**. -More details and installation instructions can be found in the [Install Memgraph guide](/getting-started/install-memgraph). +More details and installation instructions can be found in the [Memgraph deployment guide](/deployment). -## 6. Choosing the right Memgraph flag set +## Choosing the right Memgraph flag set Memgraph offers a variety of configuration flags to tailor its behavior based on your environment and use case. Here are the most important flags to consider: @@ -219,7 +221,7 @@ flags to consider: Enables logging to **standard error** in addition to log files. - Optionally, users can manually stream the logs to another system such as **Splunk**, or using a containerized setup where + Optionally, users can manually stream the logs to another system such as **Splunk**, or use a containerized setup where logs are collected from standard output streams. @@ -252,12 +254,10 @@ flags to consider: This is particularly useful in environments where you don’t want to restart Memgraph but still need to adjust timeout behavior for specific workloads. - For more information on how to configure Memgraph, as well as what are all the flags that Memgraph can be configured on, please check out the [configuration documentation page](/database-management/configuration). - -## 7. Choosing the right Memgraph storage mode +## Choosing the right Memgraph storage mode Memgraph currently supports two fully-featured and production-ready storage modes: - `IN_MEMORY_TRANSACTIONAL` @@ -301,12 +301,10 @@ Use **analytical mode** for high-speed, read-heavy, or bulk-ingest scenarios. Use **transactional mode** for anything requiring **reliability, consistency, or fault tolerance**. - For more information about the implications of Memgraph storage mode offerings, please check out the [storage mode documentation](/fundamentals/storage-memory-usage). - -## 8. Backup considerations +## Backup considerations Ensuring data durability and having a solid backup strategy is essential for any production deployment. Memgraph provides built-in mechanisms for creating **snapshots** and **write-ahead logs (WALs)** to help @@ -327,11 +325,11 @@ For now, users are encouraged to implement **manual redundancy** by syncing snap tools such as [**rclone**](https://rclone.org/), which is already in use by several Memgraph customers for this purpose. -For more detailed information, refer to: -- [Data Durability Fundamentals](/fundamentals/data-durability) -- [Backup and Restore Documentation](/database-management/backup-and-restore) +For more detailed information, refer to the following docs: +- [Data durability fundamentals](/fundamentals/data-durability) +- [Backup and restore](/database-management/backup-and-restore) -## 9. Importing mechanisms +## Importing mechanisms Memgraph supports a variety of data importing mechanisms to help you efficiently bring your data into the graph. Whether you're working with CSV files, JSON streams, Kafka topics, or external data sources, choosing the right import strategy is key to a smooth migration. @@ -339,7 +337,7 @@ you're working with CSV files, JSON streams, Kafka topics, or external data sour We strongly encourage users to review the [best practices for data migration](/data-migration/best-practices), which cover recommendations and tips to ensure a reliable and performant data import process tailored to Memgraph. -## 10. Enterprise features you might require +## Enterprise features you might require Memgraph provides a rich set of **enterprise-grade features** designed to support production workloads at scale. These include: @@ -353,7 +351,7 @@ There are additional enterprise capabilities as well, tailored for advanced perf We recommend exploring the other chapters in the **"Memgraph in Production"** guide series, as they highlight how these features can be aligned with your specific use cases. -Memgraph Enterprise License is enabled by issueing the following queries: +Memgraph Enterprise License is enabled by issuing the following queries: ``` SET DATABASE SETTING 'organization.name' TO 'Organization'; SET DATABASE SETTING 'enterprise.license' TO 'License'; @@ -367,10 +365,9 @@ MEMGRAPH_ORGANIZATION_NAME=Organization MEMGRAPH_ENTERPRISE_LICENSE=License ``` -Reason for that is because environment variables always override any system settings that are set via queries. +The reason for that is that environment variables always override any system settings that are set via queries. -Additionally, please check out [how to set up the Memgraph Lab Enterprise license](/data-visualization/user-manual/remote-storage#how-to-set-it-up) -if you require any Memgraph Lab Enterprise features. +Additionally, please check out [how to set up the Memgraph Lab Enterprise license](/memgraph-lab/configuration#adding-memgraph-enterprise-license). For more information about what Enterprise features are included with Memgraph Enterprise License, please check out the @@ -378,16 +375,18 @@ For more information about what Enterprise features are included with Memgraph E -## 11. Queries that best suit your workload +## Queries that best suit your workload Memgraph fully supports the **Cypher query language**, making it easy to express complex graph patterns. In addition to Cypher, Memgraph has **built-in path traversal capabilities** at the core of the database, enabling **lightning-fast traversals** optimized for performance-critical use cases. You can learn more about these in our -[Deep Path Traversal guide](/advanced-algorithms/deep-path-traversal). +[Deep path traversal guide](/advanced-algorithms/deep-path-traversal). For advanced analytics and utility functions, Memgraph also ships with the **[MAGE library](/advanced-algorithms/available-algorithms)**, which includes a wide range of pre-built **graph algorithms** and **procedures** for tasks like community detection, centrality scoring, node similarity, and more. -We encourage users to explore the other guides in the **"Memgraph in Production"** series, where you'll find detailed examples and -recommendations on which types of queries are most effective based on your specific **workload and use case**. +We encourage users to explore the other guides in the *Memgraph in Production* series, where you'll find detailed examples and recommendations on +which types of queries are most effective based on your specific **workload and use case**. + + diff --git a/pages/memgraph-in-production/memgraph-in-graphrag.mdx b/pages/memgraph-in-production/memgraph-in-graphrag.mdx index 9a3223992..c77b6c61d 100644 --- a/pages/memgraph-in-production/memgraph-in-graphrag.mdx +++ b/pages/memgraph-in-production/memgraph-in-graphrag.mdx @@ -14,18 +14,17 @@ Before diving into this guide, we recommend starting with the [**General suggest page. It provides **foundational, use-case-agnostic advice** for deploying Memgraph in production. This guide builds on that foundation, offering **additional recommendations tailored to specific workloads**. -In cases where guidance overlaps, the information in this chapter should be seen as **complementary or overriding**, depending +In cases where guidance overlaps, consider the information here as **complementary or overriding**, depending on the unique needs of your use case. -## When to use this guide +## Is this guide for you? This guide is for you if you're exploring or building **GraphRAG (Graph-Augmented Retrieval-Augmented Generation)** systems. -Consider diving into this guide when: +You'll benefit from this content if: - 💬 You want to **query your graph using natural language** through a proxy **LLM** interface. -- 🔄 You need to **seamlessly extract knowledge graphs from multiple source systems**, especially when graph representation - naturally suits the data structure. +- 🔄 You need to **seamlessly extract knowledge graphs from multiple source systems**, especially when a graph structure naturally fits the data. - 🏱 You're building a system where **business stakeholders need fast insights** without relying on engineers to expose data through APIs. - 🌙 Your engineers are **deep in the trenches at midnight**, and you'd rather they write **simple questions** instead of Cypher queries. - 🧠 You have **embeddings** and need to perform **vector search** across your graph, along with structured Cypher query language expressiveness. @@ -33,48 +32,39 @@ Consider diving into this guide when: If any of these resonate with your project, this guide will walk you through the best practices and configurations to bring GraphRAG to life using Memgraph. -## Why should you choose Memgraph for GraphRAG use cases +## Why choose Memgraph for GraphRAG use cases? -Choosing Memgraph for your GraphRAG use case means prioritizing **performance**, **scalability**, and a **smooth end-user experience**. -Memgraph is currently the **most performant and scalable graph database** on the market—your business stakeholders won’t be happy -waiting unreasonable amounts of time for answers, and with Memgraph, they won’t have to. You can explore our performance results in -the [**Benchgraph benchmarks**](https://memgraph.com/benchgraph). - -Thanks to its **in-memory architecture**, Memgraph delivers **predictable response times**, avoiding the inconsistency of LRU cache -strategies that sometimes hit disk and degrade performance. - -With built-in **vector search powered by [usearch](https://github.com/unum-cloud/usearch)**, Memgraph supports **high-performance retrieval** -across all your GraphRAG workloads. It also acts as a **unified analytical engine**, connecting to **legacy systems** and supporting a wide range -of **data ingestion sources** to populate your knowledge graph with ease. - -Memgraph makes **schema metadata accessible in constant time**, allowing your LLM to construct Cypher queries **efficiently and without overhead**. -To ensure a secure environment, Memgraph also provides **role-based and fine-grained access controls**, including **read-only access**, so your -GraphRAG queries never risk modifying your data. +Here's what makes Memgraph a perfect fit for GraphRAG: +- **In-memory architecture**: Delivers consistent, **predictable response times**, avoiding the inconsistency of disk-based caching strategies like LRU. +- **Built-in Vector search (powered by [usearch](https://github.com/unum-cloud/usearch))**: Supports **high-performance retrieval** across all your GraphRAG workloads. +- **Unified analytical engine**: Seamlessly integrates with **legacy systems** and supports a wide range of **data ingestion sources** to populate your knowledge graph with ease. +- **Instant schema metadata access**: Makes **schema metadata accessible in constant time**, allowing your LLM to construct Cypher queries **efficiently and without overhead**. +- **Enterprise-grade access control**: Secure your data with **role-based permissions**, including **read-only access**, so your GraphRAG queries never risk modifying your data. ## What is covered? The suggestions for GraphRAG use cases **complement** several key sections in the -[**general suggestions guide**](/memgraph-in-production/general-suggestions). These sections offer important context and +[general suggestions guide](/memgraph-in-production/general-suggestions). These sections offer important context and additional best practices tailored for performance, stability, and scalability in GraphRAG systems: -- **[Hardware sizing](#hardware-sizing)** +- [Hardware sizing](#hardware-sizing) If you're using embeddings for vector search, be aware that they can significantly impact **memory consumption**. -- **[Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set)** +- [Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set) Configure runtime flags to enable **constant-time schema retrieval** for the LLM. This reduces overhead during `text2Cypher` construction. -- **[Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode)** +- [Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode) Guidance on selecting the optimal **storage mode** for GraphRAG use cases, depending on whether your focus is analytical speed or transactional safety. -- **[Enterprise features you might require](#enterprise-features-you-might-require)** +- [Enterprise features you might require](#enterprise-features-you-might-require) Understand which **enterprise features** — such as security, access controls, and dynamic graph algorithms are essential for production-ready GraphRAG deployments. -- **[Queries that best suit your workload](#queries-that-best-suit-your-workload)** +- [Queries that best suit your workload](#queries-that-best-suit-your-workload) Learn how to use **deep path traversals**, **vector search**, and **dynamic MAGE algorithms** to efficiently retrieve contextual data and handle **high-velocity graphs**. -- **[Memgraph ecosystem](#memgraph-ecosystem)** +- [Memgraph ecosystem](#memgraph-ecosystem) Explore the tools and integrations within the **Memgraph ecosystem** that can accelerate the development and operation of your GraphRAG pipeline. ## Hardware sizing @@ -102,20 +92,19 @@ Memgraph is actively working on two short-term optimizations to reduce this memo - 🧭 **Reference-based indexing**: Embeddings will be stored only in the vector index, with the property storage holding just a reference. Since embeddings are used exclusively for vector search, this eliminates duplication. -- ⚙ **Support for float16 in usearch**: Users will be able to store embeddings as 2-byte floats (float16), commonly used in neural networks. +- ⚙ **Support for float16 in usearch**: Users will be able to store embeddings as 2-byte floats (`float16`), commonly used in neural networks. This reduces memory usage significantly while maintaining accuracy within a **\<1% margin**, making it a strong tradeoff for most applications. ### Best practices -- Consider the **appropriate embedding dimension** for your use case. While models like OpenAI use 1536 or 3072 dimensions, **lower-dimensional vectors - (e.g., 512 or 768)** often result in only a **5–6% drop in accuracy** and drastically reduce memory consumption. - -- If in-memory embedding storage is too demanding, you can **offload embeddings to a third-party vector database** and still integrate it +- **Choose the right embedding dimension**: + While models like OpenAI use 1536 or 3072 dimensions, **lower-dimensional vectors like 512 or 768** often result in only a + **5–6% drop in accuracy** and drastically reduce memory consumption. +- **Offload if needed**: If in-memory embedding storage is too demanding, you can **offload embeddings to a third-party vector database** and still integrate it into your GraphRAG pipeline alongside Memgraph. - -- If you're also storing **document context** in Memgraph, keep in mind that these long strings are currently stored in memory as well. A short-term - roadmap item will enable **offloading static text content to disk**, since these strings rarely change. This will further - **increase your memory efficiency and scalability**. +- **Watch for string context size**: If you're also storing **document context** in Memgraph, keep in mind that these long strings are + currently stored in memory as well. A short-term roadmap item will enable **offloading static text content to disk**, since these strings + rarely change. This will further **increase your memory efficiency and scalability**. ## Choosing the right Memgraph flag set @@ -170,7 +159,10 @@ enterprise features that are especially relevant: ## Queries that best suit your workload -When integrating Memgraph into open-source GraphRAG frameworks like **LlamaIndex** or **LangGraph**, +When integrating Memgraph into open-source GraphRAG frameworks like +[LlamaIndex](/ai-ecosystem/integrations#llamaindex), +[LangGraph](/ai-ecosystem/integrations#langchain) or +[MCP](/ai-ecosystem/integrations#model-context-protocol-mcp), it’s essential to incorporate this query in your retrieval pipeline: ```cypher From f2f4ff7eb2189609e738a9f7586d293be70988d2 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 12:45:41 +0200 Subject: [PATCH 42/54] Add title for evaluating memgraph --- pages/memgraph-in-production.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index b4050ae26..14051f78c 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -66,7 +66,7 @@ smooth transition to production. These guides focus on areas like performance benchmarking, testing, and operational readiness—offering additional tools and frameworks that can help you get the most out of your Memgraph deployment. -- [**📊 Evaluating Memgraph**](/memgraph-in-production/evaluating-memgraph) +### [📊 Evaluating Memgraph](/memgraph-in-production/evaluating-memgraph) Learn how to properly **test Memgraph for performance and scalability**. This guide walks you through performance and stress testing scenarios, benchmarking with real-world data, and identifying key metrics that can help validate Memgraph’s fit for your application needs. From 5a828adba67583a7e50bc0e14f102503325caf82 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 16:22:32 +0200 Subject: [PATCH 43/54] Address PR comments --- pages/memgraph-in-production.mdx | 10 +++--- .../benchmarking-memgraph.mdx | 36 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 14051f78c..242f955df 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -56,6 +56,10 @@ Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems - Memgraph in cyber security use cases - Memgraph in fraud detection use cases + +If you'd like to help us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 +Your feedback helps us build what matters most. 🙌 + ## 👀 Additional guides to consider when bringing your app to production @@ -71,10 +75,4 @@ and frameworks that can help you get the most out of your Memgraph deployment. This guide walks you through performance and stress testing scenarios, benchmarking with real-world data, and identifying key metrics that can help validate Memgraph’s fit for your application needs. - - -If you'd like to help us **prioritize** this content, feel free to reach out on [Discord](https://discord.gg/memgraph)! 💬 -Your feedback helps us build what matters most. 🙌 - - diff --git a/pages/memgraph-in-production/benchmarking-memgraph.mdx b/pages/memgraph-in-production/benchmarking-memgraph.mdx index 3d9c7ee46..8f0d79b03 100644 --- a/pages/memgraph-in-production/benchmarking-memgraph.mdx +++ b/pages/memgraph-in-production/benchmarking-memgraph.mdx @@ -9,8 +9,6 @@ import { CommunityLinks } from '/components/social-card/CommunityLinks' # Benchmarking Memgraph against others -## mgbench - framework for performance testing - At Memgraph, we believe we are building the **fastest graph database on the market**—and much of that performance comes from our **in-memory architecture**. @@ -27,28 +25,34 @@ However, even in these scenarios, **on-disk graph databases can suffer from unpr Memgraph, on the other hand, **consistently performs better** by keeping the entire dataset **in memory**, ensuring **predictable latency and fast response times** regardless of previous operations or system state. -To support fair and realistic comparisons, we built **[mgbench](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md)**—a benchmarking +## Introducing mgbench + +To support fair and realistic comparisons, we built **[mgbench](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md)**, a benchmarking framework located within our core Memgraph repository. Unlike simplistic benchmarks that run a query once and record latency, mgbench focuses on **repeatable, controlled testing** across different conditions and vendors. -We designed mgbench to: +

What sets mgbench apart?

-- Eliminate noise from unrelated system activity -- Account for whether a database is **“hot” or “cold”** -- Detect how **query repetition, data freshness**, or **system-level caching** affect results -- Support **adding new vendors and workloads** easily -- Provide both **latency and throughput metrics at scale** +- Eliminates noise from unrelated system activity +- Differentiates between **cold** and **hot** database states +- Detects how **query repetition, data freshness** or **system-level caching** affect results +- Supports **adding new vendors and workloads** easily +- Provides both **latency and throughput metrics at scale** Many teams make the mistake of testing queries in isolation. But in real production workloads, especially ones with **high write volume**, read metrics can be greatly affected. This is where Memgraph's in-memory model really shines, offering -**consistent performance even under pressure**, where disk-based solutions tend to fall behind. For this reason, *mgbench* is designed -to support both [**realistic workloads**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads), -which simulate production-like distributions of reads, writes, -and analytical queries, and [**mixed workloads**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads), -where you can analyze how the combination of different query types affects individual performance metrics. +**consistent performance even under pressure**, where disk-based solutions tend to fall behind. + +

Realistic + mixed workloads

+ +For the reasons listed above, `mgbench` supports: +- [Realistic workloads](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads): Simulate + production-like distributions of reads, writes, and analytical queries +- [Mixed workloads](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#workloads): Analyze how the + combination of different query types affects individual performance metrics. -Ready to test for yourself? Try out [**mgbench**](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md) with +Ready to test for yourself? Try out [mgbench](https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md) with your own data and workloads, and see how Memgraph performs under realistic production conditions. 🚀 @@ -72,7 +76,7 @@ By turning JSON output into interactive dashboards, Benchgraph helps you identif spot regressions, and better understand how each database behaves under pressure. -For looking into the published bechmarks, check out [our official BenchGraph site](https://memgraph.com/benchgraph)! +For looking into the published bechmarks, check out our official [BenchGraph site](https://memgraph.com/benchgraph)! From ae10ec9456c182a206084fa64e7334d12880b83a Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 17:03:09 +0200 Subject: [PATCH 44/54] Add contents before the deep-dive --- pages/memgraph-in-production.mdx | 3 + pages/memgraph-in-production/_meta.ts | 1 + .../general-suggestions.mdx | 2 +- .../memgraph-in-high-throughput-workloads.mdx | 119 ++++++++++++++++++ .../realistic-workload.png | Bin 0 -> 122388 bytes 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx create mode 100644 public/pages/memgraph-in-production/benchmarking-memgraph/realistic-workload.png diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index 242f955df..d7283e945 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -44,6 +44,9 @@ Here are the currently available guides to help you deploy Memgraph effectively: ### [General suggestions](/memgraph-in-production/general-suggestions) A foundational guide covering universal best practices for any production deployment - recommended reading before anything else. +### [Memgraph in high-throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) +Scale your write throughput while keeping up with fast-changing, high-velocity graph data. + ### [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems using graph data. diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index 3f19c94f4..cfa34ea35 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -2,4 +2,5 @@ export default { "general-suggestions": "General suggestions", "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", "benchmarking-memgraph": "Benchmarking Memgraph", + "memgraph-in-high-throughput-workloads": "Memgraph in high-throughput workloads", } diff --git a/pages/memgraph-in-production/general-suggestions.mdx b/pages/memgraph-in-production/general-suggestions.mdx index c0b2dd365..df4afdd15 100644 --- a/pages/memgraph-in-production/general-suggestions.mdx +++ b/pages/memgraph-in-production/general-suggestions.mdx @@ -44,7 +44,7 @@ based on your specific use case. 8. [Backup considerations](#backup-considerations)
Learn about how to preserve your data in Memgraph to prevent any data loss. -9. [Importing mechanisms](#importing-mechanisms)
+9. [Importing mechanisms](#importing-mechanisms)
Discover the best methods for importing your dataset into Memgraph, including Cypher queries, bulk loading, and integrations with other data sources. 10. [Enterprise features you might require](#enterprise-features-you-might-require)
diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx new file mode 100644 index 000000000..18b1f63ca --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -0,0 +1,119 @@ +--- +title: Memgraph in GraphRAG use cases +description: Suggestions on how to bring your Memgraph to production in GraphRAG use cases. +--- + +import { Callout } from 'nextra/components' +import { CommunityLinks } from '/components/social-card/CommunityLinks' + +# Memgraph in GraphRAG use cases + + +👉 **Start here first** +Before diving into this guide, we recommend starting with the [**General suggestions**](/memgraph-in-production/general-suggestions) +page. It provides **foundational, use-case-agnostic advice** for deploying Memgraph in production. + +This guide builds on that foundation, offering **additional recommendations tailored to specific workloads**. +In cases where guidance overlaps, consider the information here as **complementary or overriding**, depending +on the unique needs of your use case. + + +## Is this guide for you? + +This guide is for you if you're working with **high-throughput graph workloads** where performance, consistency, +and scale are critical. +You’ll benefit from this content if: + +- ⚡ You’re handling **more than a thousand writes per second**, and your graph data is constantly changing at high velocity. +- 🔍 You want your **read performance to remain consistent**, even as new data is continuously ingested. +- 🔁 You’re dealing with **high volumes of concurrent reads and writes**, and need a database that can handle both without performance degradation. +- 🌊 Your data is flowing in from **real-time streaming systems** like **Kafka**, and you need a database that can keep up. + +If this sounds like your use case, this guide will walk you through how to configure and scale Memgraph for **reliable, high-throughput performance** in production. + +## Why choose Memgraph for high-throughput use cases? + +When your workload involves thousands of writes per second and concurrent access to ever-changing graph data, +Memgraph provides the performance and architecture needed to keep up—without compromise. + +Here's why Memgraph is a great fit for high-throughput use cases: + +- **In-memory storage engine** + Memgraph doesn't need to write to disk on every transaction, enabling it to **scale write throughput far beyond traditional + disk-based databases**. Unlike systems that rely on LRU or OS-level caching—where **cache invalidation can degrade + read performance during heavy writes**, Memgraph offers **predictable read latency** even under constant data changes. + + While many graph databases **max out around 1,000 writes per second**, Memgraph can handle **up to 50x more** (image below), + making it ideal for **high-velocity, write-intensive workloads**. + + ![](/pages/memgraph-in-production/benchmarking-memgraph/realistic-workload.png) + +- **Non-blocking reads and writes with MVCC** + Built on **multi-version concurrency control (MVCC)**, Memgraph ensures that **writes don’t block reads** and + **reads don’t block writes**, allowing each to scale independently. + +- **Fine-grained locking** + Locking happens at the **node and relationship level**, enabling **highly concurrent writes** and minimizing + contention across threads. + +- **Lock-free skiplist storage** + Memgraph uses **lock-free, concurrent skiplist structures** for storing nodes, relationships, and indices, leading to + faster data access and minimal coordination overhead between threads. + +- **Snapshot isolation by default** + Unlike many databases that rely on **read-committed** isolation (which can return inconsistent data), Memgraph provides + **snapshot isolation**, ensuring data accuracy and consistency in real-time queries. + +- **Inter-query parallelization** + Each read and write query is handled on its own CPU core, meaning Memgraph can **scale horizontally on a + single machine** based on your hardware. + +- **Horizontal read scaling with high availability** + Memgraph supports **replication and high availability**, allowing you to distribute **read traffic across multiple replicas**. + These replicas can also power **secondary workloads** like GraphRAG, analytics, or ML pipelines, **without affecting + the performance of the main write-intensive instance**. + +## What is covered? + +The suggestions for high-throughput workloads **complement** several key sections in the +[general suggestions guide](/memgraph-in-production/general-suggestions). These sections offer important context and +additional best practices tailored for performance, stability, and scalability in high-throughput systems: + +- [Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set)
+ Memgraph offers specific flags to optimize streaming graph updates. + +- [Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode)
+ Guidance on selecting the optimal **storage mode** for high-throughput use cases, depending on whether your focus is + analytical speed or transactional safety. + +- [Importing mechanisms](#importing-mechanisms)
+ With multithreaded writes, learn how to avoid write-write conflicts. Connect your streaming sources to Memgraph. + +- [Enterprise features you might require](#enterprise-features-you-might-require)
+ Understand which **enterprise features** — such as high availability, and dynamic graph algorithms will keep your real-time use case smooth. + +- [Queries that best suit your workload](#queries-that-best-suit-your-workload) + Learn how to optimize update queries coming at the database. + +- [Memgraph ecosystem](#memgraph-ecosystem) + Memgraph offers native streaming support for Apache Kafka and Apache Pulsar. + +## Choosing the right Memgraph flag set +blahblah + +## Choosing the right Memgraph storage mode +blahblah + +## Importing mechanisms +blahblah + +## Enterprise features you might require +blahblah + +## Queries that best suit your workload +blahblah + +## Memgraph ecosystem +blahblah + + diff --git a/public/pages/memgraph-in-production/benchmarking-memgraph/realistic-workload.png b/public/pages/memgraph-in-production/benchmarking-memgraph/realistic-workload.png new file mode 100644 index 0000000000000000000000000000000000000000..58bf7d8cabfac4b0c49bcdb84f4ae6802cea8f46 GIT binary patch literal 122388 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 z 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 Jk~Mtx{{Rg^@GAfS literal 0 HcmV?d00001 From b07865d402b9c11b896f422c8e637fb38b626fe6 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 17:06:52 +0200 Subject: [PATCH 45/54] Edit titles --- .../memgraph-in-high-throughput-workloads.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index 18b1f63ca..3d31688f1 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -1,12 +1,12 @@ --- -title: Memgraph in GraphRAG use cases -description: Suggestions on how to bring your Memgraph to production in GraphRAG use cases. +title: Memgraph in high-throughput workloads +description: Suggestions on how to bring your Memgraph to production in high-throughput workloads. --- import { Callout } from 'nextra/components' import { CommunityLinks } from '/components/social-card/CommunityLinks' -# Memgraph in GraphRAG use cases +# Memgraph in high-throughput workloads 👉 **Start here first** From be537746eec5308ce6ca6c4d2bf60ff96721d6d3 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Fri, 25 Apr 2025 17:20:22 +0200 Subject: [PATCH 46/54] Add guidelines for updating the page --- .../memgraph-in-high-throughput-workloads.mdx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index 3d31688f1..6d8a95451 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -99,21 +99,25 @@ additional best practices tailored for performance, stability, and scalability i Memgraph offers native streaming support for Apache Kafka and Apache Pulsar. ## Choosing the right Memgraph flag set -blahblah +--storage-delta-on-identical-property-update=false ## Choosing the right Memgraph storage mode -blahblah +analytical if they just do updates and appends ## Importing mechanisms -blahblah +kafka native, kafka manual, write-write conflict resolution with retries +idempotent concept ## Enterprise features you might require -blahblah +high-availability +node and edge ttl +multitenancy ## Queries that best suit your workload -blahblah +easy updates + ## Memgraph ecosystem -blahblah +native kafka consumers From e9ce4ce023bf792eb694da914ea0aa7aa7997ab7 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Sat, 26 Apr 2025 17:56:26 +0200 Subject: [PATCH 47/54] Finish guide for high throughput workloads --- .../memgraph-in-high-throughput-workloads.mdx | 140 ++++++++++++++++-- 1 file changed, 128 insertions(+), 12 deletions(-) diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index 6d8a95451..a213a989b 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -95,29 +95,145 @@ additional best practices tailored for performance, stability, and scalability i - [Queries that best suit your workload](#queries-that-best-suit-your-workload) Learn how to optimize update queries coming at the database. -- [Memgraph ecosystem](#memgraph-ecosystem) - Memgraph offers native streaming support for Apache Kafka and Apache Pulsar. - ## Choosing the right Memgraph flag set + +When streaming data from systems like Kafka, the incoming payload is often **standardized**, +meaning that even when a node or relationship is updated, **some property values might remain unchanged**. + +By default, Memgraph sets the flag `--storage-delta-on-identical-property-update=true`, which **updates all properties** +of a node or relationship during an update, even if the new value is identical to the existing one. +This can introduce unnecessary write overhead. + +To optimize for **higher throughput** in scenarios where most incoming updates do not change all property values, +it’s recommended to set: + +```bash --storage-delta-on-identical-property-update=false +``` + +With this setting, Memgraph will **only create delta records for properties that have actually changed**, +reducing internal write operations and improving overall system throughput—especially important in high-velocity +streaming use cases. ## Choosing the right Memgraph storage mode -analytical if they just do updates and appends + +High-throughput scenarios in Memgraph can run effectively on both `IN_MEMORY_TRANSACTIONAL` and `IN_MEMORY_ANALYTICAL` +storage modes, depending on your specific workload requirements. + +If your workload meets the following conditions: + +- You are **updating properties** on existing nodes and relationships, +- You are **appending** new nodes and relationships to the graph, +- You are **not performing deletes**, + +then it may be worth considering switching to `IN_MEMORY_ANALYTICAL` mode. +This mode allows **writes to be multithreaded**, unlocking **near limitless write speeds** by parallelizing ingestion +across CPU cores. + +However, keep in mind: + +- If you require **replication**, **high availability**, or **ACID guarantees**, you must use `IN_MEMORY_TRANSACTIONAL` mode. +- `IN_MEMORY_ANALYTICAL` is optimized for **bulk ingestion** and **read-only analytics**, but it + **does not support transactional rollback**, as it doesn’t create delta objects during writes. + Additionally, **WALs (write-ahead logs) are not generated** in this mode, meaning recovery relies solely on **snapshot creation**. ## Importing mechanisms -kafka native, kafka manual, write-write conflict resolution with retries -idempotent concept + +Memgraph natively supports **Apache Kafka** and **Apache Pulsar** consumers to directly ingest data streams +([learn more here](/data-streams#create-a-stream)). However, depending on the system architecture and flexibility +requirements, some users prefer building **custom Kafka consumers** that programmatically push data into Memgraph. + +### Handling write-write conflicts +If you are ingesting data from **multiple topics** or multiple producers, it's important to +**handle potential write-write conflicts**—especially when running in `IN_MEMORY_TRANSACTIONAL` mode. +You can learn more about these scenarios in our +[conflicting transactions documentation](/help-center/errors/transactions#conflicting-transactions). + +To safely manage these conflicts: + +- Use a driver that supports **managed transactions** with automatic retries. For example, **Neo4j drivers** + offer an `execute_write()` method that **automatically retries transient errors** (errors that can be safely retried without client-side intervention). +- Wrap your **write operations inside retryable transactions** if there are **multiple concurrent writers**. +- If you **only have a single writer**, you generally don’t need to worry about transient errors or retries. + +### Idempotency concept +In addition, when working with streams, **unexpected errors may require replaying the stream**. +To ensure **data consistency even after multiple replays**, it's crucial to make your ingestion logic **idempotent**. +We strongly recommend using **`MERGE` clauses** instead of `CREATE` when inserting data into Memgraph. +`MERGE` ensures that nodes and relationships are **created only if they don't already exist**, preventing +duplicates and keeping your graph clean no matter how many times the same event is replayed. ## Enterprise features you might require -high-availability -node and edge ttl -multitenancy + +For production-grade high-throughput deployments, you may need advanced capabilities to ensure +**availability**, **data management**, and **tenant isolation**. Memgraph offers several enterprise features +designed to support these needs: + +- **Replication, high availability, and automatic failover** + If you require your system to be **available at all times**, Memgraph supports + [clustering and high availability](/clustering/high-availability), allowing you to minimize downtime and recover + automatically from failures. + +- **Node and relationship TTL (time-to-live)** + In high-ingestion environments, you may need to automatically **clean up stale data** after a certain retention period. + Memgraph supports [time-to-live (TTL)](/querying/time-to-live) mechanisms for both **nodes** and **relationships**, + ensuring your graph remains manageable and efficient over time. + +- **Multi-tenancy** + Some workloads require a **separate graph database per customer** to ensure strict data isolation and security. + Memgraph supports [multi-tenancy](/database-management/multi-tenancy), enabling you to manage multiple independent + graphs within a single Memgraph instance. ## Queries that best suit your workload -easy updates +When ingesting data from streams, it's best to keep your Cypher queries **simple, idempotent, and efficient**. A typical ingestion query should look like: + +```cypher +MERGE (n:Label) SET n += $row; +``` + +This approach ensures **idempotency** (safe reprocessing of the same events) and **minimizes query execution time** by keeping the transaction lightweight. +Keep in mind that adding **complex business logic** or **customization** to the ingestion queries will **increase query latency**, so it's always a good practice to **profile your queries** using Memgraph’s [`PROFILE` tool](/querying/clauses/profile) to understand and optimize performance. + +--- + +### Dynamic labels and edge types + +Memgraph also supports: + +- [**Dynamic node label creation**](/querying/clauses/create#14-creating-node-labels-dynamically) +- [**Dynamic relationship type creation**](/querying/clauses/create#23-creating-relationship-types-dynamically) + +However, **dynamic creation is only supported with `CREATE` operations**, and **matching or merging dynamically created +labels and types is not supported**. + +If your payload contains **dynamic labels or edge types** and you still need **idempotency**, you have two options: + +- **Programmatically construct your Cypher query strings** based on the payload to ensure correct label/type usage before sending the query to Memgraph. +- **Optionally use the `merge` procedure from MAGE** ([MAGE Merge documentation](/advanced-algorithms/available-algorithms/merge)) + if you want a built-in helper. + + +Note: While MAGE procedures are **written in C++ and highly optimized**, they still introduce **slightly more overhead** +compared to **pure Cypher**, as they are executed as external modules. We recommend favoring pure Cypher when +possible for the **highest performance**. + + +### Using `convert.str2object` for parsing nested properties + +When working with streamed data, sometimes your incoming payload contains **serialized JSON strings** that need to be +transformed into property maps inside your graph. +Memgraph provides the [`convert.str2object` function](/querying/functions#conversion-functions) to easily handle this scenario. + +Example usage: + +```cypher +WITH convert.str2object('{"name":"Alice", "age":30}') AS props +MERGE (n:Person) +SET n += props; +``` -## Memgraph ecosystem -native kafka consumers +This function **parses a JSON-formatted string into a Cypher map**, making it very useful for flexible ingestion pipelines +where the payload structure might vary slightly or be semi-structured. From 7c5fc5af28b780a6b7089a3d7b5aafe93ace65ef Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Sat, 26 Apr 2025 18:00:15 +0200 Subject: [PATCH 48/54] Omit constructed guide --- pages/memgraph-in-production.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index d7283e945..ebf97e208 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -54,7 +54,6 @@ Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems - Memgraph in transactional workloads - Memgraph in analytical workloads - Memgraph in mission critical workloads -- Memgraph in high throughput workloads - Memgraph in supply chain use cases - Memgraph in cyber security use cases - Memgraph in fraud detection use cases From bdc95141011d00c260b8d43e99f3796cc21cf8ca Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Sat, 26 Apr 2025 18:05:35 +0200 Subject: [PATCH 49/54] Add initial mission critical page --- pages/memgraph-in-production.mdx | 3 + pages/memgraph-in-production/_meta.ts | 1 + ...memgraph-in-mission-critical-workloads.mdx | 158 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index ebf97e208..ca0201b38 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -47,6 +47,9 @@ A foundational guide covering universal best practices for any production deploy ### [Memgraph in high-throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) Scale your write throughput while keeping up with fast-changing, high-velocity graph data. +### [Memgraph in mission critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) +Suggestions on how to bring your Memgraph to production in mission-critical and high-availability workloads. + ### [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems using graph data. diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index cfa34ea35..fec2dda4e 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -3,4 +3,5 @@ export default { "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", "benchmarking-memgraph": "Benchmarking Memgraph", "memgraph-in-high-throughput-workloads": "Memgraph in high-throughput workloads", + "memgraph-in-mission-critical-workloads": "Memgraph in mission critical workloads" } diff --git a/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx new file mode 100644 index 000000000..0ca421c73 --- /dev/null +++ b/pages/memgraph-in-production/memgraph-in-mission-critical-workloads.mdx @@ -0,0 +1,158 @@ +--- +title: Memgraph in mission-critical workloads +description: Suggestions on how to bring your Memgraph to production in mission-critical and high-availability workloads. +--- + +import { Callout } from 'nextra/components' +import { CommunityLinks } from '/components/social-card/CommunityLinks' + +# Memgraph in mission-critical workloads + + +👉 **Start here first** +Before diving into this guide, we recommend starting with the [**General suggestions**](/memgraph-in-production/general-suggestions) +page. It provides **foundational, use-case-agnostic advice** for deploying Memgraph in production. + +This guide builds on that foundation, offering **additional recommendations tailored to critical and high-availability workloads**. +In cases where guidance overlaps, consider the information here as **complementary or overriding**, depending +on the unique needs of your use case. + + +## Is this guide for you? + +This guide is for you if you're building **mission-critical systems** where uptime, data consistency, and fault tolerance are essential. +You’ll benefit from this content if: + +- đŸ›Ąïž You require **high availability** and **automatic failover** for your application. +- 🔒 You need **strong consistency guarantees** even under heavy loads. +- 🔁 You must **recover gracefully** from unexpected failures without data loss. +- 🏱 You need to **support multi-tenant environments** securely across multiple projects or customers. + +If this matches your needs, this guide will help you configure and operate Memgraph to meet the demands of **always-on production environments**. + +## Why choose Memgraph for mission-critical use cases? + +When stability, consistency, and resilience matter most, Memgraph is built to deliver. Here's why Memgraph is a great fit for mission-critical workloads: + +- **In-memory storage engine with persistence** + Memgraph keeps the working set fully in memory for fast access, while ensuring **durability** through **periodic snapshots** and **write-ahead logging (WALs)** in transactional mode. + +- **High availability with automatic failover** + Memgraph supports full [**high availability clustering**](/clustering/high-availability), allowing you to deploy **multiple instances** with automatic **leader election** and **failover** when needed. + +- **Multi-version concurrency control (MVCC)** + Built on **MVCC**, Memgraph allows **non-blocking reads and writes**, ensuring that your system remains **responsive** even under concurrent access. + +- **Snapshot isolation by default** + Memgraph uses **snapshot isolation** instead of **read-committed** isolation, preventing dirty reads and guaranteeing a **consistent view** of the graph at all times. + +- **Replication for read scaling and redundancy** + Memgraph supports **asynchronous replication**, enabling you to **scale read workloads** independently while ensuring **failover readiness**. + +- **Fine-grained access control and security** + Secure your system with [**role-based access control**](/database-management/authentication-and-authorization/role-based-access-control) and [**label-based access control**](/database-management/authentication-and-authorization/role-based-access-control#label-based-access-control) to ensure only the right users see and manipulate data. + +--- + +## What is covered? + +The suggestions for mission-critical workloads **complement** several key sections in the +[general suggestions guide](/memgraph-in-production/general-suggestions), with additional best practices to ensure uptime and data protection: + +- [Choosing the right Memgraph flag set](#choosing-the-right-memgraph-flag-set) + Memgraph offers flags to enhance recovery, snapshot management, and failover capabilities. + +- [Choosing the right Memgraph storage mode](#choosing-the-right-memgraph-storage-mode) + Guidance on selecting the **safest** and **most consistent** storage configurations. + +- [Enterprise features you might require](#enterprise-features-you-might-require) + Overview of **replication**, **multi-tenancy**, and **automatic failover** tools that are critical in production. + +- [Backup and recovery mechanisms](#backup-and-recovery-mechanisms) + Best practices to protect your data through snapshots, WALs, and external backup strategies. + +- [Queries that best suit your workload](#queries-that-best-suit-your-workload) + Designing queries that maintain consistent, safe, and predictable behavior in high-availability systems. + +--- + +## Choosing the right Memgraph flag set + +For mission-critical setups, you should configure Memgraph to optimize for **durability, fast recovery**, and **stability**. Some important flags include: + +- `--storage-snapshot-interval-sec=x` + Set how often snapshots are created. In mission-critical systems, you may want **frequent snapshots** to minimize recovery time. + +- `--storage-wal-enabled=true` + Ensure **WALs (write-ahead logs)** are enabled to protect all transactions between snapshots. + +- `--storage-parallel-schema-recovery=true` and `--storage-recovery-thread-count=x` + Enable **parallel recovery** to speed up startup time after a crash by using multiple cores. + +- `--query-execution-timeout-sec=x` + Set reasonable query timeouts to **avoid stuck queries** and prevent resource exhaustion. + +--- + +## Choosing the right Memgraph storage mode + +For mission-critical deployments: + +- Always use `IN_MEMORY_TRANSACTIONAL` mode. +- This mode provides **full ACID guarantees**, **WAL support**, and **snapshot consistency**. + +> `IN_MEMORY_ANALYTICAL` is optimized for high-speed ingestion but does **not provide transactional durability**. It is not recommended for mission-critical workloads. + +--- + +## Enterprise features you might require + +For robust production environments, consider enabling: + +- **High availability clustering** ([learn more](/clustering/high-availability)) + Deploy multiple Memgraph instances with automatic leader election and failover. + +- **Replication for resilience** + Distribute replicas geographically or across availability zones to minimize the risk of localized outages. + +- **Role-based and label-based access control** + Protect sensitive graph data and ensure only authorized operations are performed. + +- **Multi-tenancy** ([learn more](/database-management/multi-tenancy)) + Securely isolate data and permissions between different teams, projects, or customers. + +--- + +## Backup and recovery mechanisms + +Data durability is critical in mission-critical environments. Memgraph supports: + +- **Snapshots** + Automatically or manually triggered full-database snapshots. + +- **Write-ahead logging (WALs)** + Transaction logs that enable you to **replay changes** made after the last snapshot. + +- **Manual backup and offloading** + Use external tools (like `rclone`) to back up snapshots and WALs to **cloud storage** or **remote servers** for additional redundancy. + +> **Tip:** Memgraph currently does not automate external backups, so integrating a backup process into your system is highly recommended. + +--- + +## Queries that best suit your workload + +In mission-critical workloads: + +- Prefer **idempotent writes** (`MERGE`) to avoid inconsistent state during retries. +- Optimize long-running queries and **profile** them regularly ([learn more](/querying/clauses/profile)). +- Avoid complex, unpredictable queries inside critical transactional paths. +- Use **schema constraints** and **indexes** wisely to enforce data integrity without hurting performance. + +Example of safe, idempotent data ingestion: + +```cypher +MERGE (n:Customer {id: $id}) +SET n += $props; +``` + From c43bcd05d9bdc7b0a30033b4afb27983a756951b Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Sat, 26 Apr 2025 18:07:45 +0200 Subject: [PATCH 50/54] Omit guide being constructed --- pages/memgraph-in-production.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index ca0201b38..a72cd79ad 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -56,7 +56,6 @@ Learn how to optimize Memgraph for Retrieval-Augmented Generation (RAG) systems ## 🚧 Guides in construction - Memgraph in transactional workloads - Memgraph in analytical workloads -- Memgraph in mission critical workloads - Memgraph in supply chain use cases - Memgraph in cyber security use cases - Memgraph in fraud detection use cases From f6150f72a5bae0a3705df22f0c0f9ffc533dcc53 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Mon, 28 Apr 2025 10:03:39 +0200 Subject: [PATCH 51/54] Dash --- pages/memgraph-in-production.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index a72cd79ad..42e2c0cc2 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -47,7 +47,7 @@ A foundational guide covering universal best practices for any production deploy ### [Memgraph in high-throughput workloads](/memgraph-in-production/memgraph-in-high-throughput-workloads) Scale your write throughput while keeping up with fast-changing, high-velocity graph data. -### [Memgraph in mission critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) +### [Memgraph in mission-critical workloads](/memgraph-in-production/memgraph-in-mission-critical-workloads) Suggestions on how to bring your Memgraph to production in mission-critical and high-availability workloads. ### [Memgraph in GraphRAG use cases](/memgraph-in-production/memgraph-in-graphrag) From 0ef77f490d1addb45557021d079342d32f7a4760 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Mon, 28 Apr 2025 15:38:57 +0200 Subject: [PATCH 52/54] Address PR commnets --- pages/memgraph-in-production/_meta.ts | 2 +- .../memgraph-in-high-throughput-workloads.mdx | 55 +++++++++---------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/pages/memgraph-in-production/_meta.ts b/pages/memgraph-in-production/_meta.ts index cfa34ea35..e85f75f70 100644 --- a/pages/memgraph-in-production/_meta.ts +++ b/pages/memgraph-in-production/_meta.ts @@ -1,6 +1,6 @@ export default { "general-suggestions": "General suggestions", "memgraph-in-graphrag": "Memgraph in GraphRAG use cases", - "benchmarking-memgraph": "Benchmarking Memgraph", "memgraph-in-high-throughput-workloads": "Memgraph in high-throughput workloads", + "benchmarking-memgraph": "Benchmarking Memgraph", } diff --git a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx index a213a989b..e346830b7 100644 --- a/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx +++ b/pages/memgraph-in-production/memgraph-in-high-throughput-workloads.mdx @@ -38,38 +38,34 @@ Memgraph provides the performance and architecture needed to keep up—without c Here's why Memgraph is a great fit for high-throughput use cases: -- **In-memory storage engine** - Memgraph doesn't need to write to disk on every transaction, enabling it to **scale write throughput far beyond traditional - disk-based databases**. Unlike systems that rely on LRU or OS-level caching—where **cache invalidation can degrade - read performance during heavy writes**, Memgraph offers **predictable read latency** even under constant data changes. +- **In-memory storage engine**: Memgraph operates entirely in-memory, eliminating the need to write to disk on every transaction. + This allows it to **scale write throughput far beyond traditional disk-based databases**. Unlike systems that rely on LRU + or OS-level caching, where **cache invalidation can degrade read performance during heavy writes**, Memgraph offers + **predictable read latency** even under constant data changes. - While many graph databases **max out around 1,000 writes per second**, Memgraph can handle **up to 50x more** (image below), - making it ideal for **high-velocity, write-intensive workloads**. + While many graph databases **max out around 1,000 writes per second**, Memgraph can handle **up to 50x more** + (see image below), making it ideal for **high-velocity, write-intensive workloads**. ![](/pages/memgraph-in-production/benchmarking-memgraph/realistic-workload.png) -- **Non-blocking reads and writes with MVCC** - Built on **multi-version concurrency control (MVCC)**, Memgraph ensures that **writes don’t block reads** and - **reads don’t block writes**, allowing each to scale independently. +- **Non-blocking reads and writes with MVCC**: Built on multi-version concurrency control (MVCC), + Memgraph ensures that **writes don’t block reads** and **reads don’t block writes**, allowing each to scale independently. -- **Fine-grained locking** - Locking happens at the **node and relationship level**, enabling **highly concurrent writes** and minimizing - contention across threads. +- **Fine-grained locking** : Locking happens at the node and relationship level, enabling **highly concurrent writes** + and minimizing contention across threads. -- **Lock-free skiplist storage** - Memgraph uses **lock-free, concurrent skiplist structures** for storing nodes, relationships, and indices, leading to - faster data access and minimal coordination overhead between threads. +- **Lock-free skiplist storage**: Memgraph uses **lock-free, concurrent skip list structures** for storing nodes, + relationships, and indices, leading to faster data access and minimal coordination overhead between threads. -- **Snapshot isolation by default** - Unlike many databases that rely on **read-committed** isolation (which can return inconsistent data), Memgraph provides - **snapshot isolation**, ensuring data accuracy and consistency in real-time queries. +- **Snapshot isolation by default**: Unlike many databases that rely on **read-committed** isolation + (which can return inconsistent data), Memgraph provides **snapshot isolation**, ensuring data accuracy and + consistency in real-time queries. -- **Inter-query parallelization** - Each read and write query is handled on its own CPU core, meaning Memgraph can **scale horizontally on a - single machine** based on your hardware. +- **Inter-query parallelization**: Each read and write query is handled on its own CPU core, meaning Memgraph can + **scale horizontally on a single machine** based on your hardware. -- **Horizontal read scaling with high availability** - Memgraph supports **replication and high availability**, allowing you to distribute **read traffic across multiple replicas**. +- **Horizontal read scaling with high availability**: Memgraph supports [replication](/clustering/replication) and + [high availability](/clustering/high-availability), allowing you to distribute **read traffic across multiple replicas**. These replicas can also power **secondary workloads** like GraphRAG, analytics, or ML pipelines, **without affecting the performance of the main write-intensive instance**. @@ -137,10 +133,12 @@ However, keep in mind: **does not support transactional rollback**, as it doesn’t create delta objects during writes. Additionally, **WALs (write-ahead logs) are not generated** in this mode, meaning recovery relies solely on **snapshot creation**. +Learn more about [storage modes](/fundamentals/storage-memory-usage#storage-modes) in our documentation. + ## Importing mechanisms -Memgraph natively supports **Apache Kafka** and **Apache Pulsar** consumers to directly ingest data streams -([learn more here](/data-streams#create-a-stream)). However, depending on the system architecture and flexibility +Memgraph natively supports **Apache Kafka** and **Apache Pulsar** consumers to directly ingest +[data streams](/data-streams). However, depending on the system architecture and flexibility requirements, some users prefer building **custom Kafka consumers** that programmatically push data into Memgraph. ### Handling write-write conflicts @@ -201,8 +199,8 @@ Keep in mind that adding **complex business logic** or **customization** to the Memgraph also supports: -- [**Dynamic node label creation**](/querying/clauses/create#14-creating-node-labels-dynamically) -- [**Dynamic relationship type creation**](/querying/clauses/create#23-creating-relationship-types-dynamically) +- [Dynamic node label creation](/querying/clauses/create#14-creating-node-labels-dynamically) +- [Dynamic relationship type creation](/querying/clauses/create#23-creating-relationship-types-dynamically) However, **dynamic creation is only supported with `CREATE` operations**, and **matching or merging dynamically created labels and types is not supported**. @@ -210,8 +208,7 @@ labels and types is not supported**. If your payload contains **dynamic labels or edge types** and you still need **idempotency**, you have two options: - **Programmatically construct your Cypher query strings** based on the payload to ensure correct label/type usage before sending the query to Memgraph. -- **Optionally use the `merge` procedure from MAGE** ([MAGE Merge documentation](/advanced-algorithms/available-algorithms/merge)) - if you want a built-in helper. +- **Optionally use the [`merge`](/advanced-algorithms/available-algorithms/merge) procedure from MAGE** Note: While MAGE procedures are **written in C++ and highly optimized**, they still introduce **slightly more overhead** From fc5fbbc0b8fa1c0f1b487b22a8eb9d58473f590e Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Mon, 28 Apr 2025 15:42:30 +0200 Subject: [PATCH 53/54] Push memgraph up --- pages/_meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/_meta.ts b/pages/_meta.ts index c567b9fb6..742e659ef 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -13,8 +13,8 @@ export default { "database-management": "Database management", "deployment": "Deployment", "clustering": "Clustering", + "memgraph-in-production": "Memgraph in production", "data-streams": "Data streams", "help-center": "Help center", "release-notes": "Release notes", - "memgraph-in-production": "Memgraph in production" } From 17d25a8fdad27dc933554346f91b1b401fdd5bf5 Mon Sep 17 00:00:00 2001 From: Josip Mrden Date: Mon, 28 Apr 2025 15:43:25 +0200 Subject: [PATCH 54/54] Fix link for benchmarking Memgraph --- pages/memgraph-in-production.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/memgraph-in-production.mdx b/pages/memgraph-in-production.mdx index ebf97e208..c1f19e5e9 100644 --- a/pages/memgraph-in-production.mdx +++ b/pages/memgraph-in-production.mdx @@ -72,7 +72,7 @@ smooth transition to production. These guides focus on areas like performance benchmarking, testing, and operational readiness—offering additional tools and frameworks that can help you get the most out of your Memgraph deployment. -### [📊 Evaluating Memgraph](/memgraph-in-production/evaluating-memgraph) +### [📊 Benchmarking Memgraph](/memgraph-in-production/benchmarking-memgraph) Learn how to properly **test Memgraph for performance and scalability**. This guide walks you through performance and stress testing scenarios, benchmarking with real-world data, and identifying key metrics that can help validate Memgraph’s fit for your application needs.