Skip to content

Commit 3fe6838

Browse files
committed
Begin performance module
1 parent f51f61f commit 3fe6838

File tree

6 files changed

+87
-72
lines changed

6 files changed

+87
-72
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@
2929
!new_read_group/*.txt
3030
!expression_tests.sql
3131
!aggregate_perf.py
32+
!expression_performance/**
3233

3334
!read_group_replacement.py

expression_performance/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
{
5+
'name': 'Performance SQL',
6+
'version': '1.0',
7+
'category': 'Hidden',
8+
'description': """
9+
Module to test performance of query generated by expression.py
10+
""",
11+
'data': [
12+
13+
],
14+
'demo': [
15+
16+
],
17+
'installable': True,
18+
'auto_install': False,
19+
'license': 'LGPL-3',
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import perf_models
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from odoo import api, fields, models, tools, SUPERUSER_ID, _
2+
3+

psql_exists_in_join/exists_in_join_compare.py

Lines changed: 61 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict
2+
from itertools import islice
23
import random
34

45
import psycopg2
@@ -17,6 +18,8 @@
1718
'sale_order',
1819
]
1920

21+
rng = random.Random(1)
22+
2023
def drop_tables(cr):
2124
for t in tables:
2225
cr.execute(f"DROP TABLE IF EXISTS {t}")
@@ -30,17 +33,17 @@ def create_tables(cr):
3033
cr.execute("""
3134
CREATE TABLE sale_order (
3235
id SERIAL PRIMARY KEY,
33-
state VARCHAR NOT NULL, -- Can be 'draft', 'to confirm', 'confirmed', 'cancel' or 'done'
3436
partner VARCHAR NOT NULL
37+
uniform_100 INT,
38+
float_uniform_100 FLOAT,
3539
)
3640
""")
3741
cr.execute("""
3842
CREATE TABLE sale_order_line (
3943
id SERIAL PRIMARY KEY,
4044
sale_id INT NOT NULL,
41-
delivery_date DATE,
42-
qty FLOAT,
43-
amount FLOAT,
45+
uniform_100 INT,
46+
float_uniform_100 FLOAT,
4447
FOREIGN KEY (sale_id) REFERENCES sale_order (id)
4548
)
4649
""")
@@ -63,19 +66,11 @@ def create_tables(cr):
6366

6467
data_cases = {
6568
'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],
69+
'nb_sale_order': 50_000,
70+
'nb_sale_order_line': 500_000,
7771

7872
'sale_order_tags': ['urgent', 'safe', 'boring client', 'water', 'other'],
73+
'sale_order_tags_weight': [0.1, 0.1, 0.1, 0.1, 0.1],
7974
'nb_sale_order_tag_line_rel': 10000,
8075
},
8176
}
@@ -85,33 +80,53 @@ def create_indexes(cr):
8580
8681
""")
8782

88-
def fill_database(data_case):
83+
def get_values_generator_so(**kwargs):
84+
states = list(kwargs['sale_order_state_proportion'].keys())
85+
states_w = list(kwargs['sale_order_state_proportion'].values())
86+
for i in range(kwargs['nb_sale_order']):
87+
yield (
88+
rng.choices(states, states_w, k=1)[0],
89+
f'partern_{i}',
90+
)
8991

90-
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
91-
states = []
92-
for state, proportion in data_case['sale_order_state_proportion'].items():
93-
states += [state] * int(proportion * data_case['nb_sale_order'])
94-
values_sale_order = tuple(
95-
(state, f'partern_{i}')
96-
for i, state in zip(range(data_case['nb_sale_order']), random.sample(states, k=len(states)))
92+
def get_values_generator_sol(**kwargs):
93+
for i in range(kwargs['nb_sale_order_line']):
94+
yield (
95+
rng.randint(1, kwargs['nb_sale_order']),
96+
rng.randint(0, 100),
97+
rng.random() * 100,
9798
)
9899

99-
cr.execute(f"""
100-
INSERT INTO sale_order (state, partner) VALUES {','.join(['%s'] * len(values_sale_order))}
101-
""", values_sale_order)
100+
def split_every(n, iterator, piece_maker=tuple):
101+
"""Splits an iterable into length-n pieces. The last piece will be shorter
102+
if ``n`` does not evenly divide the iterable length.
103+
104+
:param int n: maximum size of each generated chunk
105+
:param Iterable iterable: iterable to chunk into pieces
106+
:param piece_maker: callable taking an iterable and collecting each
107+
chunk from its slice, *must consume the entire slice*.
108+
"""
109+
piece = piece_maker(islice(iterator, n))
110+
while piece:
111+
yield piece
112+
piece = piece_maker(islice(iterator, n))
113+
114+
def fill_database(data_case):
115+
BATCH_SIZE_CREATION = 100_000
102116

103117
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
104-
values_sale_order_line = tuple(
105-
(
106-
random.randint(1, data_case['nb_sale_order']),
107-
random.randint(data_case['qty_sale_order_line'][0], data_case['qty_sale_order_line'][1]),
108-
)
109-
for i in range(data_case['nb_sale_order_line'])
110-
)
118+
for i, pieces in enumerate(split_every(BATCH_SIZE_CREATION, get_values_generator_so(**data_case))):
119+
print(f"Insert {i * BATCH_SIZE_CREATION}/{data_case['nb_sale_order']} SO")
120+
cr.execute(f"""
121+
INSERT INTO sale_order (state, partner, uniform_100, float_uniform_100) VALUES {','.join(['%s'] * len(pieces))}
122+
""", pieces)
111123

112-
cr.execute(f"""
113-
INSERT INTO sale_order_line (sale_id, qty) VALUES {','.join(['%s'] * len(values_sale_order_line))}
114-
""", values_sale_order_line)
124+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
125+
for pieces in split_every(BATCH_SIZE_CREATION, get_values_generator_sol(**data_case)):
126+
print(f"Insert {i * BATCH_SIZE_CREATION}/{data_case['nb_sale_order_line']} SOL ")
127+
cr.execute(f"""
128+
INSERT INTO sale_order_line (sale_id, qty, price) VALUES {','.join(['%s'] * len(pieces))}
129+
""", pieces)
115130

116131

117132
SQL_alternative = {
@@ -139,7 +154,7 @@ def fill_database(data_case):
139154
ORDER BY "sale_order_line"."id"
140155
""",
141156

142-
'join':
157+
'left join':
143158
"""
144159
SELECT "sale_order_line"."id"
145160
FROM "sale_order_line"
@@ -197,12 +212,12 @@ def fill_database(data_case):
197212
ORDER BY "sale_order_line"."id"
198213
""",
199214

200-
'join':
215+
'left join':
201216
"""
202217
SELECT "sale_order_line"."id"
203218
FROM "sale_order_line"
204219
LEFT JOIN "sale_order" ON "sale_order"."id" = "sale_order_line"."sale_id"
205-
WHERE "sale_order"."state" = 'draft' OR "sale_order_line"."qty" > 50000
220+
WHERE ("sale_order"."state" = 'draft') OR "sale_order_line"."qty" > 50000
206221
ORDER BY "sale_order_line"."id"
207222
""",
208223

@@ -216,18 +231,6 @@ def fill_database(data_case):
216231
WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") OR "sale_order_line"."qty" > 50000
217232
ORDER BY "sale_order_line"."id"
218233
""",
219-
220-
# 'CTE, join':
221-
# """
222-
# WITH subquery AS (
223-
# SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
224-
# )
225-
# SELECT "sale_order_line"."id"
226-
# FROM "sale_order_line"
227-
# LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
228-
# WHERE "subquery"."id" IS NOT NULL OR "sale_order_line"."qty" > 50000
229-
# ORDER BY "sale_order_line"."id"
230-
# """,
231234
},
232235
"sale.order.line - search - [order_id.state = 'draft' AND qty > 50000]": {
233236
'in': """
@@ -255,7 +258,7 @@ def fill_database(data_case):
255258
ORDER BY "sale_order_line"."id"
256259
""",
257260

258-
'join':
261+
'left join':
259262
"""
260263
SELECT "sale_order_line"."id"
261264
FROM "sale_order_line"
@@ -274,23 +277,10 @@ def fill_database(data_case):
274277
WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") AND "sale_order_line"."qty" > 50000
275278
ORDER BY "sale_order_line"."id"
276279
""",
277-
278-
# 'CTE, join':
279-
# """
280-
# WITH subquery AS (
281-
# SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
282-
# )
283-
# SELECT "sale_order_line"."id"
284-
# FROM "sale_order_line"
285-
# LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
286-
# WHERE "subquery"."id" IS NOT NULL AND "sale_order_line"."qty" > 50000
287-
# ORDER BY "sale_order_line"."id"
288-
# """,
289280
},
290281
}
291282

292283
def one_data_case_test():
293-
294284
result_explain = defaultdict(dict)
295285
for alternative, sql_requests in SQL_alternative.items():
296286
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
@@ -300,18 +290,18 @@ def one_data_case_test():
300290
current_res = tuple(cr.fetchall())
301291
if res and current_res != res:
302292
print(f"{res} != {current_res}")
303-
raise ValueError(f"{case_str} doens't have the same result than the previous one for {alternative}")
293+
raise ValueError(f"ERROR: {case_str} doens't have the same result than the previous one for {alternative}")
304294
res = current_res
305295

306-
for case_str, sql_request in sql_requests.items():
307-
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
296+
with psycopg2.connect(CONNECTION_PARAMS) as conn, conn.cursor() as cr:
297+
for case_str, sql_request in sql_requests.items():
308298
plan = psql_explain(cr, sql_request)
309299
result_explain[alternative][case_str] = plan
310300

311301
for alternative, case_plans in result_explain.items():
312302
list_plan = list(case_plans.values())
313303
if len(set(list_plan)) != 1:
314-
print(f"\n-> Some queries has a different plan for {alternative} ({len(set(list_plan))}):")
304+
print(f"\n=> Some queries has a different plan for {alternative} ({len(set(list_plan))}):")
315305
case_by_plan = defaultdict(list)
316306
for case, plan in case_plans.items():
317307
case_by_plan[plan].append(case)
@@ -320,7 +310,7 @@ def one_data_case_test():
320310
print(f"These cases {cases} has this plan:\n{plan}")
321311
print(" ------------------------")
322312
else:
323-
print(f"\n-> {alternative} all plan is equals")
313+
print(f"\n=> {alternative} all plan is equals")
324314

325315

326316
def main():
@@ -352,8 +342,7 @@ def main():
352342
# https://github.com/postgres/postgres/commit/41efb8340877e8ffd0023bb6b2ef22ffd1ca014d
353343

354344

355-
356-
345+
# TODO Convert this stuff in a Odoo module to test it with expression.py
357346

358347
if __name__ == '__main__':
359348
main()

0 commit comments

Comments
 (0)