Neues in der Kategorie PowerDNS

Ich selbst nutze unter anderem PowerDNS als DNS-Server. Im letzten Monat betrachtete ich mir dessen MySQL-Backend, da ich über zwei Kanäle darüber informiert wurde, dass PowerDNS mit dem Backend nicht skaliert.

Die DNS-Records werden in PowerDNS in zwei Tabellen abgelegt. Eine für die Domains:

create table domains (
id INT auto_increment,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
primary key (id)
) Engine=InnoDB;

Und eine weitere für die Records:

CREATE TABLE records (
id int(11) NOT NULL auto_increment,
domain_id int(11) NOT NULL,
name varchar(255) NOT NULL,
type varchar(10) NOT NULL,
content varchar(255) NOT NULL,
ttl int(11) NOT NULL,
prio int(11) default NULL,
change_date int(11) default NULL,
PRIMARY KEY (id),
KEY name_index(name),
KEY nametype_index(name,type),
KEY domainid_index(domain_id)
);

Zudem existiert noch ein FK-Constraint, aber auch der tut hier nichts zur Sache.
Die Tabelle domains wurde mit 6.000.000 Datensätzen bestückt. Die Tabelle records wurde mit 46.195.356 Datensätzen bestückt. Ich denke damit wird schon ein größerer DNS-Server simuliert :D

Zwar schien es, als sollte man sich mal Gedanken über die Normalisierung ansich machen, doch das war nicht mein Skope. Es galt mit etwas Mikrotuning schon Erfolge zu erzielen. PowerDNS pdns-3.0-rc2 und folgende MySQL-Version kam zum Einsatz:

[pdns]> SELECT VERSION();
+-------------------+
| VERSION()         |
+-------------------+
| 5.2.5-MariaDB-log |
+-------------------+
1 row in set (0.00 sec)

Die Datenbank meinte die Tabellen würden folgenden Platzverbrauch haben.

> SELECT TABLE_NAME,INDEX_LENGTH,DATA_LENGTH from information_schema.TABLES where TABLE_NAME IN('records','domains');
+------------+--------------+-------------+
| TABLE_NAME | INDEX_LENGTH | DATA_LENGTH |
+------------+--------------+-------------+
| domains    |    475004928 |   431898624 |
| records    |  11372855296 |  5813305344 |
+------------+--------------+-------------+

Bei PowerDNS wurden alle Caches abgeschaltet (es galt die Datenbank zu testen!) und 494969 disjunkte DNS-Abfragen gestellt. Diese waren in 48.9 Sekunden durchgelaufen, was ca. 10114 qps entspricht. (Für jeden Test wurde die Datenbank restartet und der zweite Lauf genommen.)

Da Abfragen gegen die Tabelle records gehen,  nur diese Tabelle "optimiert"- Einige Änderungen sind analog in der Tabelle domains möglich.

Als erste Optimierung wurde ein redundanter Index entfernt.

drop  index `rec_name_index`  on records;


Hiernach wurden 10822 qps gemessen. Dies ist wohl nicht die Welt. Beim Platzverbrauch sieht es schon besser aus:


+------------+--------------+-------------+
| TABLE_NAME | INDEX_LENGTH | DATA_LENGTH |
+------------+--------------+-------------+
| domains    |    475004928 |   431898624 |
| records    |   6116343808 |  5813305344 |
+------------+--------------+-------------+

Die Spalte type speichert die Recordtypen. Aus dem VARCHAR wird im Index ein CHAR. Da die Menge der Werte begrenzt ist bietet sich hier ein ENUM an. Welches den Vorteil hat ein INT zu sein und zum anderen sicherstellt, dass nicht andere als die definierten Werte in die Tabelle kommen.

ALTER TABLE records   MODIFY  `type` enum('A','AAAA','SOA','NS','MX','CNAME','PTR','TXT');

Zugegeben, dies ist nur ein Subset der nötigen Recordtypen. Am Ergebnis wird dies nichts ändern. Nach dieser Änderung haben wir nun 10918 qps. Angenehmer ist die weitere Reduktion der Datengröße. Diesmal auch nicht nur bei den Indexdaten.


+------------+--------------+-------------+
| TABLE_NAME | INDEX_LENGTH | DATA_LENGTH |
+------------+--------------+-------------+
| domains    |    475004928 |   431898624 |
| records    |   5816451072 |  5696913408 |
+------------+--------------+-------------+

All diese Tests liefen mit  distributor-threads=32. Das ist eine Konfigurationseinstellung (im PowerDNS) für die Anzahl der Verbindungen, die PowerDNS zur Datenbank öffnet. Der Default liegt bei 3. Mit distributor-threads=3 erreichte ich lediglich 5656 qps.
Zu guter Letzt ändern wir noch den Index nametype_index. Die wenigsten FQDN nutzen die im RFC ermöglichten 255 Zeichen aus. Sprich hier lohnt sich ein prefix-Index. (Der alte wurde gedropt)

CREATE INDEX `nametype_index` on records(name(100),type);

Nun waren wir bei 10923 qps angelangt und was sagt der Platzverbrauch?

+------------+--------------+-------------+
| TABLE_NAME | INDEX_LENGTH | DATA_LENGTH |
+------------+--------------+-------------+
| domains    |    475004928 |   431898624 |
| records    |   3547332608 |  5696913408 |
+------------+--------------+-------------+


Sweet! Halten wir fest die Index_legth ist von 11372855296 Bytes auf 3547332608 Bytes reduziert worden. Damit wurden hier etwas über 7GB gespart \o/
An diesem Punkt angelangt wurden die records noch in PBXT geändert. Hierbei wurden 12375 qps erreicht:) Wobei der Platzverbrauch immens anstieg:

| records    |   5684629504 | 12380356432 |


Später wurde ich in #powerdns darauf hingewiesen, dass das verwendete Benchmarktool (dnsperf) auch mit einer längeren Queue ausgeführt werden kann. So wurden mit ./dnsperf -d /var/tmp/pdns.list -q 2000 -s localhost schnell 22994 qps erreicht.
Das ist selbstredend nur ein Anfang. Aber zeigt es doch, dass in vielen Projekten noch Steigerungspotential steckt. Von nicht skalieren kann aber nicht gesprochen werden. :)


Viel Spaß
Erkan