Is it "expensive" to store resultset (as array of Records) in session/persistent?

Hello. I'm rewriting shopping cart for my application. My current thinking of cart is: Two model classes: Invoice which can have many items in it and Item which has one invoice, and a Singleton class Cart for managing items in selected invoice, which stores ordered Items in session as array of records and a link to Invoice which currently in cart. It works like:

Cart::getInstance()->getItems();
Cart::getInstance()->putItem($itemCode, $qty);
Cart::getInstance()->removeItem($itemCode);
Cart::getInstance()->save()

During operation with cart it stores items as array of records in session. All changes are written to database on save only.

$cart = Invoice::find([
  'conditions'=>"user_id = :own_id: and invoice_number = :inv_num:",  
  'bind' => ['own_id'=>static::$ownerId,'inv_num'=>static::$invNum],
  'limit' => 1                                                                                     
   ])->getFirst();
$cart -> InvoiceItems = static::$cartItems;
static::$cartInstance->save(); 

This works nice in test environment, however because I'm storing the whole records in session, I doubt if this is best solution. I haven't much experience with storing large session variables, but I know they do affect performance.

So, is there a better way to store Phalcon's result set, if don't want changes to be immediately written to database? (tmp database, toarray, caching, transactions?)

Any way to measure perfomance impact?

edited Mar '16

Well you should use ram for such a things, like apc/redis/memcache - then it should be fine, of course if you have enough memory. To make sure you don't run out of ram then you can create some swap file on your machine - but then it will have performance impact beacause it will read from memory.

edited Mar '16

Hello, just going to share my way of doing ecommerce sites (in all kinds of frameworks i used so far):

I just have a session variable, lets name it Basket. In basket i store only short information about the added products like title, path to image, quantity, single price, total price. Then i have a helper file (BasketHelper) which handles operations like add,edit, remove product, calculate prices e.t.c. (its all simple array operations).

If you dont need to use object methods from items added to basket you can go with my way. If you are dependant on model methods do it your way and if your site begins to grow you can always use different service/server for session like Wojciech recommended.

P.S. Wojciech i like how you said "Dont run out of rum :)

Hahhahahah :D

Yes, it's a realy bad practice to store complete resultsets in session.

A better approach is to limit the fields you save as session variable to an array of row ids, so you can easily build up the whole resultset on demand.

edited Mar '16

You mean that he will faster select all records from database and build them again ? Nice joke. Just dont use session(and if you have to use them use them stored in memcache). Only the problem is ram usage - if you have enough then store them in ram, just dont use files as a session. If you are using files, then get it each time from database beacause most of database is using query based result cache.

Invoice::find([
  'conditions'=>"user_id = :own_id: and invoice_number = :inv_num:",  
  'bind' => ['own_id'=>static::$ownerId,'inv_num'=>static::$invNum],
  'limit' => 1                                                                                     
   ])->getFirst();

rewrite it to:

Invoice::findFirst([
  'conditions'=>"user_id = :own_id: and invoice_number = :inv_num:",  
  'bind' => ['own_id'=>static::$ownerId,'inv_num'=>static::$invNum],                                                                                   
   ]);

That requires an ORM caching to be setup in the first place. His question was about storing resultsets in session, which I've addressed. There are tons of ways to optimize application speed, but without knowing his code+db (don't even want to), it's quite hard to answer.

Also, your latest answer only changes a model to a resultset, which I don't really see how could possibly help.

You mean that he will faster select all records from database and build them again ? Nice joke. Just dont use session(and if you have to use them use them stored in memcache). Only the problem is ram usage - if you have enough then store them in ram, just dont use files as a session. If you are using files, then get it each time from database beacause most of database is using query based result cache.

Invoice::find([
 'conditions'=>"user_id = :own_id: and invoice_number = :inv_num:",  
 'bind' => ['own_id'=>static::$ownerId,'inv_num'=>static::$invNum],
 'limit' => 1                                                                                     
  ])->getFirst();

rewrite it to:

Invoice::findFirst([
 'conditions'=>"user_id = :own_id: and invoice_number = :inv_num:",  
 'bind' => ['own_id'=>static::$ownerId,'inv_num'=>static::$invNum],                                                                                   
  ]);
edited Mar '16

It's just simplified cuz he had not needed one line in code. That doesn't require a ORM caching. He can just store current cart in memcache/redis/apc, if he is using session based on memcache - then he can store them in session without caring about anything. If dont then store them in memche/redis/apc where key will be session id.

You could possibly reduce memory usage by storing everything as arrays and then build resultset/models.

Shame on me, I read ::find instead of ::findFirst, you're right on that one.

But, he wants to persist a shopping cart information and not in db. Sessions are the easiest way to go. Now, I know that factory sessions in PHP are stored in files, which are quite slow compared to memory access. However, since you can change the session backend in phalcon, it's easy to optimize it (memcached/apc/whatever) later on without changing the actual business logic.

In either case, storing resultsets in session (file or memory) is a disastrous practice, period. If he needs an additional quantity variable, he should use a $productCode => $quantity array and not resultset.

It's just simplified cuz he had not needed one line in code. That doesn't require a ORM caching. He can just store current cart in memcache/redis/apc, if he is using session based on memcache - then he can store them in session without caring about anything. If dont then store them in memche/redis/apc where key will be session id.



7.0k
edited Mar '16

Thanks for your responces guys. I've had pretty small and (IMO) neat class for cart, like (simplified):

class Cart extends Component {
    private static $items = null;
    private static $instance;    
    private static $ownerId;    
    private static $logger;    
    private static $invoiceId;

    public function __construct() {        
        if (isset($this -> persistent -> cartItems)) {            
            self::$items = $this -> persistent -> cartItems;
        }
        if ($this -> auth -> getIdentity()) {
            self::$ownerId = $this -> auth -> getIdentity()['id'];
        }
    }

    public function reset() {        
        self::$items = null;
        $this->persistent->cartItems = null;
    }

    public static function getInstance() {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getItems() {
        return self::$items;
    }

    public function putItem($itemCode, $itemType, $itemPrice, $qty = 1) {
        if (is_array(self::$items)) {
            if (array_key_exists($itemCode, self::$items)) {
                self::$items[$itemCode]['qty'] += $qty;
            } else {
                self::$items[$itemCode] = ['qty' =>$qty,'type'=>$itemType,'price'=>$itemPrice];
            }
        } else {
            self::$items = ["$itemCode" => ['qty'=>$qty,'type'=>$itemType,'price'=>$itemPrice]];
        }
         $this -> persistent -> cartItems = self::$items;
    }

    public function removeItem($itemCode) {        
        if (array_key_exists($itemCode, self::$items)) {
            unset(self::$items[$itemCode]);
        }       
       self::$items = array_filter(self::$items);          
       $this -> persistent -> cartItems = self::$items;
    }
}

However recently I've changed simple cart management page to dynamic grid, with editable rows, filtering, reordering and so on and so far. And I doubted how to reflect all the canges applied on the grid in cart correctly. I thought of making grid interact with card class(I needed Records in session for that). Now I am inclined to modify this original class to modify and reorder items, rather than expirement with Resultsets in session, and leave datagrid as is, that is affect Invoice and Item models directly.

PS. Wojciech, thanks for poiting to findFirst.