Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

redisbetween

This is a connection pooling proxy for redis. It was originally built for an application that was hitting connection limits against its redis clusters. Its purpose is to solve a specific problem: many application processes that cannot otherwise share a connection pool need to connect to a single redis cluster.

redisbetween supports both standalone and clustered redis deployments with the following caveats:

  • Blocking Commands that cause the client to hold a connection open are partially supported, with limitations. Specifically, BRPOPLPUSH and SUBSCRIBE/PSUBSCRIBE are supported in non-clustered mode. In order to prevent exhausting the connection pool, these commands are implemented using reserved connections, configured via maxsubscriptions and maxblockers (both default to 1). The supported commands are specifically intended to support sidekiq servers with reliable fetch. Other blocking commands such as BLPOP or WAIT are not yet supported.

  • Pipelines are supported, but require a client patch. Normally, redis clients may send multiple commands back-to-back before reading a batch of responses all at once from the server. Since redisbetween shares upstream connections among many clients, it relies on special "signal" messages to indicate the beginning and end of batched commands. Clients using redisbetween must prepend a GET 🔜 and append a GET 🔚 to their batch of messages in order for redisbetween to properly proxy the pipelined commands and responses.

  • Transactions are only supported within pipelines. This means that the commands DISCARD, EXEC, MULTI, UNWATCH and WATCH will return errors from the proxy before they reach an upstream host unless they occur between the two signal values described above in the Pipelines section. This is because redis stores state about open transactions on the server side, attached to each client connection. In order to support transactions without connection-pinning, we require that the full set of operations be sent in one batch so that the connection we check back into the pool does not leak state to other clients.

  • The SELECT command, which is used by redis clients when connecting to a db other than the default 0, is not allowed. However, redisbetween does support multiple dbs by specifying the db number in the endpoint url path. With an example URL of redis://example.com/3, the resulting connection pool would be mapped to the socket path /var/tmp/redisbetween-example.com-3.sock suffix, and all connections would issue a SELECT 3 command before entering the pool. Note that each db number gets its own connection pool, so adjust maxpoolsize accordingly when using this feature.

  • The AUTH command is not supported. If this is needed in the future, we could add support by pre-emptively sending the AUTH command on all new connections, like we do with SELECT.

How it works

redisbetween creates a connection pool for each upstream redis server it discovers (either via configuration at start time, snooping on CLUSTER commands or via ASK/MOVED errors) and maps a local unix socket to that pool. Applications running on the same host can connect to redis via this unix socket instead of connecting directly to the redis server, thus sharing a relatively smaller number of connections among the many processes on a machine.

Upon startup, redisbetween creates a pool of connections to the redis endpoint provided and listens on a unix socket named after the endpoint. By default, it will be named /var/tmp/redisbetween-${host}-${port}(-${db})(-ro).sock. This can be customized using the -localsocketprefix and -localsocketsuffix options. For standalone redis deployments, this will be the only socket created. However, redisbetween will inspect responses to CLUSTER commands, looking for references to cluster members that it hasn't yet seen. When it sees a new cluster member, it allocates a new connection pool and unix socket for it before relaying the response to the client.

Redisbetween Gem

The ruby directory contains a ruby gem that monkey patches the ruby redis client to support redisbetween. See the readme for more details.

Here's an example of a patch to the go-redis client. Note that this one does not handle db number selection, as that is not supported by redis cluster anyway.

readonly:=trueopt:=&redis.ClusterOptions{
Addrs: []string{address},
Dialer: func(ctx context.Context, network, addrstring) (net.Conn, error) {
ifstrings.Contains(network, "tcp") {
host, port, err:=net.SplitHostPort(addr)
iferr!=nil {
returnnil, err
}
addr="/var/tmp/redisbetween-"+host+"-"+portifreadonly {
addr+="-ro"
}
addr+=".sock"network="unix"
}
returnnet.Dial(network, addr)
},
}
client:=redis.NewClusterClient(opt)
res:=client.Do(context.Background(), "ping")

Installation

go install github.com/coinbase/redisbetween

Usage

Usage: bin/redisbetween [OPTIONS] uri1 [uri2] ...
-localsocketprefix string
prefix to use for unix socket filenames (default "/var/tmp/redisbetween-")
-localsocketsuffix string
suffix to use for unix socket filenames (default ".sock")
-loglevel string
one of: debug, info, warn, error, dpanic, panic, fatal (default "info")
-network string
one of: tcp, tcp4, tcp6, unix or unixpacket (default "unix")
-pretty
pretty print logging
-statsd string
statsd address (default "localhost:8125")
-unlink
unlink existing unix sockets before listening
-healthcheck
start a background process to check the health of server connections
-healthcheckcycle
duration after which the healthcheck process should repeat itself (default 60s)
-healthcheckthreshold
count of consecutive healthcheck failures after which a server is declared unhealthy (default 3)
-idletimeout
how long can an inactive connection remain idle in the pool (default 0 meaning no timeout)

Each URI can specify the following settings as GET params:

  • minpoolsize sets the min connection pool size for this host. Defaults to 1
  • maxpoolsize sets the max connection pool size for this host. Defaults to 10
  • label optionally tags events and metrics for proxy activity on this host or cluster. Defaults to "" (disabled)
  • readtimeout timeout for reads to this upstream. Defaults to 5s
  • writetimeout timeout for writes to this upstream. Defaults to 5s
  • readonly every connection issues a READONLY command before entering the pool. Defaults to false
  • maxsubscriptions sets the max number of channels that can be subscribed to at one time. Defaults to 1.
  • maxblockers sets the max number of commands that can be blocking at one time. Defaults to 1.
  • idletimeout how long can an inactive connection remain idle in the pool. Default is the global level idletimeout or 0 (no timeout)

Example: ./redisbetween -unlink -pretty -loglevel debug redis://localhost:7001?maxsubscriptions=2&maxblockers=2

About

No description, website, or topics provided.

Resources

Security policy

Stars

32 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages