利用線性擬合模型發現測試環境性能隱患
一個經典的性能問題
投產後數週,生產環境出現了系統響應緩慢、數據庫負載衝高、大量數據庫連接不釋放的生產事件,問題原因是由於一張表的數據量在投產後持續增長,且有聯機語句使用了全表掃描訪問了這張表,導致執行時間變長,數據庫鎖等待增加,資源被持續消耗,引發數據庫性能問題。這類問題在過去的幾年中一直困擾着我們,令我們不勝其煩。當然,也有很多朋友支了很多招,比如恢復全量的生產數據,或者杜絕所有聯機全表掃描,但是這些辦法都不太奏效。首先,測試環境資源有限,尤其是沒有那麼多存儲;其次,聯機全表掃描並不違反開發規範,而且針對數據量很小的全表掃描是非常合理的,因此很難說服開發去做修改。所以這個問題最終還得靠測試自己來解決。
解決方法
“性能指標的持續增長在未來必然會引發測試問題。”應該是性能測試領域的定理,我們根據這條定理,定製了一個趨勢分析模型,用來找出那些存在持續增長趨勢的指標。原理非常簡單:
- 利用自主開發的數據庫監控系統,記錄測試環境發生的所有全表掃描語句
- 對全表掃描的對象統計記錄數,每天記錄一次
- 利用python的一次線性擬合模型,對全表掃描對象的測試和生產環境記錄數進行線性擬合
- 根據擬合結果,對增長趨勢>0,擬合度>80%的記錄進行統計
- 根據記錄中的sqlid深入分析
同理,我們也將語句執行時間、語句cost、操作系統CPU、內存使用率,網絡連接數等指標也納入了該模型分析。這樣就能夠在日常的功能測試中,發現那些存在性能隱患,但是有沒有達到報警閾值的性能問題。
代碼
# encoding=gbk
#根據APP_SQL_COST表中記錄,統計COST和執行時間持續增長的語句
import math
import cx_Oracle
import collections
#一次線性擬合模型
def linefit(x , y):
N = float(len(x))
sx,sy,sxx,syy,sxy=0,0,0,0,0
for i in range(0,int(N)):
sx += x[i]
sy += y[i]
sxx += x[i]*x[i]
syy += y[i]*y[i]
sxy += x[i]*y[i]
a = (sy*sx/N -sxy)/( sx*sx/N -sxx)
b = (sy - a*sx)/N
if a==0:
r=0
else:
r = abs(sy*sx/N-sxy)/math.sqrt((sxx-sx*sx/N)*(syy-sy*sy/N))
return a,b,r
def func_table_full(appname,addr,dbsid):
str0="SELECT OBJECT_OWNER, OBJECT_NAME FROM APP_SQL_PLAN_STAT WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' GROUP BY OBJECT_OWNER, OBJECT_NAME"
cursor0 = conn.cursor()
cursor0.execute(str0)
res0 = cursor0.fetchall()
rowcount0 = len(res0)
x=0
result=[]
while x<rowcount0:
table_owner=res0[x][0]
table_name=res0[x][1]
str1="SELECT TABLE_NAME,rank() over(partition by TABLE_NAME order by EXEC_SEQ,CHECK_TIME,STAT_DATE) rk,nvl(rows_count,0) FROM APP_TABLE_ROWS WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' AND TABLE_NAME='"+table_name+"' AND OWNER='"+table_owner+"'"
cursor1 = conn.cursor()
cursor1.execute(str1)
res1 = cursor1.fetchall()
rowcount1 = len(res1)
RK=[]
ROWS=[]
if rowcount1>=3:
y = 0
while y<rowcount1:
RK.append(res1[y][1])
ROWS.append(res1[y][2])
y=y+1
try:
a,b,r=linefit(RK,ROWS)
except:
print APPNAME,ADDR,DBSID,ROWS
if a>0 and r>0.8 :
str2="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','ROWS','"+table_owner+"."+table_name+"','"+str(max(ROWS))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
cursor2 = conn.cursor()
cursor2.execute(str2)
cursor2.execute("commit")
str2 = "SELECT TABLE_NAME,rank() over(partition by TABLE_NAME order by to_date(COLLECT_DATE,'YYYY-MM-DD')) rk,nvl(NUM_ROWS,0) FROM MCTEST.DB_SC_ROW_NUM WHERE UPPER(DBSID)=UPPER('"+DBSID+"') AND TABLE_NAME='"+table_name+"' AND OWNER='"+table_owner+"'"
cursor1 = conn.cursor()
cursor1.execute(str2)
res1 = cursor1.fetchall()
rowcount1 = len(res1)
RK=[]
ROWS=[]
if rowcount1>=3:
y = 0
while y<rowcount1:
RK.append(res1[y][1])
ROWS.append(res1[y][2])
y=y+1
try:
a,b,r=linefit(RK,ROWS)
except:
print APPNAME,ADDR,DBSID,ROWS
if a>0 and r>0.8 :
str2="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','SCROWS','"+table_owner+"."+table_name+"','"+str(max(ROWS))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
#print str2
cursor2 = conn.cursor()
cursor2.execute(str2)
cursor2.execute("commit")
x=x+1
if __name__ == '__main__':
distinct_count=3
conn = cx_Oracle.connect('test/[email protected]/test')
strdel="DELETE FROM APP_GROWTH_MODEL"
cursordel=conn.cursor()
cursordel.execute(strdel)
cursordel.execute("commit")
str2="SELECT APPNAME,ADDR,DBSID FROM APP_SQL_COST GROUP BY APPNAME,ADDR,DBSID"
cursor2 = conn.cursor()
cursor2.execute(str2)
res2 = cursor2.fetchall()
rowcount2 = len(res2)
z=0
while z<rowcount2:
APPNAME=res2[z][0]
ADDR=res2[z][1]
DBSID=res2[z][2]
table_full=func_table_full(APPNAME,ADDR,DBSID)
str0="SELECT DISTINCT SQL_ID FROM APP_SQL_COST WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"'"
cursor0 = conn.cursor()
cursor0.execute(str0)
res0 = cursor0.fetchall()
rowcount0 = len(res0)
x=0
while x<rowcount0:
SQL_ID=str(res0[x][0])
str1="SELECT SQL_ID,rank() over(partition by SQL_ID order by STAT_DATE,SNAP_ID,PLAN_HASH_VALUE) rk,nvl(cost,0),nvl(elapsed_time,0) FROM APP_SQL_COST WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' AND SQL_ID='"+SQL_ID+"'"
cursor1 = conn.cursor()
cursor1.execute(str1)
res1 = cursor1.fetchall()
rowcount1 = len(res1)
RK=[]
COST=[]
ELAPSED_TIME=[]
if rowcount1>=3:
y = 0
while y<rowcount1:
#print res1[y][1],res1[y][2], res1[y][3]
RK.append(res1[y][1])
COST.append(res1[y][2])
ELAPSED_TIME.append(res1[y][3])
y=y+1
try:
a,b,r=linefit(RK,COST)
except:
print APPNAME,ADDR,DBSID,COST
distinct_cost=len(set(COST))
if a>0 and r>0.8 and distinct_cost>=distinct_count and max(COST)>1000:
str_ins="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','COST','"+SQL_ID+"','"+str(max(COST))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
cursor_ins = conn.cursor()
cursor_ins.execute(str_ins)
cursor_ins.execute("commit")
try:
a,b,r=linefit(RK,ELAPSED_TIME)
except:
print APPNAME,ADDR,DBSID,ELAPSED_TIME
distinct_time=len(set(ELAPSED_TIME))
if a>0 and r>0.8 and distinct_time>=distinct_count and max(ELAPSED_TIME)>60:
str_ins="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','ELAPSED_TIME','"+SQL_ID+"','"+str(max(ELAPSED_TIME))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
cursor_ins = conn.cursor()
cursor_ins.execute(str_ins)
cursor_ins.execute("commit")
x=x+1
z=z+1