Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Unix Sockets for HTTP Replication #15708

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
214b7a4
Fixup how the HTTPConnectionPool key is declared and comments around/…
realtyem May 25, 2023
b41435a
Drive by fix: remove the disambiguity of seeing 'master' instead of '…
realtyem May 26, 2023
ca21dce
Create new InstanceUNIXLocationConfig, and finish wiring up UNIX Sock…
realtyem May 26, 2023
e75b177
Changelog
realtyem Jun 2, 2023
0aeae16
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 2, 2023
7d03b2d
Adapt test_workers to handle Class rename
realtyem Jun 4, 2023
0d79903
[OPTIONAL REVERT] Change Type[Model] to TypeAlias as a Hack
realtyem Jun 4, 2023
7bc139a
Try writing some docs
realtyem Jun 4, 2023
acd56db
Experimental testing setup for Unix sockets with Complement.
realtyem Jun 4, 2023
e14422f
[REVERT THIS] Enable testing for the Complement CI, each test will us…
realtyem Jun 4, 2023
eb65470
Revert "Drive by fix: remove the disambiguity of seeing 'master' inst…
realtyem Jun 14, 2023
e234ce9
Apply suggestions from code review
realtyem Jun 14, 2023
6a5e705
Nit over formatting
realtyem Jun 14, 2023
ee55d2a
Add in error messages to unit test setup to discourage using Unix soc…
realtyem Jun 15, 2023
e3d4fb4
Unify various incarnations of main_public.sock(and main_private.sock)…
realtyem Jun 15, 2023
fe18b66
Add detail to docs about Synapse not creating a directory auto-magica…
realtyem Jun 15, 2023
9394941
Adjust TCP and UNIX to be initial caps instead of all caps(in the sco…
realtyem Jun 15, 2023
79f63fc
Try seeing what the doc renderer will do with 4 #'s for a subheading(…
realtyem Jun 15, 2023
c4646ee
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 15, 2023
9882e0a
Apply suggestions from code review
realtyem Jun 15, 2023
52f84a2
Fix merge derp that encompasses the TLS replication fix from 15746
realtyem Jun 16, 2023
86c5fd5
Fix lack of detail in contributing guide for how to use Unix socket f…
realtyem Jun 16, 2023
e091ea2
Try and fix comment in ReplicationAgent to be clearer about why not u…
realtyem Jun 16, 2023
4946cb0
Adjust changelog.d/15708.feature
realtyem Jun 16, 2023
0b1a943
Update docs/usage/configuration/config_documentation.md
realtyem Jun 16, 2023
39597fc
Update docs a bit more
realtyem Jun 20, 2023
6dbc402
Apply suggestions from code review
realtyem Jun 21, 2023
2f20dda
From review: wrap lines(and fix a comment consistency)
realtyem Jun 21, 2023
5de0ef1
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 21, 2023
85eba85
unindent entire block
realtyem Jun 21, 2023
4c9de6f
Revert to `Type[Model]` to keep type variable constrained; add type i…
reivilibre Jul 5, 2023
67c278a
Fix some nits and some lint needed to be burned off
realtyem Jul 5, 2023
a5d500f
Use more specific mypy ignores
reivilibre Jul 6, 2023
41d2aae
Merge branch 'develop' into finish-unix-replication-series
realtyem Jul 6, 2023
fdb07e8
Fixup for new proxyagent
realtyem Jul 6, 2023
cd1579b
Add Unix socket support to the proxyagent
realtyem Jul 6, 2023
4f079d8
[REVERT THIS] Hardwire testing the proxy agent into Complement tempor…
realtyem Jul 6, 2023
9f41f3e
Revert "[REVERT THIS] Hardwire testing the proxy agent into Complemen…
realtyem Jul 7, 2023
0ae6036
Revert "[REVERT THIS] Enable testing for the Complement CI, each test…
realtyem Jul 7, 2023
92c6c7b
Update version number of Synapse this was added in
realtyem Jul 7, 2023
cd047fe
Merge branch 'develop' into finish-unix-replication-series
realtyem Jul 7, 2023
517654f
Merge branch 'develop' into finish-unix-replication-series
MadLittleMods Jul 10, 2023
3789a4f
Swap getClientAddress().host call for the UNIXAddress compatible repl…
realtyem Jul 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Experimental testing setup for Unix sockets with Complement.
Enable testing Unix sockets by passing UNIX_SOCKETS=1 (yes, it's plural) to the command line when running Complement, or export to the environment ahead of time.

I apologize for the liberties I took with Opinions for this implementation.
1. The main process gets two sockets, one for public(client and federation) traffic, and one for private(replication) traffic. These were placed at /run/mainpublic.sock and /run/mainprivate.sock, respectively.
2. Additionally, for workers, the sockets were placed at /run/worker.{port_number}. I would have preferred to name them according to worker_name, but that would have taken a much deeper restructure of the configure_workers_and_start.py than I was prepared to make. This approach still allows them to be sequentially numbered just as the ports are now.
3. Redis and Postgres also get the Unix socket treatment while here.
  • Loading branch information
realtyem committed Jun 4, 2023
commit acd56dbd61323259699d29f1ba215da7fac1145d
4 changes: 4 additions & 0 deletions docker/conf-workers/nginx.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ server {

# Send all other traffic to the main process
location ~* ^(\\/_matrix|\\/_synapse) {
{% if using_unix_sockets %}
proxy_pass http://unix:/run/mainpublic.sock;
{% else %}
proxy_pass http://localhost:8080;
{% endif %}
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
Expand Down
3 changes: 3 additions & 0 deletions docker/conf-workers/shared.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{% if enable_redis %}
redis:
enabled: true
{% if using_unix_sockets %}
path: /tmp/redis.sock
{% endif %}
{% endif %}

{% if appservice_registrations is not none %}
Expand Down
4 changes: 4 additions & 0 deletions docker/conf-workers/supervisord.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ username=www-data
autorestart=true

[program:redis]
{% if using_unix_sockets %}
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server --unixsocket /tmp/redis.sock
{% else %}
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server
{% endif %}
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
Expand Down
4 changes: 4 additions & 0 deletions docker/conf-workers/worker.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ worker_name: "{{ name }}"

worker_listeners:
- type: http
{% if using_unix_sockets %}
path: "/run/worker.{{ port }}"
{% else %}
port: {{ port }}
{% endif %}
{% if listener_resources %}
resources:
- names:
Expand Down
10 changes: 9 additions & 1 deletion docker/conf/homeserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ listeners:

# Allow configuring in case we want to reverse proxy 8008
# using another process in the same container
{% if SYNAPSE_USE_UNIX_SOCKET %}
# Unix sockets don't care about TLS or IP addresses or ports
- path: '/run/mainpublic.sock'
type: http
{% else %}
- port: {{ SYNAPSE_HTTP_PORT or 8008 }}
tls: false
bind_addresses: ['::']
type: http
x_forwarded: false

{% endif %}
resources:
- names: [client]
compress: true
Expand All @@ -57,8 +62,11 @@ database:
user: "{{ POSTGRES_USER or "synapse" }}"
password: "{{ POSTGRES_PASSWORD }}"
database: "{{ POSTGRES_DB or "synapse" }}"
{% if not SYNAPSE_USE_UNIX_SOCKET %}
{# Fun fact: if left with no host or port, Synapse looks for the default Unix socket instead. #}
realtyem marked this conversation as resolved.
Show resolved Hide resolved
host: "{{ POSTGRES_HOST or "db" }}"
port: "{{ POSTGRES_PORT or "5432" }}"
{% endif %}
cp_min: 5
cp_max: 10
{% else %}
Expand Down
102 changes: 76 additions & 26 deletions docker/configure_workers_and_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
MAIN_PROCESS_INSTANCE_NAME = "main"
MAIN_PROCESS_LOCALHOST_ADDRESS = "127.0.0.1"
MAIN_PROCESS_REPLICATION_PORT = 9093
# Obviously, these would only be used with the UNIX socket option
MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH = "/run/mainpublic.sock"
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH = "/run/mainprivate.sock"

# A simple name used as a placeholder in the WORKERS_CONFIG below. This will be replaced
# during processing with the name of the worker.
Expand Down Expand Up @@ -408,11 +411,15 @@ def add_worker_roles_to_shared_config(
)

# Map of stream writer instance names to host/ports combos
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}

if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
instance_map[worker_name] = {
"path": f"/run/worker.{worker_port}",
}
else:
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}
# Update the list of stream writers. It's convenient that the name of the worker
# type is the same as the stream to write. Iterate over the whole list in case there
# is more than one.
Expand All @@ -424,10 +431,15 @@ def add_worker_roles_to_shared_config(

# Map of stream writer instance names to host/ports combos
# For now, all stream writers need http replication ports
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}
if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
instance_map[worker_name] = {
"path": f"/run/worker.{worker_port}",
}
else:
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}


def merge_worker_template_configs(
Expand Down Expand Up @@ -719,17 +731,29 @@ def generate_worker_files(
# Note that yaml cares about indentation, so care should be taken to insert lines
# into files at the correct indentation below.

# Convenience helper for if using unix sockets instead of host:port
using_unix_sockets = environ.get("SYNAPSE_USE_UNIX_SOCKET", False)
# First read the original config file and extract the listeners block. Then we'll
# add another listener for replication. Later we'll write out the result to the
# shared config file.
listeners = [
{
"port": MAIN_PROCESS_REPLICATION_PORT,
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
listeners: List[Any]
if using_unix_sockets:
listeners = [
{
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
else:
listeners = [
{
"port": MAIN_PROCESS_REPLICATION_PORT,
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
with open(config_path) as file_stream:
original_config = yaml.safe_load(file_stream)
original_listeners = original_config.get("listeners")
Expand Down Expand Up @@ -770,7 +794,15 @@ def generate_worker_files(

# A list of internal endpoints to healthcheck, starting with the main process
# which exists even if no workers do.
healthcheck_urls = ["http://localhost:8080/health"]
# This list ends up being part of the command line to curl, which added unix socket
# support in version 7.40. The scheme and hostname are ignored, the path is not.
realtyem marked this conversation as resolved.
Show resolved Hide resolved
if using_unix_sockets:
healthcheck_urls = [
f"--unix-socket {MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH} "
"http://localhost/health"
realtyem marked this conversation as resolved.
Show resolved Hide resolved
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
]
else:
healthcheck_urls = ["http://localhost:8080/health"]

# Get the set of all worker types that we have configured
all_worker_types_in_use = set(chain(*requested_worker_types.values()))
Expand Down Expand Up @@ -807,8 +839,12 @@ def generate_worker_files(
# given worker_type needs to stay assigned and not be replaced.
worker_config["shared_extra_conf"].update(shared_config)
shared_config = worker_config["shared_extra_conf"]

healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))
if using_unix_sockets:
healthcheck_urls.append(
f"--unix-socket /run/worker.{worker_port} http://localhost/health"
)
else:
healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))

# Update the shared config with sharding-related options if necessary
add_worker_roles_to_shared_config(
Expand All @@ -827,6 +863,7 @@ def generate_worker_files(
"/conf/workers/{name}.yaml".format(name=worker_name),
**worker_config,
worker_log_config_filepath=log_config_filepath,
using_unix_sockets=using_unix_sockets,
)

# Save this worker's port number to the correct nginx upstreams
Expand All @@ -847,8 +884,13 @@ def generate_worker_files(
nginx_upstream_config = ""
for upstream_worker_base_name, upstream_worker_ports in nginx_upstreams.items():
body = ""
for port in upstream_worker_ports:
body += f" server localhost:{port};\n"
if using_unix_sockets:
for port in upstream_worker_ports:
body += f" server unix:/run/worker.{port};\n"

else:
for port in upstream_worker_ports:
body += f" server localhost:{port};\n"

# Add to the list of configured upstreams
nginx_upstream_config += NGINX_UPSTREAM_CONFIG_BLOCK.format(
Expand Down Expand Up @@ -878,10 +920,15 @@ def generate_worker_files(
# If there are workers, add the main process to the instance_map too.
if workers_in_use:
instance_map = shared_config.setdefault("instance_map", {})
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
"port": MAIN_PROCESS_REPLICATION_PORT,
}
if using_unix_sockets:
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
}
else:
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
"port": MAIN_PROCESS_REPLICATION_PORT,
}

# Shared homeserver config
convert(
Expand All @@ -891,6 +938,7 @@ def generate_worker_files(
appservice_registrations=appservice_registrations,
enable_redis=workers_in_use,
workers_in_use=workers_in_use,
using_unix_sockets=using_unix_sockets,
)

# Nginx config
Expand All @@ -901,6 +949,7 @@ def generate_worker_files(
upstream_directives=nginx_upstream_config,
tls_cert_path=os.environ.get("SYNAPSE_TLS_CERT"),
tls_key_path=os.environ.get("SYNAPSE_TLS_KEY"),
using_unix_sockets=using_unix_sockets,
)

# Supervisord config
Expand All @@ -910,6 +959,7 @@ def generate_worker_files(
"/etc/supervisor/supervisord.conf",
main_config_path=config_path,
enable_redis=workers_in_use,
using_unix_sockets=using_unix_sockets,
)

convert(
Expand Down
4 changes: 4 additions & 0 deletions scripts-dev/complement.sh
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ if [[ -n "$ASYNCIO_REACTOR" ]]; then
export PASS_SYNAPSE_COMPLEMENT_USE_ASYNCIO_REACTOR=true
fi

if [[ -n "$UNIX_SOCKETS" ]]; then
# Enable full on Unix socket mode for Synapse, Redis and Postgresql
export PASS_SYNAPSE_USE_UNIX_SOCKET=1
fi

if [[ -n "$SYNAPSE_TEST_LOG_LEVEL" ]]; then
# Set the log level to what is desired
Expand Down