.sh 1 "PERFORMANCE OF THE POSTGRES OPTIMIZER" 5 .pp This section describes how we went about validating the POSTGRES optimizer. It also presents and discusses our results. .sh 2 "Testing The Optimizer" .pp To evaluate the POSTGRES optimizer's performance as well as assess the credibility of its cost formulas, we could do the following. We could measure the amount of time required to execute various query plans, and then we could compare these measurements with the optimizer's predicted costs. Ideally, the predicted cost will be identical to the actual measured cost. However, this is an unrealistic expectation since optimizer cost formulas are merely estimates. A more attainable goal is for the optimizer to select the true optimal plan in a large majority of cases. This will at least show that in almost all circumstances, a selected plan is the best plan not only according to the optimizer but also in reality. .pp The tests described above illustrate how we would have wanted to validate the optimizer. However, at the time when we wanted to run these performance tests, measuring the amount of time required to execute a query plan was impossible because other parts of POSTGRES had not been fully implemented. As an alternative, we compared POSTGRES plans with query plans selected by the optimizer of commercial INGRES [KOOI82], which also happens to use an enumerative planning algorithm. The assumption behind this is that the INGRES optimizer selects \*(lqcorrect\*(rq plans. Therefore, if a plan selected by POSTGRES is equivalent to a plan selected by INGRES, (for the same query under similar conditions), then the POSTGRES optimizer has also generated a \*(lqcorrect\*(rq plan. Although this may not always be a valid assumption, it is probably a very good one since the INGRES optimizer has been tested, tuned, and used widely; and tests have shown that it is \*(lqextremely good\*(rq [ROWE86]. Furthermore, comparing POSTGRES and INGRES query plans at least allowed us to validate our optimizer against something other than our intuition as to which plans \fIshould\fR be selected. .pp Commercial INGRES was chosen as a basis for our comparisons because it is ideal in several respects. First of all, it has a feature that allows users to examine plans selected by the optimizer. By issuing the command \*(lq\fBset qep\fR,\*(rq plan trees for all subsequent join queries will be printed on the terminal monitor, easily allowing us to make comparisons between INGRES and POSTGRES plans. In addition, both POSTGRES and INGRES store within database catalogs statistical information that can be used to compute more realistic operator selectivities. In INGRES, these statistics are generated and subsequently updated using the \*(lq\fBoptimizedb\fR\*(rq command, while POSTGRES maintains them using system demons. An example of how this extra information would be put to use is the following. Suppose a relation, \fIr\fR, has 100 tuples but only 10 distinct values in attribute, \fIr.f\fR. Because the number of distinct values is a better indicator of the distribution of data within \fIr.f\fR, a clause like \fIr.f = 1\fR would have a selectivity of $1 over 10$ rather than $1 over 100$, as a result of using this extra information. In both systems, we made use of this and other statistical information to generate more accurate cost estimations. As a result, fair comparisons could be made between the two optimizers. .pp However, the POSTGRES optimizer is \fInot\fR identical to the INGRES optimizer, and consequently, in certain situations, INGRES selected a plan that POSTGRES did not even consider. In these cases, it was impossible to determine whether POSTGRES had selected the optimal path from among those plans it had considered; but as will become evident in the next subsection, we were still able to draw some interesting conclusions. One situation where this occurs relates to the manner in which both systems treat secondary indices. Figure 5.1 illustrates how a secondary index is generally used. Given the key(s) of the index, the system locates within the secondary index relation a unique tuple identifier (tid). It then uses the tid to directly retrieve an appropriate tuple from the corresponding indexed relation. .(z .hl .GS file figure5.1 .GE .sp .ce 2 \fBFigure 5.1\fR Using secondary indices .hl .)z The POSTGRES query plan tree corresponding to such a secondary index scan is shown in figure 5.2a, while the INGRES tree is shown in figure 5.2b. .(z .hl .GS file figure5.2 .GE .sp .ce 2 \fBFigure 5.2\fR Plan trees for secondary index scans in POSTGRES and INGRES .hl .)z A join between the tidp field in EMPINDEX and tid in EMP is required because INGRES literally treats secondary indices as relations, given that relations are used to implement them. In this particular case, although the two trees are different in appearance, they are both processed in the manner shown in figure 5.1. The only difference is that joining of tids is implicit in the POSTGRES indexscan node. .pp It will not always be the case that there will be a POSTGRES tree equivalent to an INGRES tree because by treating secondary indices as relations, further strategies are available to the INGRES optimizer. In figure 5.2, the index relation, EMPINDEX, is joined directly with its corresponding indexed relation, EMP. POSTGRES will only use a secondary index in this manner. INGRES, however, may choose to sort the result of a secondary scan, or it may join the result with a relation other than the corresponding indexed relation. Figure 5.3 illustrates these two situations. Examples where this generality is advantageous will be discussed in the next subsection. .(z .hl .GS file figure5.3 .GE .sp .ce 2 \fBFigure 5.3\fR Other processing strategies using secondary indices in INGRES .hl .)z .pp Another disadvantage of testing our optimizer against INGRES's is that INGRES is a conventional database system that does not support user-defined operators and access methods or nested-attribute queries. As a result, we could only test standard operators, access methods, and queries. Fortunately, this drawback was insignificant. In POSTGRES, costs are computed using formulas as well as operator selectivities. The latter is supplied by the user. In other words, it is a parameter that is not inherent within the optimizer and thus cannot be manipulated or tuned (except by the user who supplied the routines). Consequently, provided selectivities relevant to a single operator and storage structure are accurate, one of each was sufficient for testing purposes. It would have been nice to illustrate the generality of our optimizer by using non-standard operators in our tests, but even standard operators and access methods in POSTGRES are implemented as if they were user-defined. Thus, no generality was lost in using a conventional operator and access method in our tests. .pp The single operator and access method used were the equality operator, since it is a mergesortable operator, and an ISAM storage structure [HELD75a]. To build an ISAM storage structure, tuples must first be sorted into data pages. Then, a multi-level directory is constructed indicating the high key value stored within each page. Such a structure provides quick access when used with the following operators: .(l =, <, $<=$, >, and $>=$. .)l The directory is analogous to the internal nodes of a B-tree except that once it has been built, the directory remains static. Therefore, when a data page is filled, rather than splitting nodes to maintain a balanced tree, overflow pages are created and linked to the filled data page. If a large number of overflow pages are created, finding a tuple within a page requires an inefficient linear search through the primary page as well as its overflow pages. So, given a choice between an ISAM storage structure and a B-tree, a user would probably choose a B-tree. However, we could not use B-trees in our tests because the version of INGRES that we used only supported ISAM and hash access methods. Forced to choose between the two, we chose ISAM because there is a greater overhead associated with searching through an ISAM directory, making them more interesting than hash tables. .pp Using an ISAM access method does have its disadvantages, though. Although tuples in an ISAM structure are initially sorted when the structure is built, the tuples are not maintained in sort order. As a result, we could not test the POSTGRES optimizer feature that takes advantage of access methods like B-trees to eliminate sort steps required to order a user-specified sort result or tuples for a merge-sort join. However, although merge-sorting on a tuple by tuple basis is not possible, a variation of merge-sort can be performed on a page by page basis since the range of values within each page is known. INGRES, in fact, does this. In contrast, the POSTGRES optimizer does not, and as a result, differences arose in our performance tests. We chose not to account for partial index sorts because few access methods have this unusual characteristic. Moreover, as already alluded to, users will likely opt for access methods like B-trees, which always maintain their records in sort order. In other words, this feature would probably not be employed very often, had we implemented it. This should be kept in mind when differences arise between POSTGRES and INGRES plans in the next subsection. .pp With respect to nested-attribute queries, not being able to test these either is also of minimal importance. As discussed in section 3.5, relations materialized from queries embedded within data fields will generally be small, and as a result, the cost of executing any subplan corresponding to a nested portion of a query will also be small. Therefore, it is less important if the true optimal subplan is not selected while optimizing this portion of a query. .pp In testing the optimizer, we used version 2.1 of INGRES running on a VAX 11/780. To simulate the INGRES optimizer as closely as possible, we had to determine values for two system-dependent parameters that affect cost estimations. One of these is the page size used by the database. This is required because in estimating the cost of a sort, the optimizer must determine the number of pages occupied by the tuples to be sorted. By multiplying the number of tuples in an INGRES relation by the width of a tuple (including space for internal page pointers) and dividing by the number of pages in the relation, it turns out that INGRES uses 2K pages. .pp Determining a value for the second parameter, \fIW\fR, which relates I/O to CPU cost, was less straightforward. Assuming that it takes 30 milliseconds and about 2000 instructions of operating system overhead to read an I/O page, while manipulating a tuple only consumes about 200 CPU instructions, \fIW\fR is in the range of 0.03 to 0.1 [DEWI84]. For the queries used in our tests, using various values within this range did not affect the plan choice except in a few situations where a merge-sort join was chosen over nested iteration. This occurred when \fIW\fR was close to 0.03, a value that apparently minimized the cost of CPU-intensive tasks like sorting. In these situations, the INGRES optimizer always selected a nested iteration join. Thus, to insure compatibility with INGRES plans, we assigned \fIW\fR the value 0.065, arbitrarily choosing a value in the middle of our original range. .sh 2 "Tests Performed" .pp For testing purposes, we used the following three relations: EMP, DEPT, and WATER. Schemas and relevant statistics for these relation are shown in figure 5.4. .(z .hl .sz \n(ep .nf \fBRELATIONS :\fR .sp EMP ( name = c10, salary = i4, manager = c10, age = i4, dept = c10 ) .in +0.5i # pages : 600 (unindexed) # tuples : 30,000 # distinct values in name : 30,000 # distinct values in dept = 1000 .in -0.5i .sp DEPT ( dname = c10, floor = i4, budget = i4 ) .in +0.5i # pages : 10 (unindexed), 14 (primary ISAM index on floor) # tuples : 1000 # distinct values in dname : 1000 # distinct values in floor : 9 .in -0.5i .sp WATER ( cid = i4, floor = i4 ) .in +0.5i # pages : 1 (unindexed) # tuples : 50 # distinct values in cid : 9 # distinct values in floor : 9 .in -0.5i .sp 2 \fBSECONDARY INDICES : \fR .sp EMPINDEX ( ISAM on dept ) : .in +0.5i # pages : 253 # tuples : 30,000 .in -0.5i .sp DEPTINDEX ( ISAM on floor ) : .in +0.5i # pages : 8 # tuples : 1000 .in -0.5i .sp DEPTINDEX ( ISAM on dept ) : .in +0.5i # pages : 11 # tuples : 1000 .in -0.5i .fi .sz \n(pp .sp .ce 2 \fBFigure 5.4\fR Schemas and statistics for test relations .hl .)z First, we ran the following four two-way join queries on EMP and DEPT: .(l A: \fBretrieve\fR (EMP.name, EMP.age, DEPT.all) \fBwhere\fR EMP.dept = DEPT.dname B: \fBretrieve\fR (EMP.name, EMP.age, DEPT.dname, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR DEPT.floor = 1 C: \fBretrieve\fR (EMP.age, EMP.dname, DEPT.floor, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR EMP.name = \*(lqDiamond\*(rq D: \fBretrieve\fR (EMP.age, DEPT.dname, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR DEPT.floor = 1 \fBand\fR EMP.name = \*(lqDiamond\*(rq .)l To illustrate that the optimizer chooses appropriate strategies under varying conditions, we ran the four queries for non-indexed relations as well as relations with primary and secondary indices defined on relevant attributes. Indices were selected so as to illustrate the combined effects of relation sizes, distribution of values, and secondary index overhead in determining a query's optimal plan. The results of running queries A-D using various indices are shown in table 5.1. .(z .hl .sz \n(ep .nf .in +1i A: \fBretrieve\fR (EMP.name, EMP.age, DEPT.all) \fBwhere\fR EMP.dept = DEPT.dname B: \fBretrieve\fR (EMP.name, EMP.age, DEPT.dname, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR DEPT.floor = 1 C: \fBretrieve\fR (EMP.age, EMP.dname, DEPT.floor, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR EMP.name = \*(lqDiamond\*(rq D: \fBretrieve\fR (EMP.age, DEPT.dname, DEPT.budget) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR DEPT.floor = 1 \fBand\fR EMP.name = \*(lqDiamond\*(rq .in -1i .fi .sp .TS center box; c|c|c|c|c. Storage Structure Query A Query B Query C Query D = no indices Plan (a) \(dg Plan (a) Plan (c) Plan (c) _ secondary index on DEPT (floor) Plan (a) _ primary index on DEPT (floor) Plan (b) Plan (d) _ secondary index on EMP (dept) Plan (c) Plan (e) _ primary index on DEPT (floor) & Plan (b) Plan (g) secondary index on DEPT (dname) _ secondary index on EMP (dept) Plan (f) Plan (c) _ secondary index on DEPT (dname) & Plan (f) secondary index on EMP (dept) .TE .sp .sz \n(fp $" " sup \(dg$ See figure 5.5 .sz \n(pp .sp .ce 2 \fBTable 5.1\fR Results of executing queries A-D using various storage structures .hl .)z .(z .GS file figure5.6 .GE .sz \n(fp .nf MS = merge-sort NL = nestloop .fi .sz \n(pp .ce 2 \fBFigure 5.5\fR Comparison of selected POSTGRES and INGRES plans .hl .)z For the fourteen executions shown, when indices were defined, in all but one case both POSTGRES and INGRES made similar decisions as to whether or not the indices should be used, and except in cases where INGRES took advantage of a partial ISAM sort, both selected equivalent join strategies. .pp Figure 5.5 gives a pictorial representation of the seven different corresponding plan trees mentioned in table 5.1. For each pair, the POSTGRES tree is shown on the left while the INGRES tree is on the right. Both pairs of plans (a) and (b) correspond to merge-sort joins, and both plan (b)'s scan DEPT using a primary index defined on \*(lqfloor.\*(rq In plans (c) and (d), POSTGRES uses nested iteration, scanning EMP first because the highly restrictive clause, .(l EMP.name = \*(lqDiamond,\*(rq .)l applies in these cases. INGRES, however, uses a slight variation of nested iteration in both (c) and (d). EMP is scanned and sorted into a temporary before it is nest iterated with DEPT. Sorting only the inner relation on the inner join attribute alleviates having to scan the entire inner join relation on every iteration because the scan halts after a value greater than the current outer join attribute has been reached. In certain cases, this can be helpful; however, in these cases, there is really no advantage because only a single tuple results after scanning EMP. In fact, these INGRES and POSTGRES plans are equivalent in cost because the cost of the extra sort step in the INGRES trees is zero, and in both cases, EMP and DEPT only need to be scanned once. .pp POSTGRES's plan (e) is similar to INGRES's except INGRES takes advantage of an ISAM's partial sort and thus uses a slight variation of a merge-sort join with EMP and DEPTINDEX. Again, only one tuple qualifies after scanning EMP, resulting in a sort cost of zero. Ignoring the sort step, the two plans are actually equivalent. .pp Plan (f) is an example where INGRES's treatment of an access method's partial ordering is helpful. In this particular case, INGRES takes advantage of the feature to join EMPINDEX with DEPT. The result of this join is then sorted on the tidp field so EMP can be scanned in page order. According to POSTGRES cost estimates, the INGRES plan is better by about a factor of four to five. It is significantly better than the POSTGRES plan partly because of INGRES's treatment of secondary indices, but more so due to INGRES accounting for an ISAM's partial sort. Therefore, the fact that INGRES was significantly better in this case is misleading for the reasons discussed in the previous subsection. .pp The last plan pair (g) illustrates the one case where different indices are selected by POSTGRES and INGRES. INGRES chooses to use the index defined on \*(lqfloor,\*(rq while POSTGRES chooses the index defined on \*(lqdept.\*(rq POSTGRES chose the plan it did because according to its cost estimates the selected plan was a mere 0.27% better than a plan equivalent to that chosen by INGRES. Thus, although the two optimizers chose different plans, the difference is very minor. .pp The set of tests just described indicate that differences between selected INGRES and POSTGRES plans are rather subtle, resulting in minor variations, except in one case where the difference is minimized by other circumstances. Additional strategies employed by INGRES resulted in greater differences when the following three-way join: .(l \fBretrieve\fR (EMP.name, DEPT.floor, WATER.cid) \fBwhere\fR EMP.dept = DEPT.dname \fBand\fR DEPT.floor = WATER.floor .)l was run on several different relation configurations. The results are shown in table 5.2. .(z .hl .GS file table5.2 .GE .sp .ce 2 \fBTable 5.2\fR Results of executing a three-way join on various storage structures .hl .)z .(z .hl .GS file table5.3 .GE .sp .ce 3 \fBTable 5.2\fR (continued) .hl .)z .pp Plans (1), (2), and (4) in table 5.2 illustrate another strategy employed by INGRES that POSTGRES does not consider. In these plans, INGRES first sorts DEPT on \*(lqdname\*(rq and then nest iterates the result with WATER. Since a join preserves the sort order of its outer join relation, the join result is still sorted in \*(lqdname\*(rq order and thus can be used in a merge with EMP. This is advantageous because it may be cheaper to sort DEPT prior to its join with WATER, since more tuples may have to be sorted after the join. Other differences were already discussed in reference to the previous set of tests and will not be repeated. .pp To assess the magnitude of differences in POSTGRES and INGRES plans for the four cases shown, the costs of all INGRES plans were calculated using POSTGRES cost estimation formulas. These computed costs were compared with the costs of corresponding POSTGRES plans, and the percentage differences are shown in table 5.2. The results show that INGRES was better than POSTGRES in all four cases. However, INGRES was only significantly better in one case, and this was a result of INGRES accounting for a partial ISAM ordering. In all other cases, the additional strategies considered by INGRES resulted in no more than a 13.8% difference. .pp To summarize, in spite of differences between the POSTGRES and INGRES optimizers, we were able to draw two promising conclusions from our performance tests. For the most part, both systems selected similar strategies for queries involving a single join. Assuming that the INGRES optimizer is correct, these results indicate that POSTGRES selects true optimal plans. For queries for which INGRES was able to capitalize on a broader range of strategies, INGRES did not perform considerably better. Thus, not having considered all these other processing strategies, which would have added further complexity to our implementation effort, did not dramatically affect the overall performance of our optimizer.