[php,sql] c-plusplus.de forum index schneller machen



  • Hi,
    es geht darum den index unseres forums schneller zu machen, genauer gesagt gibts da ein bestimten mod der den last-topic-on-index-mod

    hier ist der code
    alles was zwischen //Dimah und //end of Dimah ist habe ich modifiziert
    und der bereich der zwischen //Orginal und //end of Orginal ist, ist der phpbb Orginalcode

    if( ( $total_categories = count($category_rows) ) )
    {
    	//
    	// Define appropriate SQL
    	//
    	switch(SQL_LAYER)
    	{
    		case 'postgresql':
    			$sql = "SELECT f.*, p.post_time, p.post_username, u.username, u.user_id 
    				FROM " . FORUMS_TABLE . " f, " . POSTS_TABLE . " p, " . USERS_TABLE . " u
    				WHERE p.post_id = f.forum_last_post_id 
    					AND u.user_id = p.poster_id  
    					UNION (
    						SELECT f.*, NULL, NULL, NULL, NULL
    						FROM " . FORUMS_TABLE . " f
    						WHERE NOT EXISTS (
    							SELECT p.post_time
    							FROM " . POSTS_TABLE . " p
    							WHERE p.post_id = f.forum_last_post_id  
    						)
    					)
    					ORDER BY cat_id, forum_order";
    			break;
    
    		case 'oracle':
    			$sql = "SELECT f.*, p.post_time, p.post_username, u.username, u.user_id 
    				FROM " . FORUMS_TABLE . " f, " . POSTS_TABLE . " p, " . USERS_TABLE . " u
    				WHERE p.post_id = f.forum_last_post_id(+)
    					AND u.user_id = p.poster_id(+)
    				ORDER BY f.cat_id, f.forum_order";
    			break;
    
    		default:
    //Dimah
    //Orginal
    /*
    			$sql = "SELECT f.*, p.post_time, p.post_username, u.username, u.user_id
    				FROM (( " . FORUMS_TABLE . " f
    				LEFT JOIN " . POSTS_TABLE . " p ON p.post_id = f.forum_last_post_id )
    				LEFT JOIN " . USERS_TABLE . " u ON u.user_id = p.poster_id )
    				ORDER BY f.cat_id, f.forum_order";
    			break;
    	}
    	if ( !($result = $db->sql_query($sql)) )
    	{
    		message_die(GENERAL_ERROR, 'Could not query forums information', '', __LINE__, __FILE__, $sql);
    	}
    
    	$forum_data = array();
    	while( $row = $db->sql_fetchrow($result) )
    	{
    		$forum_data[] = $row;
    	}
    
    	if ( !($total_forums = count($forum_data)) )
    	{
    		message_die(GENERAL_MESSAGE, $lang['No_forums']);
    	}
    */
    //end of Orginal	
    
         		$sql = "SELECT f.*, p.post_time, p.post_username, u.username, u.user_id, t.topic_title, t.topic_last_post_id " .
        			" FROM ((( " . FORUMS_TABLE . " f " .
        			" LEFT JOIN " . POSTS_TABLE . " p ON p.post_id = f.forum_last_post_id )" .
        			" LEFT JOIN " . USERS_TABLE . " u ON u.user_id = p.poster_id ) " .
        			" LEFT JOIN " . TOPICS_TABLE . " t ON t.topic_last_post_id = p.post_id ) " .
        			" ORDER BY f.cat_id, f.forum_order";
        		break;
    	}
    	if ( !($result = $db->sql_query($sql)) )
    	{
    		message_die(GENERAL_ERROR, 'Could not query forums information', '', __LINE__, __FILE__, $sql);
    	}
    
    	$forum_data = array();
    	$last_topic_id = 0;
    	while( $row = $db->sql_fetchrow($result) )
    	{
    	    if($row['topic_last_post_id'] != $last_topic_id || $row['topic_last_post_id'] == 0)
    	    {
    	        $last_topic_id = $row['topic_last_post_id'];
                $forum_data[] = $row;
            }
    	}
    
    	if ( !($total_forums = count($forum_data)) )
    	{
    		message_die(GENERAL_MESSAGE, $lang['No_forums']);
    	}
    /* // haben wir in unseren forum nicht
       // also habe ich es auskommentiert
    
    	//
    	// Filter topic_title not allowed to read
    	// 
    
    	if ( !($userdata['user_level'] == ADMIN && $userdata['session_logged_in']) ) {
    		$auth_read_all = array();
    		$auth_read_all = auth(AUTH_READ, AUTH_LIST_ALL, $userdata, $forum_data);
    		$auth_data = '';
    		for($i=0; $i<count($forum_data); $i++)
    		{
    			if (!$auth_read_all[$forum_data[$i]['forum_id']]['auth_read']) 
    			{
    				$forum_data[$i]['topic_title']='';
    			}
    		}
    	}
    */
    
    	//
    	// Define censored word matches
    	//
    	$orig_word = array();
    	$replacement_word = array();
    	obtain_word_list($orig_word, $replacement_word);
    
    //end of Dimah
    

    und etwas weiter unten habe wir dann noch

    //Dimah
    //Orginal
    /*
    							if ( $forum_data[$j]['forum_last_post_id'] )
    							{
    								$last_post_time = create_date($board_config['default_dateformat'], $forum_data[$j]['post_time'], $board_config['board_timezone']);
    
    								$last_post = $last_post_time . '<br />';
    
    								$last_post .= ( $forum_data[$j]['user_id'] == ANONYMOUS ) ? ( ($forum_data[$j]['post_username'] != '' ) ? $forum_data[$j]['post_username'] . ' ' : $lang['Guest'] . ' ' ) : '<a href="' . append_sid("profile.$phpEx?mode=viewprofile&amp;" . POST_USERS_URL . '='  . $forum_data[$j]['user_id']) . '">' . $forum_data[$j]['username'] . '</a> ';
    
    								$last_post .= '<a href="' . append_sid("viewtopic.$phpEx?"  . POST_POST_URL . '=' . $forum_data[$j]['forum_last_post_id']) . '#' . $forum_data[$j]['forum_last_post_id'] . '"><img src="' . $images['icon_latest_reply'] . '" border="0" alt="' . $lang['View_latest_post'] . '" title="' . $lang['View_latest_post'] . '" /></a>';
    							}
    							else
    							{
    								$last_post = $lang['No_Posts'];
    							}
    */
    //end of Orginal
    							if ( $forum_data[$j]['cat_id'] == 7)
                                				{
                                    				$last_post = "&nbsp;Archiv - hier kann nur gelesen werden";
                                				}
    							else if ($forum_data[$j]['forum_last_post_id'] )
    							{
    								$topic_title = $forum_data[$j]['topic_title'];
    
    								//
    								// Censor topic title
    								//
    								if (count($orig_word))
    									$topic_title = preg_replace($orig_word, $replacement_word, $topic_title);
    
    								$topic_title2 = $topic_title;
    
    								if (strlen($topic_title) > 43) 
    								{
    									$topic_title = substr($topic_title,0,40) . '...';
    								}
    
    								$last_post_time = create_date($board_config['default_dateformat'], $forum_data[$j]['post_time'], $board_config['board_timezone']);
    
    								$last_post = '&nbsp;'.$last_post_time.'<br />&nbsp;<a href="' . append_sid("viewtopic.$phpEx?"  . POST_POST_URL . '=' . $forum_data[$j]['forum_last_post_id']) . '#' . $forum_data[$j]['forum_last_post_id'] . '" title="' . $topic_title2 . '">' . $topic_title . '</a> &nbsp;(';
    								$last_post .= ( $forum_data[$j]['user_id'] == ANONYMOUS ) ? ( ($forum_data[$j]['post_username'] != '' ) ? $forum_data[$j]['post_username'] . ' ' : $lang['Guest'] . ' ' ) : '<a href="' . append_sid("profile.$phpEx?mode=viewprofile&amp;" . POST_USERS_URL . '='  . $forum_data[$j]['user_id']) . '">' . $forum_data[$j]['username'] . '</a>';
    								$last_post .= ')&nbsp;&nbsp;';
    							}
                                				else
                            				{
                            					$last_post = $lang['No_Posts'];
                            				}
    //end of Dimah
    

    erstmal, die frage hatt dieser code überhaupt das potenzial das forum auszubremsten? (orginal und mod)

    und hier schon meine erste optimierungs idee, statt nur einen querry das alles erfasst würde nur ein querry machen welches nur die f.forum_last_post_id hollt,
    zusätlich ein static array in den das ergebnis vom letzen mal gespeichert wird, falls die f.forum_last_post_id sich unterscheidet vom letzen mal werden erst dann die daten gehollt
    nachteil: aus einen querry werden im schlechtesten fall viele kleine
    zusätlicher vorteil: die daten aus den FAQs müssen grundsetzlich nicht gehollt werden

    idee zwei: topic titel nicht zensieren

    also bevor wir mit low-level optmierungen anfangen sollten wir uns im klaren sein welche high-level-optiemierung wie benutzen



  • Also, ich denke die Datenbankabfrage -hat- das Potential.

    In meinem XML-Newsflasher habe ich folgende Abfrage stehen:

    $sql = "select * from " . TOPICS_TABLE . " t, " .
                              POSTS_TABLE . " p, " .
                              FORUMS_TABLE . " f, " .
                              USERS_TABLE . " u, " .
                              CATEGORIES_TABLE . " c ".
                              "where p.post_id = t.topic_last_post_id ".
                                "and f.forum_id = t.forum_id ".
                                "and u.user_id = p.poster_id ".
                                "and c.cat_id = f.cat_id ".
                                "and f.auth_view = 0 ".
                                "and f.forum_id != 11 ". // Projekte
                                "and f.forum_id != 12 ". // Neuigkeiten
                                "and f.forum_id != 37 ". // OT ausgenommen
                                "and f.forum_id != 38 ". // Treff
                                "and f.forum_id != 39 ". // phpBB
                                "order by p.post_time desc limit $top";
    

    Und dieser Aufruf dauert ca. 10 Sekunden. Die Komplexität und Aufgabe der Abfrage und der Joins ist ungefähr vergleichbar mit der Indexseite (aus allen Beiträgen die letzten finden).

    Vor allem die Tabellen FORUMS_TABLE und USERS_TABLE sind extrem groß.



  • Ich hab keine Ahnung von PHP, aber ist es nicht möglich, dass das "last-topic" nicht bei jedem Seitenaufruf, sondern konstant alle 3 Sekunden berechnet wird?



  • Nimm den Mod raus und guck ob es besser wird 🙂



  • Vielleicht es mit ein paar Indizes probieren?



  • Welche Datenbank habt ihr eigentlich?



  • Erstmal ne Frage:
    Was bezweckst Du mit

    while( $row = $db->sql_fetchrow($result) ) 
        { 
            if($row['topic_last_post_id'] != $last_topic_id || $row['topic_last_post_id'] == 0) 
            { 
                $last_topic_id = $row['topic_last_post_id']; 
                $forum_data[] = $row; 
            } 
        }
    

    Warum liest Du das ganze dann überhaupt aus der DB. wenn Du es eh wieder eliminierst?

    Und nun noch ein paar Anmerkungen:
    1. Schmeiß die komplette DB-Layer-Unterscheidung raus! Du verwendest wahrscheinlich eh nur ein einziges DBMS, also kannst Du die komplette DB-Klasse ersetzen.
    2. Entferne alle großen Kommentar-Blöcke. Der verursachte Overhead bei der Ladezeit ist bei großen Projekten durchaus relevant.
    3. Deine Zensurfunktion lässt sich wahrscheinlich auch noch optimieren. Wenn ich richtig rate, dann liefert Dir obtain_word_list() doch einen Satz verschiedener regulärer Ausdrücke, diese könntest Du jedoch in einem einzigen zusammenfassen und somit einen leichten Performance-Gewinn herausholen.
    4. Fasse Deine SQL-Queries in Hochkommata und nicht in Anführungszeichen.
    5. Ich bin jetzt mit dem Source des zugrunde liegenden Board-Systems nicht so vertraut, aber der gepostete Teil erweckt den Eindruck, daß man das Array $forum_data evtl. komplett eliminieren kann. Prüfe das doch mal nach.

    Gruß Jens



  • ah, gleich schon viel schneller 🙂



  • Marc++us schrieb:

    Also, ich denke die Datenbankabfrage -hat- das Potential.

    da hilft mir mein c++ wissen nicht weiter 😞

    Unregistrierter schrieb:

    Ich hab keine Ahnung von PHP, aber ist es nicht möglich, dass das "last-topic" nicht bei jedem Seitenaufruf, sondern konstant alle 3 Sekunden berechnet wird?

    das ist in php nicht so leicht möglich, außerdem wahrscheinlich langsammer

    Unregistrierter schrieb:

    Nimm den Mod raus und guck ob es besser wird 🙂

    ja leider

    O'Dog schrieb:

    Vielleicht es mit ein paar Indizes probieren?

    was ist das?

    O'Dog schrieb:

    Welche Datenbank habt ihr eigentlich?

    mysql 3.xx

    Sa(n)dman schrieb:

    Erstmal ne Frage:
    Was bezweckst Du mit

    while( $row = $db-&gt;sql_fetchrow($result) ) 
        { 
            if($row['topic_last_post_id'] != $last_topic_id || $row['topic_last_post_id'] == 0) 
            { 
                $last_topic_id = $row['topic_last_post_id']; 
                $forum_data[] = $row; 
            } 
        }
    

    ab und zu kommt ein daten satz doppelt im row vor (wahrscheinlich falscher sql code, aber ich müsste mich dann doch mehr mit sql befassen)

    Warum liest Du das ganze dann überhaupt aus der DB. wenn Du es eh wieder eliminierst?

    was meinst du

    Und nun noch ein paar Anmerkungen:
    1. Schmeiß die komplette DB-Layer-Unterscheidung raus! Du verwendest wahrscheinlich eh nur ein einziges DBMS, also kannst Du die komplette DB-Klasse ersetzen.

    finde ich nicht gut

    2. Entferne alle großen Kommentar-Blöcke. Der verursachte Overhead bei der Ladezeit ist bei großen Projekten durchaus relevant.

    so viel komentare sind da nicht drin

    3. Deine Zensurfunktion lässt sich wahrscheinlich auch noch optimieren. Wenn ich richtig rate, dann liefert Dir obtain_word_list() doch einen Satz verschiedener regulärer Ausdrücke, diese könntest Du jedoch in einem einzigen zusammenfassen und somit einen leichten Performance-Gewinn herausholen.

    ja aber dann kann ich alles nur zu eins umwandeln, es soll aber z.b. standart in standard umwandeln

    4. Fasse Deine SQL-Queries in Hochkommata und nicht in Anführungszeichen.

    ich will erstmal die high-level optimierung machen

    5. Ich bin jetzt mit dem Source des zugrunde liegenden Board-Systems nicht so vertraut, aber der gepostete Teil erweckt den Eindruck, daß man das Array $forum_data evtl. komplett eliminieren kann. Prüfe das doch mal nach.

    wenn ich wollte könnte ich ja das forum hardcoden in den php dateien 🙂 finde ich aber nicht so toll, denke nicht das man forum_data killen kann



  • Dimah schrieb:

    O'Dog schrieb:

    Vielleicht es mit ein paar Indizes probieren?

    was ist das?

    Der Plural von Index 🙂

    http://www.mysql.com/doc/en/MySQL_indexes.html

    Bringt 'ne Menge auf Spalten worüber Tabellen ge-joined werden oder auch einfach nur bei Abfragen von Spalten-Werten...wenn die Tabellen viele Sätze haben



  • ich verstehe nicht ganz wo das problem ist. jetzt wo du es rausgenommen hast, stehen doch auch Benutzername, Datum und Uhrzeit unter "Letzter Beitrag". Da wird es doch wohl kein Problem sein auch den Topic-Namen mitzuspeichern



  • Anonymous schrieb:

    ich verstehe nicht ganz wo das problem ist. jetzt wo du es rausgenommen hast, stehen doch auch Benutzername, Datum und Uhrzeit unter "Letzter Beitrag". Da wird es doch wohl kein Problem sein auch den Topic-Namen mitzuspeichern

    ohne den code und die db strucktur des phpbbs zu verändern?



  • Hast du schonmal probiert die Reihenfolge der einzelnen JOINs zu verändern - könnte evtl. schon etwas bringen (siehe dazu auch das hier)


  • Mod

    der phpbb code scheint mir ein graus zu sein...

    ich wuerde es brutal machen:

    eine neue tabelle erstellen die exakt soviele eintraege hat wie wir foren.
    bei jedem neuen posting wird brutal ein update ausgefuehrt unnd setzt in dieser tabelle den titel des letzten postings.
    da kannst du uhrzeit und user id auch dazu geben, dann bist du extrem schnell, da du mit einem query alles bekommst.

    allerdings musst du dann die struktur aendern, was sich aber IMHO auszahlt. denn ich wuesste nicht wie man es anders effektiv schaffen koennte.



  • Shade Of Mine schrieb:

    der phpbb code scheint mir ein graus zu sein...

    ich wuerde es brutal machen:

    eine neue tabelle erstellen die exakt soviele eintraege hat wie wir foren.
    bei jedem neuen posting wird brutal ein update ausgefuehrt unnd setzt in dieser tabelle den titel des letzten postings.
    da kannst du uhrzeit und user id auch dazu geben, dann bist du extrem schnell, da du mit einem query alles bekommst.

    allerdings musst du dann die struktur aendern, was sich aber IMHO auszahlt. denn ich wuesste nicht wie man es anders effektiv schaffen koennte.

    ungern, denn das bedeutet viele eingriffe in den orginal code, was sich negativ beim updaten auswirkt



  • Sollten doch nur 2 Eingriffe sein? Einmal dort wo Posting in Empfang genommen wird und einmal auf der Index-Seite.

    Man muss ja nicht unbedingt eine Datenbank nehmen. Für jedes Forum eine eigene Textdatei sollte doch auch gehen, oder?

    Also z.B. f_1.txt:

    ie starten
    abcqurz
    16 Jul 2003 07:54
    MuehBln
    

    Am Besten 2 Funktionen schreiben und die in eine separate Datei packen (z.B. SetLastPosting(forum)undGetLastPosting(forum) und GetLastPosting(forum). Das kann man dann ja schnell wieder rein/rausbauen.

    btw: C/C++ Code und Code haben die selbe Tastenkombination (Alt+c)



  • Dimah schrieb:

    Shade Of Mine schrieb:

    der phpbb code scheint mir ein graus zu sein...

    ich wuerde es brutal machen:

    eine neue tabelle erstellen die exakt soviele eintraege hat wie wir foren.
    bei jedem neuen posting wird brutal ein update ausgefuehrt unnd setzt in dieser tabelle den titel des letzten postings.
    da kannst du uhrzeit und user id auch dazu geben, dann bist du extrem schnell, da du mit einem query alles bekommst.

    allerdings musst du dann die struktur aendern, was sich aber IMHO auszahlt. denn ich wuesste nicht wie man es anders effektiv schaffen koennte.

    ungern, denn das bedeutet viele eingriffe in den orginal code, was sich negativ beim updaten auswirkt

    update wird ja wohl nicht mehr nötig sein 👎


  • Mod

    Dimah schrieb:

    ungern, denn das bedeutet viele eingriffe in den orginal code, was sich negativ beim updaten auswirkt

    ja, das ist leider der nachteil 😞

    aber ich habe mich gestern 1 stunde mit dieser datenbank sttruktur gespielt -> ich habs nicht hinbekommen mit dieser struktur den letzten beitrag effizient auszulesen.

    du kannst ja auch mal auf phpbb.com nachfragen wie man das effizient machen kann.

    wie gesagt, dass ist der einzige weg der mir eingefallen ist -> hoffentlich findet noch jemand einen besseren!



  • Vielleicht solltet ihr es doch mal mit nem Index auf die größeren Tabellen machen. Die Spalten die in dem SQL vorkommen halt in Kombination



  • Warum nicht einfach für jedes Forum eine schöne Tabelle - das ist ja kein Problem?! Und da lässt man dann brav einen Index mitlaufen (Unique oder so) und ruft einfach Element #1 für jede Tabelle auf?

    Außerdem erleichtert das das Suchen in einzelnen Foren!

    MfG SideWinder


Anmelden zum Antworten