Upgrade Notes for Developers

What's In This Presentation

These slides provide a general overview of the upgrade process, specifically "internal upgrade", as it is now implemented in ASE 15. They cover:

This presentation is a collection of information that server developers and debuggers will need. It is not an explanation of how customers upgrade their installations. It talks about specific files that exist in the server layer, but not the user application layer that invoke them.


Some Basic Facts About Upgrade

"Upgrade" is the process of changing the underlying schema of a database so that its structure is correct for the modern server.

We can't predict what changes will have to be made to make that happen.

Upgrade runs against databases that DO NOT HAVE the new features.

Upgrade runs after recovery is complete. We want it to run before any customer activity in the databases, but there are no guarantees.

The only feature set you can depend on is whatever exists in the oldest customer installation anywhere in the field.

Upgrade runs against databases loaded from older servers, as well as against complete installations.

There IS NOT an "upgrade group" that creates a particular version's upgrade -- each developer does that for their own new features.

Before a feature is "done", developers must prove that older servers can successfully upgrade to the new feature.


Steps Involved in Upgrade

Upgrade consists of three basic steps:

  1. Run preupgrade against the old server
  2. Shut down the old server and boot the new
  3. Run upgrade against the new server

Upgrade and preupgrade have two basic parts: the utilities themselves and the "internal upgrade" portion. The utilities do very little work; instead, they check things and tell ASE to perform actions internally. The bulk of the work happens in internal upgrade, which is implemented as part of ASE's online database facility.

The utility files used for this are in generic/upgrade. They are:

The internal upgrade portion are in various directories:

All upgrade files depend on quite a lot of generated source, all of which is generated by awk scripts in the utils directory.

Preupgrade checks for obvious problems that would cause upgrade to fail.

It checks for name conflicts against new reserved words, checks databases to see that they are large enough to meet the new server's requirements, and checks the server's configuration to see that memory is adequate. It will print warning messages about the things it finds, alerting the customer about changes they may have to make.

Feature developers are responsible for modifying preupgrade to make the appropriate checks for their new features.

Note that preupgrade runs against the old server, not the new one! The old server has no knowledge of any new feature; all it's doing is checking for anything obvious that would cause a feature not to install correctly.

Upgrade installs new features.

It has two basic parts: the "upgrade utility", which is a standalone program that once was the method we used to perform upgrades, and "internal upgrade", which is the method we now use. Between them, these two methods perform all the steps necessary to convert the old installation for the new server's use.

The "upgrade utility" consists mostly of upgrade/upgrade.c, its associated header files, and generated source.

"Internal upgrade" consists of utils/intupgd.c, utils/attrib.lst, include/intupgd.h, and generated source.

DO NOT add any new code to the upgrade utility!

That program is outdated, quite limited, and dangerous. It will eventually go away. (Exception: there are no upgrade methods to handle configuration changes yet; so if you retire config parameters or add new ones, you will probably have to modify the upgrade utility. This is accomplished by modifying utils/cfg_options, then rebuilding the utils generated files.)


Adding Upgrade Items

We refer to the actions upgrade does as "upgrade items". To add upgrade items, modify internal upgrade. Doing this modification is relatively simple:

Add a mnemonic for the item in utils/attrib.lst.

Assign an unused number to the item. (You may have to renumber the current version's "checkpoint" ID in order to fit your item in.) You can scan for the tag "END of UPGRADE attributes" to find the approximate place to add the new item.

Rebuild the utils generated files ("sjam gensrc" - usually not necessary)

Add the item to the Upgrade_items[] array in utils/intupgd.c.

Place it in numeric order within the array. For ease of maintenance and debugging, add a comment showing its number. Add any text strings that may be required: SQL statement text, for example, or table names.

Add the item's mnemonic to the present server version's dependency list.

If your item depends on other items, add a dependency list for your item showing what it depends on.

If you are adding an item type that requires a special function to do the work:

Before you add an upgrade function, inspect the list of already existing upgrade functions to see whether the function you need has already been written.


How-To

A Cookbook for Common Upgrade Tasks

Included in this section:

  1. Add a new system table
  2. Add a column to a system table
  3. Add an index to a system table
  4. Everything that can be done through SQL
  5. Everything else.

First, note that every upgrade item can have several status bits set:


0. Steps common to ALL modifications

Before we even get started on how to do things using internal upgrade, there are steps that happen for every upgrade item. Those steps are listed here, to give us a convenient reference for them:

Edit utils/attrib.lst.

Find the end of the list of upgrade attributes. (In that file, you can search for the tag "END of UPGRADE attributes", or you can use the array Upgrade_items[] in utils/intupgd.c to help - the attribute numbers are in comments above the items. Searching attrib.lst for that number usually works.) Add a definition for your item, using the existing items as a model. Give the item a mnemonic that will help others understand what it is doing.

As required, modify the number of the "checkpoint" attribute for the version you're working on so that your item has a lower number than it does.

Edit utils/intupgd.c.

In the array Upgrade_items[], add an upgrade item to identify the new item. Choose the appropriate action ID for your item: you can select from the list in array Upgd_fns[]. (You may have to add your own ID; we'll talk about that in section 5, below.)

If your item depends on other upgrade items, add a dependency list for it; you can use item 1133 and array Ckpt_11_9_3_attr_list as models for how to create a dependency list.

Add your item as a dependency in the present server version's dependency list.

Select attributes for the new item as appropriate, using the mnemonics listed above.

The "data" fields (one integer and one string) vary widely in how they are used. If you are reusing a previously existing upgrade function, use an example of that type to see what information you must provide. If you are adding a new upgrade function, you can design the inputs as needed for your application; but design carefully: other developers may want to reuse your work.

Note that it is not possible to change the data storage design of items already in the field - they can be extended, but not changed! The data is stored in customer databases, and there is no facility to change it.

Compile intupgd.o.

This will create the required new header and source files containing your new upgrade item definition.

Those steps happen for every upgrade item. In the text below, I will refer to them by saying "perform the common steps listed in item zero, above."


1. Add a new system table

Upgrade action ID UPGD_CREATE_TABLE is a "does-everything" item that creates a system table, including all its defined indexes. Use this in preference to typing in SQL statements; it will generate the correct SQL for this table based on information you will place in demo/syscol.c. It works both for real and fake tables.

In the include directory, add a header file for the structure for your table.

This structure definition will give you the required data types and offsets for the table's column definitions. As a model, use an example showing the appropriate locking style for your table: include/syssn.h is an example for row-locked tables, and include/lock.h is an example for page-locked tables. TAKE NOTE of the required row overhead in each table style, including the row-length word at the end of the fixed-length columns!

Edit demo/syscol.c:

Add an array of SYSCOLs for your table, showing all the columns that table should have. (The SYSCOL is defined in include/catalog.h.) All fixed and all variable-length columns must be represented in this array, including any padding space in the fixed part of the row. The only parts of the row that are not represented are the row overhead:

If your table has indexes:

Add your table's information in the Systables[] array, in the slot matching the table's ID.

Each slot is tagged to show its ID. If this is a fake table, be sure to mark it as SYSTAB_ISFAKE in that array.

Edit include/catalog.h:

Perform the common steps listed in item zero, above.


2. Add a column to a system table

Upgrade action ID UPGD_ADD_COLUMN adds a column to an existing system table. Use it in preference to typing in an "alter table" SQL statement. It will generate the correct SQL for this table based on information you will place in demo/syscol.c.

Modify include/catalog.h

(This step may be unnecessary.) If you are adding a column that code will search on, add the appropriate mnemonic for its syscol.c array index. See "add a new system table", above, for more information. If you added the new syscol.c entry above any previously existing entries, adjust the mnemonics as required for the columns that have moved.

Modify demo/syscol.c

Add an array entry to the appropriate SYSCOL array for the table you are modifying. This entry need not be in any particular order; you can add it to the end of the array.

Perform the common steps listed in item zero, above.


3. Add an index to a system table

Upgrade action ID UPGD_CREATE_INDEX creates the index on a system table having a given index ID. Use it in preference to typing in a "create index" SQL statement. It will generate the correct SQL for this table based on information you will place in demo/syscol.c.

Modify include/catalog.h

Add the appropriate mnemonic for the new index's ID. This is the index ID, not the array index. It is not used by upgrade directly, only by code that will do scans on that index.

Modify demo/syscol.c

Add an array of INDCOLs for the index you are adding, listing the index columns. Place that array below the SYSCOL array for that table. Add an entry to the array of INDDEFs for that table, pointing to the new array and giving the index name.

Perform the common steps listed in item zero, above.


4. Everything that can be done through SQL

Upgrade action ID UPGD_SQL_STMT executes random SQL. Anything that can be done using SQL, that hasn't already been addressed by other functions in internal upgrade, should be done using this method.

Perform the common steps listed in item zero, above.

No, really: that's it. No other setup required. However ...

Remember that SQL statements must be idempotent!

Any SQL statement used in upgrade must first check whether the work has been done, and only do it if it hasn't been done. The only exception to this is for statements that don't have any bad side effects from being done more than once. This is required because we don't know when a crash may happen: if the server should happen to crash between executing a statement and recording that it was done, it could be executed twice.

Check for permissions failures.

Upgrade executes SQL statements with a special flag, P2_INTERNALSQL, set in the PSS. Most server code has been modified to permit random SQL when this flag is set. However, we don't guarantee to have caught all cases. If the code area you will be exercising has special permissions requirements, check your work to ensure that it permits internal SQL statements. If it does not, find the failure point and add a check for P2_INTERNALSQL.

Unfortunately, internal SQL can be fooled into thinking it failed.

Our error handling mechanism places an error code into the PSS whenever an error above a certain severity is printed. This happens because the error is printed, without regard for whether the error was raised through ex_raise() or simply printed through ex_callprint(), and without regard for whether the operation ultimately succeeds or fails.

Internal SQL inspects that error code, and presumes that if it is non-zero then there was an error. This is required because internal SQL depends on sequencer(), which always returns "success" if it exits normally; and that can mask certain error conditions that were caught by code lower down. Fixing this will require some amount of redesign within the server.


5. Everything else

Many upgrade items can't be accomplished using SQL statements: for example, anything that requires modifying page headers, allocation pages, and similar - no SQL exists to do that.

These actions require special functions to do the work. Internal upgrade establishes a special function type, UPGD_ACTION_FN, for these functions, and collects them into an array, Upgd_fns[], in function upgd__get_method(). Before you do anything else, check that array to find out whether the function you need has already been written. If it has, or if you can reasonably easily modify an existing function to also do your task, do so.

Presuming that you need a new function, write it:

Perform the common steps listed in item zero, above.


Debugging Upgrade

Upgrade failed. Now what?

Check output from upgrade, and check the error log.

When things go wrong during upgrade, the upgrade utility prints the error messages the server sends it. Depending on their severity, those messages may also appear in the error log. There will probably be several messages, because upgrade comments on failures at every step of the way. The first message is the cause of the failure.

The log and/or the database ran out of space (error 1105).

The upgrade code is quite aggressive about dumping the transaction log, so if the server runs out of space in a database, that database probably really is out of space. If the log ran out of space, you can try "dump tran with no_log" but it is quite likely not to work. You will probably need to use alter database to add space. This command does work on offline databases.

Upgrade claims that an item my code depends on could not be installed.

You will need to figure out what the dependent item was, and why it failed. Upgrade reports the ID of the failing item; inspect that item to find out what might have gone wrong. That item might have been "optional", so a failure to install didn't cause upgrade to fail; but when another item depends on an optional item, the optional item becomes required.

Upgrade uses "dependency lists" to ensure that everything is in place for the next item it's trying to install. Further, dependencies cascade: one item can depend on another that depends on yet another, as far down the chain as necessary. The server checks dependencies until it finds the bottom of the list or finds an item that is already installed. (Installed items are assumed to have satisfied all their dependencies.)

I can't see any explanations or errors, but an item failed to install.

Did this item use internal SQL to install it? If so, check for messages that may have been printed by ex_callprint(), even if no error was raised. Internal SQL checks pss->plasterror to determine whether any errors occurred. The server's error printing mechanism sets that field depending on error severity, without regard for whether the message is part of an error that's being raised. Thus, depending on severity, informational messages can cause internal SQL to think an operation failed when it actually succeeded. The only fix for this is to change the severity of the printed message, or not to print it at all.

Some part of an upgrade step failed, so upgrade failed. It should have continued.

Upgrade stops after the first failure to install a required item. Any function that returns a failing status to the upgrade driver causes upgrade to fail.

Probably, the function that did the work returned a failure status; maybe within a loop, one operation failed, so the function stopped. Unless that really constitutes a failure, that loop should be fixed so that it does all the processing it needs, and simply reports or remembers its failures.

If the server could work without an item, even though it might work slowly, consider making that item "optional". Customers depend on upgrade working correctly the first time, so it should be as close to bullet-proof as we can make it. If an item can be gotten along without, however temporarily, consider letting it be optional and having the customer (or, better, the server itself) go back later and retry the installation.

So, how do I restart upgrade?

The upgrade utility may or may not be restartable, depending where the failure happened. If it fails before the message "Setting upgrade version to XXXXX", you can probably re-run the utility. (That will sometimes fail. The upgrade utility is fragile, and prone to failure when faced with unexpected circumstances.) If it fails after that message, you need to reset the server's idea of what version it's at. In SQL:

1> select config_admin(1, 122, NNNNN, 1, null, null)

where "NNNNN" is the OLD server's version ID. (For example, 11.9.2 has version ID 19200.)

Otherwise, the utility will not run, saying that the server is already at the target version.

(A side note: internal upgrade is simple to restart: the online database command inspects a database to see whether it is already at the target version; if it is not, it upgrades it. Internal upgrade uses a per-database version specifier, so each database can be upgraded separately. Additionally, if the failure happened because the server crashed, rebooting will cause the databases to upgrade automatically. Unfortunately, finishing internal upgrade on the databases is not sufficient by itself. The upgrade utility does some work after the databases come online, and that portion of the work can't be done without resetting the installation's version ID as noted above.)