Giter Site home page Giter Site logo

Concurrency issues about node-persist HOT 18 OPEN

simonlast avatar simonlast commented on June 28, 2024 1
Concurrency issues

from node-persist.

Comments (18)

rasgo-cc avatar rasgo-cc commented on June 28, 2024 3

I don't have time right now to create a PR but in my case I apparently fixed the issue by using Bottleneck. All you have to do is to wrap the functions with a limiter. Something like this:

const limiter = new Bottleneck({
  minTime: 1,
  maxConcurrent: 1,
});

export async function createCache(cachePath: string) {
  await storage.init({
    dir: cachePath,
    ttl: 1000 * 60 * 60 * 24, // 24hrs
  });
}

export const put = limiter.wrap(
  async (key: string, value: any, opts: CacheOptions = {}) => {
    return await storage.setItem(key, value, opts as any);
  }
);

export const get = limiter.wrap(async (key: string) => {
  const value = await storage.getItem(key);
  return value;
});

from node-persist.

akhoury avatar akhoury commented on June 28, 2024 1

at this point, I am not so sure a queue is enough, we might need some job processing mechanism that also spawns child processes, writes a batch and then close them? but that seems like an overkill. Also, with batch writing means that if the app crashes, some write-requests may get lost.

Also, that doesn't solve the issue with different apps using the same storage dir.

The best solution is to run as a separate service with the same node api to talk to, but now we're implementing a database engine, which I really don't want to do.

Really node-persist is not designed for this, it's more like a localStorage of the browser, if you want a database, i think you should use a database.

But going back to the original issue filed, a smart queue might solve OP's issue ONLY.

from node-persist.

akhoury avatar akhoury commented on June 28, 2024

setItem already returns a promise, why can you use that? wait for each promise to resolve then call the next one.
Or you can use setItemSync(), no wait, you wanted concurrency.

I'd be interested to see a test case when you have a chance (maybe paste a version it in JavaScript :) as some of us can't easily read coffee)

from node-persist.

joepie91 avatar joepie91 commented on June 28, 2024

Waiting on the returned promise is not practical - the setItem calls originate from different parts of the code (eg. an Express application processing many requests at the same time), and it's unreasonable to expect every part of the code to be aware of the state of every other part of the code. That defeats the entire concept of modularity, and this is why ensuring write safety should be a concern of the node-persist module.

I'll post a testcase (in JS) later - I mostly just dumped my Coffeescript code here because that's what my own codebase is written in, and it's better than nothing.

from node-persist.

akhoury avatar akhoury commented on June 28, 2024

@joepie91 no offense, but a major point of nodejs is for the code-execution to be asynchronous or non-blocking, since, by default-design, nodejs is single threaded. So, using the term "concurrent" is not very accurate in a single thread (unless you're talking clusters over a multi-core system)

However, I think you indirectly bring a good point, "node-persist" does not support "atomic transactions" - which is totally expected since node-persist is running in the same thread as the application using it, (where any other DB system have it's own background service, i.e. mysql, redis, postgres, mongo etc..)

That does not mean it's not possible, I think we can implement a node-service to do that or maybe just queue system (which would be less efficient), but possible.

from node-persist.

matthew-dean avatar matthew-dean commented on June 28, 2024

Waiting on the returned promise is not practical

@joepie91 It sounds like you may want to build a queue system, and after adding to the queue, return a promise. Fulfill the promise when that item in the queue is processed. Of course, something like that could also be built into the core of node-persist, no?

from node-persist.

matthew-dean avatar matthew-dean commented on June 28, 2024

In other words, the calls to setItem could be asynchronous, but the database writes could be synchronous.

from node-persist.

matthew-dean avatar matthew-dean commented on June 28, 2024

Oh wait, a queue system is what @akhoury said. Oops.

from node-persist.

joepie91 avatar joepie91 commented on June 28, 2024

no offense, but a major point of nodejs is for the code-execution to be asynchronous or non-blocking, since, by default-design, nodejs is single threaded. So, using the term "concurrent" is not very accurate in a single thread (unless you're talking clusters over a multi-core system)

I am referring to concurrent writes, which is a thing that Node.js absolutely does.

However, I think you indirectly bring a good point, "node-persist" does not support "atomic transactions" - which is totally expected since node-persist is running in the same thread as the application using it, (where any other DB system have it's own background service, i.e. mysql, redis, postgres, mongo etc..)

This is a separate concern - regardless of whether it supports atomic transactions, it should ensure that it does not corrupt the file (and currently, it corrupts the file on concurrent writes).

That does not mean it's not possible, I think we can implement a node-service to do that or maybe just queue system (which would be less efficient), but possible.

A batched queue would possibly be a solution; ie. on every set, it schedules a batch write for the next tick (but not to execute before a possible previous write has completed). That way, you can still handle all sets that occur within the same tick in a single write, which mostly resolves the efficiency issue.

from node-persist.

akhoury avatar akhoury commented on June 28, 2024

(and currently, it corrupts the file on concurrent writes)

when it shouldn't, fair enough.

@joepie91 I like the batched-queue idea. I am currently on vacation for another month. if no ones beats me to it, I might take a stab at it when i get back.

from node-persist.

millette avatar millette commented on June 28, 2024

As a general rule, files are hard to deal with.

from node-persist.

creativefctr avatar creativefctr commented on June 28, 2024

This is frustrating! Using getItemSync is the dirty way which will block an entire application!
I think the least that could be done is creating an internal queue inside node-persist for non-synced write operations that guarantees that write operations on a particular key are sequential. It will handle this situation internally and this way, calls from all contexts would be safe.

from node-persist.

creativefctr avatar creativefctr commented on June 28, 2024

@akhoury I don't think we need anything more than this issue. Everyone knows what node-persist is and I don't think anybody expects it to be write-safe regarding external writes. But being write-safe within the same app, given node's asynchronous nature, looks like a necessary feature.
For my own simple API server, I was forced to use sync methods to prevent a disaster! In fact, when writing code I thought node-persist would handle this scenario internally! Then I accidentally found this issue while I was looking up something else and was forced to change some lines to sync (I mean I expected this issue to be handled as a basic internal feature).

from node-persist.

musicin3d avatar musicin3d commented on June 28, 2024

I'm also having a problem with this. I chose node-persist for a small project, thinking it would be the fastest way to get up and running. Ran into a corrupted file while running tests.

If we're undecided on how to address it, perhaps we could at least add a warning to the docs?

Edit: It's important to stay on task here. All other concerns aside (eg. collisions with other apps), node-persist should be able to ensure that a basic application doesn't step on it's own toes when it calls setItem().

from node-persist.

mgttt avatar mgttt commented on June 28, 2024

I wrote a solution FYI at #93

from node-persist.

zdila avatar zdila commented on June 28, 2024

I've implemented serializing get and set operations per key. It is in typescript and requires node 10 because it uses asynchronous generators. But you can transcompile it to older nodes.

import storage from 'node-persist';

function createStorage<T>(name: string) {

  interface ISetOperation {
    type: 'set';
    data: T;
  }

  interface IGetOperation {
    type: 'get';
  }

  type Union = ISetOperation | IGetOperation;

  const executor = createExecutor();
  executor.next();

  async function get() {
    return (await executor.next({ type: 'get' })).value as T;
  }

  async function set(data: T) {
    await executor.next({ type: 'set', data });
  }

  async function* createExecutor(): AsyncIterableIterator<T | undefined> {
    let result;

    for (;;) {
      const item: Union = yield result;

      switch (item.type) {
        case 'get':
          result = await storage.getItem(name);
          break;
        case 'set':
          await storage.setItem(name, item.data);
          result = undefined;
          break;
      }
    }
  }

  return { get, set };
}

Example of use:

import { IHttpLog } from '~/types';
import { createStorage } from '~/storage';

const httpLogsStorage = createStorage<IHttpLog[]>('httpLogs');

export async function loadHttpLogs() {
  return await httpLogsStorage.get();
}

export async function saveHttpLogs(logs: IHttpLog[]) {
  await httpLogsStorage.set(logs);
}

from node-persist.

rasgo-cc avatar rasgo-cc commented on June 28, 2024

Hitting the same issue. Maybe node-persist could make use of bottleneck internally?
https://www.npmjs.com/package/bottleneck

from node-persist.

thelaughingwolf avatar thelaughingwolf commented on June 28, 2024

Also encountered this issue. I was also using node-persist as a sort of queuing mechanism, and I got a corrupted JSON file: {"key":"downstream.updates.zendesk-ticket-fields.hubspot-company-changed","value":{"##########":{"zip":"*****"}}}*****"}}} Looks like a previous update had changed the address, a later update changed the zip, and node-persist didn't fully replace the file.

from node-persist.

Related Issues (20)

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.