freakcommander » SQL http://www.freakcommander.de Kann alles. Weiß alles. Macht alles. Sun, 26 Aug 2012 14:37:20 +0000 de-DE hourly 1 http://wordpress.org/?v=3.4.1 “Heiße Themen”: Sortierung bei Hacker News, reddit & delicio.ushttp://www.freakcommander.de/4608/computer/heise-themen-sortierung-bei-hacker-news-reddit-delicio-us/ http://www.freakcommander.de/4608/computer/heise-themen-sortierung-bei-hacker-news-reddit-delicio-us/#comments Sun, 07 Aug 2011 16:04:38 +0000 crille http://www.freakcommander.de/?p=4608 Die Sortierung von Nachrichten nach Neuigkeit oder Popularität ist trivial und ohne größeren Einsatz von Gehirnschmalz zu bewerkstelligen: etwa mit dem SQL-Statement ORDER BY date DESC bzw. ORDER BY likes DESC. Möchte man jedoch die Themen ausgeben, die momentan populär sind, kommt man mit einfachen Statements nicht mehr weiter. Im Folgenden werden drei Algorithmen von Hacker News, Reddit und delicio.us vorgestellt, die auch hier auf Englisch zusammengefasst sind.

Intuitiver Hacker News-Ansatz

Der intuitive Ansatz für die Problemstellung ist, solche Themen anzuzeigen, deren Bewertung hoch ist und deren Veröffentlichungstermin noch nicht lange her ist. Sei p die Anzahl der positiven Bewertungen (jeder Benutzer kann bei Hacker News nur eine positive Bewertung pro Thema abgeben) und t die Anzahl der Stunden, seitdem das Thema veröffentlicht wurde:

Je länger also der Veröffentlichungszeitpunkt her ist, desto größer wird der Nenner. Da der Nenner mit der Potenz von 1,5 pro Stunde wächst, müssen die Votes ebenfalls potenziell mit der Zeit zunehmen, um f_h(p, t) auf gleichem Niveau zu halten.

Auch wenn keine Votes für das Thema eingehen, muss die Bewertungsfunktion f_h(p, t) jede Stunde neu berechnet werden. Dieses Problem hat der Reddit-Algorithmus nicht.

Reddit-Algorithmus

Anstatt die Bewertungen mit der Zeit exponentiell zu senken, bleibt bei Reddit die Bewertung über die Zeit gleich. Neue Themen erhalten stattdessen einen höheren Summanden, sodass ihre Bewertung höher ist. Der Reddit-Algorithmus ist etwas umfangreicher und passt nicht in eine einfache Formel. Zunächst definieren wir t_s als Differenz der Sekunden zwischen dem 08.12.2005 07:46:43 und dem Veröffentlichungsdatum des Themas:

Warum Reddit gerade den 08.12.2005 gewählt hat, ist nicht bekannt..

Da man bei Reddit sowohl positiv als auch negativ bewerten kann, wird x als Differenz der positiven (U) und negativen (D) Bewertungen definiert:

Wir definieren y als Signum- oder Vorzeichenfunktion von x. Wenn x > 0, ist y = +1. Wenn x = 0, ist y =0. Wenn x < 0, ist y = -1:

Und schließlich definieren wir z wie folgt

Ist der Betrag von x = 0, wird 1 genommen, sonst der Betrag von x.

Kommen wir zur eigentlichen Bewertungsfunktion. Mit dieser Funktion werden die Themen bewertet, sodass jene Themen mit der höchsten Bewertung die “heißen Themen” darstellen:

Funktionsgraph des Zehnerlogarithmus (Quelle:Wikipedia)

Der erste Summand ist der “Bewertungssummand”. Wir sehen anhand des Funktionsgraph des Zehnerlogarithmus rechts, warum z so gewählt ist, dass er mindestens 1 ist. Der Summand ist somit immer 0 oder positiv sein.

Der Eintrag mit der höchsten Bewertung bei Reddit hat ein x von 21875. Der erste Summand ist bei diesem Eintrag also

Der zweite Summand ist der “Zeitsummand”. y gibt aufgrund positiver/negativer/neutraler Bewertungen an, ob der Zeitsummand addiert, subtrahiert oder gar nicht berücksichtigt werden soll.

t_s ist die Zeitdifferenz in Sekunden. Schauen wir uns ein paar Beispiele an und stellen (Oh, Wunder!) fest, dass t_s mit der Zeit immer größer wird:

t_s wird noch durch 45.000 geteilt. 45.000 Sekunden sind 12,5 Stunden. Der Zeitsummand erhöht sich also alle 12,5 Stunden um 1. Das bedeutet, dass innerhalb von 12,5 Stunden der Bewertungssummand um 1 zulegen müsste, um das höhere Alter des Beitrags zu kompensieren. Der Bewertungssummand steigt um 1, wenn die Differenz zwischen positiven und negativen Bewertungen, um die Zehnerpotenz zunimmt. Von 1 auf 10 ist ja noch easy, aber es wird immer härter: von 10 auf 100, von 100 auf 1000, von 1000 auf 10.000 etc.

Ein Problem an dem Reddit-Algorithmus ist, dass die Bewertungen aufgrund des “Zeitsummands” immer weiter steigt und man irgendwann an Grenzen von Speichergrößen stoßen wird. Wann dieser Zeitpunkt erreicht ist, habe ich nicht errechnet.. Vielleicht liefert ja ein Leser das Datum nach? ;)

HOT & delicio.us

Generell muss man sich die Frage stellen, ob die zwei genannten Algorithmen wirklich das liefern, was gerade populär ist. Auf meinem Blog beobachte ich nämlich zwei verschiedene Besucher- und Aktivitätsverläufe. Zum Einen den Blockbuster-Verlauf, sprich: gerade einen Artikel veröffentlicht und sofort Kommentare und Besucher. Und zum Anderen den Sleeper-Verlauf: der Artikel wird veröffentlicht und erst nach Monaten oder Jahren von den Besuchern entdeckt. Auch wenn der Sleeper-Verlauf seltener auftritt, bekommen “Hacker News”- und Reddit-Besucher nichts davon mit, weil das Thema, obwohl es gerade “hot” ist, zu alt ist, um in den “heißen Themen” zu landen.

Bei aktuellen Nachrichten ist der Sleeper-Verlauf vernachlässigbar, aber bei Bookmarks wie bei delicio.us, kann es sehr wohl relevant sein, dass ein “alter Bookmark” gerade wieder “hot” ist. delicio.us hat daher einen einfachen Ansatz, um heiße Bookmarks zu erkennen:

  • Welche Links wurden in der vergangenen Stunde am häufigsten gebookmarkt?

Fazit

Es gibt verschiedene Ansätze “heiße Themen” zu erhalten.

  • Der intuitive Ansatz von Hacker News ist mit höherem Aufwand verbunden, da sich jede Stunde die Bewertung ändert und neu berechnet werden muss. Der Sleeper-Verlauf wird zudem nicht erkannt.
  • Der Reddit-Algorithmus bedeutet weniger Aufwand, allerdings erkennt er den Sleeper-Verlauf nicht.
  • Der delicio.us Ansatz erkennt zwar Blockbuster und Sleeper-Verlauf, allerdings kann nicht garantiert werden, dass auf kleinen Plattformen mit wenig User-Aktionen ausreichend Bewertungen vorhanden sind. Wenn keine oder wenig Bewertungen in dem Zeitraum abgegeben wurden, können keine “heiße Themen” angezeigt werden.
]]>
http://www.freakcommander.de/4608/computer/heise-themen-sortierung-bei-hacker-news-reddit-delicio-us/feed/ 0
MySQL-Import und pdflushhttp://www.freakcommander.de/3289/computer/mysql-import-und-pdflush/ http://www.freakcommander.de/3289/computer/mysql-import-und-pdflush/#comments Mon, 06 Dec 2010 14:17:25 +0000 crille http://www.freakcommander.de/?p=3289 Vermutlich kommt jeder in gewissen Abständen an einen Punkt, an dem man absolutes Neuland betritt, da man mit seinem bisherigen Wissen nicht weiter kommt.

Das Problem

war, dass viele Datensätze (6 Mio) per PreparedStatements in eine Datenbank importiert werden müssen. An sich kann es kein Problem sein, 6 Mio Datensätze zu importieren, aber unser Server machte da nicht mit

  1. war das Wiki kurz nach Beginn des Datenimports nicht mehr erreichbar
  2. einige Zeit später machte der komplette Server die Grätsche

Wir mutmaßten, dass die Wiki-Datenbank auf irgendeine Weise mit dem Importvorgang in einen Konflikt gerät, da diese ja als erstes ausfiel. Dieser Verdacht war falsch, da lediglich die InnoDB der Wiki-Datenbank zum Ausfall führte. Nachdem das Wiki auf MyISAM umgestellt war, funktionierte das Wiki auch während des Imports problemlos.

Das zweite Problem blieb aber bestehen. Nach einigen Versuchen und der Installation von Diagnosetools stand fest, dass die Festplatte durch den pdflush Daemon so stark beansprucht wurde, dass es für andere Prozesse nicht mehr möglich war auf die Festplatte zuzugreifen.

Wir überlegten, die Einstellungen für den pdflush Daemon zu ändern, mir fiel aber vorher auf, dass mir bei der Programmierung des Imports ein Fehler unterlaufen war: Anstatt vor dem Import die Schlüssel zu deaktivieren

ALTER TABLE ... DISABLE KEYS

und nach dem Import die Schlüssel wieder zu aktivieren

ALTER TABLE ... ENABLE KEYS

waren durch einen Zeilenverrutscher die Schlüssel für eine Tabelle schon vor dem Import wieder aktiviert worden und genau das verursachte das Zusammenbrechen des Servers, weil die Indizes während des Imports neu berechnet werden mussten.

Auch interessant:

  1. Performancevergleich: Enzelne SQL-Statements vs. MySQL LOAD DATA INFILE vs. Prepared Statements
  2. Java: Erstellen großer CSV-Dateien zum Datenbank-Import
]]>
http://www.freakcommander.de/3289/computer/mysql-import-und-pdflush/feed/ 0
Gute Bücher (XV): Stuttard,Pinto – The Web Application Hacker’s Handbookhttp://www.freakcommander.de/3257/bucher/gute-buecher-xv-stuttardpinto-the-web-application-hackers-handbook/ http://www.freakcommander.de/3257/bucher/gute-buecher-xv-stuttardpinto-the-web-application-hackers-handbook/#comments Sat, 27 Nov 2010 12:01:09 +0000 crille http://www.freakcommander.de/?p=3257

Dieses über 700 Seiten starke Buch zur Sicherheit von Web Anwendungen sollte jeder gelesen haben, der Web Anwendungen programmiert. Ich zumindest hätte dieses Buch viel früher lesen sollen, denn es wird einem nicht nur erklärt, welche Sicherheitsmechanismen es gibt, auf was man achten muss und wieso andere Vorgehensweisen in einem gewissen Kontext keinen Sinn machen, sondern auch wie ein typischer Hacker vorgeht, um Schwachstellen auszuspähen und zu benutzen.

Die Autoren arbeiten in einer Sicherheitssoftware-Firma, so dass das Buch durchweg praxisorientiert geschrieben ist. Neben den Standardschwächen wie SQL Injection, XSS, Command Injection oder Path Traversal wird auch darauf eingegangen, was ein Angreifer aus Fehlermeldungen ableiten kann. Auf Angriffspunkte von ActiveX und Java Applets wird genauso eingegangen wie auf die unterschiedlichen Methoden, mit denen ein Hacker relativ automatisiert eine Web Anwendung inklusive ihrer “versteckten” Funktionalitäten erfassen kann.

Besonders interessant sind für mich solche SQL Injections, die keine Rückmeldung geben, da Fehlermeldungen und Ausgaben unterdrückt werden, aber mit Verzögerungen (Wait For-Anweisungen) Rückschlüsse auf das Ergebnis einer SQL-Anweisung ermöglichen.

Sehr ausführlich werden die zusammen gehörenden Bereiche Authentication, Session Management und Access Control besprochen. Und hier wird nicht zu unrecht darauf hingewiesen, dass Funktionen wie Zugangsdaten vergessen oder neues Passwort setzen generell Schwachstellen bieten können, um die Authentifizierung zu umgehen und zu verifizieren, welche User es in der Anwendung gibt.

Um es auf den Punkt zu bringen:
Ein absoluter Kaufbefehl!

(Jepp Felix, dieser Eintrag ist auch an dich gerichtet! ;o))

Auch interessant:

  1. Gute Bücher (XIV): Randy Pausch – Last Lecture
  2. Gute Bücher (III): Patrick Süskind – Das Parfum
  3. Gute Bücher (VII): Walter Moers – Die Stadt der träumenden Bücher
]]>
http://www.freakcommander.de/3257/bucher/gute-buecher-xv-stuttardpinto-the-web-application-hackers-handbook/feed/ 0
SQL IN und =http://www.freakcommander.de/2759/computer/sql-in-und/ http://www.freakcommander.de/2759/computer/sql-in-und/#comments Wed, 12 May 2010 17:55:05 +0000 crille http://www.freakcommander.de/?p=2759 Da erschafft man einen völlig neuen Wettkampf, der von 31 Teams weltweit wahrgenommen wird. Hunderte von Menschen diskutieren in zig Foren in deutsch, englisch, französisch, russisch, chinesisch, spanisch, japanisch, tschechisch etc. über die beste Taktik, um diese Challenge zu gewinnen. Und man selber ist heilfroh, dass alles funktioniert: die Statistiken werden korrekt erstellt, die Daten werden richtig importiert, man hat sogar die Muße den Importvorgang in der laufenden Challenge noch ein wenig zu optimieren – Operation am offenen Herzen quasi…

Selbst der alte Server (im nächsten Monat gibt’s einen neuen) funktioniert trotz fast doppelt so hohen Besucherzahlen. Und man denkt: Mensch, wat biste fürn dufter Typ?! Klappt ja alles 1A!

Und dann kommt die Hiobsbotschaft der Serverload ist manchmal 10.00. Irgendetwas stimmt nicht. Irgendeine Anfrage dauert zu lange. Man überprüft die Seiten und stellt fest, dass eine Abfrage dabei ist, die anfangs nur wenige Millisekunden gebraucht hat, jetzt aber 70 Sekunden braucht.. Eine typische Anfrage:

SELECT
	[...]
	output_24h
FROM
	pentathlon_score
		INNER JOIN
			pentathlon_team
		ON pentathlon_score.teamid = pentathlon_team.teamid
WHERE
	zeitpunkt IN (SELECT max(zeitpunkt) FROM pentathlon_score WHERE disziplinid= 1) AND
	disziplinid = 1 AND
	Rank > 0
ORDER BY Rank ASC

Und schließlich stellt man fest, dass wenn man das IN durch ein = ersetzt, die Anfrage plötzlich nur noch 0,05 Sekunden dauert.

SELECT
	[...]
	output_24h
FROM
	pentathlon_score
		INNER JOIN
			pentathlon_team
		ON pentathlon_score.teamid = pentathlon_team.teamid
WHERE
	zeitpunkt = (SELECT max(zeitpunkt) FROM pentathlon_score WHERE disziplinid= 1) AND
	disziplinid = 1 AND
	Rank > 0
ORDER BY Rank ASC

und das, obwohl die Unterabfrage SELECT max(zeitpunkt) [...] nur einen Wert zurück gibt..!?!

Auch interessant:

  1. Komplizierteste SQL-Query ever
  2. MySQL: UPDATE auf mehreren Tabellen und Subqueries
]]>
http://www.freakcommander.de/2759/computer/sql-in-und/feed/ 0
Komplizierteste SQL-Query everhttp://www.freakcommander.de/2652/computer/komplizierteste-sql-query-ever/ http://www.freakcommander.de/2652/computer/komplizierteste-sql-query-ever/#comments Sat, 20 Feb 2010 21:35:07 +0000 crille http://www.freakcommander.de/?p=2652 Es gibt so ein tolles Zitat:

“Was nicht auf einer einzigen Manuskriptseite zusammengefasst werden kann, ist nicht durchdacht und nicht entscheidungsreif.”

Wenn man das auf meine SQL-Abfrage überträgt, muss ich wohl eingestehen, dass diese Abfrage undurchdacht wäre. Ist sie aber nicht! Ich habe 3 Tage an dieser Abfrage zugebracht, damit sie das Ergebnis ausgibt, welches sie jetzt ausgibt.

Die Abfrage ist für ein Wettkampf, der aus mehreren Einzeldisziplinen besteht, die an unterschiedlichen Tagen veranstaltet werden (zeitgleich und hintereinander). Der Wettkampf ist für Teams. Für jede Einzeldisziplin ist die DisziplinId, TeamId, Platz, Datum, Punktestand vorhanden. Allerdings wird für die Gesamtwertung nicht der Punktestand in der Einzeldisziplin genommen, sondern inder Gesamtwertung werden neue Punkte anhand der Platzierung in der Einzeldisziplin vergeben – z.B. so: Platz 1 – 100 Punkte, Platz 2 – 90 Punkte, usw.

Es klingt schon so natürlich sprachlich kompliziert und ehe ich hier weitere Einzelheiten versuche zu erklären, platziere ich hier einfach die 117 Zeilen lange SQL-Abfrage:

SELECT
	@anzTeam := (SELECT COUNT(*) FROM pentathlon_team WHERE freigeschaltet='1'),
	@seq := @anzTeam + 1,
	@rank := @anzTeam,
	@letzteDatum := (SELECT DISTINCT SUBSTR(MIN(zeitpunkt),1,10) FROM pentathlon_score WHERE pentathlon_credits != 0),
	@letztePunkte := 999999;
SELECT
	gesamt.teamid,
	@seq := IF(@letzteDatum = gesamt.zeitpunkt, @seq - 1, @anzTeam) AS seq, /*Sequenz ...,3,2,1 je Datum*/
	@rank := IF(@letztePunkte = gesamt.punkte, @rank, @seq) AS rank, /*Der Rank eines Teams (wenn gleiche Punktzahl gleicher Rank)*/
	@letztePunkte := gesamt.punkte AS punkte,
	@letzteDatum := gesamt.zeitpunkt AS zeitpunkt
FROM
	(SELECT
		einzelgesamt.teamid,
		SUM(einzelgesamt.punkte) AS punkte,
		einzelgesamt.zeitpunkt AS zeitpunkt
	FROM
		(SELECT DISTINCT
			normale_daten.zeitpunkt,
			normale_daten.teamid,
			normale_daten.disziplinid,
			IF(normale_daten.punkte IS NOT NULL,
				normale_daten.punkte,
				IF((SELECT von FROM pentathlon_disziplin WHERE disziplinid = normale_daten.disziplinid) > normale_daten.zeitpunkt,
					0,	/*Disziplin noch nicht gestartet*/
					(SELECT	DISTINCT /*Disziplin ist beendet: Punkte des letzten Stands*/
						IF (pentathlon_score.rank > 25,
							0, /*Keine Punkte mehr ab Platz 25*/
							pentathlon_punkte.punkte
						) AS punkte
					FROM
						pentathlon_score
						LEFT JOIN
							pentathlon_punkte
							ON
								pentathlon_score.rank = pentathlon_punkte.rank
					WHERE
						pentathlon_score.disziplinid = normale_daten.disziplinid AND
						pentathlon_score.teamid = normale_daten.teamid AND
						pentathlon_score.zeitpunkt = (SELECT DISTINCT
															MAX(zeitpunkt)
														FROM pentathlon_score
														WHERE disziplinid = normale_daten.disziplinid)
					)
				)
			) AS punkte,
			IF(normale_daten.rank IS NOT NULL,
				normale_daten.rank,
				IF((SELECT von FROM pentathlon_disziplin WHERE disziplinid = normale_daten.disziplinid) > normale_daten.zeitpunkt,
					0,	/*Disziplin noch nicht gestartet*/
					(SELECT	DISTINCT /*Disziplin ist beendet: Punkte des letzten Stands*/
						pentathlon_score.rank
					FROM
						pentathlon_score
					WHERE
						pentathlon_score.disziplinid = normale_daten.disziplinid AND
						pentathlon_score.teamid = normale_daten.teamid AND
						pentathlon_score.zeitpunkt = (SELECT DISTINCT
															MAX(zeitpunkt)
														FROM pentathlon_score
														WHERE disziplinid = normale_daten.disziplinid)
					)
				)
			) AS rank
		FROM
			(SELECT DISTINCT
				alle_daten.zeitpunkt,
				alle_daten.teamid,
				alle_daten.disziplinid,
				pentathlon_score.rank,
				IF (pentathlon_score.rank > 25,
					0, /*Keine Punkte mehr ab Platz 25*/
					pentathlon_punkte.punkte
				) AS punkte
				FROM
					(SELECT DISTINCT
						SUBSTR(pentathlon_score.zeitpunkt,1,10) as zeitpunkt,
						pentathlon_team.teamid,
						pentathlon_disziplin.disziplinid,
						pentathlon_teamdisziplin.webrpc
					FROM
						pentathlon_score
						JOIN
							pentathlon_team
						JOIN
							pentathlon_disziplin
						INNER JOIN
							pentathlon_teamdisziplin
							ON pentathlon_disziplin.disziplinid = pentathlon_teamdisziplin.disziplinid
					WHERE
						pentathlon_score.pentathlon_credits != 0 AND
						pentathlon_team.freigeschaltet = '1') AS alle_daten
				LEFT JOIN
					pentathlon_score
						ON
							alle_daten.teamid = pentathlon_score.teamid AND
							alle_daten.disziplinid = pentathlon_score.disziplinid AND
							IF(SUBSTR(alle_daten.webrpc,1,33) = 'http://www.worldcommunitygrid.org',
								alle_daten.zeitpunkt = SUBSTR(pentathlon_score.zeitpunkt,1,10),
									alle_daten.zeitpunkt = DATE_ADD(SUBSTR(pentathlon_score.zeitpunkt,1,10), INTERVAL -1 DAY) AND
									SUBSTR(pentathlon_score.zeitpunkt,12,2)='01'
							)
				LEFT JOIN
					pentathlon_punkte
						ON
							pentathlon_score.rank = pentathlon_punkte.rank) AS normale_daten
	) AS einzelgesamt
	GROUP BY
		einzelgesamt.zeitpunkt,
		einzelgesamt.teamid
	) AS gesamt
ORDER BY
		gesamt.zeitpunkt,
		gesamt.punkte DESC

Natürlich hätte ich das auch alles durch einfache Abfragen lösen können, die ich dann in PHP zusammenfüge, aber in PHP ist das einfügen und sortieren der Daten so ineffizient.

Die @variable sind User-Variablen in MySQL, mit denen man die Platzierung in der Abfrage berechnen kann.

Jetzt würde ich meine Fähigkeiten in SQL mit einer 1,2 benoten.. ;)

Auch interessant:

  1. SQL IN und =
  2. MySQL: UPDATE auf mehreren Tabellen und Subqueries
]]>
http://www.freakcommander.de/2652/computer/komplizierteste-sql-query-ever/feed/ 0