E-commerce con carrello temporizzato

Ciao a tutti,
innanzitutto grazie per il vostro aiuto,
avrei un esigenza di un cliente …

dovrei realizzare un carrello temporizato …

ho creato la seguente entità

CART

  • created-at
  • update_at
  • expired_at


ora secondo voi quale è la migliore strada da seguire per creare un
controllo sul carrello ??

supponendo che ogni 15 min, il carrello si deve svuotare ??

Grazie mille.

Ciao Roberto, le opzioni sono tante (worker Sidekiq, per esempio), ma
prima
di analizzarle in dettaglio penso sarebbe sensato descrivere la tua
visione
dell’esperienza utente relativa a questo aspetto.
On Mar 28, 2015 9:28 AM, “Roberto S.” [email protected]

ciao Maurizio … grazie per la risposta …

io mi stavo immaginando un controllore in crontab che andasse a
eliminare tutti i carrelli scaduti …

te mi consigli Sidekiq … come potrei integrare i suoi worker nella mia
reale esigenza ??

per quanto riguarda l’esperienza utente … ho bisogno del contatore
carrello sempre ben visibile come un count down …

grazie mille.

Sidekiq si occupa solo di lanciare un job asincrono.
Per la soluzione che stai pensando te puoi usare whenever (andr comunque
a
scrivere in crontab)

Altrimenti potresti provare delayed_job che ti permette di lanciare un
job
async ad un orario specifico.

Il giorno sab 28 mar 2015 alle ore 09:41 Roberto S. <
[email protected]> ha scritto:

sì, ripensando un attimo al tuo caso d’uso, al posto di Sidekiq,
consiglio
anche io whenever, in modo da impostare un’esecuzione periodica di uno
sweeper dei carrelli scaduti.
Un dubbio però riguardo gli attributi che hai elencato prima: ti basi
sull’attributo expired_at per capire se un carrello è scaduto?
Non sarebbe meglio evitare quell’attributo e basarsi solamente
sull’attributo updated_at, in modo da verificare se cart.updated_at +
15.minutes >= DateTime.now e in tal caso cancellarlo dal database o
impostare un booleano (active)?

2015-03-28 22:45 GMT+01:00 Daniele P. [email protected]:

Se il tempo di scadenza è costante, expired_at non ti serve.
A seconda della mole di lavoro del tuo database, fare una query molto
frequente con un filtro su un campo datetime non è il massimo, quindi
sto pesando che sarebbe interessante imposare un booleano ad un
determinato tempo.

Se stai giù usando Sidekiq nell’applicazione, potresti fare un worker
che setta un booleano expired' nel futuro, usando perform_at`.
La cosa funzionerebbe così.

class Carrello < ActiveRecord::Base
after_commit(on: :update) do |record| # mi piace tanto passare
record come variabile locale qui non so perchè
CarrelloExpireCheckWorker.perform_at(15.minutes.from_now, record.id)
end

def expire_if_expired
update_column(:expired, true) if updated_at < 15.minutes.ago # lo
so questa si può comprimere ma così è più leggibile
end
end

class CarrelloExpireCheckWorker
include Sidekiq::Worker

def perform(record_id)
Carrello.find(record.id).expire_if_expired # questo fallo fare al
modello, è il posto giusto per farlo, ti potrebbe servire in altre
situazioni
end
end

In questo modo ad ogni update del carrello viene fatto un controllo 15
minuti dopo. Se vengono fatti altri update nel frattempo, i più vecchi
sidekiq jobs nella coda non aggiorneranno il flag, perchè nel
frattempo updated_at si sarà spostato avanti nel tempo.

Vale la pena fare tutto questo? Forse no, però ti evita di mettere
qualcosa nel cron che fa una query ogni minuto.

2015-03-28 22:56 GMT+01:00 maurizio de magnis
[email protected]:

2015-03-28 23:38 GMT+01:00 Fabrizio R. [email protected]:
[cut]

Vale la pena fare tutto questo? Forse no, però ti evita di mettere
qualcosa nel cron che fa una query ogni minuto.

Con whenever può impostare un controllo periodico (non necessariamente
ogni
minuto, anzi, ipotizziamolo anche ogni 6 ore) al quale affiancare un
controllo “quando serve”.

Mi spiego meglio: con whenever cerchi di fare pulizia tra il cimitero
dei
carrelli, ovvero quelli che sono stati iniziati e poi abbandonati,
quindi
per questa tipologia di carrelli, non hai la necessità di invalidazione
tempestiva, perché l’unico scopo dello sweeper è fare pulizia nel
database.

Se poi vuoi che quando un utente aggiorna un carrello lasciato a riposo
per
più di 15 minuti, questo venga resettato, allora basta aggiungere un
controllo di “freschezza” del carrello ad ogni aggiornamento del
carrello
stesso.

Così salvi capra e cavoli: relativa semplicità d’implementazione,
consistenza dei dati ed efficienza dei processi.

E comunque non riesco a farmi piacere le callback nei model
ActiveRecord…
:-/
Appesantire il layer di persistenza, con addirittura il lancio di un
worker
per ogni update…
#esepoitenepenti ?

2015-03-29 16:25 GMT+02:00 maurizio de magnis
[email protected]:

Sicuramente quello che ho scritto è sovradimensionato per il problema
da risolvere, ma mi è passato per la testa e mi faceva piacere
condividerlo. Potrebbero esserci scenari analoghi per i quali una
soluzione del genere ha più senso. Ripulire il cimitero dei carrelli
una volta al giorno va più che bene nella maggior parte delle
situazioni.

Riguardo l’appesantimento, non lo vedo tanto un appesantimento. Alla
fine il modello sta mandando un segnale nell’universo ‘ho aggiornato
updated_at su questo record’. Ci sono molti modi per tenere il modello
all’oscuro dei dettagli dell’implementazione.

Tanto per scherzare un pò con l’economia delle cose: nella soluzione
Cart Samatary se in un anno nessuno usa il carrello avrai fatto 365
query a vuoto :slight_smile:

2015-03-29 17:32 GMT+02:00 maurizio de magnis
[email protected]:

L’approccio che hai descritto te è simile a quello di Fabrizio, che usa
Sidekiq.
Sinceramente penso che sia l’approccio a worker (sidekiq o delayed_job o
vattelapesca) +15 minuti post update che quello dello sweeper via
whenever/cron con il check durante l’update siano validi, con differenze
di
efficienza difficilmente rilevabili in contesti anche di media entità.

Non sono un grande fan di delayed_job, soprattutto perché penso che sidekiq faccia meglio.

2015-03-29 22:51 GMT+02:00 Daniele P. [email protected]:

Il problema del background job in questo caso è che

  1. non hai garanzia di precisione temporale, per cui ti potresti trovare
    in
    casi in cui il carrello sarebbe vuoto prima del tempo stabilito o pieno
    dopo
  2. non hai garanzia di esecuzione, cioè se il demone è giù per qualche
    motivo ti ritroveresti coi carrelli pieni fin quando il demone non torna
    su

Io farei un check in lettura con un after_find di sto tipo:

class Cart
after_find :timeout_reset

def timeout_reset
if created_at (o quel che l’è) < 15.minutes.from_now
empty
end
end

def empty
end
end

Maurizio De Santis

Il giorno 29 marzo 2015 22:51, Daniele P.
[email protected]
ha scritto:

Scusate ma l’idea di delayed_job sarebbe davvero cosí brutta?

Una cosa tipo:

handle_asynchronously :reset_cart, :run_at => Proc.new { 15

.minutes.from_now }

dopo 15 minuti il carrello si resetta.

Il giorno dom 29 mar 2015 alle ore 17:32 maurizio de magnis <
[email protected]> ha scritto:

2015-03-29 20:57 GMT+02:00 Fabrizio R. [email protected]:
[cut]

Riguardo l’appesantimento, non lo vedo tanto un appesantimento. Alla
fine il modello sta mandando un segnale nell’universo ‘ho aggiornato
updated_at su questo record’. Ci sono molti modi per tenere il modello
all’oscuro dei dettagli dell’implementazione.

con una sola callback presente, ok, ma è la direzione che cerco di
evitare.
Per esempio, se in futuro dovrò fare un batch import con degli update
sul
model, non voglio dovermi inventare degli stratagemmi per evitare che le
callback intervengano (post in ml di qualche tempo fa ;-)).

Chiamala convenzione, ma mi sento molto più sicuro quando so che il
layer
ActiveRecord si preoccupa solo della persistenza.

Anche perché la stessa funzionalità la puoi ottenere senza perdere in
pulizia e concisione: nel contesto di update del cart
(CartsController#{create,update} ?), dopo l’update/create posso lanciare
esplicitamente CarrelloExpireCheckWorker.perform_at(15.minutes.from_now,
record.id) o, se voglio proprio esagerare e/o se i contesti di
aggiornamento sono un numero significativo:

class CartValidityChecker
def initialize(cart:)
@cart = cart
end

def perform
CarrelloExpireCheckWorker.perform_at(15.minutes.from_now, @cart.id
http://record.id/)
end
end

e nei contesti di aggiornamento del cart:

CartValidityChecker.new(cart: cart).perform

in modo da aver un accentramento della logica responsabile
dell’aggiornamento del cart.

Tanto per scherzare un pò con l’economia delle cose: nella soluzione

Cart Samatary se in un anno nessuno usa il carrello avrai fatto 365
query a vuoto :slight_smile:

in tal caso ripenserei alla sostenibilità del mio e-commerce :smiley:

2015-03-29 23:10 GMT+02:00 Maurizio De Santis
[email protected]:

Il problema del background job in questo caso è che

  1. non hai garanzia di precisione temporale, per cui ti potresti trovare in
    casi in cui il carrello sarebbe vuoto prima del tempo stabilito o pieno dopo
    Non mi sembra un gran problema, a meno che non sia una gara di
    carrelli a cronometro, immagino tu parli di tempi trascurabili
    nell’ordine del secondo.
  1. non hai garanzia di esecuzione, cioè se il demone è giù per qualche
    motivo ti ritroveresti coi carrelli pieni fin quando il demone non torna su
    Il demone non deve essere giù, come non deve essere giù il webserver.
    È il prezzo da pagare se vuoi usare un background jobs nella tua
    architettura, cosa di cui prima o poi avrai bisogno comunque.

end

Interessante l’after_find, non lo ricordavo. Mai usato, però porebbe
essere utile. Comunque anche in questo caso è un callback, per cui
potrebbe capitare la volta in cui vuoi fare una find che escluda
quella logica. Caso che secondo me è molto più comune che non voler
escludere una callback after_save. Oltre tutto, per skippare una
callback su una istanza puoi usare una variabile di istanza. Per
skippare una callback su class method è un pò più complicato.

Scusate, la empty voleva essere

def empty

TODO: implementazione svuotamento carrello

end

Maurizio De Santis

Il giorno 29 marzo 2015 23:10, Maurizio De Santis <
[email protected]> ha scritto:

Non mi sembra un gran problema, a meno che non sia una gara di
carrelli a cronometro, immagino tu parli di tempi trascurabili
nell’ordine del secondo.

Prendiamo il caso in cui worker sia occupato nel momento in cui deve
partire il job che svuota il carrello, ed il worker ci mette 5 secondi a
finire il job che sta facendo: in questo caso ti troveresti il job del
cart
spostato 5 secondi pi avanti. 5 come 15 come boh, dipende da quanto ci
mette il worker a finire la queue precedente al job del cart.

Non so per questo caso quanto facilmente si verifichi nella vita vera,
immagino dipenda dalla situazione dei jobs.

Maurizio De Santis

Il giorno 30 marzo 2015 10:02, Roberto S.
[email protected]
ha scritto:

Se appesantisci il server dipende da troppe cose per poterlo dire ora,
quindi dovresti dotarti di strumenti di analisi e monitoraggio per
capirlo strada facendo. Se non hai particolari esigenze vai con la
soluzione pi semplice lasciando perdere i background jobs, una
pulizia periodica dei carrelli morti sufficiente, come gi detto,
nella maggior parte dei casi.

Le differenze con Sidekiq sono pi che altro nella velocit dello
smaltimento delle code, se non ricordo male.

2015-03-30 10:02 GMT+02:00 Roberto S.
[email protected]:

ciao a tutti e grazie per i vostri consigli,

vado ad esplicitare meglio le funzionalità:

il carrello a ogni aggiunta del prodotto dovrà aggiornare la sua
validità

ogni 15 minuti i carrelli che hanno la loro validità (scaduta), dovranno
cessare di vivere e i loro prodotti dovranno riacquistare la quantità
sottratta.

penso che tutte le soluzioni elencate siano ottime per la risoluzione
del problema.

che differenza cè nell’utilizzare delayed_job OR Sidekiq ??

posso utilizzare uno dei due per rinnovare la vita al carrello (magari
con un after_save OR after_update)

e un cronjob per pulire i carrelli morti???

unica cosa il cronjob dovrebbe partire ogni 15 min, rischio di
appesantire il server ??

Grazie.

Prendiamo il caso in cui worker sia occupato nel momento in cui deve
partire il job che svuota il carrello, ed il worker ci mette 5 secondi a
finire il job che sta facendo: in questo caso ti troveresti il job del cart
spostato 5 secondi pi avanti. 5 come 15 come boh, dipende da quanto ci
mette il worker a finire la queue precedente al job del cart.

Si questo scenario possibile. E comunque ancora nei 15 secondi
l’expiry di un carrello secondo me un range tollerabile. Anche un
minuto se vogliamo. Ma capisco il punto.

Mettiamola cos, il tutto funziona se il tuo sistema di background
jobs in salute. E per in salute intendo avere sempre a disposizione
un “congruo” numero di workers “idle” per fare il lavoro che ci si
aspetta nei tempi che ci si aspetta.

Aggiungere un controllo sincrono durante le show/update del cart
indispensabile per mantenere consistenza nella base dati.

I worker Sidekiq o il job cron con whenever servono solo per ricordarsi
di
tenere il database pulito, non per mantenerlo consistente durante le
request dei client.
On Mar 30, 2015 11:29 AM, “Maurizio De Santis”
[email protected]
wrote:

Non mi sembra un gran problema, a meno che non sia una gara di
carrelli a cronometro, immagino tu parli di tempi trascurabili
nell’ordine del secondo.

Prendiamo il caso in cui worker sia occupato nel momento in cui deve
partire il job che svuota il carrello, ed il worker ci mette 5 secondi a
finire il job che sta facendo: in questo caso ti troveresti il job del
cart
spostato 5 secondi pi avanti. 5 come 15 come boh, dipende da quanto ci
mette il worker a finire la queue precedente al job del cart.

Non so per questo caso quanto facilmente si verifichi nella vita vera,
immagino dipenda dalla situazione dei jobs.

Maurizio De Santis

Il giorno 30 marzo 2015 10:02, Roberto S.
[email protected]
ha scritto:

unica cosa il cronjob dovrebbe partire ogni 15 min, rischio di


Ml mailing list
[email protected]
http://lists.ruby-it.org/mailman/listinfo/ml