Giter VIP home page Giter VIP logo

multilevel's Introduction

multilevel

Expose a levelDB over the network, to be used by multiple processes, with levelUp's API.

Build Status downloads

Usage

Expose a db on the server:

var multilevel = require('multilevel');
var net = require('net');
var level = require('level');

var db = level('/my/db');

net.createServer(function (con) {
  con.pipe(multilevel.server(db)).pipe(con);
}).listen(3000);

And connect to it from the client:

var multilevel = require('multilevel');
var net = require('net');

var db = multilevel.client();
var con = net.connect(3000);
con.pipe(db.createRpcStream()).pipe(con);

// asynchronous methods
db.get('foo', function () { /* */ });

// streams
db.createReadStream().on('data', function () { /* */ });

Compatibility and binary data

multilevel works in the browser too - via browserify - and has full support for binary data. For getting a connection between browser and server I recommend either websocket-stream, which treats binary data well, or engine.io-stream, which has websocket fallbacks.

When using a binary capable transport, require multilevel like this:

var multilevel = require('multilevel/msgpack');

This way it uses msgpack-stream instead of json-buffer which uses way less bandwidth - but needs a binary capable transport.

Plugins

You can also expose custom methods and sublevels with multilevel!

When using plugins, you must generate a manifest and require it in the client.

Here's an example:

// server.js
// create `db`
var level = require('level');
var multilevel = require('multilevel');
var db = level(PATH);

// extend `db` with a foo(cb) method
db.methods = db.methods || {};
db.methods['foo'] = { type: 'async' };
db.foo = function (cb) {
  cb(null, 'bar');
};

// now write the manifest to a file
multilevel.writeManifest(db, __dirname + '/manifest.json');

// then expose `db` via shoe or any other streaming transport.
var shoe = require('shoe');
var sock = shoe(function (stream) {
  stream.pipe(multilevel.server(db)).pipe(stream);
});
sock.install(http.createServer(/* ... */), '/websocket');

Manifests are generated using level-manifest, which doesn't only support async functions but e.g. streams as well. For more, check its README.

Then require the manifest on the client when bundling with browserify or in any other nodejs compatible environment.

// client.js
// instantiate a multilevel client with the `manifest.json` we just generated
var multilevel = require('multilevel');
var manifest = require('./manifest.json');
var db = multilevel.client(manifest);

// now pipe the db to the server
var stream = shoe('/websocket');
stream.pipe(db.createRpcStream()).pipe(stream);

// and you can call the custom `foo` method!
db.foo(function (err, res) {
  console.log(res); // => "bar"
});

Authentication

You do not want to expose every database feature to every user, you might e.g. only want to provide read-only access to some users.

Auth controls may be injected when creating the server stream.

In this example, allow read only access, unless logged in as root.

//server.js
var db = require('./setup-db'); //all your database customizations
var multilevel = require('multilevel');

//write out manifest
multilevel.writeManifest(db, __dirname + '/manifest.json');

shoe(function (stream) {
  stream.pipe(multilevel.server(db, {
    auth: function (user, cb) {
      if (user.name == 'root' && user.pass == 'toor') {
        //the data returned will be attached to the mulilevel stream
        //and passed to `access`
        cb(null, {name: 'root'});
      } else {
        cb(new Error('not authorized');
      }
    },
    access: function (user, db, method, args) {
      //`user` is the {name: 'root'} object that `auth`
      //returned. 

      //if not a privliged user...
      if (!user || user.name !== 'root') {
        //do not allow any write access
        if (/^put|^del|^batch|write/i.test(method)) {
          throw new Error('read-only access');
        }
      }        
    })
  })).pipe(stream);
});

// ...

The client authorizes by calling the auth method.

var multilevel = require('multilevel');
var shoe = require('shoe');

var stream = shoe();
var db = multilevel.client();
stream.pipe(db.createRpcStream()).pipe(stream);

db.auth({ name: 'root', pass: 'toor' }, function (err, data) {
  if (err) throw err
  //later, they can sign out, too.

  db.deauth(function (err) {
    //signed out!
  });
});

API

The exposed DB has the exact same API as levelUp, except

  • db#close() closes the connection, instead of the database.
  • the synchronous versions of db#isOpen() and db#isClose() tell you if you currently have a connection to the remote db.
  • events, like db.on("put", ...) are not emitted. If you need updates, you can use level-live-stream.

multilevel.server(db[, authOpts])

Returns a server-stream that exposes db, an instance of levelUp. authOpts is optional and should be of this form:

var authOpts = {
  auth: function (userData, cb) {
    //call back an error, if the user is not authorized.
  },
  access: function (userData, db, method, args) {
    //throw if this user is not authorized for this action.
  }
}

It is necessary that you create one server stream per client.

multilevel.writeManifest(db, path)

Synchronoulsy write db's manifest to path.

Also returns the manifest as json.

var db = multilevel.client([manifest])

Return a new client db. manifest may optionally be provided, which will grant the client access to extensions and sublevels.

db.createRpcStream()

Pipe this into a server stream.

Listen for the error event on this stream to handle / swallow reconnect events.

db.auth(data, cb)

Authorize with the server.

db.deauth (cb)

Deauthorize with the server.

Performance

On my macbook pro one multilevel server handles ~15k ops/s over a local tcp socket.

   bench (master) : node index.js 

writing "1234567890abcdef" 100 times

 native             : 2ms (50000 ops/s)
 multilevel direct  : 21ms (4762 ops/s)
 multilevel network : 14ms (7143 ops/s)

writing "1234567890abcdef" 1000 times

 native             : 12ms (83333 ops/s)
 multilevel direct  : 71ms (14085 ops/s)
 multilevel network : 77ms (12987 ops/s)

writing "1234567890abcdef" 10000 times

 native             : 88ms (113636 ops/s)
 multilevel direct  : 594ms (16835 ops/s)
 multilevel network : 590ms (16949 ops/s)

writing "1234567890abcdef" 100000 times

 native             : 927ms (107875 ops/s)
 multilevel direct  : 10925ms (9153 ops/s)
 multilevel network : 9839ms (10164 ops/s)

Installation

With npm do:

$ npm install multilevel

Contributing

$ npm install
$ npm test
$ npm run test-browser

Contributors

Sponsors

This module is proudly supported by my Sponsors!

Do you want to support modules like this to improve their quality, stability and weigh in on new features? Then please consider donating to my Patreon. Not sure how much of my modules you're using? Try feross/thanks!

License

(MIT)

Copyright (c) 2013 Julian Gruber <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

multilevel's People

Contributors

deanlandolt avatar dominictarr avatar dstokes avatar eiriksm avatar feross avatar greenkeeperio-bot avatar juliangruber avatar kesla avatar pgte avatar ralphtheninja avatar soldair avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

multilevel's Issues

No indication that connection is lost

I'm using multilevel with a browser client connected through websocket-stream. I've run in to the following problem: If I kill the node server (after which the client surely can no longer be connected with it) db.isOpen() still returns true and the callback to db.put is still called without an error parameter.

There seem to be no way of knowing that the connection with the server has actually disappeared. Which is not the impression the documentation gives.

I can see that there are other open issues related to disconnecting. But none of them seemed to be the exactly the same as this one.

Just in case it has any relevance I'm using level-live-stream along with multilevel.

multilevel-down

Similar to how subleveldown fixes some issues sublevel has (not judging whether it's better in total), let's discuss whether implementing multilevel as a backend would be more useful.

Some things to discuss:

  • streams: we need to expose iterators, probably with implicit buffering
  • batches: we'd get the chained batch for free since that's in levelup
  • encodings: we'd get encodings for free since those are in levelup
  • deferred open: that's also already handled in levelup
  • bundle size: when bundling for the browser, could shipping the full levelup plus read streams implementations be a problem?
  • maintenance
  • performance in general
  • etc...

What do you think?

From the top of my head: @dominictarr @hij1nx @ralphtheninja @substack @mafintosh @maxogden @feross @jcrugzz

events

sending events back to the client is not gonna be scaleable if we do it for every put, batch, del

That would mean sending every piece of data twice!

And that is only if there is a single connection. If there are multiple clients all writing to the database then it would send every put from each write to every client.

I recommend we just remove this feature, if a client wants to receive changes they should use a live-stream (will be supported in forthcoming multilevel PR)

implement chained batch

it doesn't look like chained batch is implemented so i'm opening this issue to remind myself or whoever else to implement it! I need it for dat so I'll probably do it soon

EventEmitter memory leak detected when running 'get'

After our client runs for a couple of hours, when calling

db.get(xxx, function(...

we get:

(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
   at Db.EventEmitter.addListener (events.js:160:15)
   at Db.addEventListener.Db.on (/xx/node_modules/multilevel/lib/client.js:110:13)
   at Db.EventEmitter.once (events.js:185:8)
   at Db._queue (/xx/node_modules/multilevel/lib/client.js:209:13)
   at Db.db.(anonymous function) [as get] (/opt/xx/node_modules/multilevel/lib/client.js:173:10)
  [...]
  1. Any idea what could trigger this? This is a normal nodejs server, running with nodejs cluster (nr of instances = 4)
  2. Since this exception happens async, and it is not catch and returned as 'err', it stops the execution. It should be catch and return as err.

thanks a lot.

PS: This is with multilevel running on the same box as the client, and the DB is on a local HDD.

Multilevel not returning same error object

When using level:

{ name: 'NotFoundError',
  cause: [Error: NotFound: ],
  message: 'Key not found in database [foo-key]' }

when using multilevel:

{ message: 'Key not found in database [foo-key]',
  error: true }

It would be awesome if multilevel returned the same error object, or at least the error name :)

level-live test broken

The events that level-live-stream emits aren't transferred to the client anymore. I think that is because something changed from using sublevel to post hooks.

This also refers to #6.

I'm not sure how events used to be transmitted before that, but it worked. I disabled the live-stream test for now.

cc @dominictarr

Encoding

Hey there again!

We should either adapt to LevelDOWN or add an abstraction layer allowing binary as well as non-binary data (f.i. JSON). So either the client specifies the encoding for the .put-process or the server specifies the encoding for all the keys (hammer variant, but makes sense somewhat).

isOpen, etc

Could make these synchronous, and make them return whether the connection to the database is open or not.

on a new connection, the server could tell the client whether it's open or closed, then the same code would work with the local or remote database.

doesn't transmit changes to live-stream installed client

with a server doing updates like this:

var db = levelup('/tmp/dummy')

setInterval(function () {
  db.put('time', new Date().toString())
}, 1000)

net.createServer(function (con) {
  con.pipe(multilevel.server(db)).pipe(con);
}).listen(8888)

can multilevel detect those changes on the client like this?:

var db = multilevel.client()
var con = net.connect(8888)
con.pipe(db.createRpcStream()).pipe(con)

var liveStream = require('level-live-stream')
liveStream.install(db)
var ls = db.liveStream()
ls.on('data', console.log)

this isn't working for me. wondering if it could be in the realm of enhancement for multilevel or if I'm doing something obviously wrong :)

multilevel client doesn't work if you defer pipe to another tick

it seems like this should work. i really just want to understand why it doesn't. I don't know if its a bug. here is a working reduced test case of the issue. tested on node v0.10.5 and v0.8.22

var net = require('net');
var ml = require('multilevel');

// created client some time before.
var client = ml.client();

var db = require('levelup')('example');
var server = ml.server(db);
net.createServer(function(s){

  console.log('client connected');
  s.pipe(server).pipe(s);

}).listen(3000,function(){

 client.pipe(net.connect(3000)).pipe(client);

 console.log('about to call get on client');
 client.get('hi',console.log); // < never calls back
 console.log(client.isOpen()) //< returns undefined

});

my real use case is that i wanted to use reconnect but return the client immediately before piped to the reconnect stream in the reconnect connect callback.

i was under the impression that multlevel queued the commands until connected to a server so something like this should work.

multilevel-dev

I just pushed multilevel-dev to npm which will become version 5.0.0, currently living in the reconnect branch. The main change is caused by a "rewrite" of lib/client.js, exposing a new interface:

var db = multilevel.client();
reconnect(function (con) {
  con.pipe(db.createRpcStream()).pipe(con);
});

db isn't a stream anymore and you can use it immediately and when there is no connection, ops will pause. See the test.

@dominictarr @hij1nx @rvagg (?) and anyone else using this, please review and test this out in your applications and see if it works. Especially @dominictarr sind he might be working with some of multilevel's internal state.

...eventually reconnection will become really smart, like it should resume a readable stream etc., but that will come after this is stable.

Also, isOpen and isClosed work again! If you pass a callback it return's the leveldb's isOpen status, if you don't, it returns whether the connection is up.

Thanks!

client side multilevel broken with latest browserify

Since [email protected] multilevel doesn't bundle properly, instead it raises the following error:

/Users/thlorenz/dev/projects/level-json-edit/node_modules/browserify/index.js:0
(function (exports, require, module, __filename, __dirname) { var crypto = req
^
RangeError: Maximum call stack size exceeded

[email protected] and the versions I tested below work, but all versions above don't.

I'll investigate on the browserify side, but just wanted to raise this here as well.

Workaround for now: npm install -S [email protected]

transmit raw leveldb folder through multilevel

this is a bit of a crazy idea, so stay with me

for bulk replication where you want a client who has never replicated with you before to get fully up to date with a server, the fastest way by orders of magnitude is to just copy the entire .leveldb folder to the client. having to do a full table scan on the server and write everything out to the network is really slow

given that the leveldb folder is locked while node is accessing it I can't think of any ways to actually achieve this but I figured it would be worth a discussion anyway. is it possible to send the entire leveldb folder to clients?

some possible but not ideal scenarios:

  • close levelup, read leveldb folder out to network, open levelup
  • have a second leveldb on the same disk that is a replicated copy of the first leveldb. if you need to disconnect from the second one to transmit it then you can still be connected to the first one for availability reasons. this approach takes twice as much disk space, though

i'm not expecting to support mutlilevels either, just entire leveldbs

any other ideas?

crash creating readstream with an undefined option

Heres some code to duplicate this problem:

var multilevel = require('..');
var level = require('level');

var DB = level(__dirname + '/.db');
var db = multilevel.client();
var server = multilevel.server(DB);
server.pipe(db.createRpcStream()).pipe(server);

for(var i = 0; i < 10; i++){
  db.put('test'+i,'test'+i)
}

db.createValueStream({unused:undefined,start:'test'})  //if you have more than one parameter, with one undefined, it breaks
.on('data',console.log)
.on('error',console.log)

DB.on('error', console.log)
server.on('error',console.log)  //this ends up throwing the error

error output:

{ [SyntaxError: Unexpected token ,]
  line: '["5c6b7f5d26fedee6","new",{"meta":["createValueStream",{,"start":"test"}],"opts":{"writable":true,"readable":false}}]' }

I should note that this type of call works in levelup without multilevel.

Errors while accessing read-stream are not propagated

Take the following access method:

...
  access: function() {
    throw new Error('unauthorized');
  }
...

Now get a read-stream on the client:

db.createReadStream()
  .on('error', err => console.log(err));

What I expect now, is that the event handler is called. Instead nothing happens and the stream stays open. Is this a bug or am I doing something wrong? How is error handling in this situation supposed to be done?

Multilevel client crashes the server if encoding an object that has a function on a prop

While I agree that's a stupid think to do, I think the client should encode this properly (like JSON.stringify would for example).

Here's how to replicate it:

server.js

var net = require('net')
var level = require('level')
var multilevel = require('multilevel')

var db = level(__dirname + '/my_db')

net.createServer(function (c) {
  var multiStream = multilevel.server(db);

  c.on('data', function(data) {
    console.log('data', data.toString());
  })
  multiStream.on('error', function(err) {
    throw err;
  });
  c.pipe(multiStream).pipe(c)

}).listen(7000)

client.js

var multilevel = require('multilevel')
var net = require('net')

var db = multilevel.client()
db.pipe(net.connect(7000)).pipe(db)

var value = {
  getImage: function() {},
  a: 2
};

db.put('foo', value, function(err) {
  if (err) { throw err; }

  db.get('foo', function(val) {
    if (err) { throw err; }

    console.log(val);
  })
});

Publish latest multilevel

All the latest nested object goodness is still not in the latest version in npm. Please publish :-)

"npm install multilevel" seems to be broken

When I do this:
npm install multilevel

I get this:

npm ERR! git fetch -a origin (git://github.com/dominictarr/rpc-stream) error: cannot open FETCH_HEAD: Permission denied
npm ERR! Error: Command failed: error: cannot open FETCH_HEAD: Permission denied
npm ERR! 
npm ERR! 
npm ERR!     at ChildProcess.exithandler (child_process.js:637:15)
npm ERR!     at ChildProcess.EventEmitter.emit (events.js:98:17)
npm ERR!     at maybeClose (child_process.js:743:16)
npm ERR!     at Socket.<anonymous> (child_process.js:956:11)
npm ERR!     at Socket.EventEmitter.emit (events.js:95:17)
npm ERR!     at Pipe.close (net.js:466:12)
npm ERR! If you need help, you may report this log at:
npm ERR!     <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR!     <[email protected]>

npm ERR! System Linux 3.8.11
npm ERR! command "/usr/bin/nodejs" "/usr/bin/npm" "install" "multilevel"
npm ERR! cwd /home/fergie/search-index
npm ERR! node -v v0.10.25
npm ERR! npm -v 1.3.10
npm ERR! code 1

npm installing rpc-stream on its own seems to work fine...

How to handle `access` asynchronously?

I've put my authorization server on the network as a dnode service, so I need an async call to that from the access function. Would it be possible to add an optional callback to access?

gr,

Tom

Proper approach to close/disconnect

I'm pretty baffled by this. I can't seem to trigger a db.close() without getting an Error: unexpected disconnection. I've attached handlers to every event emitted by the db itself and the Net client connection, and it doesn't seem that I have any activities that are waiting to be done -- no drain or close events are triggered. I've even tried a simple setTimeout() to see if it's some sort of race condition.

I'm making a multilevel client with the approach in the readme -- Net.connect() -- and I do a simple connect then disconnect, without any activity in between, but still the error.

simplify streams

instead of calling a method via rpc, and then waiting for the stream to come back,
just create a stream, and pass the argument as metadata.

args.unshift(method)
return mx.createStream(args)

close event on streams

So, the close event in levelup is schemantic, on a write stream
It means that all the records have been saved.

So, the problem here is that mux-demux uses through, which closes automatically.
and then, 'close' does not propagate backwards in regular streams (new, or classic)

So you have to do this:

var ms = mx.createStream()
ms.pipe(ts)
ts.on('close', function () {
  ms.destroy()
})

the other problem is that through automatically emits close when the writable and readable side have both closed. need a way to disable that... just thinking out loud here...

error: unknown callback id: 2 [] when 2 clients connect to server

multilevel v5.6.0

I've been getting this error when i attach more than 1 client to a multi level server. It happens when one client inserts data, the other client will spit this error. The error originates from the rpc-stream/index.js:68

Heres some code to replicate what im seeing:

var MemDB = require('memdb');
var multilevel = require('multilevel');
var createManifest = require('level-manifest');
var db = MemDB({ valueEncoding: 'json' });
var manifest = createManifest(db);

var server = multilevel.server(db);
var client = multilevel.client(manifest);
var client2 = multilevel.client(manifest);

server.pipe(client.createRpcStream()).pipe(server);
server.pipe(client2.createRpcStream()).pipe(server);

var post = {
    title: 'a title',
    body: 'lorem ipsum',
    author: 'julian'
};
client.put('1337',post)

handling disconnect/reconnect

I can't seem to make reconnection work simply. This issue is probably a continuation of #9.

In the multilevel server/client there's a MuxDemux({error: true}) introduced by @soldair, presumably as part of getting reconnection to work (though I can't find the issue/PR where this change was introduced).

Curiously, this change actually breaks my use of reconnect with multilevel? If I pull out this error: true, reconnection works as expected. Also not sure where to even attach the error event listener to capture the "unexpected disconnection" error.

Sample:

// client.js
var reconnect = require('reconnect-engine');
var multilevel = require('multilevel')
var manifest = require('./manifest.json')
var db = multilevel.client(manifest)

reconnect(function(con) {
  con.pipe(db.createRpcStream()).pipe(con)
  db.liveStream().on('data', function(message) {
    console.log('message', message)
  })
})
.connect('/engine');

[Error: write after end]

server sends 'close' after transport emits 'finish' causing write after end

  • transport emits finish
  • [Error: write after end]
  • server emits close

client.createReadableStream()

Hello - thanks again for the nice work on multilevel. Works great for the most part, and is making my life much easier. I recently ran into an issue with the createReadableStream().

My scenario: I have a 60 gig level database with about 1.5m keys. In one process (my master process) I am traversing through the database and doing a 5-6 second operation on each item in batches of 10 or so and updating those items...so a createReadableStream() piped into a number of in process 'workers', each of which are calling db.put() when they're done. In another process I am using multilevel to get a readable stream to my master process database and doing a different set of operations using the another set of workers. The output of the client process is into a separate system, so the multilevel client process does not call anything other than a single multilevel.createReadableStream() when it boots up.

I have run into a few issues specifically related to the readable stream I'd like to bring up...I'm probably missing something in how I'm using it.

  1. When the client connects to the master process and creates the readable stream the master process CPU usage spikes to 100% from around 30%. I assume this is because the master is reading through the first part of the database and pushing it into the network socket (via rpc-stream, via mux-demux) to fill the buffer. When I then disconnect the client process, the master process CPU usage does not return to normal; rather, it remains pegged at 100% CPU for a few minutes...after this amount of time I generally grow tired of waiting and restart the master process. I'm not sure what the cause of this is exactly, or if I waited long enough the CPU usage would return to normal. What I would expect to happen is perhaps an initial burst of CPU and then a return to normalcy as the client stream starts to exert back-pressure on the master. At the very least I would expect CPU usage to return to initial state when the client disconnects completely. Perhaps the iterator is left open and burning CPU until it reaches the end of the requested range (in my case, the entire 1.5m keyset)
  2. More troubling than point 1 actually is that memory usage grows unbounded within the master process and eventually the master is killed by ubuntu's OOM monster. If I create a readable stream in the client with bounds of say the first 10-50k keys it works fine, but if I say "Just start iterating through the database until you reach the end" I always run out of memory. My server code is basically a copy/paste from the example in the readme here, so nothing special about it.

I apologize if my examples are kinda hard to understand...I spent the better part of Wednesday thrashing on this and fiddling various things very unscientifically without taking extremely detailed notes. I eventually came to the conclusion something in the multilevel "stack" is either buffering endlessly or being very inefficient with allocations. As a temporary solution I created a module to handle streaming large result-sets from leveldb more efficiently. It only works with leveldb because it uses leveldown to avoid turning records from binary into objects and directly back into binary to go out over the network. I've dropped it in as an addition to multilevel, and CPU usage is down from 100% pegged when a client is connected to barely increasing at all, the client can read rows much more quickly, and when the client connects the server CPU usage returns to an idle state.

https://github.com/brianc/node-level-readable

I think the main differences are instead of using the rpc-stream it uses a custom, light-weight binary protocol (based on the PostgreSQL client/server protocol), avoids object allocations on the server, and uses's node's stream2 stuff internally to respond to back-pressure the best I can make it. I'd be happy to try to work this into a pull request instead of having it be a separate thing...but it is specific directly to level-down (using db.iterator()) and doesn't conform to the rest of the library using rpc-stream so I'm not sure how you feel about that.

It's not battle hardened yet by any means, and lacks documentation because its not quite ready yet, but some folks on the ##leveldb irc suggested I drop by and talk about it a bit. It has proved to be faster, use less memory and less CPU. It's completely not general purpose like multilevel, but thought maybe it could be useful.

Thanks again and sorry for the ramblings!

auth?

How best to handle auth on remote connections to level?

You could do auth through a separate channel,
or through first, and then open the connection to the database.

But, it might be best to provide access to some parts of the api,
and then sign in to the rest - that way you get the minimum wait.

To be honest, I don't really know that much about how authorization usually works in most web apps, single page or not.

People with more experience @juliangruber @Raynos @hij1nx @st-luke what do you think?

stack overflow when installing live-stream on a root db which has sublevels when using multilevel

Moving this from level-live-stream over here. Discussion so far

The gist of the problem:

Let's say on the server you have something like:

var db = sublevel(db);
var foosub = db.sublevel('foo');

// incorrect
liveStream.install(db);

// correct would be
// liveStream.install(foo);

Consuming on the client side like this:

// incorrect
db.liveStream().on('data', console.dir);

// correct would be
// db.sublevel('foo').liveStream().on('data', console.dir);

Since we did this incorrectly (i.e. attached live stream to root level instead of to the sublevels separately) we'll get a stack overflow error in 'json-buffer' due to the _parent on the sublevels which causes a circular reference:

...../node_modules/multilevel/node_modules/mux-demux/node_modules/json-buffer/index.js:34
    return JSON.stringify(/^:/.test(o) ? ':' + o : o)
                               ^
RangeError: Maximum call stack size exceeded

custom encoding options on db from client.

if you pass require('bytewise/hex') as the keyEncoding in a .batch, .put, .createReadStream... (as passed in the opts parameter) on the client it'll just receive an error back saying .encode is not a method.

I'm guessing this isn't something that could be stored in the manifest. So is this something that one would have to tweak/setup server-side? Shim the client's methods to do the key-encoding client side?

The documentation says binary encoding is supported, but maybe it would help other's to say custom encodings aren't. Bytewise seems to be a pretty fundamental encoding for level... I almost wish it were native like json and binary but maybe it's bloat. Thoughts?

Probably this is related to #17 and #12 but not certain.

create sublevels from client

Currently clients cannot create sublevels that aren't part of the manifest. I find that to be cumbersome as I have to recreate manifests etc. for every new sublevel I want to use.

Of course clients shouldn't be able to go above their current sublevel in their hirarchy, but in the sublevel they're in right now they should be able to do whatever they want.

@dominictarr

handling disconnect/reconnect

posting this mostly as notes, and thinking out loud.

handling disconnections well is fairly important.
when I'm building apps, I often end up with this pattern:

reconnect(function (stream) {
  hash(function (hash) {
    //this is called immediately, and then whenever the hash changes
    //you this could represent other things, basically - it's just when the ui
    //mode changes.
  })
})

This is quite awkward, because whenever the app changes modes/state
there are streams/mux-demux/scuttlebutts to disconnect, and new things to connect.

in rumours, I managed to pave over this problem for scuttlebutts.
https://github.com/dominictarr/rumours#example-using-defaults

Basically, the challenge here, is creating an interface to each type of abstraction that works well over reconnections.

Just buffering all requests is not efficient - you don't know if a disconnection is temporary or not.

there are various approaches that could be used for different streaming abstractions.
For example, with a range query, that is append only - so, like in twitter - new tweets are always added to the end of the feed - so, after disconnecting, you just ask for the changes since the last thing you received.

That only works for cases that are append only - if a updates may come into the middle of a range, then you'd have to keep a buffer, of say, the last N updates, and then if the disconnection is brief, it will be fine - but if it's long, just resend the whole range, or use something like level-master instead.

get, put, batch can be buffered like in levelup.

The problem here, is that it depends on the particular way that data is updated and replicated.

Parse bug

So I have an app where I tried to switch from level to multilevel and I've setup the multilevel server with a level database with JSON encoding. For a few records it worked fine, but then I've encountered this problem:

ERROR !!! [SyntaxError: Unexpected token u] SyntaxError: Unexpected token u
    at Object.parse (native)
    at Object.exports.parse (/Users/alexandruvladutu/www/pushcards/trunk/node_modules/multilevel/node_modules/mux-demux/node_modules/json-buffer/index.js:39:15)
    at parse (/Users/alexandruvladutu/www/pushcards/trunk/node_modules/multilevel/node_modules/mux-demux/node_modules/stream-serializer/index.js:21:18)
    at Stream.onData [as write] (/Users/alexandruvladutu/www/pushcards/trunk/node_modules/multilevel/node_modules/mux-demux/node_modules/stream-serializer/index.js:35:7)
    at Socket.ondata (stream.js:38:26)
    at Socket.EventEmitter.emit (events.js:123:20)
    at TCP.onread (net.js:396:14)

The data that was received was the following:

["fe02c3f64a3da70d","data",["put",["campaign-inter2",{"id":26,"user_id":4,"creation_date":"2013-04-21T12:58:33.000Z","title":"Inter23","url":"inter2","brief":"CAmpaign issue description","card_text":"Forza Inter Per Sempre Campione!2","image":null,"name":"Target Name","address1":"Target Address","address2":"Target Address2","city":"Pitesti","state":"Arges","zip":"0300","country":"RO","expiration_date":null,"status":"stopped","getCountryName":undefined,"addImage":undefined,"getImage":undefined,"getCardsSent":undefined,"_image":{"id":83,"path":"d66d19f0-ebd2-11e2-9838-8d9678fa8db9-inter_9_1280x1024.jpeg","created":"2013-07-13T15:42:37.000Z"},"total":15}],6]]

I suspect the problem is because of those undefined, but that's supposed to work, since it works in level, right?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.