6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 21:43:06 +01:00
firebird-qa/tests/bugs/core_5393_test.py

319 lines
12 KiB
Python
Raw Normal View History

2021-04-26 20:07:00 +02:00
#coding:utf-8
#
# id: bugs.core_5393
# title: Bad optimization of some operations with views containing subqueries
# decription:
2021-12-31 12:06:51 +01:00
# https://github.com/FirebirdSQL/firebird/issues/5666
#
# Re-implemented 21.11.2021: avoid compare values of datediff(millisecond from ... to ...) because heavy concurrent
# workload can occur at the time when this test is running (encountered multiple cases of this for last year).
#
# CPU User Time values are used instead, package 'psutil' must be installed for this test.
#
# Two procedures are created in order to check issue, each with pair of UPDATE statements inside cursor loop:
# * sp_upd_using_primary_key_field - with 1st UPDATE that uses primary key;
# * sp_upd_using_current_of_cursor - with 1st UPDATE that uses 'WHERE CURRENT OF <C>' feature.
# (second UPDATE in both procedures always uses PK of view v_test2)
#
# We make several calls of each procedure (see N_MEASURES) and do invocation of psutil.Process(fb_pid).cpu_times()
# before and after each call, thus and obtain CPU User Time values for each iteration.
# Difference between them can be considered as much more accurate performance estimation.
#
# ::: NB :::
# It was decided NOT to analyze table statistics (via trace or get_table_access_stats() method of db_conn)
# because access paths can change in the future. Particularly, if HASH will be used for joining indexed columns
# and optimizer will choose it for one of statements then statistics can have big numbers of natural reads for
# if and still no nat. reads in another statement.
#
# Checked on Windows:
# 3.0.1.32609 (27-sep-2016, before fix):
# ratio_lst: [222.0, 110.5, 220.0, 220.0, 111.0, 222.0, 109.5, 123.5, 148.0, 133.0, 240.0, 228.0, 224.0, 221.0, 219.0, 217.0, 222.0, 222.0, 221.0, 222.0]
# median_ratio= 220.5
#
# 3.0.2.32703 (21-mar-2017, after fix):
# ratio_lst: [0.73, 0.73, 0.8, 0.68, 0.73, 0.8, 0.68, 0.94, 0.82, 0.75, 0.66, 0.75, 0.62, 0.65, 0.57, 0.8, 0.86, 0.63, 0.52, 0.72]
# median_ratio= 0.73
#
# recent versions (3.0.8.33535, 4.0.1.2660, 5.0.0.311):
# ratio_lst: [2.0, 0.5, 1.0, 2.0, 0.5, 1.0, 15625.0, 0.5, 1.0, 0.5, 0.5, 1.0, 3.2e-05, 1.0, 1.0, 0.5, 2.0, 0.5, 1.0, 1.0]
# median_ratio= 1.0
#
# 50sS, build 5.0.0.311 : 2.814s ; 50Cs, build 5.0.0.311 : 3.153s.
# 40sS, build 4.0.1.2660 : 2.818s ; 40Cs, build 4.0.1.2660 : 3.532s.
# 30sS, build 3.0.8.33535 : 2.548s ; 30Cs, build 3.0.8.33535 : 3.162s.
#
# 21.11.2021. Checked on Linux (after installing pakage psutil):
# 5.0.0.313 SS: 3.264s
# 4.0.1.2668 SS: 2.968s
# 3.0.8.33540 SS: 3.285s
#
# OLD COMMENTS:
# -------------
# Confirmed trouble on WI-V3.0.2.3262, ratio was ~146(!).
# Performance is OK on WI-V3.0.2.32629 and WI-T4.0.0.450
# Plans differ on 3.0.2 and 4.0 thus they are excluded from the output!
# Performance of 4.0 significantly _WORSE_ than of 3.0.2, sent letter to dimitr, 11.11.2016 13:47.
#
2021-04-26 20:07:00 +02:00
# tracker_id: CORE-5393
# min_versions: ['3.0.2']
# versions: 3.0.2
# qmid: None
import pytest
2021-12-31 12:06:51 +01:00
from firebird.qa import db_factory, python_act, Action
2021-04-26 20:07:00 +02:00
# version: 3.0.2
# resources: None
substitutions_1 = []
init_script_1 = """"""
db_1 = db_factory(sql_dialect=3, init=init_script_1)
2021-12-31 12:06:51 +01:00
# test_script_1
#---
#
# import os
# import subprocess
# import psutil
# import time
#
# os.environ["ISC_USER"] = user_name
# os.environ["ISC_PASSWORD"] = user_password
# engine = db_conn.engine_version
#
# #--------------------------------------------
#
# def median(lst):
# n = len(lst)
# s = sorted(lst)
# return (sum(s[n//2-1:n//2+1])/2.0, s[n//2])[n % 2] if n else None
#
# #--------------------------------------------
#
# def flush_and_close( file_handle ):
# # https://docs.python.org/2/library/os.html#os.fsync
# # If you're starting with a Python file object f,
# # first do f.flush(), and
# # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk.
# global os
#
# file_handle.flush()
# if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull:
# # otherwise: "OSError: [Errno 9] Bad file descriptor"!
# os.fsync(file_handle.fileno())
# file_handle.close()
#
# #--------------------------------------------
#
# def cleanup( f_names_list ):
# global os
# for i in range(len( f_names_list )):
# if type(f_names_list[i]) == file:
# del_name = f_names_list[i].name
# elif type(f_names_list[i]) == str:
# del_name = f_names_list[i]
# else:
# print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.')
# print('type(f_names_list[i])=',type(f_names_list[i]))
# del_name = None
#
# if del_name and os.path.isfile( del_name ):
# os.remove( del_name )
#
# #--------------------------------------------
#
#
# ###########################
# ### S E T T I N G S ###
# ###########################
# # Number of PSQL calls:
# N_MEASURES = 20
#
# # How many rows must be inserted in the table:
# N_COUNT_PER_MEASURE = 1000
#
# # Maximal value for MEDIAN of ratios between CPU user time when comparison was made.
# #
# MEDIAN_TIME_MAX_RATIO = 1.10
# ############################
#
# sql_init = '''
# set bail on;
# set echo on;
# set term ^;
# recreate global temporary table test (id int primary key using index test_pk, col int) on commit delete rows
# --recreate table test (id int primary key using index test_pk, col int)
# ^
# -- view1, must contain a subquery:
# create or alter view v_test1 (id1, id2, col1, col2, dummy) as
# select t1.id, t2.id, t1.col, t2.col, (select 1 from rdb$database)
# from test t1
# join test t2 on t1.col = t2.id
# ^
#
# -- view2, must contain a subquery:
# create or alter view v_test2 (id1, id2, col1, col2, dummy) as
# select ta.id, tb.id, ta.col, tb.col, (select 1 from rdb$database)
# from test ta
# join test tb on ta.col = tb.id
# ^
#
# -- empty triggers to make both views updatable:
# create or alter trigger trg_vtest1_bu for v_test1 before update as
# begin
# end
# ^
# create or alter trigger trg_vtest2_bu for v_test2 before update as
# begin
# end
# ^
#
# create procedure sp_insert_rows( a_rows_cnt int ) as
# declare i int = 0;
# begin
# while ( i < a_rows_cnt ) do
# begin
# insert into test(id, col) values(:i, :i);
# i = i + 1;
# end
# end
# ^
#
# create procedure sp_upd_using_primary_key_field as
# begin
#
# for select id1 from v_test1 as cursor c do
# begin
# -- ####################################
# -- "Both updates utilize the primary key index for table T1. So far so good."
# -- ####################################
# update v_test1 x set x.col1 = 1
# where x.id1 = c.id1; ------------------ u s i n g t a b l e s P K
#
# update v_test2 y set y.col1 = 1
# where y.id1 = c.id1;
#
# -- execute statement ('update v_test2 set col1 = 1 where id1 = ?') (c.id1);
#
# end
# end
# ^
#
# create procedure sp_upd_using_current_of_cursor as
# begin
# for select id1 from v_test1 as cursor c do
# begin
#
# -- first update is not reported in the plan because it's based on the same cursor as the select itself
# update v_test1 u set u.col1 = 1
# where current of c; ------------------ u s i n g c u r r e n t o f
#
# -- ######################################################
# -- "... second update is unable to utilize the primary key index for table T1 anymore"
# -- ######################################################
# -- In the production database, this issue is causing 100x degradation in execution time.
# update v_test2 v set v.col1 = 1
# where v.id1 = c.id1;
#
# -- execute statement ('update v_test2 set col1 = 1 where id1 = ?') (c.id1);
#
# end
# end
# ^
# set term ;^
# commit;
# '''
#
#
# f_init_sql=open( os.path.join(context['temp_directory'],'tmp_5393_init.sql'), 'w')
# f_init_sql.write(sql_init)
# flush_and_close( f_init_sql )
#
# f_init_log=open( os.path.join(context['temp_directory'],'tmp_5393_init.log'), 'w')
# f_init_err=open( os.path.join(context['temp_directory'],'tmp_5393_init.err'), 'w')
# subprocess.call( [ context['isql_path'], dsn, '-q', '-i', f_init_sql.name], stdout=f_init_log, stderr=f_init_err )
# flush_and_close( f_init_log )
# flush_and_close( f_init_err )
#
# cur=db_conn.cursor()
#
# cur.execute("select rdb$relation_id from rdb$relations where rdb$relation_name = upper('test')")
# test_rel_id = int(cur.fetchone()[0])
#
# cur.execute('select mon$server_pid as p from mon$attachments where mon$attachment_id = current_connection')
# fb_pid = int(cur.fetchone()[0])
#
# name_suffixes = ( 'using_primary_key_field', 'using_current_of_cursor' )
# sp_time = {}
#
# for i in range(0, N_MEASURES):
# cur.callproc( 'sp_insert_rows', (N_COUNT_PER_MEASURE,) )
# for x_name in name_suffixes:
#
# tabstat1 = [ p for p in db_conn.get_table_access_stats() if p.table_id == test_rel_id ]
#
# fb_info_init = psutil.Process(fb_pid).cpu_times()
# cur.callproc( 'sp_upd_%(x_name)s' % locals() )
# fb_info_curr = psutil.Process(fb_pid).cpu_times()
#
# tabstat2 = [ p for p in db_conn.get_table_access_stats() if p.table_id == test_rel_id ]
#
# # [ ... 'backouts', 'deletes', 'expunges', 'indexed', 'inserts', 'purges', 'sequential', 'table_id', 'table_name', 'updates']
# #print('mode:',x_name)
# #print('stat before:')
# #for t in tabstat1:
# # print( t.table_id, t.sequential, t.indexed )
# #print('stat after:')
# #for t in tabstat2:
# # print( t.table_id, t.sequential, t.indexed )
#
# sp_time[ x_name, i ] = max(fb_info_curr.user - fb_info_init.user, 0.000001)
#
# # commit will truncate GTT, so we can insert again into empty table on next iteration:
# db_conn.commit()
#
#
# ratio_lst = []
# for i in range(0, N_MEASURES):
# ratio_lst.append( sp_time['using_current_of_cursor',i] / sp_time['using_primary_key_field',i] )
#
# median_ratio = median(ratio_lst)
#
# #for k,v in sorted(sp_time.items()):
# # print(k,':::',v)
# #print('ratio_lst:', [round(p,2) for p in ratio_lst] )
# #print('median_ratio=',median_ratio)
# msg = 'Median value of elapsed time ratios: '
# if median_ratio < MEDIAN_TIME_MAX_RATIO:
# msg += 'acceptable.'
# print(msg)
# else:
# msg += 'INACCEPTABLE, more than threshold = %5.2f' % MEDIAN_TIME_MAX_RATIO
# print(msg)
#
# print('Check values for %d measurements:' % N_MEASURES)
# for i,p in enumerate(ratio_lst):
# print( '%3d : %12.2f' % (i,p) )
#
# print('Check values of elapsed time:')
# for k,v in sorted(sp_time.items()):
# print('proc_name, measure: ',k,' - elapsed time, ms: ',v)
#
#
# cleanup( (f_init_sql, f_init_log,f_init_err) )
#
#---
2021-04-26 20:07:00 +02:00
2021-12-31 12:06:51 +01:00
act_1 = python_act('db_1', substitutions=substitutions_1)
2021-04-26 20:07:00 +02:00
expected_stdout_1 = """
2021-12-31 12:06:51 +01:00
Median value of elapsed time ratios: acceptable.
2021-12-22 20:23:11 +01:00
"""
2021-04-26 20:07:00 +02:00
@pytest.mark.version('>=3.0.2')
2021-12-31 12:06:51 +01:00
@pytest.mark.xfail
2021-04-28 12:42:11 +02:00
def test_1(act_1: Action):
2021-12-31 12:06:51 +01:00
pytest.fail("Test not IMPLEMENTED")