diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 645cc28525..cf0f570f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -216,10 +216,10 @@ jobs: with: script: | // TEMP: forcing SQLite/shared on this PR until it goes green; revert before merge. - const fullMatrixDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB']; + const fullMatrixDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB', 'Redis']; const fullMatrixModes = ['dedicated', 'shared']; - const defaultDatabases = ['SQLite']; + const defaultDatabases = ['SQLite', 'Redis']; const defaultModes = ['shared']; core.setOutput('databases', JSON.stringify(defaultDatabases)); @@ -496,6 +496,11 @@ jobs: echo "_APP_DB_HOST=mongodb" >> $GITHUB_ENV echo "_APP_DB_PORT=0" >> $GITHUB_ENV echo "_APP_DB_SQLITE_PATH=/storage/sqlite/appwrite.db" >> $GITHUB_ENV + elif [ "${{ matrix.database }}" = "Redis" ]; then + echo "COMPOSE_PROFILES=mongodb" >> $GITHUB_ENV + echo "_APP_DB_ADAPTER=redis" >> $GITHUB_ENV + echo "_APP_DB_HOST=redis-mirror" >> $GITHUB_ENV + echo "_APP_DB_PORT=6379" >> $GITHUB_ENV fi - name: Login to Docker Hub diff --git a/CHANGES.md b/CHANGES.md index 6894322043..51ba075edf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# Unreleased + +- Add Redis as a supported database adapter for tests (use `_APP_DB_ADAPTER=redis _APP_DB_HOST=redis-mirror`) + # Version 1.9.0 ## What's Changed diff --git a/app/init/registers.php b/app/init/registers.php index 5a2d002a67..8d413fd61d 100644 --- a/app/init/registers.php +++ b/app/init/registers.php @@ -14,6 +14,7 @@ use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\Mongo; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Adapter\Postgres; +use Utopia\Database\Adapter\Redis as RedisAdapter; use Utopia\Database\Adapter\SQL; use Utopia\Database\Adapter\SQLite; use Utopia\Database\PDO; @@ -191,13 +192,13 @@ $register->set('pools', function () { 'type' => 'database', 'dsns' => $fallbackForDB, 'multiple' => false, - 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql', 'sqlite'], + 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql', 'redis', 'sqlite'], ], 'database' => [ 'type' => 'database', 'dsns' => $fallbackForDB, 'multiple' => true, - 'schemes' => ['mongodb', 'mariadb', 'mysql', 'postgresql', 'sqlite'], + 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql', 'redis', 'sqlite'], ], 'documentsdb' => [ 'type' => 'database', @@ -215,7 +216,7 @@ $register->set('pools', function () { 'type' => 'database', 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB), 'multiple' => false, - 'schemes' => ['mongodb', 'mariadb', 'mysql', 'postgresql', 'sqlite'], + 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql', 'redis', 'sqlite'], ], 'publisher' => [ 'type' => 'publisher', @@ -351,6 +352,22 @@ $register->set('pools', function () { return $pdo; }); }, + // DSN format: redis://[user:pass@]host:port[/db] + // - "db" segment is the logical Redis DB index (0-15) and is + // optional. Query parameters are not supported. + 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass, $dsnDatabase) { + $redis = new \Redis(); + @$redis->pconnect($dsnHost, (int)$dsnPort); + if ($dsnPass) { + $redis->auth($dsnPass); + } + if ($dsnDatabase !== '' && \is_numeric($dsnDatabase)) { + $redis->select((int)$dsnDatabase); + } + $redis->setOption(\Redis::OPT_READ_TIMEOUT, -1); + + return $redis; + }, default => function () use ($dsnHost, $dsnPort, $dsnPass) { $redis = new \Redis(); @$redis->pconnect($dsnHost, (int)$dsnPort); @@ -374,6 +391,7 @@ $register->set('pools', function () { 'mysql' => new MySQL($resource()), 'mongodb' => new Mongo($resource()), 'postgresql' => new Postgres($resource()), + 'redis' => new RedisAdapter($resource()), 'sqlite' => (new SQLite($resource()))->setEmulateMySQL(true), default => null }; diff --git a/composer.json b/composer.json index 20b5468e27..12ecf2a8ea 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ "utopia-php/compression": "0.1.*", "utopia-php/config": "1.*", "utopia-php/console": "0.1.*", - "utopia-php/database": "dev-feat-sqlite-fts as 5.999.0", + "utopia-php/database": "dev-feat-redis-adapter as 5.999.0", "utopia-php/detector": "0.2.*", "utopia-php/domains": "1.*", "utopia-php/emails": "0.6.*", diff --git a/docker-compose.yml b/docker-compose.yml index c14a8c14cd..8bc9d9e07e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1422,6 +1422,27 @@ services: timeout: 5s retries: 12 + # Dedicated Redis instance used as the primary database backend when + # _APP_DB_ADAPTER=redis. Kept separate from the cache instance above so + # that tuning (eviction policy, persistence) can be set for durability + # rather than cache semantics. maxmemory-policy=noeviction is mandatory: + # a database that silently evicts keys would lose user data. + redis-mirror: + image: redis:7.4-alpine + container_name: appwrite-redis-mirror + <<: *x-logging + command: redis-server --maxmemory 1gb --maxmemory-policy noeviction --appendonly yes --save "" + ports: [] + networks: + - appwrite + volumes: + - appwrite-redis-mirror:/data:rw + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + coredns: # DNS server for testing purposes (Proxy APIs) image: coredns/coredns:1.12.4 container_name: appwrite-coredns @@ -1514,6 +1535,7 @@ volumes: appwrite-mongodb-keyfile: appwrite-postgresql: appwrite-redis: + appwrite-redis-mirror: appwrite-cache: appwrite-uploads: appwrite-imports: