Browse Source

Expose more session control knobs to the operator

patch-2
Neil Booth 6 years ago
parent
commit
91576490c1
  1. 39
      docs/environment.rst
  2. 4
      electrumx/server/env.py
  3. 5
      electrumx/server/session.py
  4. 10
      tests/server/test_env.py

39
docs/environment.rst

@ -248,12 +248,15 @@ raise them.
.. envvar:: COST_SOFT_LIMIT
.. envvar:: COST_HARD_LIMIT
.. envvar:: REQUEST_SLEEP
.. envvar:: INITIAL_CONCURRENT
Session cost soft and hard limits as integers. The default values are :const:`500`
and :const:`10,000` respectively.
All values are integers. :envar:`COST_SOFT_LIMIT` defaults to :const:`1,000`,
:envvar:`COST_HARD_LIMIT` to :const:`10,000`, :envvar:`REQUEST_SLEEP` to :const:`2,500`
milliseconds, and :envvar:`INITIAL_CONCURRENT` to :const:`10` concurrent requests.
The server prices each request made to it based upon an estimate of the resources needed
to process it . Factors include whether the request uses bitcoind, how much bandwidth
to process it. Factors include whether the request uses bitcoind, how much bandwidth
it uses, and how hard it hits the databases.
To set a base for the units, a :func:`blockchain.scripthash.subscribe` subscription to
@ -261,14 +264,28 @@ raise them.
considering the bandwidth consumed. :func:`server.ping` is costed at :const:`0.1`.
As the total cost of a session goes over the soft limit, its requests start to be
throttled in two ways. First, each request sleeps a little before being handled.
Second, the number of requests that the server will handle concurrently reduces. Both
effects increase as the hard limit is approached, at which point the session is
disconnected.
So non-abusive sessions can continue to be served, a session's cost gradually decays
over time. Subscriptions have an ongoing servicing cost, so the decay is slower as the
number of subscriptions increases.
throttled in two ways. First, the number of requests for that session that the server
will process concurrently is reduced. Second, each request starts to sleep a little
before being handled.
Before throttling starts, the server will process up to :envvar:`INITIAL_CONCURRENT`
requests concurrently without sleeping. As the session cost ranges from
:envar:`COST_SOFT_LIMIT` to :envvar:`COST_HARD_LIMIT`, concurrency drops linearly to
zero and each request's sleep time increases linearly up to :envvar:`REQUEST_SLEEP`
milliseconds. Once the hard limit is reached, the session is disconnected.
In order that non-abusive sessions can continue to be served, a session's cost gradually
decays over time. Subscriptions have an ongoing servicing cost, so the decay is slower
as the number of subscriptions increases.
If a session disconnects, ElectrumX continues to associate its cost with its IP address,
so if it immediately reconnects it will re-acquire its previous cost allocation.
A server operator should experiment with different values according to server loads. It
is not necessarily true that e.g. having a low soft limit, decreasing concurrency and
increasing sleep will help handling heavy loads, as it will also increase the backlog of
requests the server has to manage in memory. It will also give a much worse experience
for genuine connections.
.. envvar:: BANDWIDTH_UNIT_COST

4
electrumx/server/env.py

@ -68,9 +68,11 @@ class Env(EnvBase):
# Server limits to help prevent DoS
self.max_send = self.integer('MAX_SEND', self.coin.DEFAULT_MAX_SEND)
self.max_sessions = self.sane_max_sessions()
self.cost_soft_limit = self.integer('COST_SOFT_LIMIT', 500)
self.cost_soft_limit = self.integer('COST_SOFT_LIMIT', 1000)
self.cost_hard_limit = self.integer('COST_HARD_LIMIT', 10000)
self.bw_unit_cost = self.integer('BANDWIDTH_UNIT_COST', 5000)
self.initial_concurrent = self.integer('INITIAL_CONCURRENT', 10)
self.request_sleep = self.integer('REQUEST_SLEEP', 2500)
self.session_timeout = self.integer('SESSION_TIMEOUT', 600)
self.drop_client = self.custom("DROP_CLIENT", None, re.compile)
self.blacklist_url = self.default('BLACKLIST_URL', self.coin.BLACKLIST_URL)

5
electrumx/server/session.py

@ -484,12 +484,17 @@ class SessionManager(object):
session_class.cost_hard_limit = self.env.cost_hard_limit
session_class.cost_decay_per_sec = session_class.cost_hard_limit / 10000
session_class.bw_cost_per_byte = 1.0 / self.env.bw_unit_cost
session_class.cost_sleep = self.env.request_sleep / 1000
session_class.initial_concurrent = self.env.initial_concurrent
self.logger.info(f'max session count: {self.env.max_sessions:,d}')
self.logger.info(f'session timeout: {self.env.session_timeout:,d} seconds')
self.logger.info(f'session cost hard limit {self.env.cost_hard_limit:,d}')
self.logger.info(f'session cost soft limit {self.env.cost_soft_limit:,d}')
self.logger.info(f'bandwidth unit cost {self.env.bw_unit_cost:,d}')
self.logger.info(f'request sleep {self.env.request_sleep:,d}ms')
self.logger.info(f'initial concurrent {self.env.initial_concurrent:,d}')
self.logger.info(f'max response size {self.env.max_send:,d} bytes')
if self.env.drop_client is not None:
self.logger.info('drop clients matching: {}'

10
tests/server/test_env.py

@ -201,7 +201,15 @@ def test_COST_HARD_LIMIT():
def test_COST_SOFT_LIMIT():
assert_integer('COST_SOFT_LIMIT', 'cost_soft_limit', 500)
assert_integer('COST_SOFT_LIMIT', 'cost_soft_limit', 1000)
def test_INITIAL_CONCURRENT():
assert_integer('INITIAL_CONCURRENT', 'initial_concurrent', 10)
def test_REQUEST_SLEEP():
assert_integer('REQUEST_SLEEP', 'request_sleep', 2500)
def test_BANDWIDTH_UNIT_COST():

Loading…
Cancel
Save