Database Tuning

  • Tuning is the act of changing an application and DBMS environment to improve system performance

  • Performance is usually measured in terms of response time

    • Time to get the first tuple

    • Time to get all the tuples to a query

  • A workload consists of various operations performed by the system and their frequency

    • It is important to tune to improve workload, not just a single query

    • Some queries are bottlenecks: performed frequently and/or are very expensive

  • Remember: when you improve the performance of one operation, other operations may slow down

    For example, creating an index to speed up a query will slow down insert/update operations

Database Tuning Steps

  • Step 1: buy more or faster hardware

    memory is crucial for buffering query operations and caches for various operations

    hard disk speed is crucial, buy faster and more disks to improve the parallelism

  • Step 2: tune the system installation

    databases provide a large number of tunable parameters, read database administration books

    learn about making better use of multiple CPUs

Disk caches

  • A cache is a set of buffer pages maintained by the DBMS for a specific purpose

  • Data cache for reading pages containing the index or the relation

  • Procedure cache for storing previously constructed query plans

  • Caches are usually shared between concurrent users

  • Any requested item must be brought to cache from disk to read/modified

  • If it is already in the cache, then the cache has a hit, otherwise the cache has a miss

  • Since each hit is a savings in time, hit ratio must be maximized (some application designers seek 90% hit ratio)

Cache replacement algorithms

  • If a new item has to be inserted into the cache, another item might need to be removed.

  • Cache replacement algorithm decides what should be removed, LRU (least recently used), MRU (most recently used)

    • A recently used page may be used for an update in near future (LRU)

    • A page read in table scan is no longer needed (MRU)

    • Sophisticated caches may take the algorithm using the database into account

    • How would you use the cache for an index page?

  • A dirty page is a page modified by an uncommitted transaction -if this page is moved out of cache, it must be written back to disk

Tuning the cache

  • Divide the cache and bind a specific item to a cache (different tables may be cached in different caches)

  • Divide the cache into pools of varied size, 2K, 4K, 8K, etc.

    • The query processor can choose the best available cache for a query (retrieve large sequences for table scans, even prefetch disk pages that are expected to be requested next)

  • Procedure cache may use previously optimized query plans

    Hint: use program variables to increase possible reuse

    SELECT P.name FROM Professor P WHERE P.deptId = :deptid
    

    When you use prepared statements such as this, query and parameters are sent separately, allowing the same query for different parameters to use the same query plan.

Partitioning

  • Step 3: partition your data

  • Vertical partitioning divides the attributes in the relation and distributes them to different disks or tablespaces

    Frequently queried attributes could be separated from infrequently queried attributes.

  • Horizontal partitioning divides the tuples in the relation to multiple disks

    Allows parallelism in reading data from disk

    Some optimizers are able to concentrate on a single partition given a specific query

Denormalization

  • Step 4: change your data model

  • Normalization reduces redundancy and null values

    • Lower storage requirements, simple queries and updates will be faster

    • Results in more tables, hence complex queries need more joins

      SELECT
          FAN.Title
      FROM
          Films F
          , FilmAlternateNames FAN
      WHERE
          F.filmid = FAN.filmid;
      
  • Denormalization stores relations in a non-optimal manner

Examples of denormalization

  • Store alternate names in a string and use application code to update and print the alternate names

  • Add extra columns for frequently accessed information

    Example: number of movies per actor:

    SELECT
       a.name
       , a.surname
       , count(distinct mr.movieid)
    FROM
       actors a
       , movieroles mr
    WHERE
       a.id=mr.actorid
    GROUP BY
       a.id
       , a.name
       , a.surname ;
    
  • Add a column “NumMovies” instead for each actor, no join or group by is needed.

    This column must be updated in the application anytime an update is made to the casts relation. If updates are not frequent, then this could be acceptable.

  • Certain attributes might be duplicated if they are used often

    • Example: Store stagename attribute in the casts relation

      Queries involving this attribute are now fully answered from casts (avoiding a costly join)

      Attributes other than stagename might be queried rarely but take a lot of space causing the stagename to take up a lot of space

      Anytime a new actor is added or stagename is changed, the changes must be reflected to the casts relation by updating

      multiple tuples (this may be rare compared to the queries)

      The CASTS relation now stores redundant information and is larger in size

Query Restructuring

  • Step 5: Rewrite queries to improve their speed, avoid nested queries, use joins whenever possible

  • Nested queries are hard to optimize.

  • Inner and outer expressions are optimized separately.

  • For correlated expressions, inner query is executed many times for each tuple in the outer expression.

  • Certain possible optimizations could be missed with a nested query (suppose an index for casts on (actorid, filmid) existed)

    SELECT DISTINCT
         m.title
    FROM
         movies m
         , movieroles mr
    WHERE
         m.id=mr.movieid
         and exists (
             select
               *
             from
               actors a
             where
               a.surname like '%Bacon'
               and  a.id = mr.actorid);
    
    • All queries below are equivalent to the above one

      -- uncorrelated query is better
      SELECT DISTINCT
         m.title
      FROM
         movies m
         , movieroles mr
      WHERE
         m.id=mr.movieid
         and mr.actorid in (
             select
               a.id
             from
               actors a
             where
               a.surname like '%Bacon');
      
      -- join query is even better
      SELECT DISTINCT
         m.title
      FROM
         movies m
         , movieroles mr
         , actors a
      WHERE
         m.id=mr.movieid
         and mr.actorid=a.id
         and a.surname like '%Bacon';
      

Drops of wisdom

  • Avoid sorts (distinct, order by, group by, union, except) if possible, they are costly

  • Some queries do not need a distinct or can be rewritten to avoid sorts

  • Avoid full table scans

    • A search on a condition like A <> 3 or A like ‘%Bacon’ might result in a table scan

    • A search like A in (1,2,4) might be preferable (depending on the availability of statistics)

  • Avoid retrieving tuples into application code, use stored procedures and even complicated queries might be preferable to the added communication cost

  • Use views wisely

    • Even though views are useful in application development, use a view in an application that is useful for the given query

      CREATE VIEW together(actorid1, stagename1, actorid2, stagename2) AS
      SELECT a1.actorid, a1.stagename, a2.actorid, a2.stagename
      FROM
          actors a1
          , actors a2
          , movieroles mr1
          , movieroles mr2
      WHERE
          a1.id=mr1.actorid
          and mr1.movieid=mr2.movieid
          and a2.id=mr2.actorid
          and a1.id <> a2.id ;
      
      SELECT
          t.actorid1
      FROM
          together t
      WHERE
          t.name = 'Kevin'
          and t.surname = 'Bacon';
      
      • None of the joins are necessary to answer this query. The optimizer might miss some faster query plans

The use of indices

  • Indices speed up query, but slow down insert/delete/update operations

  • A clustered index allows fast access to a range query

    • There is only one clustered index per relation

    • Databases usually create one for the primary key by default

    • Reconstruction of clustered indices is costly

  • Step 6: choose the most useful indices

    • Find the most useful clusters and use them if they are very useful for a range of queries and supported by the database

    • Next, find the most selective indices to add

      • Finally, find indices that might help with index only scans

Clustered Indices

  • We can create clustered indices in Postgresql by using an index:

    create index mr_idx on movieroles(movieid,actorid) ;
    cluster movieroles using mr_idx ;
    
  • Clusters are generated once and are not modified incrementally

  • You need to recluster periodically if there are frequent updates:

    cluster movieroles;
    
  • The reorganization may be too costly for very large tables.

  • Create a clustered index for attributes frequently queried with a range or has multiple matching attributes for a value

    Above cluster is very useful for finding actors in a movie:

    select
       a.name
       , a.surname
    from
       movies m
       , movieroles mr
       , actors a
    where
       m.name = 'Harry Potter and the Goblet of Fire'
       and m.id = mr.movieid
       and mr.actorid = a.id;
    
  • Clustered indices also provide a sorted order to the relation

  • Create unclustered indices on attributes with high selectivity

    SELECT A.name, A.surname FROM Actors A WHERE A.gender = 'F';
    
    SELECT
        A.firstname
        , A.lastname
    FROM
        Actors A
    WHERE
        A.name = 'Kevin'
        and A.surname ='Bacon';
    
    • Gender is not a selective condition, but name and surname are.

  • Index nested loop join is also beneficial when there is a highly selective index

    SELECT
       mr.movieid
    FROM
       actors a, movieroles mr
    WHERE
       a.name = 'Kevin'
       and a.surname = 'Bacon'
       and a.id = mr.actorid;
    
  • For frequently asked queries, indices might be created to allow index only searches.

    For example, given (name, surname, id) for actors, answering a query like one below now requires only an index search for actors.

    ::
    SELECT

    mr.movieid

    FROM

    actors a , movieroles mr

    WHERE

    a.name = ‘Kevin’ and a.surname = ‘Bacon’ and a.id = mr.actorid;

    • This is in effect a type of vertical partitioning.

  • For example, given a query like the one below:

    SELECT
        a2.name
        , a2.surname
    FROM
        actors a1
        , movieroles mr1
        , movieroles mr2
        , actors a2
    WHERE
        a1.name = 'Kevin'
        and a1.surname = 'Bacon'
        and a1.id = mr1.actorid
        and mr1.movieid = mr2.movieid
        and a2.id = mr2.actorid;
    

    for A1, the index is searched in the usual way.

    For A2, the index on(stagename, actorid) can be searched fully instead of the relation to speed up the query.

  • Indices do not always help reduce the cost of queries:

    • they must be selective

    • they must be significantly smaller in size than the relation they are indexing

    • they must be used often in queries where they make a difference

  • Foreign keys introduce hidden costs to updates since they must be checked for all updates that relate to them

  • Count queries can be answered using indices on attributes with a “NOT NULL”” constraint (check if the index indices null values)

Other hints

  • Partition data to multiple disks

    • Place data that is accessed sequentially on its own disk

  • Invoke parallel query processing when multiple CPUs are available

  • Create more detailed statistics (histograms)

  • Recompute statistics periodically as needed

  • Examine the query plans generated by the system and influence them as necessary

Postgresql Optimizer

  • Postgresql’s CBO (cost-based-optimizer) relies heavily on table statistics being available for all tables used in a query.

    analyze;
    

    will recompute the statistics for all the tables in a database.

    Must be run periodically for updated statistics.

  • You can ask the optimizer to give you the query plan for a query.

    EXPLAIN query ;
    
    EXPLAIN
    SELECT
       mr.movieid
    FROM
       actors a, movieroles mr
    WHERE
       a.name = 'Kevin'
       and a.surname = 'Bacon'
       and a.id = mr.actorid;
    
                                              QUERY PLAN
    ----------------------------------------------------------------------------------------------
     Hash Join  (cost=3302.62..8991.86 rows=2 width=4)
       Hash Cond: (mr.actorid = a.id)
       ->  Seq Scan on movieroles mr  (cost=0.00..4695.07 rows=265107 width=8)
       ->  Hash  (cost=3302.61..3302.61 rows=1 width=4)
             ->  Seq Scan on actors a  (cost=0.00..3302.61 rows=1 width=4)
                   Filter: (((name)::text = 'Kevin'::text) AND ((surname)::text = 'Bacon'::text))
    (6 rows)
    

Summary

  • There are many methods from changing data model, queries and storage methods to improve performance.

  • Performance must be improved for the whole workload. Compute the total cost of all queries multiplied by their frequency before and after tuning.

  • Tuning is a complex optimization problem due to dependencies between different actions. Concentrate on queries that contribute most to the workload.