Hi,
you're right. It's hard to get an idea of what I'm talking about when you don't have the whole picture. So here we go:
First thing that should be changed is the indexing feature that indexes pictures on the fly while someone is watching the gallery. I'm not saying it should be completely gone, but there should be an option to disable it. It's one of the most performance killing things. Put a knob in the config section and then put things indexing images like in Album::getImages() into the garbageCollect() method so that getImages() only returns the known images from the database (and if OTF indexing is needed calls garbageCollect() ).
One could create three settings:
1 - always on (do indexing every time)
2 - only after a number of queries/number of days passed/whatever fits here
3 - off - to index you have to run a script (via cron maybe)
Second thing is that only those parts of a class get populated which are really needed the moment they're needed. From the OOP point of view it's a good idea to have everything at hand, but not from the php point of view, as those are scripts where you can't reuse the results in a later connection/query again.
Third thing is: let the database do most of the work. IMHO the stuff get's heavy and slow when more data is transferred between the database and the php script than really needed.
E.g. if someone calls Album::getNumImages() you should do a "select count(1) from zp_images where albumid = $this->id;
The database is ten times faster in counting the rows than the whole process is when you're selecting the rows, transferring them to the php script and then doing a sizeof() or looping over the returned array. You can even create callbacks that are executed if someone tries to access a value of the class like Album->$images and stuff.
Same goes for the search I was talking about yesterday:
In your code the following happens inside SearchEngine::getSearchImages() - I used nopaste for this, it's easier to read if the code is colored and formatted.
So read on here: http://pastebin.com/p2e2636E I created comments in the code.
There are many solutions to this problem. One could be to just create a select statement that joins the result id's of the pictures with the album information.
like "select zp_images.id,zp_images.albumid,zp_images.`desc`from zp_images left join (zp_albums) on (zp_images.albumid=zp_albums.id) where $searchcolumn1 = '%search1%' and …. ; -- btw. the guy that had the idea to name the column for the description "desc" is a genius
This would give you a huge dataset that you can just loop on and create everything you need without bugging the database again and again…. You could also work with the "limit" statement here.
Other way could be to just query for the album ids via distinct and then get the album information for all albums used in this search from the database. The bad thing about this is that you have to run the query twice - one time for the unique set of the album ids, second time to get your results so it's too expensive.
The compromise could look like this:
http://pastebin.com/xkK8J9n2
It loops over the search results twice, but it only uses a small amount of memory as the information for each album is only queried and stored in memory once and not over and over again. One could also use the "limit" statement here. If the indexing OTF is not used you can also skip the tests if the album/file exists on the storage device and just return the array of images. That would speed things up a lot as you can just return an array that you got back from the database. Imagine this used together with LIMIT
To your questions:
- Default search fields can change
One way could be to drop the old index and create a new one. Another way could be to create a second index (maybe up to $number_defined_in_config). If the database was used for a few days with your default index you can run "analyze table zp_images" and check for the most used columns in a search and then create an index based on this result.
= getSearchImages() caches images for further use.
The moment you call getSearchImages() the SearchClass() only gets the results created by the sql query statement. And everything inside this class and other classes that use/rely on the SearchClass() are only seeing and working on the result set returned by the database query - so IMHO it doesn't make a difference for the classes if they work on those 20 or 10000 pictures - they're working with the stuff they get. But it's a lot faster because all the other things that come after the database query are working with a smaller result set …
= Theme counting the results before going on
While explaining the getSearchImages() changes I made, I already talked about this. There are multiple ways todo it - run a "select count(1) …" as the search query, or just count the rows returned. Speed will always be a problem at this particular point because you can run the same query two times or just count the returned values while creating the unique id set. The problem here is that you have to remember that the only "constant" thing in all this is the database. When the php script served the first page of the result set it dies and when you go to page two a new script is launched, initialized, doing it's work, returning the result set and dies again.
So if you hit a big result set there are multiple ways to go:
Just fyi: when I talk about temporary tables, I'm talking about static tables generated for temporary use. The temporary tables of MySQL are destroyed the moment the connection is dropped and are only visible to the connection that created them, so the data can't be saved across the calls of the script.
To store a temporary search result into a "temp" table the following construct could be used:
- We're running a cron job that indexes the files
- We're using two tables:
= The first one holds the search fields, their values, the temporary table name and the date the result set was created.
= The second one is the table named in table one and created by a "select into".
Something like this:
create table temp_tables(
id int auto_increment key,
query_fields text,
query_values text,
query_done TIMESTAMP default now()
);
So a new search does something like this:
insert into temp_tables(query_fields,query_values) values("desc,title,filename","blah,bleh,blubb");
select last_insert_id();
Btw: On a "real" DBMS the above would be "insert into temp_tables(query_fields,query_values) values("desc,title,filename","blah,blah,blubb") returning id;"
And then you create a new table like:
create table tmp_$id_from_previous_insert select a,b,c from zp_images where …. $your_search_query_here…
Then you get a table with your results and a timestamp to it. So if one does a search you check if the search already exists *AND* if the script indexing the pictures ran since the time the result was created and then just output the results you need (via limit) and you can get the number of results by an easy count(1) over the results table. One could use serialize() to put the query's columns and values into one table - could be easier - maybe. As storage is cheap and memory gets cheaper everyday it's a good way to use those tables and - if enough memory is available - create the temporary tables just in memory.
If the indexing script runs, it just sets an option in zp_options to the timestamp the moment it starts running or just deletes all temporary tables after it ran (another option). So that you generate new result sets in the end.
Just my $0.02
KR,
Grimeton