add horizontal file sharding based on filename matches
This commit is contained in:
@@ -12,18 +12,19 @@ It is possible to build all required images at once by running `docker-build.bat
|
|||||||
|
|
||||||
## 2. Configure ports + token
|
## 2. Configure ports + token
|
||||||
You should set up 2 environment variables that hold server specific configuration and open up ports.
|
You should set up 2 environment variables that hold server specific configuration and open up ports.
|
||||||
The default ports used through the provided configuration are `6000` for the main server and `6200` for the files downloads.
|
The default ports used through the provided configuration are `6000` for the main server and `6200` as well as `6201` for the files downloads.
|
||||||
Both ports should be open to your computer through your router if you wish to test this with clients.
|
Both ports should be open to your computer through your router if you wish to test this with clients.
|
||||||
|
|
||||||
Furthermore there are two environment variables `DEV_MARE_CDNURL` and `DEV_MARE_DISCORDTOKEN` which you are required to set.
|
Furthermore there are two environment variables `DEV_MARE_CDNURL` and `DEV_MARE_DISCORDTOKEN` which you are required to set.
|
||||||
`DEV_MARE_CDNURL` should point to `http://<yourip or dyndns>:6200/cache/` and `DEV_MARE_DISCORDTOKEN` is an oauth token from a bot you need to create through the Discord bot portal.
|
`DEV_MARE_CDNURL` should point to `http://<yourip or dyndns>:6200/cache/` and `DEV_MARE_DISCORDTOKEN` is an oauth token from a bot you need to create through the Discord bot portal.
|
||||||
|
You should also set `DEV_MARE_CDNURL2` to `http://<yourip or dyndns>:6201/cache/`
|
||||||
It is enough to set them as User variables. The compose files refer to those environment variables to overwrite configuration settings for the Server and Services to set those respective values.
|
It is enough to set them as User variables. The compose files refer to those environment variables to overwrite configuration settings for the Server and Services to set those respective values.
|
||||||
It is also possible to set those values in the configuration.json files themselves.
|
It is also possible to set those values in the configuration.json files themselves.
|
||||||
Without a valid Discord bot you will not be able to register accounts without fumbling around in the PostgreSQL database.
|
Without a valid Discord bot you will not be able to register accounts without fumbling around in the PostgreSQL database.
|
||||||
|
|
||||||
## 3. Run Mare Server
|
## 3. Run Mare Server
|
||||||
The run folder contains two major Mare configurations which is `standalone` and `sharded`.
|
The run folder contains two major Mare configurations which is `standalone` and `sharded`.
|
||||||
Both configurations default to port `6000` for the main server connection and `6200` for the files downloads. No HTTPS.
|
Both configurations default to port `6000` for the main server connection and `6200` for the files downloads. Sharded configuration additionally uses `6201` for downloads. No HTTPS.
|
||||||
All `appsettings.json` configurations provided are extensive at the point of writing, note the differences between the shard configurations and the main servers respectively.
|
All `appsettings.json` configurations provided are extensive at the point of writing, note the differences between the shard configurations and the main servers respectively.
|
||||||
They can be used as examples if you want to spin up your own servers otherwise.
|
They can be used as examples if you want to spin up your own servers otherwise.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 6000:6000/tcp
|
- 6000:6000/tcp
|
||||||
- 6200:6200/tcp
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../config/sharded/haproxy-shards.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
- ../config/sharded/haproxy-shards.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
|
|
||||||
@@ -35,6 +34,10 @@ services:
|
|||||||
restart: on-failure
|
restart: on-failure
|
||||||
environment:
|
environment:
|
||||||
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}"
|
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}"
|
||||||
|
MareSynchronos__CdnShardConfiguration__0__CdnFullUrl: "${DEV_MARE_CDNURL}"
|
||||||
|
MareSynchronos__CdnShardConfiguration__0__FileMatch: "^[01234567]"
|
||||||
|
MareSynchronos__CdnShardConfiguration__1__CdnFullUrl: "${DEV_MARE_CDNURL2}"
|
||||||
|
MareSynchronos__CdnShardConfiguration__1__FileMatch: "^[89ABCDEF]"
|
||||||
volumes:
|
volumes:
|
||||||
- ../config/sharded/server-shard-main.json:/opt/MareSynchronosServer/appsettings.json
|
- ../config/sharded/server-shard-main.json:/opt/MareSynchronosServer/appsettings.json
|
||||||
- ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw
|
- ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw
|
||||||
@@ -98,6 +101,8 @@ services:
|
|||||||
- ../log/files-shard-1/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
- ../log/files-shard-1/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
- ../data/files-shard-1/:/marecache/:rw
|
- ../data/files-shard-1/:/marecache/:rw
|
||||||
|
ports:
|
||||||
|
- 6200:6200/tcp
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
- "postgres"
|
||||||
- "mare-files"
|
- "mare-files"
|
||||||
@@ -110,6 +115,8 @@ services:
|
|||||||
- ../log/files-shard-2/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
- ../log/files-shard-2/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
- ../data/files-shard-2/:/marecache/:rw
|
- ../data/files-shard-2/:/marecache/:rw
|
||||||
|
ports:
|
||||||
|
- 6201:6200/tcp
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
- "postgres"
|
||||||
- "mare-files"
|
- "mare-files"
|
||||||
|
|||||||
@@ -23,17 +23,8 @@ frontend mare
|
|||||||
bind :6000
|
bind :6000
|
||||||
default_backend mare-servers
|
default_backend mare-servers
|
||||||
|
|
||||||
frontend mare-files
|
|
||||||
bind :6200
|
|
||||||
default_backend mare-files
|
|
||||||
|
|
||||||
backend mare-servers
|
backend mare-servers
|
||||||
balance leastconn
|
balance leastconn
|
||||||
cookie SERVER insert indirect nocache
|
cookie SERVER insert indirect nocache
|
||||||
server mare1 mare-shard-1:6000 cookie mare1
|
server mare1 mare-shard-1:6000 cookie mare1
|
||||||
server mare2 mare-shard-2:6000 cookie mare2
|
server mare2 mare-shard-2:6000 cookie mare2
|
||||||
|
|
||||||
backend mare-files
|
|
||||||
balance roundrobin
|
|
||||||
server files1 mare-files-shard-1:6200
|
|
||||||
server files2 mare-files-shard-2:6200
|
|
||||||
|
|||||||
@@ -41,7 +41,17 @@
|
|||||||
"MaxJoinedGroupsByUser": 6,
|
"MaxJoinedGroupsByUser": 6,
|
||||||
"MaxGroupUserCount": 100,
|
"MaxGroupUserCount": 100,
|
||||||
"PurgeUnusedAccounts": false,
|
"PurgeUnusedAccounts": false,
|
||||||
"PurgeUnusedAccountsPeriodInDays": 14
|
"PurgeUnusedAccountsPeriodInDays": 14,
|
||||||
|
"CdnShardConfiguration": [
|
||||||
|
{
|
||||||
|
"FileMatch": "^[01234567]",
|
||||||
|
"CdnFullUrl": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FileMatch": "^[89ABCDEF]",
|
||||||
|
"CdnFullUrl": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using MareSynchronos.API;
|
|||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
@@ -52,10 +53,14 @@ public partial class MareHub
|
|||||||
|
|
||||||
var cacheFile = await _dbContext.Files.AsNoTracking().Where(f => hashes.Contains(f.Hash)).AsNoTracking().Select(k => new { k.Hash, k.Size }).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
var cacheFile = await _dbContext.Files.AsNoTracking().Where(f => hashes.Contains(f.Hash)).AsNoTracking().Select(k => new { k.Hash, k.Size }).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var shardConfig = _configurationService.GetValueOrDefault(nameof(ServerConfiguration.CdnShardConfiguration), new List<CdnShardConfiguration>());
|
||||||
|
|
||||||
foreach (var file in cacheFile)
|
foreach (var file in cacheFile)
|
||||||
{
|
{
|
||||||
var forbiddenFile = forbiddenFiles.SingleOrDefault(f => string.Equals(f.Hash, file.Hash, StringComparison.OrdinalIgnoreCase));
|
var forbiddenFile = forbiddenFiles.SingleOrDefault(f => string.Equals(f.Hash, file.Hash, StringComparison.OrdinalIgnoreCase));
|
||||||
var downloadFile = allFiles.SingleOrDefault(f => string.Equals(f.Hash, file.Hash, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
var matchedShardConfig = shardConfig.Find(f => f.FileMatchRegex.Match(file.Hash).Success);
|
||||||
|
var baseUrl = matchedShardConfig?.CdnFullUrl ?? _mainCdnFullUrl;
|
||||||
|
|
||||||
response.Add(new DownloadFileDto
|
response.Add(new DownloadFileDto
|
||||||
{
|
{
|
||||||
@@ -64,7 +69,7 @@ public partial class MareHub
|
|||||||
IsForbidden = forbiddenFile != null,
|
IsForbidden = forbiddenFile != null,
|
||||||
Hash = file.Hash,
|
Hash = file.Hash,
|
||||||
Size = file.Size,
|
Size = file.Size,
|
||||||
Url = new Uri(_cdnFullUri, file.Hash.ToUpperInvariant()).ToString()
|
Url = new Uri(baseUrl, file.Hash.ToUpperInvariant()).ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
private readonly IClientIdentificationService _clientIdentService;
|
private readonly IClientIdentificationService _clientIdentService;
|
||||||
private readonly MareHubLogger _logger;
|
private readonly MareHubLogger _logger;
|
||||||
private readonly MareDbContext _dbContext;
|
private readonly MareDbContext _dbContext;
|
||||||
private readonly Uri _cdnFullUri;
|
private readonly Uri _mainCdnFullUrl;
|
||||||
private readonly string _shardName;
|
private readonly string _shardName;
|
||||||
private readonly int _maxExistingGroupsByUser;
|
private readonly int _maxExistingGroupsByUser;
|
||||||
private readonly int _maxJoinedGroupsByUser;
|
private readonly int _maxJoinedGroupsByUser;
|
||||||
private readonly int _maxGroupUserCount;
|
private readonly int _maxGroupUserCount;
|
||||||
|
private IConfigurationService<ServerConfiguration> _configurationService;
|
||||||
|
|
||||||
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
|
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
|
||||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService,
|
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService,
|
||||||
@@ -37,7 +38,8 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
_mareMetrics = mareMetrics;
|
_mareMetrics = mareMetrics;
|
||||||
_fileServiceClient = fileServiceClient;
|
_fileServiceClient = fileServiceClient;
|
||||||
_systemInfoService = systemInfoService;
|
_systemInfoService = systemInfoService;
|
||||||
_cdnFullUri = configuration.GetValue<Uri>(nameof(ServerConfiguration.CdnFullUrl));
|
_configurationService = configuration;
|
||||||
|
_mainCdnFullUrl = configuration.GetValue<Uri>(nameof(ServerConfiguration.CdnFullUrl));
|
||||||
_shardName = configuration.GetValue<string>(nameof(ServerConfiguration.ShardName));
|
_shardName = configuration.GetValue<string>(nameof(ServerConfiguration.ShardName));
|
||||||
_maxExistingGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3);
|
_maxExistingGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3);
|
||||||
_maxJoinedGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxJoinedGroupsByUser), 6);
|
_maxJoinedGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxJoinedGroupsByUser), 6);
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ public class Program
|
|||||||
context.RemoveRange(unfinishedRegistrations);
|
context.RemoveRange(unfinishedRegistrations);
|
||||||
context.RemoveRange(looseFiles);
|
context.RemoveRange(looseFiles);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
|
logger.LogInformation(options.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var metrics = services.GetRequiredService<MareMetrics>();
|
var metrics = services.GetRequiredService<MareMetrics>();
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ using MareSynchronosShared.Utils;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using static MareSynchronosShared.Protos.ConfigurationService;
|
using static MareSynchronosShared.Protos.ConfigurationService;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
namespace MareSynchronosShared.Services;
|
||||||
@@ -75,9 +77,17 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
|
|||||||
foreach (var prop in props)
|
foreach (var prop in props)
|
||||||
{
|
{
|
||||||
var isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any();
|
var isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any();
|
||||||
var mi = GetType().GetMethod(nameof(GetValue)).MakeGenericMethod(prop.PropertyType);
|
var getValueMethod = GetType().GetMethod(nameof(GetValue)).MakeGenericMethod(prop.PropertyType);
|
||||||
var val = mi.Invoke(this, new[] { prop.Name });
|
var value = isRemote ? getValueMethod.Invoke(this, new[] { prop.Name }) : prop.GetValue(_config);
|
||||||
var value = isRemote ? val : prop.GetValue(_config);
|
if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && !typeof(string).IsAssignableFrom(prop.PropertyType))
|
||||||
|
{
|
||||||
|
var enumVal = (IEnumerable)value;
|
||||||
|
value = string.Empty;
|
||||||
|
foreach (var listVal in enumVal)
|
||||||
|
{
|
||||||
|
value += listVal.ToString() + ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
sb.AppendLine($"{prop.Name} (IsRemote: {isRemote}) => {value}");
|
sb.AppendLine($"{prop.Name} (IsRemote: {isRemote}) => {value}");
|
||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public class MareConfigurationServiceServer<T> : IConfigurationService<T> where
|
|||||||
{
|
{
|
||||||
sb.AppendLine($"{prop.Name} (IsRemote: {prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()}) => {prop.GetValue(_config)}");
|
sb.AppendLine($"{prop.Name} (IsRemote: {prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()}) => {prop.GetValue(_config)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(_config.ToString());
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
public class CdnShardConfiguration
|
||||||
|
{
|
||||||
|
public string FileMatch { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public Regex FileMatchRegex => new Regex(FileMatch);
|
||||||
|
public Uri CdnFullUrl { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return CdnFullUrl.ToString() + " == " + FileMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ public class ServerConfiguration : MareConfigurationAuthBase
|
|||||||
public bool PurgeUnusedAccounts { get; set; } = false;
|
public bool PurgeUnusedAccounts { get; set; } = false;
|
||||||
[RemoteConfiguration]
|
[RemoteConfiguration]
|
||||||
public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14;
|
public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public List<CdnShardConfiguration> CdnShardConfiguration { get; set; } = new();
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
@@ -27,6 +29,7 @@ public class ServerConfiguration : MareConfigurationAuthBase
|
|||||||
sb.AppendLine(base.ToString());
|
sb.AppendLine(base.ToString());
|
||||||
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
||||||
sb.AppendLine($"{nameof(CdnFullUrl)} => {CdnFullUrl}");
|
sb.AppendLine($"{nameof(CdnFullUrl)} => {CdnFullUrl}");
|
||||||
|
sb.AppendLine($"{nameof(CdnShardConfiguration)} => {string.Join(", ", CdnShardConfiguration.Select(c => c.ToString()))}");
|
||||||
sb.AppendLine($"{nameof(StaticFileServiceAddress)} => {StaticFileServiceAddress}");
|
sb.AppendLine($"{nameof(StaticFileServiceAddress)} => {StaticFileServiceAddress}");
|
||||||
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
||||||
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
||||||
|
|||||||
Reference in New Issue
Block a user