{"id":26976119,"url":"https://github.com/alexandrughinea/rust-actix-postgres-multi-tenant","last_synced_at":"2025-07-20T20:37:03.079Z","repository":{"id":272793575,"uuid":"875035596","full_name":"alexandrughinea/rust-actix-postgres-multi-tenant","owner":"alexandrughinea","description":"A multi-threaded(real), multi-tenancy setup written in Rust with ARC and PostgreSQL (complete with results)","archived":false,"fork":false,"pushed_at":"2025-02-03T18:49:05.000Z","size":281,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-03T11:40:18.181Z","etag":null,"topics":["postgresql","rust","sqlx"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexandrughinea.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-19T00:17:48.000Z","updated_at":"2025-02-05T16:36:09.000Z","dependencies_parsed_at":"2025-01-16T17:47:20.202Z","dependency_job_id":"3e75d57f-64fc-489a-950b-bb42490c0419","html_url":"https://github.com/alexandrughinea/rust-actix-postgres-multi-tenant","commit_stats":null,"previous_names":["alexandrughinea/rust-actix-postgres-multi-tenant"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/alexandrughinea/rust-actix-postgres-multi-tenant","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Frust-actix-postgres-multi-tenant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Frust-actix-postgres-multi-tenant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Frust-actix-postgres-multi-tenant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Frust-actix-postgres-multi-tenant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexandrughinea","download_url":"https://codeload.github.com/alexandrughinea/rust-actix-postgres-multi-tenant/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Frust-actix-postgres-multi-tenant/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266195237,"owners_count":23891158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["postgresql","rust","sqlx"],"created_at":"2025-04-03T11:35:48.483Z","updated_at":"2025-07-20T20:37:03.033Z","avatar_url":"https://github.com/alexandrughinea.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Multi-Tenant Architecture template written in Rust for PostgreSQL\nMulti-threaded DB pool lifecycle management implementation is available in the source provided.\n\n[![Rust](https://github.com/alexandrughinea/rust-actix-postgres-multi-tenant/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/alexandrughinea/rust-actix-postgres-multi-tenant/actions/workflows/rust.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nThis project implements a blazingly fast (yes, really fast 🔥) multi-tenant connection pooling strategy for PostgreSQL using Rust.\nIt dynamically manages database connection pools for tenants based on their unique identifiers (UUIDs).\nThe architecture leverages the use of `Arc` (Atomic Reference Counting), `Mutex`, and SQLx to ensure efficient, safe, and scalable connection management in a multi-threaded environment.\n\n## Table of Contents\n- [Overview](#overview)\n- [Architecture](#architecture)\n- [Components](#components)\n- [How it Works](#how-it-works)\n- [Benefits](#benefits)\n- [Documentation](#documentation)\n- [Disclaimer](#disclaimer)\n\n## Overview\n\nIn a multi-tenant application, each tenant often requires its own isolated database or schema.\nManaging connections to these tenants in an efficient way is extremely critical for performance and resource re-utilization.\nThis project provides one way to handle tenant-specific database connections using connection pools that are cached and reused, ensuring optimized resource management.\nThe main database we're working with is going to be PostgreSQL and it assumes that RLS (Row Level Security) is enabled as a security policy for each connecting tenant as the following diagram:\n\n```mermaid\nflowchart LR\n    subgraph Clients\n        A[Client from Tenant A]\n        B[Client from Tenant B]\n        C[Client from Tenant C]\n    end\n\n    subgraph Application\n        D[Tenant A context]\n        E[Tenant B context]\n        F[Tenant C context]\n    end\n\n    subgraph DB_Connections\n        G[DB connection for Tenant A]\n        H[DB connection for Tenant B]\n        I[DB connection for Tenant C]\n    end\n\n    subgraph Database\n        J[row data for Tenant A]\n        K[row data for Tenant B]\n        L[row data for Tenant C]\n    end\n\n    A --\u003e D\n    B --\u003e E\n    C --\u003e F\n    D --\u003e G\n    E --\u003e H\n    F --\u003e I\n    G --\u003e J\n    H --\u003e K\n    I --\u003e L\n```\nEach tenant role has its own credentials stored and encrypted (not hashed) at rest with an AES256 GCM algorithm.\nThe encryption key is not part of the app binary and is configured per environment.\n\nThe architecture supports:\n- **Tenant-specific connection pooling**: A separate connection pool for each tenant, ensuring a good level of isolation between tenants.\n- **Efficient reuse of connections**: Pools are cached and reused to avoid repeatedly creating and tearing down connections.\n- **Pool eviction strategy**: Pools are evicted and cleaned up if they become stale by an automatic background task.\n- **Thread-safe management**: Using `Arc` and `Mutex` to ensure that connection pools can be accessed and modified safely in a multi-threaded environment, without cloning them.\n\n## Architecture\n\n### Components\n\n- **`AppState`**: A global state shared across the application that holds all tenant connection pools in a thread-safe manner using a `Arc\u003cMutex\u003cHashMap\u003cUuid, Arc\u003cMutex\u003cTenantPool\u003e\u003e\u003e\u003e\u003e`. This allows tenant-specific pools to be fetched, inserted, and updated safely. Each tenant can have their own credentials, and these pools are isolated from one another.\n\n- **`TenantPool`**: A struct representing the connection pool for a specific tenant, along with some metadata such as the last time it was accessed. This metadata helps with our eviction policies if needed.\n\n- **PostgreSQL Pool (`PgPool`)**: The principal pool of database connections which initializes once with the application.\n\n### Diagram\n\n```mermaid\nclassDiagram\n    class AppState {\n        +Arc\u003cMutex\u003cHashMap\u003cUuid, Arc\u003cMutex\u003cTenantPool\u003e\u003e\u003e\u003e\u003e pools\n    }\n    class TenantPool {\n        +Arc\u003cPgPool\u003e pool\n        +DateTime\u003cUtc\u003e last_accessed\n    }\n    class Tenant {\n        +Option\u003cUuid\u003e id\n        +String name\n        +String db_user\n        -Option\u003cString\u003e db_password_encrypted\n        +Option\u003cDateTime\u003cUtc\u003e\u003e created_at\n        +Option\u003cDateTime\u003cUtc\u003e\u003e updated_at\n    }\n    class TenantCredentials {\n        +String db_user\n        -String db_password\n    }\n    class User {\n        +Option\u003cUuid\u003e id\n        -Option\u003cUuid\u003e tenant_id\n        +String name\n        +Option\u003cDateTime\u003cUtc\u003e\u003e created_at\n        +Option\u003cDateTime\u003cUtc\u003e\u003e updated_at\n    }\n    class PgPool {\n        +get_connection() Connection\n    }\n\n    AppState \"1\" *-- \"*\" TenantPool : contains\n    TenantPool --\u003e \"1\" PgPool : contains reference to\n    Tenant \"1\" -- \"1\" TenantCredentials : uses\n    Tenant \"1\" -- \"*\" User : has\n\n    %% Notes\n    note for AppState \"Shared state with thread-safe access to tenant pools\"\n    note for TenantPool \"Represents a single tenant's database connection pool\"\n    note for Tenant \"Tenant information including database credentials\"\n    note for TenantCredentials \"Separate struct for tenant's database credentials\"\n    note for User \"User information associated with a tenant\"\n    note for PgPool \"Actual database connection pool (from external crate)\"\n```\n\n## How it Works\n\n1. **Request Handling**:\n   - When a request is made on behalf of a tenant, the `tenant_id` (UUID) is extracted from the request.\n\n2. **Connection Pool Lookup**:\n   - The system checks if there is an existing connection pool for that tenant in the `AppState`. The pool is stored in a thread-safe `HashMap`, with the `tenant_id` as the key and an `Arc\u003cMutex\u003cTenantPool\u003e\u003e` as the value.\n\n3. **Pool Creation** (if not found):\n   - If no connection pool is found for the tenant, the system fetches tenant-specific database credentials and creates a new `PgPool`.\n   - The `PgPool` is stored inside a `TenantPool` struct, along with the `last_accessed` timestamp, and the entire `TenantPool` is stored inside an `Arc\u003cMutex\u003c_\u003e\u003e` for safe access.\n\n4. **Safe Access and Mutation**:\n   - When accessing or modifying a `TenantPool`, the system uses `Mutex` locks to ensure safe concurrent access.\n   - The `last_accessed` field is updated whenever the tenant’s pool is retrieved, allowing optimizations such as evicting idle pools.\n\n5. **Reusing Pools**:\n   - Once a pool is created for a tenant, subsequent requests from that tenant reuse the same pool, avoiding the overhead of creating new connections repeatedly.\n\n## Benefits\n\n### 1. **Efficient Connection Management**:\n- The architecture ensures that connections to tenant databases are pooled and reused. Each tenant gets its own `PgPool`, and the pool is created only when needed, reducing overhead.\n\n### 2. **Scalability**:\n- By using `Arc` to share ownership and `Mutex` for thread-safe access, this system can scale across multiple threads, allowing safe concurrent requests to access and modify tenant pools.\n\n### 3. **Optimized Resource Usage**:\n- The architecture reduces the cost of frequently opening and closing database connections by keeping idle connections around for a specified amount of time (configurable using the `idle_timeout` setting).\n\n### 4. **Isolation Between Tenants**:\n- Each tenant’s database pool is isolated, ensuring that tenants don’t interfere with one another’s connections. This is crucial for maintaining security and data integrity in multi-tenant systems.\n\n### 5. **Cache Management**:\n- The `last_accessed` timestamp for each pool allows the system to track activity and potentially implement strategies like idle pool eviction or pool timeouts, further improving resource management.\n\n## Documentation\n\nFor extra info you should really check out the `documentation/` directory.\nI have left some resources that I think could bring even more insight.\n\nIt is split in three main sections:\n- Stress test results for custom and default pool settings: [stress test results](./documentation/stress_test)\n- SQL migration steps with comments:  [SQL docs](./documentation/sql)\n- [Bruno](https://github.com/usebruno/bruno) API collections: [Bruno templates](./documentation/bruno)\n\n### Stress test result example\n\n#### Result format (jmx output) [512GB_1vCPU_250_CT_summary](./documentation/stress_test/default_pool_options/512GB_1vCPU_250_CT_summary.csv):\n\n| Label                    | # Samples | Average | Min | Max  | Std. Dev. | Error % | Throughput | Received KB/sec | Sent KB/sec | Avg. Bytes |\n|--------------------------|-----------|---------|-----|------|-----------|---------|------------|----------------|-------------|------------|\n| Thread Group:HTTP Request | 5000      | 828     | 83  | 3326 | 604.89    | 0.000%  | 98.33035   | 260.61         | 25.45       | 2714.0     |\n| TOTAL                    | 5000      | 828     | 83  | 3326 | 604.89    | 0.000%  | 98.33035   | 260.61         | 25.45       | 2714.0     |\n\n#### Result graph - Resource Utilization vs Load (1vCPU, 512MB RAM) - Default Pool:\n\u003cimg src=\"./documentation/stress_test/default_pool_options/result-resource-utilization.svg\" alt=\"Resource Utilization vs Load (1vCPU, 512MB RAM) - Default Pool\" width=\"650px\"\u003e\n\n## Usage\n\n### Prerequisites\n- Rust\n- PostgreSQL\n- SQLx with PostgreSQL feature\n\n### Setting up the Project\n\n1. Clone the repository:\n   ```bash\n   git clone \u003crepository-url\u003e\n   ```\n2.\tConfigure your own PostgreSQL connection settings for your tenants.\n      Look for .env and enable it accordingly.\n\n3. Seed your `tenants` table accordingly (see `/sql` directory)\n\n4. Making tenant specific requests\n\n- `POST` request to create a user for a specific tenant:\n  ```bash\n  curl -X POST https://localhost:8080/v1/internal/users \\\n       -H \"x-tenant-id: 1b7db059-91bc-42d6-9d0e-93ca03d644a8\" \\\n       -H \"Content-Type: application/json\" \\\n       -d '{\n             \"name\": \"John Doe\",\n           }'\n  ```\n\n- `GET` request to retrieve users of a specific tenant:\n  ```bash\n  curl -X GET https://localhost:8080/v1/internal/users \\\n     -H \"x-tenant-id: 1b7db059-91bc-42d6-9d0e-93ca03d644a8\"\n  ```\nBe sure to add an existing and correct tenant unique identifier in the headers (must be UUID v4)\n\n## Disclaimer\n\nThis should be considered a starting point or a good conversation starter.\nIt is missing many important items such as a proper build pipeline, telemetry and security aspects such as a secret vault for storing database connection credentials or a rotating encryption key, etc.\nPlease be sure to upgrade your security posture.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandrughinea%2Frust-actix-postgres-multi-tenant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexandrughinea%2Frust-actix-postgres-multi-tenant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandrughinea%2Frust-actix-postgres-multi-tenant/lists"}