Skip to content

Commit 3f3fa28

Browse files
committed
Add perf test
1 parent 972e0dd commit 3f3fa28

File tree

3 files changed

+360
-8
lines changed

3 files changed

+360
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
!exists_smarty.py
1919
!modified_trigger_perf.py
2020
!new_domain_object.py
21+
!exists_in_join_compare.py
2122

2223
!res_exists_perf/*.log
2324
!memory_shared_lru/*.py

exists_in_join_compare.py

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
from collections import defaultdict
2+
import random
3+
4+
import psycopg2
5+
import psycopg2.extensions
6+
import psycopg2.errors
7+
8+
from misc import psql_explain, psql_vacuum_analyse, psql_set_timeout
9+
10+
CONNECTION_PARAMS = "dbname=master"
11+
12+
13+
tables = [
14+
'sale_order_tag_line_rel',
15+
'sale_order_tag',
16+
'sale_order_line',
17+
'sale_order',
18+
]
19+
20+
def drop_tables(cr):
21+
for t in tables:
22+
cr.execute(f"DROP TABLE IF EXISTS {t}")
23+
24+
def create_tables(cr):
25+
# I want 4 table:
26+
# - Sale Order (main table), have a one2many -> sale.order.line via 'sale_id'
27+
# - Sale Order Line (sub table) link to sale_id
28+
# - Sale Order Tag ()
29+
# - Many2many table between Sale Order Tab and Sale Order Line
30+
cr.execute("""
31+
CREATE TABLE sale_order (
32+
id SERIAL PRIMARY KEY,
33+
state VARCHAR NOT NULL, -- Can be 'draft', 'to confirm', 'confirmed', 'cancel' or 'done'
34+
partner VARCHAR NOT NULL
35+
)
36+
""")
37+
cr.execute("""
38+
CREATE TABLE sale_order_line (
39+
id SERIAL PRIMARY KEY,
40+
sale_id INT NOT NULL,
41+
delivery_date DATE,
42+
qty FLOAT,
43+
amount FLOAT,
44+
FOREIGN KEY (sale_id) REFERENCES sale_order (id)
45+
)
46+
""")
47+
cr.execute("""
48+
CREATE TABLE sale_order_tag (
49+
id SERIAL PRIMARY KEY,
50+
name VARCHAR,
51+
color INT
52+
)
53+
""")
54+
cr.execute("""
55+
CREATE TABLE sale_order_tag_line_rel (
56+
tag_id INT,
57+
line_id INT,
58+
PRIMARY KEY (tag_id, line_id),
59+
FOREIGN KEY (tag_id) REFERENCES sale_order_tag (id),
60+
FOREIGN KEY (line_id) REFERENCES sale_order_line (id)
61+
)
62+
""")
63+
64+
data_cases = {
65+
'small': {
66+
'nb_sale_order': 50000,
67+
'sale_order_state_proportion': {
68+
'draft': 0.2,
69+
'to confirm': 0.2,
70+
'confirmed': 0.2,
71+
'cancel': 0.2,
72+
'done': 0.2,
73+
},
74+
75+
'nb_sale_order_line': 500000,
76+
'qty_sale_order_line': [0, 100000],
77+
78+
'sale_order_tags': ['urgent', 'safe', 'boring client', 'water', 'other'],
79+
'nb_sale_order_tag_line_rel': 10000,
80+
},
81+
}
82+
83+
def create_indexes(cr):
84+
cr.execute("""
85+
86+
""")
87+
88+
def fill_database(data_case):
89+
90+
print("create sale_order")
91+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
92+
states = []
93+
for state, proportion in data_case['sale_order_state_proportion'].items():
94+
states += [state] * int(proportion * data_case['nb_sale_order'])
95+
values_sale_order = tuple(
96+
(state, f'partern_{i}')
97+
for i, state in zip(range(data_case['nb_sale_order']), random.sample(states, k=len(states)))
98+
)
99+
100+
cr.execute(f"""
101+
INSERT INTO sale_order (state, partner) VALUES {','.join(['%s'] * len(values_sale_order))}
102+
""", values_sale_order)
103+
104+
print("create sale_order_line")
105+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
106+
values_sale_order_line = tuple(
107+
(
108+
random.randint(1, data_case['nb_sale_order']),
109+
random.randint(data_case['qty_sale_order_line'][0], data_case['qty_sale_order_line'][1]),
110+
)
111+
for i in range(data_case['nb_sale_order_line'])
112+
)
113+
114+
cr.execute(f"""
115+
INSERT INTO sale_order_line (sale_id, qty) VALUES {','.join(['%s'] * len(values_sale_order_line))}
116+
""", values_sale_order_line)
117+
118+
119+
SQL_alternative = {
120+
"sale.order.line - search - [order_id.state = 'draft']": {
121+
'in': """
122+
SELECT "sale_order_line"."id"
123+
FROM "sale_order_line"
124+
WHERE (
125+
"sale_order_line"."sale_id" IN (
126+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
127+
)
128+
)
129+
ORDER BY "sale_order_line"."id"
130+
""",
131+
132+
'exists':
133+
"""
134+
SELECT "sale_order_line"."id"
135+
FROM "sale_order_line"
136+
WHERE (
137+
EXISTS (
138+
SELECT FROM "sale_order" WHERE "sale_order"."state" = 'draft' AND "sale_order"."id" = "sale_order_line"."sale_id"
139+
)
140+
)
141+
ORDER BY "sale_order_line"."id"
142+
""",
143+
144+
'join':
145+
"""
146+
SELECT "sale_order_line"."id"
147+
FROM "sale_order_line"
148+
LEFT JOIN "sale_order" ON "sale_order"."id" = "sale_order_line"."sale_id"
149+
WHERE "sale_order"."state" = 'draft'
150+
ORDER BY "sale_order_line"."id"
151+
""",
152+
153+
'CTE, in':
154+
"""
155+
WITH subquery AS (
156+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
157+
)
158+
SELECT "sale_order_line"."id"
159+
FROM "sale_order_line"
160+
WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery")
161+
ORDER BY "sale_order_line"."id"
162+
""",
163+
164+
# 'CTE, join':
165+
# """
166+
# WITH subquery AS (
167+
# SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
168+
# )
169+
# SELECT "sale_order_line"."id"
170+
# FROM "sale_order_line"
171+
# LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
172+
# WHERE "subquery"."id" IS NOT NULL
173+
# ORDER BY "sale_order_line"."id"
174+
# """,
175+
},
176+
"sale.order.line - search - [order_id.state = 'draft' OR qty > 50000]": {
177+
'in': """
178+
SELECT "sale_order_line"."id"
179+
FROM "sale_order_line"
180+
WHERE (
181+
"sale_order_line"."sale_id" IN (
182+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
183+
)
184+
OR "sale_order_line"."qty" > 50000
185+
)
186+
ORDER BY "sale_order_line"."id"
187+
""",
188+
189+
'exists':
190+
"""
191+
SELECT "sale_order_line"."id"
192+
FROM "sale_order_line"
193+
WHERE (
194+
EXISTS (
195+
SELECT FROM "sale_order" WHERE "sale_order"."state" = 'draft' AND "sale_order"."id" = "sale_order_line"."sale_id"
196+
)
197+
OR "sale_order_line"."qty" > 50000
198+
)
199+
ORDER BY "sale_order_line"."id"
200+
""",
201+
202+
'join':
203+
"""
204+
SELECT "sale_order_line"."id"
205+
FROM "sale_order_line"
206+
LEFT JOIN "sale_order" ON "sale_order"."id" = "sale_order_line"."sale_id"
207+
WHERE "sale_order"."state" = 'draft' OR "sale_order_line"."qty" > 50000
208+
ORDER BY "sale_order_line"."id"
209+
""",
210+
211+
'CTE, in':
212+
"""
213+
WITH subquery AS (
214+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
215+
)
216+
SELECT "sale_order_line"."id"
217+
FROM "sale_order_line"
218+
WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") OR "sale_order_line"."qty" > 50000
219+
ORDER BY "sale_order_line"."id"
220+
""",
221+
222+
# 'CTE, join':
223+
# """
224+
# WITH subquery AS (
225+
# SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
226+
# )
227+
# SELECT "sale_order_line"."id"
228+
# FROM "sale_order_line"
229+
# LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
230+
# WHERE "subquery"."id" IS NOT NULL OR "sale_order_line"."qty" > 50000
231+
# ORDER BY "sale_order_line"."id"
232+
# """,
233+
},
234+
"sale.order.line - search - [order_id.state = 'draft' AND qty > 50000]": {
235+
'in': """
236+
SELECT "sale_order_line"."id"
237+
FROM "sale_order_line"
238+
WHERE (
239+
"sale_order_line"."sale_id" IN (
240+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
241+
)
242+
AND "sale_order_line"."qty" > 50000
243+
)
244+
ORDER BY "sale_order_line"."id"
245+
""",
246+
247+
'exists':
248+
"""
249+
SELECT "sale_order_line"."id"
250+
FROM "sale_order_line"
251+
WHERE (
252+
EXISTS (
253+
SELECT FROM "sale_order" WHERE "sale_order"."state" = 'draft' AND "sale_order"."id" = "sale_order_line"."sale_id"
254+
)
255+
AND "sale_order_line"."qty" > 50000
256+
)
257+
ORDER BY "sale_order_line"."id"
258+
""",
259+
260+
'join':
261+
"""
262+
SELECT "sale_order_line"."id"
263+
FROM "sale_order_line"
264+
LEFT JOIN "sale_order" ON "sale_order"."id" = "sale_order_line"."sale_id"
265+
WHERE "sale_order"."state" = 'draft' AND "sale_order_line"."qty" > 50000
266+
ORDER BY "sale_order_line"."id"
267+
""",
268+
269+
'CTE, in':
270+
"""
271+
WITH subquery AS (
272+
SELECT "sale_order"."id" FROM "sale_order" WHERE "sale_order"."state" = 'draft'
273+
)
274+
SELECT "sale_order_line"."id"
275+
FROM "sale_order_line"
276+
WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") AND "sale_order_line"."qty" > 50000
277+
ORDER BY "sale_order_line"."id"
278+
""",
279+
280+
# 'CTE, join':
281+
# """
282+
# WITH subquery AS (
283+
# SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
284+
# )
285+
# SELECT "sale_order_line"."id"
286+
# FROM "sale_order_line"
287+
# LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
288+
# WHERE "subquery"."id" IS NOT NULL AND "sale_order_line"."qty" > 50000
289+
# ORDER BY "sale_order_line"."id"
290+
# """,
291+
},
292+
}
293+
294+
def one_data_case_test():
295+
296+
print("Launch test")
297+
result_explain = defaultdict(dict)
298+
for alternative, sql_requests in SQL_alternative.items():
299+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
300+
res = None
301+
for case_str, sql_request in sql_requests.items():
302+
cr.execute(sql_request)
303+
current_res = tuple(cr.fetchall())
304+
if res and current_res != res:
305+
print(f"{res} != {current_res}")
306+
raise ValueError(f"{case_str} doens't have the same result than the previous one for {alternative}")
307+
res = current_res
308+
309+
for case_str, sql_request in sql_requests.items():
310+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
311+
plan = psql_explain(cr, sql_request)
312+
result_explain[alternative][case_str] = plan
313+
314+
for alternative, case_plans in result_explain.items():
315+
list_plan = list(case_plans.values())
316+
if len(set(list_plan)) != 1:
317+
print(f"Some queries has a different plan for {alternative} ({len(set(list_plan))}):")
318+
case_by_plan = defaultdict(list)
319+
for case, plan in case_plans.items():
320+
case_by_plan[plan].append(case)
321+
for plan, cases in case_by_plan.items():
322+
print(" ------------------------")
323+
print(f"These cases {cases} has this plan:\n{plan}")
324+
print(" ------------------------")
325+
else:
326+
print(f"{alternative} all plan is equals")
327+
328+
329+
def main():
330+
psql_set_timeout(CONNECTION_PARAMS, 30)
331+
332+
for str_case, data_case in data_cases.items():
333+
print(" ################# ", str_case, ' #####################')
334+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
335+
drop_tables(cr)
336+
create_tables(cr)
337+
338+
339+
fill_database(data_case)
340+
psql_vacuum_analyse(CONNECTION_PARAMS, None)
341+
342+
one_data_case_test()
343+
344+
345+
346+
if __name__ == '__main__':
347+
main()

misc.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ def remove_outliers(values, outlier_thr = 2):
3131
return list(filter(lambda v: v > down_threeshold and v < up_threeshold, values))
3232

3333
def statically_faster(values_1, values_2):
34-
""" Return true if values_1 is statically
35-
less (faster) than values_2
36-
"""
37-
n1 = NormalDist.from_samples(values_1)
38-
n2 = NormalDist.from_samples(values_2)
39-
p = n1.overlap(n2)
40-
return p < 0.01 and fmean(values_1) < fmean(values_2)
34+
""" Return true if values_1 is statically
35+
less (faster) than values_2
36+
"""
37+
n1 = NormalDist.from_samples(values_1)
38+
n2 = NormalDist.from_samples(values_2)
39+
p = n1.overlap(n2)
40+
return p < 0.01 and fmean(values_1) < fmean(values_2)
4141

4242
def x_bests(values, x):
4343
return sorted(values)[:x]
@@ -162,9 +162,13 @@ def psql_vacuum_analyse(CONNECTION_PARAMS, table_name):
162162
cur.execute(f"VACUUM ANALYZE {table_name if table_name else ''}")
163163

164164
def psql_explain(cur, query):
165-
cur.execute("EXPLAIN ANALYZE " + query)
165+
cur.execute("EXPLAIN " + query)
166166
return "\n".join(s for s, in cur.fetchall())
167167

168168
def psql_explain_analyse(cur, query):
169169
cur.execute("EXPLAIN ANALYZE " + query)
170170
return "\n".join(s for s, in cur.fetchall())
171+
172+
def psql_explain_complete_analyse(cur, query):
173+
cur.execute("EXPLAIN (ANALYZE, SETTINGS, VERBOSE) " + query)
174+
return "\n".join(s for s, in cur.fetchall())

0 commit comments

Comments
 (0)