Skip to content

Commit fce5fe7

Browse files
committed
Trim per-request overhead in the server loop
Driven by eprof and perf under load (64 callers, pool of 16, HTTP test server via buoy): The backlog moves from an ETS table to an atomics array, with the ref reachable through a persistent_term; two ets:update_counter calls per request (6.8% of the profile) become two atomic ops. Per-request timeouts no longer create and cancel a BIF timer each (3.9%): the queue stores absolute deadlines and a single timer per server is armed to the earliest one. Replies never cancel it; an idle fire sweeps nothing and re-arms to the new minimum, so it fires at most once per timeout window. Server-side telemetry reads the enabled flag once per connection into the server state instead of hitting persistent_term four to five times per request (5.4% when disabled), and skips the iolist_size/byte_size measurement arguments when disabled. Casts already queued in the server mailbox are drained (up to 64) and written with one Protocol:send, cutting send NIF calls and syscalls under load. Socket messages are never reordered ahead of the casts that preceded them; UDP keeps one datagram per request. buoy benchmark (Linux, 8 client cores, telemetry off): shackle_tcp 222k -> 273k requests/s (-21% VM CPU per request), shackle_socket 261k -> 292k requests/s (-19%), combined with the socket:sendv change.
1 parent e1d333d commit fce5fe7

5 files changed

Lines changed: 234 additions & 156 deletions

File tree

include/shackle_defaults.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
-define(DEFAULT_INIT_OPTS, undefined).
55
-define(DEFAULT_CONNECT_TIMEOUT, 500).
66
-define(DEFAULT_MAX_RETRIES, 0).
7+
-define(MAX_CAST_BATCH, 64).
78
-define(DEFAULT_POOL_SIZE, 16).
89
-define(DEFAULT_POOL_STRATEGY, random).
910
-define(DEFAULT_PROTOCOL, shackle_tcp).

include/shackle_internal.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
-define(LOOKUP(Key, List), ?LOOKUP(Key, List, undefined)).
99
-define(LOOKUP(Key, List, Default), shackle_utils:lookup(Key, List, Default)).
1010
-define(MSG_CONNECT, connect).
11+
-define(MSG_SWEEP, sweep).
1112
-define(SERVER, shackle_server).
1213
-define(SUPERVISOR, shackle_sup).
1314

src/shackle_backlog.erl

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@
33
%% internal
44
-export([
55
check/3,
6-
check/4,
76
decrement/2,
8-
decrement/3,
97
delete/1,
10-
delete/2,
11-
new/1,
128
new/2,
13-
table_name/1
9+
ref/1,
10+
reset/2
1411
]).
1512

16-
-define(DEFAULT_DECREMENT, -1).
17-
-define(DEFAULT_INCREMENT, 1).
18-
1913
%% types
2014
-type backlog_size() :: pos_integer() | infinity.
2115

@@ -24,73 +18,61 @@
2418
]).
2519

2620
%% internal
27-
-spec check(shackle:table(), shackle_server:id(), backlog_size()) ->
28-
boolean().
29-
30-
check(Table, ServerId, BacklogSize) ->
31-
check(Table, ServerId, BacklogSize, ?DEFAULT_INCREMENT).
32-
33-
-spec check(shackle:table(), shackle_server:id(), backlog_size(), pos_integer()) ->
21+
-spec check(atomics:atomics_ref(), shackle_server:id(), backlog_size()) ->
3422
boolean().
3523

36-
check(_Table, _ServerId, infinity, _Increment) ->
24+
check(_Ref, _ServerId, infinity) ->
3725
true;
38-
check(Table, ServerId, BacklogSize, Increment) ->
39-
case increment(Table, ServerId, BacklogSize, Increment) of
40-
[BacklogSize, BacklogSize] ->
41-
false;
42-
[_, Value] when Value =< BacklogSize ->
43-
true
26+
check(Ref, {_PoolName, Index}, BacklogSize) ->
27+
case atomics:add_get(Ref, Index, 1) of
28+
Value when Value =< BacklogSize ->
29+
true;
30+
_Value ->
31+
atomics:sub(Ref, Index, 1),
32+
false
4433
end.
4534

46-
-spec decrement(shackle:table(), shackle_server:id()) ->
47-
non_neg_integer().
48-
49-
decrement(Table, ServerId) ->
50-
decrement(Table, ServerId, ?DEFAULT_DECREMENT).
51-
52-
-spec decrement(shackle:table(), shackle_server:id(), neg_integer()) ->
53-
non_neg_integer().
54-
55-
decrement(Table, ServerId, Decrement) ->
56-
ets:update_counter(Table, ServerId, {2, Decrement, 0, 0}).
35+
-spec decrement(atomics:atomics_ref(), shackle_server:id()) ->
36+
integer().
37+
38+
decrement(Ref, {_PoolName, Index}) ->
39+
case atomics:sub_get(Ref, Index, 1) of
40+
Value when Value < 0 ->
41+
%% reset or infinity backlog: never go below empty
42+
atomics:add(Ref, Index, 1),
43+
0;
44+
Value ->
45+
Value
46+
end.
5747

5848
-spec delete(shackle_pool:name()) ->
5949
ok.
6050

6151
delete(PoolName) ->
62-
ets:delete(table_name(PoolName)),
52+
persistent_term:erase({?MODULE, PoolName}),
6353
ok.
6454

65-
-spec delete(shackle_pool:name(), shackle_server:id()) ->
55+
-spec new(shackle_pool:name(), shackle_pool:pool_size()) ->
6656
ok.
6757

68-
delete(PoolName, ServerId) ->
69-
ets:delete(table_name(PoolName), ServerId),
58+
new(PoolName, PoolSize) ->
59+
Ref = atomics:new(PoolSize, []),
60+
persistent_term:put({?MODULE, PoolName}, Ref),
7061
ok.
7162

72-
-spec new(shackle_pool:name()) ->
73-
ok.
74-
75-
new(PoolName) ->
76-
Table = ets:new(table_name(PoolName), shackle_utils:ets_options()),
77-
ets:give_away(Table, whereis(shackle_ets_manager), undefined),
78-
ok.
63+
-spec ref(shackle_pool:name()) ->
64+
atomics:atomics_ref().
7965

80-
-spec new(shackle_pool:name(), shackle_server:id()) ->
81-
ok.
66+
ref(PoolName) ->
67+
persistent_term:get({?MODULE, PoolName}).
8268

83-
new(PoolName, ServerId) ->
84-
ets:insert(table_name(PoolName), {ServerId, 0}),
69+
-spec reset(shackle_pool:name(), shackle_server:id()) ->
8570
ok.
8671

87-
-spec table_name(shackle_pool:name()) ->
88-
shackle:table().
89-
90-
table_name(PoolName) ->
91-
list_to_atom("shackle_backlog_" ++ atom_to_list(PoolName)).
92-
93-
%% private
94-
increment(Table, ServerId, BacklogSize, Increment) ->
95-
UpdateOps = [{2, 0}, {2, Increment, BacklogSize, BacklogSize}],
96-
ets:update_counter(Table, ServerId, UpdateOps).
72+
reset(PoolName, {_PoolName, Index}) ->
73+
case persistent_term:get({?MODULE, PoolName}, undefined) of
74+
undefined ->
75+
ok;
76+
Ref ->
77+
atomics:put(Ref, Index, 0)
78+
end.

src/shackle_pool.erl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ cleanup_ets(_Name, _OptionsRec) ->
132132
cleanup_foil(Name, #pool_options {pool_size = PoolSize}) ->
133133
foil:delete(?MODULE, Name),
134134
[foil:delete(?MODULE, {Name, N}) || N <- lists:seq(1, PoolSize)],
135-
foil:delete(?MODULE, {Name, backlog}),
136135
foil:load(?MODULE).
137136

138137
options(Name) ->
@@ -176,7 +175,7 @@ server(Name, #pool_options {
176175
ServerId = server_id(Name, PoolSize, PoolStrategy),
177176
case shackle_status:active(ServerId) of
178177
true ->
179-
{ok, Backlog} = shackle_pool_foil:lookup({Name, backlog}),
178+
Backlog = shackle_backlog:ref(Name),
180179
{ok, ServerName} = shackle_pool_foil:lookup(ServerId),
181180
case shackle_backlog:check(Backlog, ServerId, BacklogSize) of
182181
true ->
@@ -199,7 +198,7 @@ server_id(Name, PoolSize, round_robin) ->
199198
{Name, ServerId}.
200199

201200
setup(Name, #pool_options {pool_size = PoolSize} = OptionsRec) ->
202-
shackle_backlog:new(Name),
201+
shackle_backlog:new(Name, PoolSize),
203202
shackle_status:new(Name, PoolSize),
204203
setup_ets(Name, OptionsRec),
205204
setup_foil(Name, OptionsRec).
@@ -213,7 +212,6 @@ setup_foil(Name, #pool_options {pool_size = PoolSize} = OptionsRec) ->
213212
foil:insert(?MODULE, Name, OptionsRec),
214213
[foil:insert(?MODULE, {Name, N}, server_name(Name, N)) ||
215214
N <- lists:seq(1, PoolSize)],
216-
foil:insert(?MODULE, {Name, backlog}, shackle_backlog:table_name(Name)),
217215
foil:load(?MODULE).
218216

219217
server_name(Name, Index) ->

0 commit comments

Comments
 (0)