Experimenting with Nginx worker_processes
February 14, 2019
0 comments Web development, Nginx, MacOSX, Linux
I have Nginx 1.15.8 installed with Homebrew on my macOS. By default the /usr/local/etc/nginx/nginx.conf
it set to...:
worker_processes 1;
But, from the documentation, it says:
"The optimal value depends on many factors including (but not limited to) the number of CPU cores, the number of hard disk drives that store data, and load pattern. When one is in doubt, setting it to the number of available CPU cores would be a good start (the value “auto” will try to autodetect it)." (bold emphasis mine)
What is the ideal number for me? The performance of Nginx on my laptop doesn't really matter. But for my side-projects it's important to have a fast Nginx since it serves static HTML and lots of static assets. However, on my personal servers I have a bunch of other resource hungry stuff going on that I know is more likely to need the resources, like Elasticsearch and uwsgi
.
To figure this out, I wrote a benchmark program that requested a small index.html
about 10,000 times across 10 concurrent clients with hey.
hey -n 10000 -c 10 http://peterbecom.local/plog/variable_cache_control/awspa
I ran this 10 times between changing the worker_processes
in the nginx.conf
file. Here's the output:
1 WORKER PROCESSES BEST : 13,607.24 reqs/s 2 WORKER PROCESSES BEST : 17,422.76 reqs/s 3 WORKER PROCESSES BEST : 18,886.60 reqs/s 4 WORKER PROCESSES BEST : 19,417.35 reqs/s 5 WORKER PROCESSES BEST : 19,094.18 reqs/s 6 WORKER PROCESSES BEST : 19,855.32 reqs/s 7 WORKER PROCESSES BEST : 19,824.86 reqs/s 8 WORKER PROCESSES BEST : 20,118.25 reqs/s
Or, as a graph:
Now note, this is done here on my MacBook Pro. Not on my Ubuntu DigitalOcean servers. For now, I just want to get a feeling for how these numbers correlate.
Conclusion
The benchmark isn't good enough. The numbers are pretty stable but I'm doing this on my laptop with multiple browsers idling, Slack, and Spotify running. Clearly, the throughput goes up a bit when you allocate more workers but if anything can be learned from this, start with going beyond 1 for a quick fix and from there start poking and more exhaustive benchmarks. And don't forget, if you have time to go deeper on this, to look at the combination of worker_connections
and worker_processes
.
create-react-app, SCSS, and Bulmaswatch
February 12, 2019
2 comments Web development, React, JavaScript
1. Create a create-react-app
first:
create-react-app myapp
2. Enter it and install node-sass
and bulmaswatch
cd myapp
yarn add bulma bulmaswatch node-sass
3. Edit the src/index.js
to import index.scss
instead:
-import "./index.css";
+import "./index.scss";
4. "Rename" the index.css
file:
git rm src/index.css
touch src/index.scss
git add src/index.scss
5. Now edit the src/index.scss
to look like this:
@import "node_modules/bulmaswatch/darkly/bulmaswatch";
This assumes your favorite theme was the darkly
one. You can obviously change that later.
6. Run the app:
BROWSER=none yarn start
7. Open the browser at http://localhost:3000
That's it! However, the create-react-app
default look doesn't expose any of the cool stuff that Bulma can style. So let's rewrite our src/App.js
by copying the minimal starter HTML from the Bulma documentation. So make the src/App.js
component look something like this:
class App extends Component {
render() {
return (
<section className="section">
<div className="container">
<h1 className="title">Hello World</h1>
<p className="subtitle">
My first website with <strong>Bulma</strong>!
</p>
</div>
</section>
);
}
}
Now it'll look like this:
Yes, it's not much but it's a great start. Over to you to take this to infinity and beyond!
Not So Secret Sauce
In the rushed instructions above the choice of theme was darkly
. But what you need to do next is go to https://jenil.github.io/bulmaswatch/, click around and eventually pick the one you like. Suppose you like spacelab
, then you just change that @import ...
line to be:
@import "node_modules/bulmaswatch/spacelab/bulmaswatch";
TEMPORARY:
h1 { color: red; font-size: 5em; }
TEST CHANGE 3.
Optimize DOM selector lookups by pre-warming by selectors' parents
February 11, 2019
0 comments Web development, Node, Web Performance, JavaScript
tl;dr; minimalcss 0.8.2 introduces a 20% post-processing optimization by lumping many CSS selectors to their parent CSS selectors as a pre-emptive cache.
In minimalcss the general core of it is that it downloads a DOM tree, as HTML, parses it and parses all the CSS stylesheets associated. These might be from <link ref="stylesheet">
or <style>
tags.
Once the CSS stylesheets are turned into an AST it loops over each and every CSS selector and asks a simple question; "Does this CSS selector exist in the DOM?". The equivalent is to open your browser's Web Console and type:
>>> document.querySelectorAll('div.foo span.bar b').length > 0 false
For each of these lookups (which is done with cheerio
by the way), minimalcss
reduces the CSS, as an AST, and eventually spits the AST back out as a CSS string. The only problem is; it's slow. In the case of view-source:https://semantic-ui.com/
in the CSS it uses, there are 6,784
of them. What to do?
First of all, there isn't a lot you can do. This is the work that needs to be done. But one thing you can do is be smart about which selectors you look at and use a "decision cache" to pre-emptively draw conclusions. So, if this is what you have to check:
#example .alternate.stripe
#example .theming.stripe
#example .solid .column p b
#example .solid .column p
As you process the first one you extract that the parent CSS selector is #example
and if that doesn't exist in the DOM, you can efficiently draw conclusion about all preceeding selectors that all start with #example ...
. Granted, if they call exist you will pay a penalty of doing an extra lookup. But that's the trade-off that this optimization is worth.
Check out the comments where I tested a bloated page that uses Semantic-UI before and after. Instead of doing 3,285 of these document.querySelector(selector)
calls, it's now able too come to the exact same conclusion with just 1,563 lookups.
Sadly, the majority of the time spent processing lies in network I/O and other overheads but this work did reduce something that used to take 6.3s (median) too 5.1s (median).
Displaying fetch() errors and unwanted responses in React
February 6, 2019
0 comments Web development, React, JavaScript
tl;dr; You can use error instanceof window.Response
to distinguish between fetch
exceptions and fetch
responses.
When you do something like...
const response = await fetch(URL);
...two bad things can happen.
- The XHR request fails entirely. I.e. there's not even a response with a HTTP status code.
- The response "worked" but the HTTP status code was not to your liking.
Either way, your React app needs to deal with this. Ideally in a not-too-clunky way. So here is one take on this challenge/opportunity which I hope can inspire you to extend it the way you need it to go.
The trick is to "clump" exceptions with responses. Then you can do this:
function ShowServerError({ error }) {
if (!error) {
return null;
}
return (
<div className="alert">
<h3>Server Error</h3>
{error instanceof window.Response ? (
<p>
<b>{error.status}</b> on <b>{error.url}</b>
<br />
<small>{error.statusText}</small>
</p>
) : (
<p>
<code>{error.toString()}</code>
</p>
)}
</div>
);
}
The greatest trick the devil ever pulled was to use if (error instanceof window.Reponse) {
. Then you know that error
thing is the outcome of THIS = await fetch(URL)
(or fetch(URL).then(THIS)
if you prefer). Another good trick the devil pulled was to be aware that exceptions, when asked to render in React does not naturally call its .toString()
so you have to do that yourself with {error.toString()}
.
This codesandbox demonstrates it quite well. (Although, at the time of writing, codesandbox will spew warnings related to testing React components in the console log. Ignore that.)
If you can't open that codesandbox, here's the gist of it:
React.useEffect(() => {
url &&
(async () => {
let response;
try {
response = await fetch(url);
} catch (ex) {
return setServerError(ex);
}
if (!response.ok) {
return setServerError(response);
}
// do something here with `await response.json()`
})(url);
}, [url]);
By the way, another important trick is to be subtle with how you put the try {
and } catch(ex) {
.
// DON'T DO THIS
try {
const response = await fetch(url);
if (!response.ok) {
setServerError(response);
}
// do something here with `await response.json()`
} catch (ex) {
setServerError(ex);
}
Instead...
// DO THIS
let response;
try {
response = await fetch(url);
} catch (ex) {
return setServerError(ex);
}
if (!response.ok) {
return setServerError(response);
}
// do something here with `await response.json()`
If you don't do that you risk catching other exceptions that aren't exclusively the fetch()
call. Also, notice the use of return
inside the catch block which will exit the function early leaving you the rest of the code (de-dented 1 level) to deal with the happy-path response object.
Be aware that the test if (!response.ok)
is simplistic. It's just a shorthand for checking if the "status in the range 200 to 299, inclusive". Realistically getting a response.status === 400
isn't an "error" really. It might just be a validation error hint from a server, and likely the await response.json()
will work and contain useful information. No need to throw up a toast or a flash message that the communication with the server failed.
Conclusion
The details matter. You might want to deal with exceptions entirely differently from successful responses with bad HTTP status codes. It's nevertheless important to appreciate two things:
-
Handle complete
fetch()
failures and feed your UI or your retry mechanisms. -
You can, in one component distinguish between a "successful"
fetch()
call and thrown JavaScript exceptions.
Concurrent download with hashin without --update-all
December 18, 2018
0 comments Web development, Python
Last week, I landed concurrent downloads in hashin
. The example was that you do something like...
$ time hashin -r some/requirements.txt --update-all
...and the whole thing takes ~2 seconds even though it that some/requirements.txt
file might contain 50 different packages, and thus 50 different PyPI.org lookups.
Just wanted to point out, this is not unique to use with --update-all
. It's for any list of packages. And I want to put some better numbers on that so here goes...
Suppose you want to create a requirements file for every package in the current virtualenv you might do it like this:
# the -e filtering removes locally installed packages from git URLs
$ pip freeze | grep -v '-e ' | xargs hashin -r /tmp/reqs.txt
Before running that I injected a little timer on each pypi.org download. It looked like this:
def get_package_data(package, verbose=False):
url = "https://pypi.org/pypi/%s/json" % package
if verbose:
print(url)
+ t0 = time.time()
content = json.loads(_download(url))
if "releases" not in content:
raise PackageError("package JSON is not sane")
+ t1 = time.time()
+ print(t1 - t0)
I also put a print around the call to pre_download_packages(lookup_memory, specs, verbose=verbose)
to see what the "total time" was.
The output looked like this:
▶ pip freeze | grep -v '-e ' | xargs python hashin.py -r /tmp/reqs.txt 0.22896194458007812 0.2900810241699219 0.2814369201660156 0.22658205032348633 0.24882292747497559 0.268247127532959 0.29332590103149414 0.23981380462646484 0.2930259704589844 0.29442572593688965 0.25312376022338867 0.34232664108276367 0.49491214752197266 0.23823285102844238 0.3221290111541748 0.28302812576293945 0.567702054977417 0.3089122772216797 0.5273139476776123 0.31477880477905273 0.6202089786529541 0.28571176528930664 0.24558186531066895 0.5810830593109131 0.5219211578369141 0.23252081871032715 0.4650228023529053 0.6127192974090576 0.6000659465789795 0.30976200103759766 0.44440698623657227 0.3135409355163574 0.638585090637207 0.297544002532959 0.6462509632110596 0.45389699935913086 0.34597206115722656 0.3462028503417969 0.6250648498535156 0.44159507751464844 0.5733060836791992 0.6739277839660645 0.6560370922088623 SUM TOTAL TOOK 0.8481268882751465
If you sum up all the individual times it would have become 17.3 seconds. It's 43 individual packages and 8 CPUs multiplied by 5 means it had to wait with some before downloading the rest.
Clearly, this works nicely.
How I performance test PostgreSQL locally on macOS
December 10, 2018
2 comments Web development, MacOSX, PostgreSQL
It's weird to do performance analysis of a database you run on your laptop. When testing some app, your local instance probably has 1/1000 the amount of realistic data compared to a production server. Or, you're running a bunch of end-to-end integration tests whose PostgreSQL performance doesn't make sense to measure.
Anyway, if you are doing some performance testing of an app that uses PostgreSQL one great tool to use is pghero. I use it for my side-projects and it gives me such nice insights into slow queries that I'm willing to live with the cost that it is to run it on a production database.
This is more of a brain dump of how I run it locally:
First, you need to edit your postgresql.conf
. Even if you used Homebrew to install it, it's not clear where the right config file is. Start psql
(on any database) and type this to find out which file is the one:
$ psql kintobench
kintobench=# show config_file;
config_file
-----------------------------------------
/usr/local/var/postgres/postgresql.conf
(1 row)
Now, open /usr/local/var/postgres/postgresql.conf
and add the following lines:
# Peterbe: From Pghero's configuration help. shared_preload_libraries = 'pg_stat_statements' pg_stat_statements.track = all
Now, to restart the server use:
▶ brew services restart postgresql
Stopping `postgresql`... (might take a while)
==> Successfully stopped `postgresql` (label: homebrew.mxcl.postgresql)
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)
The next thing you need is pghero
itself and it's easy to run in docker. So to start, you need Docker for mac installed. You also need to know the database URL. Here's how I ran it:
docker run -ti -e DATABASE_URL=postgres://peterbe:@host.docker.internal:5432/kintobench -p 8080:8080 ankane/pghero
Note the trick of peterbe:@host.docker.internal
because I don't use a password but inside the Docker container it doesn't know my terminal username. And the host.docker.internal
is so the Docker container can reach the PostgreSQL installed on the host.
Once that starts up you can go to http://localhost:8080
in a browser and see a listing of all the cumulatively slowest queries. There are other cool features in pghero
too that you can immediately benefit from such as hints about unused/redundent database indices.
Hope it helps!