Persistent Objects and Storage Globals
Persistent classes allow you to save objects to the database and retrieve them as objects or via SQL. Regardless of how they are accessed, these objects are stored in globals, which can be thought of as persistent multidimensional arrays.
This page describes how the global names are determined and how you can control them. It also looks at the contents of the globals, for illustration purposes. The best way to ensure data consistency is to access the data via objects or SQL.
This page also introduces two other variations:
-
Columnar storage, which has performance benefits in some scenarios; column storage is also discussed more fully in Choose an SQL Table Storage Layout
Standard Global Names
For a persistent class, when you create and compile a class definition, global names for the class are generated based on the class name.
For example, let’s define the following class, GlobalsTest.President:
Class GlobalsTest.President Extends %Persistent
{
/// President's name (last,first)
Property Name As %String(PATTERN="1U.L1"",""1U.L");
/// Year of birth
Property BirthYear As %Integer;
/// Short biography
Property Bio As %Stream.GlobalCharacter;
/// Index for Name
Index NameIndex On Name;
/// Index for BirthYear
Index DOBIndex On BirthYear;
}
After compiling the class, we can see the following storage definition generated at the bottom of the class:
Storage Default
{
<Data name="PresidentDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>BirthYear</Value>
</Value>
<Value name="4">
<Value>Bio</Value>
</Value>
</Data>
<DataLocation>^GlobalsTest.PresidentD</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<IdLocation>^GlobalsTest.PresidentD</IdLocation>
<IndexLocation>^GlobalsTest.PresidentI</IndexLocation>
<StreamLocation>^GlobalsTest.PresidentS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Notice, in particular, the following storage keywords:
-
The DataLocation is the global where class data will be stored. The name of the global is the complete class name (including the package name) with a “D” appended to the name, in this case, ^GlobalsTest.PresidentD.
-
The IdLocation (often the same as the DataLocation) is the global where the ID counter will be stored, at its root.
-
The IndexLocation is the global where the indexes for the class will be stored. The name of the global is the complete class name with an “I” appended to the name, or, ^GlobalsTest.PresidentI.
-
The StreamLocation is the global where any stream properties will be stored. The name of the global is the complete class name with an “S” appended to the name, or, ^GlobalsTest.PresidentS.
After creating and storing a few objects of our class, we can view the contents of these globals in the Terminal:
USER>zwrite ^GlobalsTest.PresidentD
^GlobalsTest.PresidentD=3
^GlobalsTest.PresidentD(1)=$lb("",1732,"1","Washington,George")
^GlobalsTest.PresidentD(2)=$lb("",1735,"2","Adams,John")
^GlobalsTest.PresidentD(3)=$lb("",1743,"3","Jefferson,Thomas")
USER>zwrite ^GlobalsTest.PresidentI
^GlobalsTest.PresidentI("DOBIndex",1732,1)=""
^GlobalsTest.PresidentI("DOBIndex",1735,2)=""
^GlobalsTest.PresidentI("DOBIndex",1743,3)=""
^GlobalsTest.PresidentI("NameIndex"," ADAMS,JOHN",2)=""
^GlobalsTest.PresidentI("NameIndex"," JEFFERSON,THOMAS",3)=""
^GlobalsTest.PresidentI("NameIndex"," WASHINGTON,GEORGE",1)=""
USER>zwrite ^GlobalsTest.PresidentS
^GlobalsTest.PresidentS=3
^GlobalsTest.PresidentS(1)="1,239"
^GlobalsTest.PresidentS(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^GlobalsTest.PresidentS(2)="1,195"
^GlobalsTest.PresidentS(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^GlobalsTest.PresidentS(3)="1,202"
^GlobalsTest.PresidentS(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."
The subscript of ^GlobalsTest.PresidentD is the IDKey. Since we did not define one of our indexes as the IDKey, the ID is used as the IDKey. For more information on IDs, see Controlling How IDs Are Generated.
The first subscript of ^GlobalsTest.PresidentI is the name of the index.
The first subscript of ^GlobalsTest.PresidentS is the ID of the bio entry, not the ID of the president.
You can also view these globals in the Management Portal (System Explorer > Globals).
Only the first 31 characters in a global name are significant, so if a complete class name is very long, you might see global names like ^package1.pC347.VeryLongCla4F4AD. The system generates names such as these to ensure that all of the global names for your class are unique. If you plan to work directly with the globals of a class, make sure to examine the storage definition so that you know the actual name of the global. Alternatively, you can control the global names by using the DEFAULTGLOBAL parameter in your class definition. See User-Defined Global Names.
Hashed Global Names
The system will generate shorter global names if you set the USEEXTENTSET parameter to the value 1. (The default value for this parameter is 0, meaning use the standard global names.) These shorter global names are created from a hash of the package name and a hash of the class name, followed by a suffix. While the standard names are more readable, the shorter names can contribute to better performance.
When you set USEEXTENTSET to 1, each index is also assigned to a separate global, instead of using a single index global with different first subscripts. Again, this is done for increased performance.
To use hashed global names for the GlobalsTest.President class we defined earlier, we would add the following to the class definition:
/// Use hashed global names
Parameter USEEXTENTSET = 1;
After deleting the data, deleting the storage definition, and recompiling the class, we can see the new storage definition with hashed global names:
Storage Default
{
...
<DataLocation>^Ebnm.EKUy.1</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<ExtentLocation>^Ebnm.EKUy</ExtentLocation>
<IdLocation>^Ebnm.EKUy.1</IdLocation>
<Index name="DOBIndex">
<Location>^Ebnm.EKUy.2</Location>
</Index>
<Index name="IDKEY">
<Location>^Ebnm.EKUy.1</Location>
</Index>
<Index name="NameIndex">
<Location>^Ebnm.EKUy.3</Location>
</Index>
<IndexLocation>^Ebnm.EKUy.I</IndexLocation>
<StreamLocation>^Ebnm.EKUy.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Notice, in particular, the following storage keywords:
-
The ExtentLocation is the hashed value that will be used to calculate global names for this class, in this case, ^Ebnm.EKUy.
-
The DataLocation (equivalent to the IDKEY index), where class data will be stored, is now the hashed value with a “.1” appended to the name, in this case, ^Ebnm.EKUy.1.
-
Each index now has its own Location and thus its own separate global. The name of the IdKey index global is equivalent to the hashed value with a ”.1” appended to the name, in this example, ^Ebnm.EKUy.1. The globals for the remaining indexes have “.2” to “.N” appended to the name. Here, the DOBIndex is stored in global ^Ebnm.EKUy.2 and the NameIndex is stored in ^Ebnm.EKUy.3.
-
The IndexLocation is the hashed value with “.I” appended to the name, or ^Ebnm.EKUy.I, however, this global is often not used.
-
The StreamLocation is the hashed value with “.S” appended to the name, or ^Ebnm.EKUy.S.
After creating and storing a few objects, the contents of these globals might look as follows, again using the Terminal:
USER>zwrite ^Ebnm.EKUy.1
^Ebnm.EKUy.1=3
^Ebnm.EKUy.1(1)=$lb("","Washington,George",1732,"1")
^Ebnm.EKUy.1(2)=$lb("","Adams,John",1735,"2")
^Ebnm.EKUy.1(3)=$lb("","Jefferson,Thomas",1743,"3")
USER>zwrite ^Ebnm.EKUy.2
^Ebnm.EKUy.2(1732,1)=""
^Ebnm.EKUy.2(1735,2)=""
^Ebnm.EKUy.2(1743,3)=""
USER>zwrite ^Ebnm.EKUy.3
^Ebnm.EKUy.3(" ADAMS,JOHN",2)=""
^Ebnm.EKUy.3(" JEFFERSON,THOMAS",3)=""
^Ebnm.EKUy.3(" WASHINGTON,GEORGE",1)=""
USER>zwrite ^Ebnm.EKUy.S
^Ebnm.EKUy.S=3
^Ebnm.EKUy.S(1)="1,239"
^Ebnm.EKUy.S(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^Ebnm.EKUy.S(2)="1,195"
^Ebnm.EKUy.S(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^Ebnm.EKUy.S(3)="1,202"
^Ebnm.EKUy.S(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."
For classes defined using an SQL CREATE TABLE statement, the default for the USEEXTENTSET parameter is 1. For more information on creating tables, see Defining Tables.
For example, let’s create a table using the Management Portal (System Explorer > SQL > Execute Query):
CREATE TABLE GlobalsTest.State (NAME CHAR (30) NOT NULL, ADMITYEAR INT)
After populating the table with some data, we see the globals ^Ebnm.BndZ.1 and ^Ebnm.BndZ.2 in the Management Portal (System Explorer > Globals). Notice that the package name is still GlobalsTest, so the first segment of the global names for GlobalsTest.State is the same as for GlobalsTest.President.
Using the Terminal, the contents of the globals might look like:
USER>zwrite ^Ebnm.BndZ.1
^Ebnm.BndZ.1=3
^Ebnm.BndZ.1(1)=$lb("Delaware",1787)
^Ebnm.BndZ.1(2)=$lb("Pennsylvania",1787)
^Ebnm.BndZ.1(3)=$lb("New Jersey",1787)
USER>zwrite ^Ebnm.BndZ.2
^Ebnm.BndZ.2(1)=$zwc(412,1,0)/*$bit(2..4)*/
The global ^Ebnm.BndZ.1 contains the States data and ^Ebnm.BndZ.2 is a bitmap extent index. See Bitmap Extent Index.
If we wanted to use standard global names with a class created via SQL, we could set the USEEXTENTSET parameter to the value 0:
CREATE TABLE GlobalsTest.State (%CLASSPARAMETER USEEXTENTSET 0, NAME CHAR (30) NOT NULL, ADMITYEAR INT)
This would generate the standard global names ^GlobalsTest.StateD and ^GlobalsTest.StateI.
You can change the default value used for the USEEXTENTSET parameter to 0 for classes created via a CREATE TABLE statement by executing the command do $SYSTEM.SQL.SetDDLUseExtentSet(0, .oldval). The previous default value is returned in oldval.
For classes created via XEP, the default for the USEEXTENTSET parameter is 1 and may not be changed. You can read more about XEP in Persisting Java Objects with InterSystems XEP.
User-Defined Global Names
For finer control of the global names for a class, use the DEFAULTGLOBAL parameter. This parameter works in conjunction with the USEEXTENTSET parameter to determine the global naming scheme.
For example, let’s add the DEFAULTGLOBAL parameter to set the root of the global names for the GlobalsTest.President class to ^GT.Pres:
/// Use hashed global names
Parameter USEEXTENTSET = 1;
/// Set the root of the global names
Parameter DEFAULTGLOBAL = "^GT.Pres";
After deleting the data, deleting the storage definition, and recompiling the class, we can see the following global names:
Storage Default
{
...
<DataLocation>^GT.Pres.1</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<ExtentLocation>^GT.Pres</ExtentLocation>
<IdLocation>^GT.Pres.1</IdLocation>
<Index name="DOBIndex">
<Location>^GT.Pres.2</Location>
</Index>
<Index name="IDKEY">
<Location>^GT.Pres.1</Location>
</Index>
<Index name="NameIndex">
<Location>^GT.Pres.3</Location>
</Index>
<IndexLocation>^GT.Pres.I</IndexLocation>
<StreamLocation>^GT.Pres.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Likewise, we can use the DEFAULTGLOBAL parameter when defining a class using SQL:
CREATE TABLE GlobalsTest.State (%CLASSPARAMETER USEEXTENTSET 0, %CLASSPARAMETER DEFAULTGLOBAL = '^GT.State',
NAME CHAR (30) NOT NULL, ADMITYEAR INT)
This would generate the global names ^GT.StateD and ^GT.StateI.
Redefining Global Names
If you edit a class definition in a way that redefines the previously existing global names, for example, by changing the values of the USEEXTENTSET or DEFAULTGLOBAL parameters, you must delete the existing storage definition to allow the compiler to generate a new storage definition. Note that any data in the existing globals is preserved. Any data to be retained must be migrated to the new global structure.
For more information, see Redefining a Persistent Class That Has Stored Data.
Using Columnar Storage
When you use persistent classes to store data in InterSystems IRIS, the data is typically stored in rows. This storage layout is appropriate in cases where you are doing online transaction processing, with frequent inserts, updates, and deletes. However, storing data in columns can be more appropriate for online analytical processing, where you are aggregating data in specific columns across the database, and real-time inserts, updates, and deletes are less common. For example, using when row storage, calculating the average of all values for a given property requires you to load all of the rows in the database. Columnar storage makes that computation faster by letting you load only the column that contains the values for that property.
To use columnar storage for a class:
-
Specify the STORAGEDEFAULT class parameter as "columnar":
Parameter STORAGEDEFAULT = "columnar";
-
For the same class, specify either the Final class keyword or the NoExtent class keyword, with any immediate subclasses defined explicitly as Final.
If we make these changes to the example class, delete the data for that class, delete the storage definition for the class and then recompile, the resulting storage definition is as follows:
When you are using transaction-based processing, but you have a few properties you frequently perform analytical queries on, you can use columnar storage for just those properties. To use columnar storage for a single property in a class, specify the STORAGEDEFAULT property parameter as "columnar":
Property Amount As %Numeric(STORAGEDEFAULT = "columnar");
You can use also use row storage for a class and create a columnar index for a property that is frequently queried.
To create a columnar index on a property in a class, use the keyword type = columnar on that index:
Class Sample.BankTransaction Extends %Persistent [ DdlAllowed ]
{
/// Line below is optional
Parameter STORAGEDEFAULT = "row";
Property Amount As %Numeric(SCALE = 2);
Index AmountIndex On Amount [ type = columnar ];
//other class members...
}
For more information on indexes, see Adding Indexes.
For more information on columnar storage, see Choose an SQL Table Storage Layout.
Using Separate Globals for Properties
As of release 2024.3, you can store a literal property in its own global. This option is the default for %VectorOpens in a new tab and its subclasses. For other literal properties, to store the property in its own global, specify the STORAGEDEFAULT property parameter as globalnode.
You may want to consider using this option for other literal properties that hold large amounts of data. For example:
Property MyLongString as %String (MAXLEN="",STORAGEDEFAULT="globalnode");
For a property of type %VectorOpens in a new tab or a subclass, you can override the default behavior by setting the STORAGEDEFAULT property parameter to a different value.
Property MyVector as %Vector (STORAGEDEFAULT="row");