Investigation: mod-orders/mod-orders-storage performance

Investigation: mod-orders/mod-orders-storage performance

1. Performance testing methods (general information)

Performance testing methods for mod-orders-storage should cover common operations: search (by one/several* fields), filtering (by one/several* fields), filtering & sorting (by one/several* fields). Corresponding endpoints for GET requests for covering common operations are presented in the Table 1.1. JMeter script for measurement performance markers can be founded here . Default script configuration designed to perform 1K requests in one thread for each endpoint from Table 1.1. This configuration can be simple improved/changed in future to obtain more precise performance markers.

Table 1.1 - Endpoints for reference GET requests

Search

/orders-storage/purchase-orders?query=poNumber==${poNumber}

Searching purchase order/po-line records by unique value for both direct endpoints and po+po-line view. One need manually set poNumber/poLineNumber value whenever possible in the middle of the range of corresponding values. 

/orders-storage/orders?query=poNumber==${poNumber}

/orders-storage/po-lines?query=poLineNumber==${poLineNumber}

/orders-storage/order-lines?query=poLineNumber==${poLineNumber}

Filtering

/orders-storage/purchase-orders?query=workflowStatus==Open

Filtering purchase order/po-line records by multiple same value for both direct endpoints and po+po-line view. 

/orders-storage/orders?query=workflowStatus==Open

/orders-storage/po-lines?query=orderFormat==Physical Resource

/orders-storage/order-lines?query=orderFormat==Physical Resource

Sorting

/orders-storage/orders?query=workflowStatus==Open AND orderFormat=Physical Resource sortBy metadata.createdDate/sort.ascending

Filtering and sorting purchase order/po-line records by multiple same value for po+po-line view. 

/orders-storage/order-lines?query=workflowStatus==Open AND orderFormat=Physical Resource sortBy metadata.createdDate/sort.ascending

 

At the present moment searching and filtering performance testing implemented only for one field. It is interesting to add scenarios for several fields searching/filtering.

 

2. Indexes

2.1 General information

Current RMB implementation contains some PostgreSQL indexes. Correspondence between RMB indexes and standard PostgreSQL indexes is presented in the Table 2.1.

Table 2.1 - Correspondence between RMB indexes and standard PostgreSQL indexes

PostgreSQL index

Description

RMB index type

RMB-generated snippet

Folio schemas application area

PostgreSQL index

Description

RMB index type

RMB-generated snippet

Folio schemas application area

B-tree

B-trees fit the most common situations. B-trees can handle equality and range queries on data that can be sorted into some ordering.

index

CREATE INDEX IF NOT EXISTS ${table.tableName}_${indexes.fieldName}_idx ON ${myuniversity}_${mymodule}.${table.tableName}

B-trees can handle equality and range queries on data that can be sorted into some ordering. This is most common case.

uniqueIndex

CREATE UNIQUE INDEX IF NOT EXISTS ${table.tableName}_${indexes.fieldName}_idx_unique ON ${myuniversity}_${mymodule}.${table.tableName}
( ${indexes.fieldPath} )

Unique indexes. This should be used for unique fields, for example, poNumber.

*Currently, only B-tree indexes can be declared unique https://www.postgresql.org/docs/11/indexes-unique.html.

likeIndex

CREATE INDEX IF NOT EXISTS ${table.tableName}_${indexes.fieldName}_idx_like ON ${myuniversity}_${mymodule}.${table.tableName}
((${indexes.fieldPath}) text_pattern_ops)

Due to text_pattern_ops the values are compared strictly character by character rather than according to the locale-specific collation rules. This makes these operator classes suitable for use by queries involving pattern matching expressions.

Hash

Hash indexes can only handle simple equality comparisons. The query planner will consider using a hash index whenever an indexed column is involved in a comparison using the = operator.

Not supported

 

 

GiST

GiST indexes are not a single kind of index, but rather an infrastructure within which many different indexing strategies can be implemented. The standard distribution of PostgreSQL includes GiST operator classes for several two-dimensional geometric data types, which support indexed queries using these operators: <<, &<, &>, >>, <<|, &<|, |&>, |>>, @>, <@, ~=, &&. GiST indexes are also capable of optimizing “nearest-neighbor” searches.

Not supported

 

 

SP-GiST

SP-GiST indexes, like GiST indexes, offer an infrastructure that supports various kinds of searches. SP-GiST permits implementation of a wide range of different non-balanced disk-based data structures, such as quadtrees, k-d trees, and radix trees (tries). As an example, the standard distribution of PostgreSQL includes SP-GiST operator classes for two-dimensional points, which support indexed queries using these operators: <<, >>, ~=, <@, <^, >^.

Not supported

 

 

GIN

GIN indexes are “inverted indexes” which are appropriate for data values that contain multiple component values, such as arrays. An inverted index contains a separate entry for each component value, and can efficiently handle queries that test for the presence of specific component values.

ginIndex

CREATE INDEX IF NOT EXISTS ${table}_${field}_idx_gin ON ${tenant}_${module}.${table} USING GIN ((${indexes.fieldPath}) gin_trgm_ops)

The gin_trgm_ops module provides GIN index operator classes that allows to create an index over a text column for the purpose of very fast similarity searches. These indexes do not support equality nor simple comparison operators.

fullTextIndex

CREATE INDEX IF NOT EXISTS ${table}_${indexes.fieldName}_idx_ft ON ${myuniversity}_${mymodule}.${table.tableName} USING GIN
( to_tsvector('${ft_defaultDictionary}', ${indexes.fieldPath}) );

This index can be used for used for full text searching, for example, comments, notes, etc. by using function to_tsvector that converts a document to the tsvector data type.

BRIN

BRIN indexes (a shorthand for Block Range INdexes) store summaries about the values stored in consecutive physical block ranges of a table.

Not supported

 

 

 

2.2 Purchase order, purchase order line, piece indexes

In scope of https://folio-org.atlassian.net/browse/MODORDSTOR-85 the following indexes were introduced for PO, PO Line, Piece (Table 2.2)

Table 2.2 - Correspondence between RMB indexes and PO, PO Line, Piece fields

RMB index

Field

RMB index

Field

index

PO

assignedTo, metadata.createdDate, metadata.createdByUserId, workflowStatus, approved, dateOrdered, orderType, reEncumber, renewal.manualRenewal, renewal.renewalDate, renewal.reviewPeriod

PO Line

eresource.accessProvider, eresource.activated, eresource.expectedActivation, eresource.trial, eresource.materialType, details.subscriptionFrom, details.subscriptionTo, location.locationId, physical.receiptDue, physical.expectedReceiptDate, physical.volumes, physical.materialType, claim.grace, claim.sent, collection, receiptStatus, paymentStatus, orderFormat,
receiptDate, metadata.createdDate, rush, source

Piece

format, itemId, locationId, poLineId, receivingStatus, receivedDate, supplement

uniqueIndex

PO

poNumber

PO Line

-

Piece

-

ginIndex

PO

-

PO Line

fundDistribution.code, details.productIds, title, vendor.vendorAccount, vendor.refNumber, tags, contributors, poLineNumber, requester

Piece

caption

fullTextIndex

PO

closeReason.reason, closeReason.note

PO Line

publisher, donor, selector

Piece

comment, caption

Indexes can be verified by using query:

SELECT tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'diku_mod_orders_storage' ORDER BY tablename, indexname;

tablename

indexname

indexdef

tablename

indexname

indexdef

acquisitions_units

acquisitions_units_pkey

CREATE UNIQUE INDEX acquisitions_units_pkey ON diku_mod_orders_storage.acquisitions_units USING btree (id)

alert

alert_pkey

CREATE UNIQUE INDEX alert_pkey ON diku_mod_orders_storage.alert USING btree (id)

order_invoice_relationship

order_invoice_relationship_pkey

CREATE UNIQUE INDEX order_invoice_relationship_pkey ON diku_mod_orders_storage.order_invoice_relationship USING btree (id)

pieces

pieces_caption_idx_gin

CREATE INDEX pieces_caption_idx_gin ON diku_mod_orders_storage.pieces USING gin (lower(f_unaccent((jsonb ->> 'caption'::text))) gin_trgm_ops)

pieces

pieces_comment_idx_ft

CREATE INDEX pieces_comment_idx_ft ON diku_mod_orders_storage.pieces USING gin (to_tsvector('english'::regconfig, (jsonb ->> 'comment'::text)))

pieces

pieces_format_idx

CREATE INDEX pieces_format_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'format'::text))))

pieces

pieces_itemid_idx

CREATE INDEX pieces_itemid_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'itemId'::text))))

pieces

pieces_locationid_idx

CREATE INDEX pieces_locationid_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'locationId'::text))))

pieces

pieces_pkey

CREATE UNIQUE INDEX pieces_pkey ON diku_mod_orders_storage.pieces USING btree (id)

pieces

pieces_polineid_idx

CREATE INDEX pieces_polineid_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'poLineId'::text))))

pieces

pieces_receiveddate_idx

CREATE INDEX pieces_receiveddate_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'receivedDate'::text))))

pieces

pieces_receivingstatus_idx

CREATE INDEX pieces_receivingstatus_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'receivingStatus'::text))))

pieces

pieces_supplement_idx

CREATE INDEX pieces_supplement_idx ON diku_mod_orders_storage.pieces USING btree (lower(f_unaccent((jsonb ->> 'supplement'::text))))

po_line

po_line_claim_grace_idx

CREATE INDEX po_line_claim_grace_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'claim'::text) ->> 'grace'::text))))

po_line

po_line_claim_sent_idx

CREATE INDEX po_line_claim_sent_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'claim'::text) ->> 'sent'::text))))

po_line

po_line_collection_idx

CREATE INDEX po_line_collection_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'collection'::text))))

po_line

po_line_contributors_idx_gin

CREATE INDEX po_line_contributors_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent((jsonb ->> 'contributors'::text))) gin_trgm_ops)

po_line

po_line_details_productids_idx_gin

CREATE INDEX po_line_details_productids_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent(((jsonb -> 'details'::text) ->> 'productIds'::text))) gin_trgm_ops)

po_line

po_line_details_subscriptionfrom_idx

CREATE INDEX po_line_details_subscriptionfrom_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'details'::text) ->> 'subscriptionFrom'::text))))

po_line

po_line_details_subscriptionto_idx

CREATE INDEX po_line_details_subscriptionto_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'details'::text) ->> 'subscriptionTo'::text))))

po_line

po_line_donor_idx_ft

CREATE INDEX po_line_donor_idx_ft ON diku_mod_orders_storage.po_line USING gin (to_tsvector('english'::regconfig, (jsonb ->> 'donor'::text)))

po_line

po_line_eresource_accessprovider_idx

CREATE INDEX po_line_eresource_accessprovider_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'eresource'::text) ->> 'accessProvider'::text))))

po_line

po_line_eresource_activated_idx

CREATE INDEX po_line_eresource_activated_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'eresource'::text) ->> 'activated'::text))))

po_line

po_line_eresource_expectedactivation_idx

CREATE INDEX po_line_eresource_expectedactivation_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'eresource'::text) ->> 'expectedActivation'::text))))

po_line

po_line_eresource_materialtype_idx

CREATE INDEX po_line_eresource_materialtype_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'eresource'::text) ->> 'materialType'::text))))

po_line

po_line_eresource_trial_idx

CREATE INDEX po_line_eresource_trial_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'eresource'::text) ->> 'trial'::text))))

po_line

po_line_funddistribution_code_idx_gin

CREATE INDEX po_line_funddistribution_code_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent(((jsonb -> 'fundDistribution'::text) ->> 'code'::text))) gin_trgm_ops)

po_line

po_line_location_locationid_idx

CREATE INDEX po_line_location_locationid_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'location'::text) ->> 'locationId'::text))))

po_line

po_line_metadata_createddate_idx

CREATE INDEX po_line_metadata_createddate_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'metadata'::text) ->> 'createdDate'::text))))

po_line

po_line_orderformat_idx

CREATE INDEX po_line_orderformat_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'orderFormat'::text))))

po_line

po_line_paymentstatus_idx

CREATE INDEX po_line_paymentstatus_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'paymentStatus'::text))))

po_line

po_line_physical_expectedreceiptdate_idx

CREATE INDEX po_line_physical_expectedreceiptdate_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'physical'::text) ->> 'expectedReceiptDate'::text))))

po_line

po_line_physical_materialtype_idx

CREATE INDEX po_line_physical_materialtype_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'physical'::text) ->> 'materialType'::text))))

po_line

po_line_physical_receiptdue_idx

CREATE INDEX po_line_physical_receiptdue_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'physical'::text) ->> 'receiptDue'::text))))

po_line

po_line_physical_volumes_idx

CREATE INDEX po_line_physical_volumes_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent(((jsonb -> 'physical'::text) ->> 'volumes'::text))))

po_line

po_line_pkey

CREATE UNIQUE INDEX po_line_pkey ON diku_mod_orders_storage.po_line USING btree (id)

po_line

po_line_polinenumber_idx_gin

CREATE INDEX po_line_polinenumber_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent((jsonb ->> 'poLineNumber'::text))) gin_trgm_ops)

po_line

po_line_publisher_idx_ft

CREATE INDEX po_line_publisher_idx_ft ON diku_mod_orders_storage.po_line USING gin (to_tsvector('english'::regconfig, (jsonb ->> 'publisher'::text)))

po_line

po_line_receiptdate_idx

CREATE INDEX po_line_receiptdate_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'receiptDate'::text))))

po_line

po_line_receiptstatus_idx

CREATE INDEX po_line_receiptstatus_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'receiptStatus'::text))))

po_line

po_line_requester_idx_gin

CREATE INDEX po_line_requester_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent((jsonb ->> 'requester'::text))) gin_trgm_ops)

po_line

po_line_rush_idx

CREATE INDEX po_line_rush_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'rush'::text))))

po_line

po_line_selector_idx_ft

CREATE INDEX po_line_selector_idx_ft ON diku_mod_orders_storage.po_line USING gin (to_tsvector('english'::regconfig, (jsonb ->> 'selector'::text)))

po_line

po_line_source_idx

CREATE INDEX po_line_source_idx ON diku_mod_orders_storage.po_line USING btree (lower(f_unaccent((jsonb ->> 'source'::text))))

po_line

po_line_tags_idx_gin

CREATE INDEX po_line_tags_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent((jsonb ->> 'tags'::text))) gin_trgm_ops)

po_line

po_line_title_idx_gin

CREATE INDEX po_line_title_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent((jsonb ->> 'title'::text))) gin_trgm_ops)

po_line

po_line_vendor_refnumber_idx_gin

CREATE INDEX po_line_vendor_refnumber_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent(((jsonb -> 'vendor'::text) ->> 'refNumber'::text))) gin_trgm_ops)

po_line

po_line_vendor_vendoraccount_idx_gin

CREATE INDEX po_line_vendor_vendoraccount_idx_gin ON diku_mod_orders_storage.po_line USING gin (lower(f_unaccent(((jsonb -> 'vendor'::text) ->> 'vendorAccount'::text))) gin_trgm_ops)

purchase_order

purchase_order_approved_idx

CREATE INDEX purchase_order_approved_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent((jsonb ->> 'approved'::text))))

purchase_order

purchase_order_assignedto_idx

CREATE INDEX purchase_order_assignedto_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent((jsonb ->> 'assignedTo'::text))))

purchase_order

purchase_order_closereason_note_idx_ft

CREATE INDEX purchase_order_closereason_note_idx_ft ON diku_mod_orders_storage.purchase_order USING gin (to_tsvector('english'::regconfig, ((jsonb -> 'closeReason'::text) ->> 'note'::text)))

purchase_order

purchase_order_closereason_reason_idx_ft

CREATE INDEX purchase_order_closereason_reason_idx_ft ON diku_mod_orders_storage.purchase_order USING gin (to_tsvector('english'::regconfig, ((jsonb -> 'closeReason'::text) ->> 'reason'::text)))

purchase_order

purchase_order_dateordered_idx

CREATE INDEX purchase_order_dateordered_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent((jsonb ->> 'dateOrdered'::text))))

purchase_order

purchase_order_metadata_createdbyuserid_idx

CREATE INDEX purchase_order_metadata_createdbyuserid_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent(((jsonb -> 'metadata'::text) ->> 'createdByUserId'::text))))

purchase_order

purchase_order_metadata_createddate_idx

CREATE INDEX purchase_order_metadata_createddate_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent(((jsonb -> 'metadata'::text) ->> 'createdDate'::text))))

purchase_order

purchase_order_ordertype_idx

CREATE INDEX purchase_order_ordertype_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent((jsonb ->> 'orderType'::text))))

purchase_order

purchase_order_pkey

CREATE UNIQUE INDEX purchase_order_pkey ON diku_mod_orders_storage.purchase_order USING btree (id)

purchase_order

purchase_order_po_number_unique_idx

CREATE UNIQUE INDEX purchase_order_po_number_unique_idx ON diku_mod_orders_storage.purchase_order USING btree (((jsonb ->> 'poNumber'::text)))

purchase_order

purchase_order_reencumber_idx

CREATE INDEX purchase_order_reencumber_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent((jsonb ->> 'reEncumber'::text))))

purchase_order

purchase_order_renewal_manualrenewal_idx

CREATE INDEX purchase_order_renewal_manualrenewal_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent(((jsonb -> 'renewal'::text) ->> 'manualRenewal'::text))))

purchase_order

purchase_order_renewal_renewaldate_idx

CREATE INDEX purchase_order_renewal_renewaldate_idx ON diku_mod_orders_storage.purchase_order USING btree (lower(f_unaccent(((jsonb -> 'renewal'::text) ->> 'renewalDate'::text))))