phpv8 / v8js

V8 Javascript Engine for PHP — This PHP extension embeds the Google V8 Javascript Engine
http://pecl.php.net/package/v8js
MIT License
1.83k stars 200 forks source link

Scaling V8Js #279

Closed tlovett1 closed 7 years ago

tlovett1 commented 7 years ago

I'm building a high performance web application using:

CentOS 7.2.1511 PHP 7.0.13 FPM V8JS: 1.0.0 V8: 4.9.0

The application executes Node.js React components via PHP. We've built the application using V8Js and without. We siege tested both applications and came up with the following results:

With V8Js:

Transactions 1659 Availability (%) 99.58 Elapsed time (secs) 59.36 Data transferred (MB) 132.77 Response time (secs) 1.54 Transaction rate (trans/sec) 27.95 Throughput (MB/sec) 2.24 Concurrency 43.17 Successful transactions 1200 Failed transactions 7 Longest transaction 8.18 Shortest transaction 0

Without V8Js:

Transactions 6441 Availability (%) 100 Elapsed time (secs) 59.99 Data transferred (MB) 122.2 Response time (secs) 0.21 Transaction rate (trans/sec) 107.37 Throughput (MB/sec) 2.04 Concurrency 22.04 Successful transactions 4218 Failed transactions 0 Longest transaction 3.04 Shortest transaction 0

When testing with V8Js we are seeing a lot of errors like this in the logs:

PHP Fatal error: v8::Isolate::Dispose() Disposing the isolate that is entered by a thread. in Unknown on line 0

Does anyone have any suggestions on how to improve the performance of V8Js at scale? We'd love to use this in production. I realize it won't be the most performant solution but believe it could do much better.

If anyone is curious, the code is here.

stesie commented 7 years ago

Hey, thanks for writing, this is an interesting project.

The Docker base image you're using doesn't use snapshots (see the snapshot=off part in https://hub.docker.com/r/virusvn/docker-centos-v8js/~/dockerfile/), these cost you at least 50ms in startup time.

... and you also might want to use customized snapshots to pre-load react. Maybe see http://stesie.github.io/2016/02/snapshot-performance if you haven't already

Regarding stability, v8js 1.0.0 is pretty old. And it was the first version for PHP 7.0; newer versions might have that already fixed.

So much for now, I'm definitely interested And of course it shouldn't crash; yet haven't investigated that further

stesie commented 7 years ago

So regarding the PHP Fatal error: v8::Isolate::Dispose() Disposing the isolate that is entered by a thread. in Unknown on line 0 error, I can reproduce this under one condition: it reproducably happens if I stop siege ... then it obviously cancels all the http requests ... in turn causing fpm to stop php ... and hence v8js to stop v8 engine (and as the php script spends most time in v8 the error is reported).

As a proof I simply added ignore_user_abort(true); to wp-config.php, which generally is a bad idea, ... yet if I do it all the error messages are gone.

And v8js does not (yet) specially handle user aborts...

So could you please check if the error messages you're seeing also disappear if you let your php ignore user aborts ... or if you see the error in different situations also.

virgofx commented 7 years ago

Just a follow up on performance -- One of our production applications uses v8js 1.3.3 with V8 5.4.x using full snapshots of React bundle that is loaded from in-memory cache (Redis) using a custom PHP Wrapper and we are still achieving very high throughput/performance. More info is found on this old thread here:

https://github.com/phpv8/v8js/issues/205

The psuedo code for getting things quickly is:

if ($cacheBlob === false) {
    // First time only
    $cacheBlob = V8Js::createSnapshot($cacheBlob);

    // Save to cache
    $this->cache->save('bundle-cache-snapshot', $cacheBlob, 86400);
} else {
    $this->v8js = new V8Js('PHP', [], [], true, $cacheBlob);
}

// Override random - bugs with react and other stuff
$this->v8js->random = function ()
{
    return mt_rand() / mt_getrandmax();
};
$this->v8js->executeString('Math.random = PHP.random;');

// Now render application comopnents as necessary (We actually
// cache these based on a hash of the properties since most 
// all components are PureComponents) so a majority of the time we
// don't even hit the V8JS engine.
// ... $this->v8js->executeString('ReactDOMServer.renderToString(....)
tlovett1 commented 7 years ago

Thanks guys. I'll update the thread when I'm able to test all of this.

tlovett1 commented 7 years ago

Anyone have any insight on this error? Here is my Dockerfile:

FROM centos:7
RUN yum -y update

# Install Nginx Latest
ADD repos/nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum -y install nginx

RUN rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

# Install build tools
RUN yum -y install gcc-c++ pcre-devel zlib-devel make unzip 
RUN rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

# Install PHP 7
RUN yum -y install --enablerepo=webtatic php70w php70w-common php70w-fpm php70w-cli php70w-opcache php70w-pear php70w-devel php70w-intl php70w-mbstring php70w-mcrypt

# Install Git latest  
RUN yum -y install curl-devel expat-devel gettext-devel openssl openssl-devel zlib-devel bzip2
RUN yum -y install gcc perl-ExtUtils-MakeMaker

RUN cd /usr/src && \
    git clone https://github.com/git/git
RUN cd /usr/src/git && make prefix=/usr/local/git all && make prefix=/usr/local/git install
RUN yum -y remove git

# Use new Git
env PATH /usr/local/git/bin:$PATH

# Install Depot Tools
RUN cd /usr/src && git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
env PATH /usr/src/depot_tools:$PATH

# Install v8
RUN cd /usr/src && fetch v8
RUN cd /usr/src && gclient sync
RUN cd /usr/src/v8 && make native library=shared -j 4

RUN cp -R /usr/src/v8/out/native/lib.target/lib* /lib64/
RUN cp /usr/src/v8/out/native/obj.target/tools/gyp/libv8_libplatform.a /usr/lib64/
RUN cp -R /usr/src/v8/include /usr/local
RUN cp /usr/src/v8/out/native/natives_blob.bin /usr/local/lib
RUN cp /usr/src/v8/out/native/snapshot_blob.bin /usr/local/lib

# Install v8js
RUN echo "/usr/lib64" | pecl install v8js-1.3.3

ENV NO_INTERACTION 1
RUN echo extension=v8js.so > /etc/php.d/v8js.ini
RUN php -m | grep v8

Error message: cp: cannot stat '/usr/src/v8/out/native/obj.target/tools/gyp/libv8_libplatform.a': No such file or directory

virgofx commented 7 years ago

Looks like you may have older installation commands. From the updated Linux documentation it has some updated instructions -- maybe try to integrate these :

https://github.com/phpv8/v8js/blob/php7/README.Linux.md

# Build (with internal snapshots)
export GYPFLAGS="-Dv8_use_external_startup_data=0"

# Force gyp to use system-wide ld.gold
export GYPFLAGS="${GYPFLAGS} -Dlinux_use_bundled_gold=0"

# Compile V8 (using up to 8 CPU cores, requires a lot of RAM, adapt as needed)
make native library=shared snapshot=on -j8

# Install to /opt/v8
sudo mkdir -p /opt/v8/{lib,include}
sudo cp out/native/lib.target/lib*.so /opt/v8/lib/
sudo cp -R include/* /opt/v8/include

# Fix libv8.so's RUNPATH header
sudo chrpath -r '$ORIGIN' /opt/v8/lib/libv8.so

# Install libv8_libplatform.a (V8 >= 5.2.51)
echo -e "create /opt/v8/lib/libv8_libplatform.a\naddlib out/native/obj.target/src/libv8_libplatform.a\nsave\nend" | sudo ar -M

# ... same for V8 < 5.2.51, libv8_libplatform.a is built in tools/gyp directory
echo -e "create /opt/v8/lib/libv8_libplatform.a\naddlib out/native/obj.target/tools/gyp/libv8_libplatform.a\nsave\nend" | sudo ar -M
tlovett1 commented 7 years ago

Ok, got the latest V8Js working. I'm getting this error when I try to take a heap shapshot:

Warning: V8Js::createSnapshot(): Failed to create V8 heap snapshot. Check $embed_source for errors.

virgofx commented 7 years ago

You're getting closer. At this point .. there is just a few issues with your bundle that your sending into to do a snapshot. So it's purely a javascript bundle issue at this point.

Remember there are some known compatibility issues with specific functions in React like Math.random() and a few other things. You need to override those and ensure you're including proper global overrides.

In terms of testing .. need to slice and dice which components of bundle are causing issues until you find the error. Like I said .. the usual culprits are Math.random and require

tlovett1 commented 7 years ago

How did I check $embed_source?

virgofx commented 7 years ago

$embed_source is your JavaScript that you're attempting to bundle as a snapshot .. this is what you need to debug

stesie commented 7 years ago

@tlovett1 keep in mind, that the source code you pass to createSnapshot must not reference any of V8Js' built-ins, i.e. print, var_dump et al + any stuff you might want to supply via PHP (later on). So the idea is to just pass the react stuff ... and then just invoke that code.

Or to put it differently make sure to still execute the actual code from your js/src/server.js to executeString; just react itself may go into the snapshot.

moon0326 commented 6 years ago

@tlovett1 I know this thread is old and closed. Did you get performance you need? Could you share some numbers please?

vertic4l commented 5 years ago

I'm also highly interested in some numbers from 2018 :)

BenMorel commented 5 years ago

Same here. @tlovett1 would you mind sharing your experience with us? You got quite a lot of support here, it would be nice if you could give back a bit!

laikee99 commented 3 years ago

Just a follow up on performance -- One of our production applications uses v8js 1.3.3 with V8 5.4.x using full snapshots of React bundle that is loaded from in-memory cache (Redis) using a custom PHP Wrapper and we are still achieving very high throughput/performance. More info is found on this old thread here:

205

The psuedo code for getting things quickly is:

if ($cacheBlob === false) {
    // First time only
    $cacheBlob = V8Js::createSnapshot($cacheBlob);

    // Save to cache
    $this->cache->save('bundle-cache-snapshot', $cacheBlob, 86400);
} else {
    $this->v8js = new V8Js('PHP', [], [], true, $cacheBlob);
}

// Override random - bugs with react and other stuff
$this->v8js->random = function ()
{
    return mt_rand() / mt_getrandmax();
};
$this->v8js->executeString('Math.random = PHP.random;');

// Now render application comopnents as necessary (We actually
// cache these based on a hash of the properties since most 
// all components are PureComponents) so a majority of the time we
// don't even hit the V8JS engine.
// ... $this->v8js->executeString('ReactDOMServer.renderToString(....)

But I wonder how to create variable $cacheBlob???

stesie commented 3 years ago

@sddwt sticking with pseudo-code it's something like $cacheBlob = $this->cache->load('bundle-cache-snapshot')

... the idea is to run V8Js::createSnapshot once and keep its contents $somewhere (local file, APCu, redis, memcache, ...) ... then use that snapshot blob (named $cacheBlob in the quoted example) and provide it to \V8Js constructor.