PostgreSQLストレステストテスターの作成

この投影(つまり「投影」)のアイデアは、自然に生まれました。 同社はメモリDB TimesTenを使用しており、1つの大きなテーブルにデータ、1億5000万件を超えるレコード、および約15ギガのボリュームが含まれています。 TimesTenは常に適切に機能し、リクエストに対するレスポンスは数ミリ秒で受信され、すべての人に適していました。 ある日、T10は非常に長い時間リクエストに応答し始め、応答時間は3〜5秒に増加しました。 技術サポートはもちろん問題の作業を開始しましたが、同時にT10を使用する理由を自問しました。なぜデータベースを通常のOracleまたはPostgres RDBMSに転送できないのですか? 適切なテストを実施するために調べる必要がありました。 その結果、インターネット上で少し掘り下げた後、テストに必要なフリーソフトウェアが見つかりませんでした。 その結果、その日の「ひざまずく」小さなコンソールユーティリティが作成されました。これは、さまざまな種類のクエリに対するDBMSからの応答時間を測定し、統計を収集し、さらに負荷テストが最も客観的であるようにマルチスレッド化されます。



テストのために、Postgres DBMSが選択され、対応する構造を持つテーブルがデータベースに作成され、最適なインデックスが構築されました。

CREATE TABLE "public"."numbers" ( "contract" int8 NOT NULL, "account" int8 NOT NULL, "number" int8 NOT NULL, "system_id" int2 NOT NULL, "region_id" int2 NOT NULL, "storage_id" int2 NOT NULL ); CREATE INDEX "ix_contract" ON "public"."numbers" USING btree ("contract"); CREATE UNIQUE INDEX "ix_number" ON "public"."numbers" USING btree ("number"); CREATE INDEX "ix_account" ON "public"."numbers" USING btree ("account");
      
      







テーブルへの入力は、手順を介して行われました(1億件のレコードを入力する時間は7時間でした)。

  CREATE OR REPLACE FUNCTION "public"."fill"(_count int8) RETURNS "pg_catalog"."int8" AS $BODY$DECLARE i int8; j int2; _number_start int8; _pa_start int8; _pa int8; _countract int8; BEGIN _number_start:=70000000000; _pa_start:=1000000000000; FOR i IN 0.._count BY 5 LOOP INSERT INTO number(contract,account,number,system_id,region_id,storage_id) VALUES (round(random()*100000000000+100000000000),round(random()*100000000+_pa_start),_number_start+0+i,round(random()*10+1),round(random()*10+101),round(random()*5+1)), (round(random()*100000000000+100000000000),round(random()*100000000+_pa_start),_number_start+1+i,round(random()*10+1),round(random()*10+101),round(random()*5+1)), (round(random()*100000000000+100000000000),round(random()*100000000+_pa_start),_number_start+2+i,round(random()*10+1),round(random()*10+101),round(random()*5+1)), (round(random()*100000000000+100000000000),round(random()*100000000+_pa_start),_number_start+3+i,round(random()*10+1),round(random()*10+101),round(random()*5+1)), (round(random()*100000000000+100000000000),round(random()*100000000+_pa_start),_number_start+4+i,round(random()*10+1),round(random()*10+101),round(random()*5+1)); END LOOP; RETURN 1; END; $BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100 ;
      
      







並行して、さまざまな種類のクエリでDBMSの負荷をエミュレートするコンソールアプリケーションが作成されました。 PostgreSQLに接続するために、 npgsqlプロジェクトが使用されました。

C#のメインプログラムのテキスト:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Npgsql; using System.Xml; using System.IO; using System.Threading; namespace sqlPerformer { class Program { public static Configuration c; public static string config_file = "config.xml"; //public static config config = new config(); public static Performance performance = new Performance(); public static List<Thread> threads = new List<Thread>(); static void Main(string[] args) { //saveConfig(); Console.WindowWidth = 120; loadConfig(); for (int i = 1; i <= Program.c.threads; i++) { Thread thread = new Thread(Program.thread); thread.Start(i); threads.Add(thread); } //Program.thread(); Console.ReadLine(); } public static void thread(object thread_id) { NpgsqlConnection cn = new NpgsqlConnection(); cn.ConnectionString = string.Format("Server={0}; Port={1}; Database={2}; User Id={3}; Password={4};", Program.c.host, Program.c.port, Program.c.database, Program.c.user, Program.c.password); cn.Open(); for (int i = 0; i <= Program.c.count; i++) { foreach (query q in Program.c.queries.query) { NpgsqlCommand cm = new NpgsqlCommand(); cm.Connection = cn; cm.CommandText = q.text; if (!q.status) continue; foreach (parameter p in q.parameters.parameter) { NpgsqlTypes.NpgsqlDbType _temp_type = NpgsqlTypes.NpgsqlDbType.Integer; object _val = null; switch (p.GetType().Name) { case "bigint": _temp_type = NpgsqlTypes.NpgsqlDbType.Bigint; _val = Program.getRandomInt64(((bigint)p).min, ((bigint)p).max); break; case "integer": _temp_type = NpgsqlTypes.NpgsqlDbType.Integer; _val = Program.getRandomInt32(((integer)p).min, ((integer)p).max); break; case "date": _temp_type = NpgsqlTypes.NpgsqlDbType.Timestamp; _val = Program.getRandomDate(((date)p).min, ((date)p).max); break; case "string_line": _temp_type = NpgsqlTypes.NpgsqlDbType.Varchar; _val = Program.getRandomStringLine(((string_line)p).chars, ((string_line)p).max, ((string_line)p).max); break; case "text": _temp_type = NpgsqlTypes.NpgsqlDbType.Text; _val = Program.getRandomText(((text)p).words, ((text)p).max, ((text)p).max); break; } cm.Parameters.Add(p.id, _temp_type); cm.Parameters[p.id].Value = _val; } Program.delay(); lock (Program.performance) { Program.performance.start(q.id, thread_id.ToString()); } cm.ExecuteNonQuery(); lock (Program.performance) { Program.performance.stop(); } } } } public static long getRandomInt64(long min, long max) { return Convert.ToInt64(Math.Round((new Random(unchecked((int)(DateTime.Now.Ticks)))).NextDouble() * (max - min) + min)); } public static int getRandomInt32(int min, int max) { return Convert.ToInt32(Math.Round((new Random(unchecked((int)(DateTime.Now.Ticks)))).NextDouble() * (max - min) + min)); } public static DateTime getRandomDate(DateTime min, DateTime max) { long stamp_min = min.Ticks; long stamp_max = max.Ticks; long stamp_new = Program.getRandomInt64(stamp_min, stamp_max); return (new DateTime(stamp_new)); } public static string getRandomStringLine(string chars, int min, int max) { string retval = ""; Random r = new Random(unchecked((int)(DateTime.Now.Ticks))); for (int i = 1; i <= r.Next(min, max); i++) retval += chars[r.Next(0, chars.Length - 1)]; return retval; } public static string getRandomText(List<string> words, int min, int max) { string retval = ""; Random r = new Random(unchecked((int)(DateTime.Now.Ticks))); for (int i = 1; i <= r.Next(min, max); i++) retval += " " + words[r.Next(0, words.Count - 1)]; return retval.Trim(); } public static void delay() { if (Program.c.delay.status) System.Threading.Thread.Sleep(Program.getRandomInt32(Program.c.delay.min, Program.c.delay.max)); } static void saveConfig() { Program.c = new Configuration("localhost", 5432, "postgres", "postgres"); query q = new query("SELECT * FROM \"public\".\"table\"(?)"); q.parameters.parameter.Add(new bigint(100000000, 200000000)); c.queries.query.Add(q); System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(c.GetType()); StreamWriter writer = File.CreateText(Program.config_file); xs.Serialize(writer, c); writer.Flush(); writer.Close(); } static void loadConfig() { System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer( typeof(Configuration)); StreamReader reader = File.OpenText(Program.config_file); Program.c = (Configuration)xs.Deserialize(reader); reader.Close(); } } }
      
      







設定には、XML形式が使用されました(より正確には、XMLでの構成オブジェクトのシリアル化)。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.Xml.Serialization; namespace sqlPerformer { [Serializable] public class Configuration { [System.Xml.Serialization.XmlElement()] public string host = "localhost"; [System.Xml.Serialization.XmlElement()] public int port = 5432; [System.Xml.Serialization.XmlElement()] public string user = "postgres"; [System.Xml.Serialization.XmlElement()] public string database = "postgres"; [System.Xml.Serialization.XmlElement()] public string password = "postgres"; [System.Xml.Serialization.XmlElement()] public queries queries = new queries(); [System.Xml.Serialization.XmlElement()] public delay delay = new delay(); [System.Xml.Serialization.XmlElement()] public int threads = 1; [System.Xml.Serialization.XmlElement()] public int count = 1; public Configuration() { } public Configuration(string host, int port, string user, string password) { this.host = host; this.port = port; this.user = user; this.password = password; } } [Serializable] public class queries { [System.Xml.Serialization.XmlElement()] public List<query> query = new List<query>(); public queries() { } } [Serializable] public class query { [System.Xml.Serialization.XmlAttribute()] public bool status = false; [System.Xml.Serialization.XmlAttribute()] public string id = ""; [System.Xml.Serialization.XmlElement()] public string text = ""; [System.Xml.Serialization.XmlElement()] public parameters parameters = new parameters(); public query() { } public query(string text) { this.text = text; } } [Serializable] public class parameters { [System.Xml.Serialization.XmlElement()] public List<parameter> parameter = new List<parameter>(); public parameters() { } } [Serializable] [System.Xml.Serialization.XmlInclude(typeof(bigint))] [System.Xml.Serialization.XmlInclude(typeof(integer))] [System.Xml.Serialization.XmlInclude(typeof(string_line))] [System.Xml.Serialization.XmlInclude(typeof(text))] [System.Xml.Serialization.XmlInclude(typeof(date))] public abstract class parameter { [System.Xml.Serialization.XmlAttribute()] public string id = ""; public parameter() { } } [Serializable] public class bigint : parameter { [System.Xml.Serialization.XmlElement()] public long min = 0; [System.Xml.Serialization.XmlElement()] public long max = 0; public bigint() { } public bigint(long min, long max) { this.min = min; this.max = max; } } [Serializable] public class integer : parameter { [System.Xml.Serialization.XmlElement()] public int min = 0; [System.Xml.Serialization.XmlElement()] public int max = 0; public integer() { } public integer(int min, int max) { this.min = min; this.max = max; } } [Serializable] public class date : parameter { [System.Xml.Serialization.XmlElement()] public DateTime min = DateTime.Now; [System.Xml.Serialization.XmlElement()] public DateTime max = DateTime.Now; public date() { } public date(DateTime min, DateTime max) { this.min = min; this.max = max; } } [Serializable] public class string_line : parameter { [System.Xml.Serialization.XmlElement()] public string chars = "qwertyuiopasdfghjklzxcvbnm"; [System.Xml.Serialization.XmlElement()] public int min = 2; [System.Xml.Serialization.XmlElement()] public int max = 10; public string_line() { } public string_line(string chars, int min, int max) { this.chars = chars; this.min = min; this.max = max; } } [Serializable] public class text : parameter { [System.Xml.Serialization.XmlElement()] public List<string> words = new List<string>() { "word1", "word2" }; [System.Xml.Serialization.XmlElement()] public int min = 2; [System.Xml.Serialization.XmlElement()] public int max = 10; public text() { } public text(List<string> words, int min, int max) { this.words = words; this.min = min; this.max = max; } } [Serializable] public class delay { [System.Xml.Serialization.XmlAttribute()] public bool status = false; [System.Xml.Serialization.XmlElement()] public int min = 10; [System.Xml.Serialization.XmlElement()] public int max = 100; } }
      
      







パフォーマンスを測定するために、パフォーマンスクラスが作成されました。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace sqlPerformer { class Performance { public Stopwatch sw = new Stopwatch(); public Dictionary<string, stat> stat = new Dictionary<string, stat>(); public string current_id = ""; public void start(string id, string thread_id) { this.sw.Reset(); this.current_id = thread_id + "-" + id; if (!this.stat.ContainsKey(this.current_id)) { this.stat.Add(this.current_id, new stat()); } this.sw.Start(); } public void stop() { this.sw.Stop(); addTick(); } public void addTick() { this.stat[this.current_id].add(this.sw.ElapsedTicks); Console.Clear(); foreach (KeyValuePair<string, stat> k in this.stat) Console.WriteLine(string.Format("{0}\tCount: {1}\tAverage: {2:#0.00}\tMin: {3:#0.00}\tMax: {4:#0.00}\tTotal: {5:#}" , k.Key , k.Value.count , k.Value.timeAvg , k.Value.timeMin , k.Value.timeMax , k.Value.timeTotal )); } } public class stat { public Int64 count = 0; public double timeTotal = 0; public double timeLast = 0; public double timeMin = 9999999; public double timeMax = 0; public double timeAvg = 0; public stat() { } public void add(long ticks) { this.count++; this.timeLast = ticks / 10000000.0; this.timeTotal += this.timeLast; this.timeAvg = this.timeTotal / this.count; this.timeMin = Math.Min(this.timeLast, this.timeMin); this.timeMax = Math.Max(this.timeLast, this.timeMax); } } }
      
      







テストサイトとして、2ギガバイトのRAM、Core2Duoプロセス、およびブレーキネジを備えたワークステーションを使用しました。 結果は驚くべきものでした.50スレッドでは、平均応答は8〜10の範囲で、最大で30ミリ秒でしたが、ディスクの負荷が高くなりました。 深刻な機器では、これらの値がはるかに低いことは明らかです。 しかし、他の何かが喜んでいます-大きなテーブルで作業するとき、DBMSは非常に適切に応答します。

原則として、このプログラムを使用して、任意のデータセットと任意の数のテーブルを含む任意のデータベースの負荷テストを行うことを妨げるものはありません。



All Articles