Try µWebSockets support in the Meteor. Pure speed, no memory leaks


#1

Greetings.

Let me introduce you to the µWebSockets support in Meteor:

If you want try it today, then follow this guide.

Prerequsites

Open your project:

cd your-project-root

If your project not a Git repository then initialize it:

git init

Installing

  1. Replace the ddp-server package, and install packages stream-server and stream-server-uws via Git:

    git submodule add https://github.com/mrauhu/ddp-server packages/ddp-server
    git submodule add https://github.com/mrauhu/stream-server packages/stream-server
    git submodule add https://github.com/mrauhu/stream-server-uws packages/stream-server-uws
    

    (optional) If your Meteor has version >= 1.7.1

    Checkout the commit b912a48 of patched ddp-server:

    cd packages/ddp-server
    git checkout b912a48
    cd ../..
    
  2. Pin the older version of the socket-stream-client package,
    for using native WebSocket instead of SockJS:

    meteor add socket-stream-client@=0.2.1
    
  3. Enable Stream Server with uWebSockets support:

    meteor add stream-server-uws
    

Usage

Run the Meteor:

meteor

Benchmark

Windows 8.1
CPU i5-5200U 2x2.20 GHz
RAM 16 GB

SockJS stream-server

Standard Meteor solution since 2012.

Memory usage for processing 10 000 method calls is critical — approximate 8 GB.

Method calls Time to process, s Calls per second
1 000 0.8 1 250
10 000 28 357
100 000 fatal error out of memory
1 000 000 fatal error out of memory

uWebSockets stream-server-uws

Method calls Time to process, s Calls per second
1 000 0.7 1 428
10 000 11.82 846
100 000 136.70 726
1 000 000 1 785.53 560

I hope the µWebSockets support will be useful to you.

Best wishes,
Sergey.


#2

Thanks Sergey, the numbers look very promising and are addressing a real problem with the memory consumption of the standard approach.

Well done!


#3

Impressive stuff! Looking forward to seeing where this goes.


#4

Very nice! And it’s good to see some kind of rough measure for how many calls a Meteor server can take per second.


#5

Interesting. Does it support all meteor version?


#6

@minhna I’m tested it on the Meteor 1.7.

The ddp-server has’t groundbreaking changes since the Meteor 1.5.

So it will work with Meteor version >= 1.5.

There is some changes in an interaction between ddp-client and ddp-server in versions from 1.4.4 to 1.5, so I not recommended try it on the Meteor 1.4.

If you wish to try on Meteor 1.4, you probably need to replace the original ddp-client with more recent version.


#7

By the doing these two

meteor add socket-stream-client@=0.2.1
meteor add stream-server-uws

are we replacing the ddp-server automatically?

how do we revert back if we don’t want to use it? Are we just going to remove the uws from package and ddp-client will kick in ?


#8

Step one of the install instructions replace ddp-server and stream-server

Meteor lets you overwrite ANY package by including a package of the same name in your packages directory, which is the technique used here


#9

How to revert back to the SockJS

  1. Disable Stream Server µWS:

    meteor remove stream-server-uws
    
  2. Remove pinned package via update:

    meteor update socket-stream-client
    
  3. (optional) Remove Git submodule with patched ddp-server:

    git submodule deinit -f -- packages/ddp-server
    rm -rf .git/modules/packages/ddp-server
    git rm -f packages/ddp-server
    

#10

Thanks for this. The benchmark looks a very nice performance bump.

What is the original goal here
a. Replace the old implementation
Or
b. Give developers options

What is the status of the PR now based on your communication with Meteor?


#11

Make Meteor great again.

In addition to use the µWebSockets, this pull request give developers the option to write a custom stream server and use it instead the standard SockJS-based stream-server.

Status unknown.

I made changes requested by @benjamn, but I didn’t receive any feedback from him after.


#12

Then add to project.

Meteor.loginWithPassword(‘xxxx@xxx.com’,‘xxxx’,function(err){
console.log(‘error’,err);
});

Show Error

I20180926-11:33:17.588(7)? Exception while invoking method ‘login’ TypeError: DiffSequence.diffMaps is not a function
I20180926-11:33:17.588(7)? at Session._diffCollectionViews (packages/ddp-server/livedata_server.js:762:18)
I20180926-11:33:17.589(7)? at packages/ddp-server/livedata_server.js:842:12
I20180926-11:33:17.589(7)? at Object.Meteor._noYieldsAllowed (packages/meteor.js:848:12)
I20180926-11:33:17.590(7)? at Session._setUserId (packages/ddp-server/livedata_server.js:840:12)
I20180926-11:33:17.590(7)? at MethodInvocation.setUserId [as _setUserId] (packages/ddp-server/livedata_server.js:677:14)
I20180926-11:33:17.591(7)? at MethodInvocation.setUserId (packages/ddp-common/method_invocation.js:92:10)
I20180926-11:33:17.592(7)? at AccountsServer.Ap._loginUser (packages/accounts-base/accounts_server.js:306:20)
I20180926-11:33:17.593(7)? at AccountsServer.Ap._attemptLogin (packages/accounts-base/accounts_server.js:360:12)
I20180926-11:33:17.593(7)? at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:545:21)
I20180926-11:33:17.593(7)? at currentArgumentChecker.withValue (packages/check/match.js:118:15)
I20180926-11:33:17.593(7)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20180926-11:33:17.594(7)? at Object._failIfArgumentsAreNotAllChecked (packages/check/match.js:116:43)
I20180926-11:33:17.613(7)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1780:18)
I20180926-11:33:17.613(7)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20180926-11:33:17.614(7)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20180926-11:33:17.614(7)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20180926-11:33:17.614(7)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20180926-11:33:17.615(7)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20180926-11:33:17.615(7)? at new Promise ()
I20180926-11:33:17.615(7)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20180926-11:33:17.616(7)? at packages/ddp-server/livedata_server.js:559:43


#13

@mahadoang, I had tried to reproduce your bug, but unfortunately all works.

How to check accounts-password

Prerequisites

  • Meteor 1.7
  • Git

Steps

  1. Create the Meteor project via:

    meteor create --full meteor-ddp-test
    cd meteor-ddp-test
    
  2. Add the Meteor Accounts Password login and the Accounts UI packages:

    meteor add accounts-password accounts-ui
    
  3. Install the custom ddp-server and stream-server-uws using guide in the first post.

  4. Create file imports/startup/server/users.js:

    Meteor.startup(() => {
      const Users = [];
    
      const users_roles = new Map([
        ['user', 'user'],
      ]);
    
      for (const [name, role] of users_roles) {
        Users.push({
          username: name,
          password: name,
          email: `${name}@example.com`,
          profile: { name },
          roles: [role],
        });
      }
    
      if (Meteor.users.find().count() > 0) {
        return;
      }
    
      for (const user of Users) {
        const id = Accounts.createUser(user);
    
        Meteor.users.update(id, { $set: { 'emails.0.verified': true } });
        /*
        if (typeof user.roles !== 'undefined'
          && user.roles.length > 0) {
          Roles.addUsersToRoles(id, user.roles);
        }
        */
        console.log(`Created user ${user.username}, id: `, id);
      }
    });
    
  5. Update imports/startup/server/index.js, add line:

    import './users.js';
    
  6. Update imports/ui/pages/home/home.js, add lines:

    Template.App_home.events({
      'click #login'(event) {
        Meteor.loginWithPassword(
          'user@example.com',
          'user',
          (error, result) => {
            if (error) {
              console.error('loginWithPassword()- error', error);
              return;
            }
            console.log('loginWithPassword()- result', result);
          }
        );
      }
    });
    
  7. Update imports/ui/pages/home/home.html:

    <template name="App_home">
      {{> hello}}
      {{> info}}
    
      {{! Check login }}
      {{! via `accounts-ui` package }}
      {{> loginButtons }}
      {{! via `Meteor.loginWithPassword()` }}
      {{#unless currentUser }}
        <button id="login">Login</button>
      {{/unless}}
    </template>
    
  8. Run Meteor:

    meteor
    

Now on the http://localhost:3000 you can login via:

  • accounts-ui Sign-in drop-down;
  • [Login] button via Meteor.loginWithPassword().

I hope this guide helps you.


#14

create –minimal

meteor create --minimal meteor-ddp-test

All Package

meteor@1.9.0                  # Shared foundation for all Meteor packages
static-html             # Define static page content in .html files
standard-minifier-css@1.4.1   # CSS minifier run for production mode
standard-minifier-js@2.3.4    # JS minifier run for production mode
es5-shim@4.8.0                # ECMAScript 5 compatibility for older browsers
ecmascript@0.11.1              # Enable ECMAScript2015+ syntax in app code
shell-server@0.3.1            # Server-side component of the `meteor shell` command
webapp@1.6.0                  # Serves a Meteor app over HTTP
server-render@0.3.1           # Support for server-side rendering

accounts-base@1.4.2
accounts-facebook@1.3.1
accounts-password@1.5.1
socket-stream-client@=0.2.1
stream-server-uws

client/main.js

import { Meteor } from 'meteor/meteor'

Meteor.startup(() => {
Meteor.loginWithPassword('test@test.com','11111',function(err){
    console.log('error',err);
    console.log(Meteor.user());
});
});

server/main.js

import { Meteor } from "meteor/meteor";
import { onPageLoad } from "meteor/server-render";

Meteor.startup(() => {
  // Code to run on server startup.
  console.log(`Greetings from ${module.id}!`);
});

onPageLoad(sink => {
  // Code to run on every request.
  sink.renderIntoElementById(
    "server-render-target",
    `Server time: ${new Date}`
  );
});

Meteor.startup(function(){
  if (Meteor.users.find().count() === 0) {
    Accounts.createUser({
        email:'test@test.com',
        password:'11111',
        profile:{
          full_name:'Test DDP'
        }
    });
  }
});

the project work as expected after inititialized.
But got error after added these 2 plugin

socket-stream-client@=0.2.1
stream-server-uws

error message as below

I20180926-15:34:54.729(7)? Exception while invoking method 'login' TypeError: DiffSequence.diffMaps is not a function
I20180926-15:34:54.731(7)?     at Session._diffCollectionViews (packages/ddp-server/livedata_server.js:762:18)
I20180926-15:34:54.732(7)?     at packages/ddp-server/livedata_server.js:842:12
I20180926-15:34:54.732(7)?     at Object.Meteor._noYieldsAllowed (packages/meteor.js:848:12)
I20180926-15:34:54.732(7)?     at Session._setUserId (packages/ddp-server/livedata_server.js:840:12)
I20180926-15:34:54.732(7)?     at MethodInvocation.setUserId [as _setUserId] (packages/ddp-server/livedata_server.js:677:14)
I20180926-15:34:54.733(7)?     at MethodInvocation.setUserId (packages/ddp-common/method_invocation.js:92:10)
I20180926-15:34:54.733(7)?     at AccountsServer.Ap._loginUser (packages/accounts-base/accounts_server.js:306:20)
I20180926-15:34:54.733(7)?     at AccountsServer.Ap._attemptLogin (packages/accounts-base/accounts_server.js:360:12)
I20180926-15:34:54.733(7)?     at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:545:21)
I20180926-15:34:54.734(7)?     at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1783:12)
I20180926-15:34:54.734(7)?     at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20180926-15:34:54.734(7)?     at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20180926-15:34:54.734(7)?     at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20180926-15:34:54.735(7)?     at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20180926-15:34:54.735(7)?     at Promise (packages/ddp-server/livedata_server.js:715:46)
I20180926-15:34:54.745(7)?     at new Promise (<anonymous>)
I20180926-15:34:54.746(7)?     at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20180926-15:34:54.746(7)?     at packages/ddp-server/livedata_server.js:559:43

#15

@mahadoang thanks for reporting and detail guide.

Problem was introduced in:

ddp-server: replace usage of Object with Map & Set
https://github.com/meteor/meteor/commit/79ae1849f5846e207ed4dfa49831ba409252fd03

I’m patched the ddp-server for full compatibility with current and old Meteor versions.

Installing patch

Meteor >= 1.5.0 and < 1.7.1

Update the patched ddp-server to the latest version:

git submodule update --remote packages/ddp-server

Meteor >= 1.7.1

Checkout the commit b912a48:

cd packages/ddp-server
git checkout b912a48
cd ../..

Also, I’m updated the guide.


#16

@mrauhu Nice job on the PR. Any idea when it will be pushed to official Meteor Repo?

Don’t want to add to a production application in its current form.


#17

@mrauhu after update is ok.
Thank you.


#18

@primerlabs I have no idea.

If you are worried about what you are installing from repositories, you can always look at the commit history.

Packages

stream-server-uws

StreamServerUWS is well documented ECMAScript 2015+ class with three methods:

  • constructor() — creating µWebSockets server based on the internal Meteor’s HTTP WebApp.server;
  • connection(socket, req) — getting HTTP headers and calling registered callbacks on an every new connection;
  • register(callback) — registration of a callback and call it for all connected clients.

old ddp-server == new ddp-server + SockJS stream-server

I made no breaking changes, differences between old and new:

  1. SockJS stream_server.js is cut out from ddp-server as the separated package stream-server.
  2. Added loading of a custom stream server in ddp-server.
  3. Added event listener for the message.
  4. Used .map(), Object.assign(), Set() and then removed the underscore package from stream-server as @benjamn asked.

#19

Thanks for your reply. Will take a look at the code changes to understand more, then.


#20

@mrauhu after tried it. I got this error

=> Errors prevented startup:                  
   
   While selecting package versions:
   error: No version of ddp-server satisfies all constraints: @=2.2.0, @~2.1.2
   Constraints on package "ddp-server":
   * ddp-server@=2.2.0 <- top level
   * ddp-server@~2.1.2 <- top level
   * ddp-server@2.1.0 <- ddp 1.4.0 <- accounts-base 1.4.2 <- accounts-password
   1.5.0
   * ddp-server@2.1.0 <- ddp 1.4.0 <- accounts-password 1.5.0
   
=> Your application has errors. Waiting for file change.

Any solution to this ?? Thank’s