8 ms·
KV.js
- omneity 3y agoI don't see this mentioned, but is KV.js used somewhere in production? Is there a recommended way to use it? For example in a separate worker, in a child process or in the same process as the node.js app, or embedded in an domain-specific server. Side note, is lua support in consideration? Some redis workloads rely heavily on it. Looks cool and exhaustive nevertheless, the author did a great job!
- ent101 3y ago"I don't see this mentioned, but is KV.js used somewhere in production? " Hey, author here. It's used in puter.com, my startup. Still in alpha though. "For example in a separate worker, in a child process or in the same process as the node.js app," Right now I'd say in the same process as the node.js app. Open to suggestions. "Side note, is lua support in consideration?" Yes. "the author did a great job!" Thank you! Please do let me know how I could make it better :)
- pavlov 3y ago> "It's used in puter.com, my startup" Great domain, congratulations :)
- omneity 3y agoIn the past I needed something like redis, but in-process and “programmable from the outside”. I used Go libraries to do it then, but as I prefer JS/TS I’ll surely look at KV.js next time the need arises. Were you driven by any specific need/requirement while building KV.js?
- nine_k 3y agoRedis only uses Lua because Redis is written in C, and Lua is dynamic but has a good JIT compiler. For software written in JS, it would be natural to use another dynamic language with a good JIT compiler, namely JS again. I could see how introducing Lua might make sense if KV.js decided to be a faithful reimplementation of Redis's interface and (parts of) semantics, only in JS. Then dropping it in as a compatible replacement might be easier if Lua were supported, like in the original Redis.
- xiphias2 3y agoIt would be better to use camelCase to be compatible with node-redis, and then it could be a drop-in replacement in the future.
- ent101 3y agoThat’s a great idea! Thank you :)
- nine_k 3y agoSo, basically it's an in-process, single-threaded KV store, made out of a standard JS Object with a bunch of operations over keys or values, vaguely along the lines of Redis. These operations make it stand out a bit from just typing `const kV = {}`, especially set operations, and the presence of TTL for keys. Could be useful. For me though the usefulness of Redis comes mostly from its being a common store across many processes / nodes, so it can serve ads a cache, a queue, etc. Maybe it's also useful for a single JS process in a similar capacity.
- tunesmith 3y agoWhat I've wanted before is an easy in-memory NodeJS (typescript) cache that multiple processes can share. Like, we deploy on AWS EC2s with multiple vCPUs; I don't want a separate in-memory cache for each of them, but I also don't want the latency of Redis.
- bennettbackward 3y agoYou can run Redis on the same machine with a Unix socket to share it between processes without the network latency
- Liberonostrud 3y ago[dead]
- llamaLord 3y agoCould you use Kubernetes to solve this? Have a single pod running the Redis instance and then multiple running Node.js talking to the Redis instance via something like DAPR (https://dapr.io/ https://dapr.io/)
- actionfromafar 3y agoYes, but the question is should you. If you already have Kubernetes knowledge and infrastructure, why not? Otherwise, maybe not.
- simlevesque 3y agoDoes it work with ESM ? Like: > import { kvjs } from '@heyputer/kv.js' If it does you could add an example in the readme to make that clear. I try to avoid commonjs-only modules nowadays. Edit: Also, is it really a single source file or it's compiled ? It'd be hard to code review this 2.6k lines file.
- kerneldeimos 3y agoIsn't one of the merits of ESM that it can import either? I believe this is supposed to work: import theDefaultExport from '@heyputer/kv.js';
- spankalee 3y agoBrowsers can not import CommonJS
- jsf01 3y agoI’ll admit I only skimmed it for a few minutes but I found the code really nice to read through! I highly doubt it is compiled output. When the entire code base essentially just consists of a single data structure I think that a single file, even if it is long, is perfectly appropriate. What I’d suggest for reviewing it is to code fold all the methods in your editor and then expand each one as you get to it. Props to the author for such an elegant implementation.
- jitl 3y agoThis really is just “new Map()” with a little bit of set dressing.
- nbittich 3y agoExactly. Just an extra layer of abstraction, no idea what it tries to solve.
- IMTDb 3y agoMaybe... the need to have a map with some additional set of accessors and/or utility functions around it ? If you look at the code; it's literally just a map with some function around it. If you end up in a situation where you feel the need to wrap a map with some function to handle some typical needs (eg: access a random key; manage a ttl/expire for a key; ...) you might as well be using this. The fact that the name of the functions have been taken from redis means that if/when you move to an external KV store, the transition will be trivial. It also means you don't really need to learn it if you already know redis. I don't see becoming the #1 library on npm; with conferences and speakers debating the intricacies of its architecture. But hey it might solve someones need.
- enjoylife 3y agoYep. This is a quintessential example of “I’m going to learn by recreating the essence of a complex thing”… taken to the end. And then a logo is slapped on top. I’m all for js libs but this is not performant. Just use redis.
- hem777 3y agoCan you run Redis in browser? You can’t. Is an in-process, in-memory database faster than making a query over network? Yes it is.
- nbittich 3y agono need an extra layer of abstraction to get an "in memory" database in javascript. no need, babel, webpack, ChatGpt, npm and 10000 libraries for this really. Let me show (works even on the browser): const KV = {}; KV["credentials"] = {access_key: "XXXX", secret: "YYYY"}; console.log(KV["credentials"].secret);
- rektide 3y agoSharedArrayBuffer is back right? It works across different worker instances now right? It'd be fun to see a cross-thread kv library emerge.
- enw 3y agoNo tests?
- sieabahlpark 3y ago[dead]
- paradite 3y agoI struggle to see how this is better than just {} or Map, except as a mock for Redis.
- deleted 3y ago[deleted]
- 0xblinq 3y agoDisclaimer: I'm not the author, but I have shared personal projects here as well in the past. HackerNews is the worst place on earth to share a side project, specially on its early days. You will get all the hate and push back you can get, even if you did it for fun, for learning purposes or because you don't need "the real professional thing" because you're using your library on a small command line application that doesn't need to ship redis with it. - "It doesn't scale" - "Just use Redis" - "It's too simple" - "I'd rather just use new Map()" - "It's just copying the basics of a large project and putting a logo on it" So what? Innovation in the world should stop right now because everything is already invented? Or because you would do it better? Or because this is not web scale enough? or not used in production? Maybe the author doesn't need an external project for their small scale personal project and redis is overkill? Maybe the author is a 15 year old person in a third world country which you're discouraging from getting in the industry? who knows. IMHO, all of you with these negative opinions towards any not yet popular side project that's shared here would make the world a bit better by either leaving positive comments, constructive criticism or just shutting up. Thanks for your downvotes.
- Dylan16807 3y ago> You will get all the hate and push back you can get, even if you did it for fun, for learning purposes If you post code that you did for fun or learning purposes, and you didn't write an article about what you learned either, I don't see what's wrong about getting pushback. It doesn't make for a very good post. > or because you don't need "the real professional thing" because you're using your library on a small command line application that doesn't need to ship redis with it. Being standalone doesn't get downvotes.
- pavlov 3y ago> "HackerNews is the worst place on earth to share a side project, specially on its early days." Depends on your expectations. A lot of online forums are hobbyist cheerleading groups. You get a flood of likes and heart emojis, but when it subsides, you're left with very little that would actually help you improve your craft. HN is closer to the style of brutally direct criticism you get in art school (I know because I went to one). When professionals are tearing your work apart, it definitely doesn't feel good. But artists know they need to train themselves to receive that kind of feedback and process it beyond the initial rush of defensive emotions. The other people aren't necessarily right in what they say, but they decided to tell you something you didn't want to hear even though it would have been more comfortable for everyone to stay quiet. (And it's always possible that some negative feedback is simply active meanness — but that's also a useful lesson because every profession has that kind of people. You need exposure to different feedback styles to filter out what can help you grow.)
- khalidx 3y agoI like this style... one big file with everything in there, and finding things is just a Ctrl+F away.
- foepys 3y agoYou could just use a good IDE or editor instead which indexes the code for you...
- chrismorgan 3y agoHere is somewhat of a technical review. I have attempted to be objective, and also to provide some useful insights in the middle of it, as well as pointing out a few bugs here and there. Not to beat about the bush: I would strongly recommend against using (or designing) something like this unless you’re actively aiming for usage compatibility with Redis (I don’t know how actually compatible it might be; I don’t use Redis; as for whether aiming for that is worthwhile, my opinion is generally not). If you aren’t explicitly aiming for that, the code is significant bloat around very simple patterns and you would do much better to use those underlying patterns. The original source code is 100KB, and 3023 lines long (including 1021 comment and 346 blank lines). Minified, it’s 24KB, and gzipped 5.3KB. Except it also depends on glob, so add on another 75KB minified/23KB gzipped for it and its transitive dependencies, for this functionality that you probably don’t use but which probably won’t be eliminated from your build. The typical user of something like this should really just use a Map directly, with maybe a second Map and auxiliary function for expiring keys, if they need that. Much less code, much faster, much simpler. If you need any of the more exotic functionality, code it yourself, it’s simple enough; then you can also choose more meaningful names, too. > fast Well, I took a look at the first method after the constructor, set, and found stuff like this: for (const opt of options) { switch (opt.toUpperCase()) { case 'NX': nx = true; break; … case 'EX': ex = Number.parseInt(options[options.indexOf(opt) + 1], 10); Ugh. Gratuitous and unnecessary case-insensitivity, use of an array instead of an object, unwise indexOf because you used a values iterator rather than retaining the index… I suppose quite a bit of this is bound up in a probably-misguided (in my opinion) mirroring of Redis’s API. But in general, this direction of design is bad for JavaScript. But to go deeper: The thing to realise about this set method is that despite being 83 lines long, the most common case of passing no options makes it precisely equivalent to `this.store.set(key, value);`—just slower and heavier. As time goes by I become more and more strongly opposed to options objects because they harm performance, code size and complexity, unless mitigated at build time by partial evaluation, which is not something done in JavaScript-land (Facebook tried it a few years ago with Prepack, but quickly gave up, and I’m not sure quite why). Many of these options are either unwise variation (EX/PX/EXAT/PXAT) or to make it do materially different things (GET—I note it makes a lie of the return type declared in the JSDoc comment); my typical recommendation would be splitting things out into different methods, because tooling can cut away unused methods (… though it mostly won’t actually do so—unused functions, sure, but tooling is more leery about culling unused methods), and it helps the code to run fast. I won’t give examples because the method is messy enough in its diversity that it’d be painful. But even retaining the current behaviour, there is one technique that I would expect to significantly improve throughput: add a little more code, a fast path for the no-options case. set(key, value, options) { if (!options) { // Fast path. this.store.set(key, value); } else { // Slow path. … the 81 lines of current method body … } } —⁂— get’s few instructions show just how simplistic and imitable the expiry feature is: get(key) { const isExpired = this._checkAndRemoveExpiredKey(key); if (isExpired) { return null; } return this.store.get(key) || null; } Pretty sure that return statement is buggy, `|| null` will mangle falsy values (e.g. false, 0, "") to null. —⁂— Skimming through much of the rest, there are quite a few weird and/or inefficient and/or wrong things. Examples: incrby() uses expired values (looks like that might happen fairly commonly); incrby() happily mangles types; decr() has a superfluous try-catch-rethrow; decrby(key, decrement) isn’t just incrby(key, -decrement); keys() uses a .entries() iterator for no reason; also produces an array rather than an iterator (mildly more subjective); mset()… eww array rather than object; various list methods: wow these are verbose and often very bad fit for JavaScript, but I’ll limit myself to just remarking that in `list === undefined || !Array.isArray(list)` the `list === undefined ||` is superfluous; zdiffstore: clearly not tested, this.ZDIFF is undefined; blocking list methods like brpop(): um, you know JavaScript is single-threaded, right? You’re busy-waiting until the timeout for something that cannot possibly change. (And no, there’s no SharedArrayBuffer or such here which could be multi-threaded, because it checks Array.isArray.) —⁂— I think a lot of this would be better as free-standing functions that operate on a simple Map. It seems to me that expiry is really the only reason you can’t obviously and completely do this. For that reason, the direction I’d suggest would instead be making a new class that exposes the Map interface with only the minimum changes required to support expiry (only working with timestamps in milliseconds, no relative values or seconds, both of which are just begging for trouble), and then have all the rest of the more exotic functionality sit on top of that, perhaps then just as standalone functions.
- m1sta_ 3y agoAdd decent persistence. If you can just run express+KV to get a webserver with a database on any platform then this would be cool.
- coneonthefloor 3y ago> KV.JS is a fast… What makes it “fast”? Is there an explanation or comparison?
- edf13 3y agoNo performance metrics? I've done something similar in the past but find JS just too slow
- ccorcos 3y agoSubscriptions would be cool, allowing you to leverage this library for state management. Add an async storage interface so you can persist and you’ll have an awesome little database.
- markthethomas 3y agoThis looks super cool! I have a use case where I store some stateful stuff in Redis that a worker constantly interacts w/ and updates. Redis is great, but the project is a very side thing and would love not to have to pay for bandwidth / just embed it inside of it. Going to take a look at this and see if I can make it work! Didn't see any serialization formats, so might just do that myself. Ty for building this!
- realPubkey 3y agoThe expire() function should be made to work without a setTimeout. This makes it possible for the kvjs() instance to be garbage collected automatically when there is no more reference to it. I recommend to use window.requestIdleCallback() directly after each read/write and the try to clean up old values, otherwise leave them there. That will also ensure that the cleanup never happens while the event loop is in a hot path.
- mohitgangrade 3y agoI've been looking at the source code, and I can't understand why they are converting Number to String before storing it in the map. What's the reason for it? Is it something specific to JavaScript?
- KRAKRISMOTT 3y agoWindow drag and drop doesn’t work on touch devices for puter.com