diff --git a/README.md b/README.md index 6d3a627a..b23635cf 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,14 @@ This is a project to create a federated social network based on the [Lysand](https://lysand.org) protocol. It is currently in alpha phase, with basic federation and API support. -This project aims to be a fully featured social network, with a focus on privacy and security. It will implement the Mastodon API for support with clients that already support Mastodon or Pleroma. +This project aims to be a fully featured social network, with a focus on privacy, security, and performance. It will implement the Mastodon API for support with clients that already support Mastodon or Pleroma. > **Note:** This project is not affiliated with Mastodon or Pleroma, and is not a fork of either project. It is a new project built from the ground up. ## Features - [x] Inbound federation +- [x] Hyper fast (thousands of HTTP requests per second) - [x] S3 or local media storage - [x] Deduplication of uploaded files - [x] Federation limits @@ -42,6 +43,28 @@ TOKEN=token_here bun benchmark:timeline The `request_count` variable is optional and defaults to 100. `TOKEN` is your personal user token, used to login to the API. +On a quad-core laptop: + +``` +$ bun run benchmarks/timelines.ts 100 +✓ All requests succeeded +✓ 100 requests fulfilled in 0.12611s +``` + +``` +$ bun run benchmarks/timelines.ts 1000 +✓ All requests succeeded +✓ 1000 requests fulfilled in 0.90925s +``` + +``` +$ bun run benchmarks/timelines.ts 10000 +✓ All requests succeeded +✓ 10000 requests fulfilled in 12.44852s +``` + +Lysand is extremely fast and can handle tens of thousands of HTTP requests per second on a good server. + ## How do I run it? ### Requirements diff --git a/bun.lockb b/bun.lockb index 0f28a864..2cfe1886 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/config/config.example.toml b/config/config.example.toml index 8c8e92bd..603eb05a 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -7,9 +7,16 @@ database = "lysand" [redis.queue] host = "localhost" -post = 6379 +port = 6379 password = "" -# database = 0 +database = 0 + +[redis.cache] +host = "localhost" +port = 6379 +password = "" +database = 1 +enabled = false [http] base_url = "https://lysand.social" diff --git a/index.ts b/index.ts index ce7ababe..38a9de04 100644 --- a/index.ts +++ b/index.ts @@ -11,6 +11,7 @@ import { mkdir } from "fs/promises"; import { client } from "~database/datasource"; import type { PrismaClientInitializationError } from "@prisma/client/runtime/library"; import { HookTypes, Server } from "~plugins/types"; +import { initializeRedisCache } from "@redis"; const timeAtStart = performance.now(); const server = new Server(); @@ -33,6 +34,12 @@ if (!(await requests_log.exists())) { await Bun.write(process.cwd() + "/logs/requests.log", ""); } +const redisCache = await initializeRedisCache(); + +if (redisCache) { + client.$use(redisCache); +} + // Check if database is reachable let postCount = 0; try { diff --git a/package.json b/package.json index 90da5362..224545b0 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@microsoft/eslint-formatter-sarif": "^3.0.0", "@types/cli-table": "^0.3.4", "@types/html-to-text": "^9.0.4", + "@types/ioredis": "^5.0.0", "@types/jsonld": "^1.5.13", "@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/parser": "^6.13.1", @@ -78,12 +79,14 @@ "cli-table": "^0.3.11", "eventemitter3": "^5.0.1", "html-to-text": "^9.0.5", + "ioredis": "^5.3.2", "ip-matching": "^2.1.2", "iso-639-1": "^3.1.0", "isomorphic-dompurify": "^1.10.0", "jsonld": "^8.3.1", "marked": "^9.1.2", "prisma": "^5.6.0", + "prisma-redis-middleware": "^4.8.0", "semver": "^7.5.4", "sharp": "^0.33.0-rc.2" } diff --git a/utils/config.ts b/utils/config.ts index eddec0b6..92553c16 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -16,6 +16,13 @@ export interface ConfigType { password: string; database: number | null; }; + cache: { + host: string; + port: number; + password: string; + database: number | null; + enabled: boolean; + }; }; http: { @@ -159,7 +166,14 @@ export const configDefaults: ConfigType = { host: "localhost", port: 6379, password: "", - database: null, + database: 0, + }, + cache: { + host: "localhost", + port: 6379, + password: "", + database: 1, + enabled: false, }, }, instance: { diff --git a/utils/redis.ts b/utils/redis.ts new file mode 100644 index 00000000..a5715bc1 --- /dev/null +++ b/utils/redis.ts @@ -0,0 +1,60 @@ +import { getConfig } from "@config"; +import type { Prisma } from "@prisma/client"; +import chalk from "chalk"; +import Redis from "ioredis"; +import { createPrismaRedisCache } from "prisma-redis-middleware"; + +const config = getConfig(); + +const cacheRedis = config.redis.cache.enabled + ? new Redis({ + host: config.redis.cache.host, + port: Number(config.redis.cache.port), + password: config.redis.cache.password, + db: Number(config.redis.cache.database ?? 0), + }) + : null; + +cacheRedis?.on("error", e => { + console.log(e); +}); + +export { cacheRedis }; + +export const initializeRedisCache = async () => { + if (cacheRedis) { + // Test connection + try { + await cacheRedis.ping(); + } catch (e) { + console.error( + `${chalk.red(`✗`)} ${chalk.bold( + `Error while connecting to Redis` + )}` + ); + throw e; + } + + console.log(`${chalk.green(`✓`)} ${chalk.bold(`Connected to Redis`)}`); + + const cacheMiddleware: Prisma.Middleware = createPrismaRedisCache({ + storage: { + type: "redis", + options: { + client: cacheRedis, + invalidation: { + referencesTTL: 300, + }, + }, + }, + cacheTime: 300, + onError: e => { + console.error(e); + }, + }); + + return cacheMiddleware; + } + + return null; +};