THE IMPLEMENTATION OF POSTGRES

36 312 0
THE IMPLEMENTATION OF POSTGRES

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

THE IMPLEMENTATION OF POSTGRES Michael Stonebraker, Lawrence A. Rowe and Michael Hirohama EECS Department University of California, Berkeley Abstract Currently, POSTGRES is about 90,000 lines of code in C and is being used by assorted ‘‘bold and brave’’ early users. The system has been constructed by a team of 5 part time students led by a full time chief programmer over the last three years. During this period, we have made a large number of design and implementation choices. Moreover, in some areas we would do things quite differently if we were to start from scratch again. The purpose of this paper is to reflect on the design and implementation decisions we made and to offer advice to implementors who might follow some of our paths. In this paper we res- trict our attention to the DBMS ‘‘backend’’ functions. In another paper some of us treat PICASSO, the application development environment that is being built on top of POSTGRES. 1. INTRODUCTION Current relational DBMSs are oriented toward efficient support for business data processing applica- tions where large numbers of instances of fixed format records must be stored and accessed. Traditional transaction management and query facilities for this application area will be termed data management. To satisfy the broader application community outside of business applications, DBMSs will have to expand to offer services in two other dimensions, namely object management and knowledge manage- ment. Object management entails efficiently storing and manipulating non-traditional data types such as bitmaps, icons, text, and polygons. Object management problems abound in CAD and many other engineering applications. Object-oriented programming languages and data bases provide services in this area. This research was sponsored by the Defense Advanced Research Projects Agency through NASA Grant NAG 2-530 and by the Army Research Office through Grant DAALO3-87-K-0083. Knowledge management entails the ability to store and enforce a collection of rules that are part of the semantics of an application. Such rules describe integrity constraints about the application, as well as allowing the derivation of data that is not directly stored in the data base. We now indicate a simple example which requires services in all three dimensions. Consider an application that stores and manipulates text and graphics to facilitate the layout of newspaper copy. Such a system will be naturally integrated with subscription and classified advertisement data. Billing customers for these services will require traditional data management services. In addition, this application must store non-traditional objects including text, bitmaps (pictures), and icons (the banner across the top of the paper). Hence, object management services are required. Lastly, there are many rules that control newspaper lay- out. For example, the ad copy for two major department stores can never be on facing pages. Support for such rules is desirable in this application. We believe that most real world data management problems are three dimensional. Like the news- paper application, they will require a three dimensional solution. The fundamental goal of POSTGRES [STON86, WENS88] is to provide support for such three dimensional applications. To the best of our knowledge it is the first three dimensional data manager. However, we expect that most DBMSs will fol- low the lead of POSTGRES into these new dimensions. To accomplish this objective, object and rule management capabilities were added to the services found in a traditional data manager. In the next two sections we describe the capabilities provided and comment on our implementation decisions. Then, in Section 4 we discuss the novel no-overwrite storage manager that we implemented in POSTGRES. Other papers have explained the major POSTGRES design decisions in these areas, and we assume that the reader is familiar with [ROWE87] on the data model, [STON88] on rule management, and [STON87] on storage management. Hence, in these three sections we stress considerations that led to our design, what we liked about the design, and the mistakes that we felt we made. Where appropriate we make suggestions for future implementors based on our experience. Section 5 of the paper comments on specific issues in the implementation of POSTGRES and cri- tiques the choices that we made. In this section we discuss how we interfaced to the operating system, our choice of programming languages and some of our implementation philosophy. The final section concludes with some performance measurements of POSTGRES. Specifically, we report the results of some of the queries in the Wisconsin benchmark [BITT83]. 2. THE POSTGRES DATA MODEL AND QUERY LANGUAGE 2.1. Introduction Traditional relational DBMSs support a data model consisting of a collection of named relations, each attribute of which has a specific type. In current commercial systems possible types are floating point 2 numbers, integers, character strings, and dates. It is commonly recognized that this data model is insufficient for non-business data processing applications. In designing a new data model and query language, we were guided by the following three design criteria. 1) orientation toward data base access from a query language We expect POSTGRES users to interact with their data bases primarily by using the set-oriented query language, POSTQUEL. Hence, inclusion of a query language, an optimizer and the corresponding run-time system was a primary design goal. It is also possible to interact with a POSTGRES data base by utilizing a navigational interface. Such interfaces were popularized by the CODASYL proposals of the 1970’s and are enjoying a renaissance in recent object-oriented proposals such as ORION [BANE87] or O2 [VELE89]. Because POSTGRES gives each record a unique identifier (OID), it is possible to use the identifier for one record as a data item in a second record. Using optionally definable indexes on OIDs, it is then possible to navigate from one record to the next by running one query per navigation step. In addition, POSTGRES allows a user to define func- tions (methods) to the DBMS. Such functions can intersperce statements in a programming language, query language commands, and direct calls to internal POSTGRES interfaces. The ability to directly exe- cute functions which we call fast path is provided in POSTGRES and allows a user to navigate the data base by executing a sequence of functions. However, we do not expect this sort of mechanism to become popular. All navigational interfaces have the same disadvantages of CODASYL systems, namely the application programmer must construct a query plan for each task he wants to accomplish and substantial application maintenance is required when- ever the schema changes. 2) Orientation toward multi-lingual access We could have picked our favorite programming language and then tightly coupled POSTGRES to the compiler and run-time environment of that language. Such an approach would offer persistence for variables in this programming language, as well as a query language integrated with the control statements of the language. This approach has been followed in ODE [AGRA89] and many of the recent commercial start-ups doing object-oriented data bases. Our point of view is that most data bases are accessed by programs written in several different languages, and we do not see any programming language Esperanto on the horizon. Therefore, most appli- cation development organizations are multi-lingual and require access to a data base from different languages. In addition, data base application packages that a user might acquire, for example to perform statistical or spreadsheet services, are often not coded in the language being used for developing applica- tions. Again, this results in a multi-lingual environment. 3 Hence, POSTGRES is programming language neutral, that is, it can be called from many different languages. Tight integration of POSTGRES to a particular language requires compiler extensions and a run time system specific to that programming language. One of us has built an implementation of per- sistent CLOS (Common LISP Object System) on top of POSTGRES. Persistent CLOS (or persistent X for any programming language, X) is inevitably language specific. The run-time system must map the disk representation for language objects, including pointers, into the main memory representation expected by the language. Moreover, an object cache must be maintained in the program address space, or performance will suffer badly. Both tasks are inherently language specific. We expect many language specific interfaces to be built for POSTGRES and believe that the query language plus the fast path interface available in POSTGRES offers a powerful, convenient abstraction against which to build these programming language interfaces. 3) small number of concepts We tried to build a data model with as few concepts as possible. The relational model succeeded in replacing previous data models in part because of its simplicity. We wanted to have as few concepts as possible so that users would have minimum complexity to contend with. Hence, POSTGRES leverages the following three constructs: types functions inheritance In the next subsection we briefly review the POSTGRES data model. Then, we turn to a short description of POSTQUEL and fast path. We conclude the section with a discussion of whether POSTGRES is object-oriented followed by a critique of our data model and query language. 2.2. The POSTGRES Data Model As mentioned in the previous section POSTGRES leverages types and functions as fundamental constructs. There are three kinds of types in POSTGRES and three kinds of functions and we discuss the six possibilities in this section. Some researchers, e.g. [STON86b, OSBO86], have argued that one should be able to construct new base types such as bits, bitstrings, encoded character strings, bitmaps, compressed integers, packed decimal numbers, radix 50 decimal numbers, money, etc. Unlike most next generation DBMSs which have a hard-wired collection of base types (typically integers, floats and character strings), POSTGRES contains an abstract data type facility whereby any user can construct an arbitrary number of new base types. Such types can be added to the system while it is executing and require the defining user to specify functions to convert instances of the type to and from the character string data type. Details of the syntax appear in 4 [WENS88]. The second kind of type available in POSTGRES is a constructed type.** A user can create a new type by constructing a record of base types and instances of other constructed types. For example: create DEPT (dname = c10, floor = integer, floorspace = polygon) create EMP (name = c12, dept = DEPT, salary = float) Here, DEPT is a type constructed from an instance of each of three base types, a character string, an integer and a polygon. EMP, on the other hand, is fabricated from base types and other constructed types. A constructed type can optionally inherit data elements from other constructed types. For example, a SALESMAN type can be created as follows: create SALESMAN (quota = float) inherits EMP In this case, an instance of SALESMAN has a quota and inherits all data elements from EMP, namely name, dept and salary. We had the standard discussion about whether to include single or multiple inheri- tance and concluded that a single inheritance scheme would simply be too restrictive. As a result POSTGRES allows a constructed type to inherit from an arbitrary collection of other constructed types. When ambiguities arise because an object has multiple parents with the same field name, we elected to refuse to create the new type. However, we isolated the resolution semantics in a single routine, which can be easily changed to track multiple inheritance semantics as they unfold over time in programming languages. We now turn to the POSTGRES notion of functions. There are three different classes of POSTGRES functions, normal functions operators POSTQUEL functions and we discuss each in turn. A user can define an arbitrary collection of normal functions whose operands are base types or con- structed types. For example, he can define a function, area, which maps an instance of a polygon into an instance of a floating point number. Such functions are automatically available in the query language as illustrated in the following example: retrieve (DEPT.dname) where area (DEPT.floorspace) > 500 ** In this section the reader can use the words constructed type, relation, and class interchangeably. Moreover, the words record, instance, and tuple are similarly interchangeable. This section has been purposely written with the chosen notation to illus- trate a point about object-oriented data bases which is discussed in Section 2.5. 5 Normal functions can be defined to POSTGRES while the system is running and are dynamically loaded when required during query execution. Functions are allowed on constructed types, e.g: retrieve (EMP.name) where overpaid (EMP) In this case overpaid has an operand of type EMP and returns a boolean. Functions whose operands are constructed types are inherited down the type hierarchy in the standard way. Normal functions are arbitrary procedures written in a general purpose programming language (in our case C or LISP). Hence, they have arbitrary semantics and can run other POSTQUEL commands dur- ing execution. Therefore, queries with normal functions in the qualification cannot be optimized by the POSTGRES query optimizer. For example, the above query on overpaid employees will result in a sequential scan of all employees. To utilize indexes in processing queries, POSTGRES supports a second class of functions, called operators. Operators are functions with one or two operands which use the standard operator notation in the query language. For example the following query looks for departments whose floor space has a greater area than that of a specific polygon: retrieve (DEPT.dname) where DEPT.floorspace AGT polygon["(0,0), (1,1), (0,2)"] The "area greater than" operator AGT is defined by indicating the token to use in the query language as well as the function to call to evaluate the operator. Moreover, several hints can also be included in the definition which assist the query optimizer. One of these hints is that ALE is the negator of this operator. Therefore, the query optimizer can transform the query: retrieve (DEPT.dname) where not (DEPT.floorspace ALE polygon["(0,0), (1,1), (0,2)"]) which cannot be optimized into the one above which can be. In addition, the design of the POSTGRES access methods allows a B+-tree index to be constructed for the instances of floorspace appearing in DEPT records. This index can support efficient access for the class of operators {ALT, ALE, AE, AGT, AGE}. Information on the access paths available to the various operators is recorded in the POSTGRES system catalogs. As pointed out in [STON87b] it is imperative that a user be able to construct new access methods to provide efficient access to instances of non-traditional base types. For example, suppose a user introduces a new operator "!!" defined on polygons that returns true if two polygons overlap. Then, he might ask a query such as: retrieve (DEPT.dname) where DEPT.floorspace !! polygon["(0,0), (1,1), (0,2)"] There is no B+-tree or hash access method that will allow this query to be rapidly executed. Rather, the query must be supported by some multidimensional access method such as R-trees, grid files, K-D-B trees, 6 etc. Hence, POSTGRES was designed to allow new access methods to be written by POSTGRES users and then dynamically added to the system. Basically, an access method to POSTGRES is a collection of 13 normal functions which perform record level operations such as fetching the next record in a scan, inserting a new record, deleting a specific record, etc. All a user need do is define implementations for each of these functions and make a collection of entries in the system catalogs. Operators are only available for operands which are base types because access methods traditionally support fast access to specific fields in records. It is unclear what an access method for a constructed type should do, and therefore POSTGRES does not include this capability. The third kind of function available in POSTGRES is POSTQUEL functions. Any collection of commands in the POSTQUEL query language can be packaged together and defined as a function. For example, the following function defines the overpaid employees: define function high-pay as retrieve (EMP.all) where EMP.salary > 50000 POSTQUEL functions can also have parameters, for example: define function ret-sal as retrieve (EMP.salary) where EMP.name = $1 Notice that ret-sal has one parameter in the body of the function, the name of the person involved. Such parameters must be provided at the time the function is called. A third example POSTQUEL function is: define function set-of-DEPT as retrieve (DEPT.all) where DEPT.floor = $.floor This function has a single parameter "$.floor". It is expected to appear in a record and receives the value of its parameter from the floor field defined elsewhere in the same record. Each POSTQUEL function is automatically a constructed type. For example, one can define a FLOORS type as follows: create FLOORS (floor = i2, depts = set-of-DEPT) This constructed type uses the set-of-DEPT function as a constructed type. In this case, each instance of FLOORS has a value for depts which is the value of the function set-of-DEPT for that record. In addition, POSTGRES allows a user to form a constructed type, one or more of whose fields has the special type POSTQUEL. For example, a user can construct the following type: create PERSON (name = c12, hobbies = POSTQUEL) In this case, each instance of hobbies contains a different POSTQUEL function, and therefore each person has a name and a POSTQUEL function that defines his particular hobbies. This support for POSTQUEL as a type allows the system to simulate non-normalized relations as found in NF**2 [DADA86]. POSTQUEL functions can appear in the query language in the same manner as normal functions. The following example ensures that Joe has the same salary as Sam: 7 replace EMP (salary = ret-sal("Joe")) where EMP.name = "Sam" In addition, since POSTQUEL functions are a constructed type, queries can be executed against POSTQUEL functions just like other constructed types. For example, the following query can be run on the constructed type, high-pay: retrieve (high-pay.salary) where high-pay.name = "george" If a POSTQUEL function contains a single retrieve command, then it is very similar to a relational view definition, and this capability allows retrieval operations to be performed on objects which are essentially relational views. Lastly, every time a user defines a constructed type, a POSTQUEL function is automatically defined with the same name. For example, when DEPT is constructed, the following function is automatically defined: define function DEPT as retrieve (DEPT.all) where DEPT.OID = $1 When EMP was defined earlier in this section, it contained a field dept which was of type DEPT. In fact, DEPT was the above automatically defined POSTQUEL function. As a result, instance of a constructed type is available as a type because POSTGRES automatically defines a POSTQUEL function for each such type. POSTQUEL functions are a very powerful notion because they allow arbitrary collections of instances of types to be returned as the value of the function. Since POSTQUEL functions can reference other POSTQUEL functions, arbitrary structures of complex objects can be assembled. Lastly, POST- QUEL functions allow collections of commands such as the 5 SQL commands that make up TP1 [ANON85] to be assembled into a single function and stored inside the DBMS. Then, one can execute TP1 by executing the single function. This approach is preferred to having to submit the 5 SQL commands in TP1 one by one from an application program. Using a POSTQUEL function, one replaces 5 round trips between the application and the DBMS with 1, which results in a 25% performance improvement in a typi- cal OLTP application. 2.3. The POSTGRES Query Language The previous section presented several examples of the POSTQUEL language. It is a set oriented query language that resembles a superset of a relational query language. Besides user defined functions and operators which were illustrated earlier, the features which have been added to a traditional relational language include: path expressions support for nested queries transitive closure 8 support for inheritance support for time travel Path expressions are included because POSTQUEL allows constructed types which contain other constructed types to be hierarchically referenced. For example, the EMP type defined above contains a field which is an instance of the constructed type, DEPT. Hence, one can ask for the names of employees who work on the first floor as follows: retrieve (EMP.name) where EMP.dept.floor = 1 rather than being forced to do a join, e.g: retrieve (EMP.name) where EMP.dept = DEPT.OID and DEPT.floor = 1 POSTQUEL also allows queries to be nested and has operators that have sets of instances as operands. For example, to find the departments which occupy an entire floor, one would query: retrieve (DEPT.dname) where DEPT.floor NOTIN {D.floor from D in DEPT where D.dname != DEPT.dname} In this case, the expression inside the curly braces represents a set of instances and NOTIN is an operator which takes a set of instances as its right operand. The transitive closure operation allows one to explode a parts or ancestor hierarchy. Consider for example the constructed type: parent (older, younger) One can ask for all the ancestors of John as follows: retrieve* into answer (parent.older) using a in answer where parent.younger = "John" or parent.younger = a.older In this case the * after retrieve indicates that the associated query should be run until answer fails to grow. If one wishes to find the names of all employees over 40, one would write: retrieve (E.name) using E in EMP where E.age > 40 On the other hand, if one wanted the names of all salesmen or employees over 40, the notation is: retrieve (E.name) using E in EMP* where E.age > 40 Here the * after the constructed type EMP indicates that the query should be run over EMP and all 9 constructed types under EMP in the inheritance hierarchy. This use of * allows a user to easily run queries over a constructed type and all its descendents. Lastly, POSTGRES supports the notion of time travel. This feature allows a user to run historical queries. For example to find the salary of Sam at time T one would query: retrieve (EMP.salary) using EMP [T] where EMP.name = "Sam" POSTGRES will automatically find the version of Sam’s record valid at the correct time and get the appropriate salary. Like relational systems, the result of a POSTQUEL command can be added to the data base as a new constructed type. In this case, POSTQUEL follows the lead of relational systems by removing duplicate records from the result. The user who is interested in retaining duplicates can do so by ensuring that the OID field of some instance is included in the target list being selected. For a full description of POST- QUEL the interested reader should consult [WENS88]. 2.4. Fast Path There are three reasons why we chose to implement a fast path feature. First, a user who wishes to interact with a data base by executing a sequence of functions to navigate to desired data can use fast path to accomplish his objective. Second, there are a variety of decision support applications in which the end user is given a specialized query language. In such environments, it is often easier for the application developer to construct a parse tree representation for a query rather than an ASCII one. Hence, it would be desirable for the application designer to be able to directly call the POSTGRES optimizer or executor. Most DBMSs do not allow direct access to internal system modules. The third reason is a bit more complex. In the persistent CLOS layer of PICASSO, it is necessary for the run time system to assign a unique identifier (OID) to every constructed object that is persistent. It is undesirable for the system to synchronously insert each object directly into a POSTGRES data base and thereby assign a POSTGRES identifier to the object. This would result in poor performance in executing a persistent CLOS program. Rather, persistent CLOS maintains a cache of objects in the address space of the program and only inserts a persistent object into this cache synchronously. There are several options which control how the cache is written out to the data base at a later time. Unfortunately, it is essential that a persistent object be assigned a unique identifier at the time it enters the cache, because other objects may have to point to the newly created object and use its OID to do so. If persistent CLOS assigns unique identifiers, then there will be a complex mapping that must be per- formed when objects are written out to the data base and real POSTGRES unique identifiers are assigned. Alternately, persistent CLOS must maintain its own system for unique identifiers, independent of the 10 [...]... instances in the system catalogs Moreover, the query rewrite algorithm is nearly the same as in the first implementation The triggering system can be supported by the same instance markers as in PRS In fact, the implementation is bit simpler because a couple of the types of markers are not required Because the implementation of PRS II is so similar to our initial rules system, we expect to have the conversion... deliver the query to be run The first POSTGRES would run the query and then send results to the user This would require careful synchronization of the input socket among multiple independent command streams Moreover, it would require the second POSTGRES to know the portal name on which the first user’s rule was running d) The POSTGRES process for the second user could alert a special process called the POSTMASTER... for the type The implementation complexity of union types is thus forced into the routines for the operators and functions and onto the implementor of the type Moreover, it is clear that there are a vast number of union types and an extensive type library must be constructed by the application designer The PICASSO team stated that this approach placed an unacceptably difficult burden on them, and therefore... and we would caution others not to follow in our footsteps 6 STATUS AND PERFORMANCE At the current time (October 1989) the LISP-less Version 1 of POSTGRES has been in the hands of users for a short time, and we are shaking the last bugs out of the C port In addition, we have designed all of the additional functionality to appear in Version 2 The characteristics of Version 1 are: a) The query language... algorithms embodied in the code The footprint of the LISP system is about 4.5 Mbytes while the C system is about 1 Mbyte For comparison purposes we also include the performance numbers for the commercial version of INGRES in the third column As can be seen, the LISP system is several times slower than the C system In various other benchmarks we have never seen the C 31 POSTGRES POSTGRES INGRES C-based... another implementation Hence, we seized on the idea of implementing a ‘‘no-overwrite’’ storage manager Using this technique the old record remains in the data base whenever an update occurs, and serves the purpose normally performed by a write-ahead log Consequently, POSTGRES has no log in the conventional sense of the term Instead the POSTGRES log is simply 2 bits per transaction indicating whether... one does not need to process the log undoing the effects of updates; the previous records are readily available in the data base More generally, to recover from a crash, one must abort all the transactions in progress at the time of the crash This process can be effectively instantaneous in POSTGRES The second benefit of a no-overwrite storage manager is the possibility of time travel As noted earlier,... while the system is executing Moreover, we have designed the system so that it can accommodate a potentially very large number of types and operators Consequently, the user functions that support the implementation of a type must be dynamically loaded and unloaded Hence, POSTGRES maintains a cache of currently loaded functions and dynamically moves functions into the cache and then ages them out of the. .. same budget as the candy department However, it is impossible for the budget of the shoe department to be specified as: if floor = 1 then retrieve (DEPT.budget) where DEPT.dname = "candy" else retrieve (DEPT.budget) where DEPT.dname = "toy" This specification defines the budget of the shoe department to be the candy department budget if it is on the first floor Otherwise, it is the same as the toy department... execution by the query rewrite implementation to: retrieve (age = 40) where EMP.name = "Sam" and EMP.name != "Bill" 19 At the current time much of the POSTGRES Rules System (PRS) as described in [STON88] is operational, and there are three aspects of the design which we wish to discuss in the next three subsections, namely: complexity absence of needed function and efficiency Then, we close with the second . introduces a new operator "!!" defined on polygons that returns true if two polygons overlap. Then, he might ask a query such as: retrieve (DEPT.dname) where DEPT.floorspace !! polygon["(0,0),

Ngày đăng: 28/04/2014, 13:32

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan