Nānā i ka mea hana manuahi SQLIndexManager

E like me kāu e ʻike ai, he kuleana koʻikoʻi nā indexes i kahi DBMS, e hāʻawi ana i kahi huli wikiwiki i nā moʻolelo i koi ʻia. ʻO ia ke kumu he mea nui e lawelawe iā lākou i ka manawa kūpono. Ua kākau ʻia ka nui o nā mea e pili ana i ka nānā ʻana a me ka optimization, me ka pūnaewele. No ka laʻana, ua nānā hou ʻia kēia kumuhana ma kēia paʻi.

Nui nā uku uku a manuahi no kēia. Eia kekahi laʻana, aia kahi mākaukau ka hoʻoholo, e pili ana i kahi ʻano hoʻoponopono kuhikuhi kuhikuhi adaptive.

A laila, e nānā kākou i ka pono manuahi SQLIndexManager, haku ʻia e AlanDenton.

ʻO ka ʻokoʻa ʻenehana nui ma waena o SQLIndexManager a me kekahi mau analogues ʻē aʻe i hāʻawi ʻia e ka mea kākau ponoʻī maanei и maanei.

Ma kēiaʻatikala, e nānā mākou i waho i ka papahana a me nā mana hana o kēia hoʻonā polokalamu.

E kūkākūkā ana i kēia pono maanei.
I ka wā lōʻihi, ua hoʻoponopono ʻia ka hapa nui o nā manaʻo a me nā hewa.

No laila, e neʻe kākou i ka pono SQLIndexManager pono'ī.

Ua kākau ʻia ka palapala noi ma C# .NET Framework 4.5 ma Visual Studio 2017 a hoʻohana iā DevExpress no nā ʻano:

Nānā i ka mea hana manuahi SQLIndexManager

a penei ke ano:

Nānā i ka mea hana manuahi SQLIndexManager

Hana ʻia nā noi a pau ma nā faila:

  1. Index
  2. Uiui
  3. QueryEngine
  4. ServerInfo

Nānā i ka mea hana manuahi SQLIndexManager

I ka hoʻopili ʻana i kahi waihona a me ka hoʻouna ʻana i nā nīnau i ka DBMS, ua kau inoa ʻia ka palapala noi penei:

ApplicationName=”SQLIndexManager”

Ke hoʻomaka ʻoe i ka noi, e wehe ʻia kahi puka aniani e hoʻohui i kahi pilina:
Nānā i ka mea hana manuahi SQLIndexManager

Maʻaneʻi, ʻaʻole i holo ka hoʻouka ʻana i kahi papa inoa piha o nā manawa MS SQL Server āpau i loaʻa ma nā pūnaewele kūloko.

Hiki iā ʻoe ke hoʻohui i kahi pilina me ka pihi hema loa ma ka papa kuhikuhi nui:

Nānā i ka mea hana manuahi SQLIndexManager

A laila, e hoʻomaka ʻia nā nīnau e pili ana i ka DBMS:

  1. Loaʻa ka ʻike e pili ana i ka DBMS
    SELECT ProductLevel  = SERVERPROPERTY('ProductLevel')
         , Edition       = SERVERPROPERTY('Edition')
         , ServerVersion = SERVERPROPERTY('ProductVersion')
         , IsSysAdmin    = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT)
    

  2. Loaʻa i kahi papa inoa o nā ʻikepili i loaʻa me kā lākou mau waiwai pōkole
    SELECT DatabaseName = t.[name]
         , d.DataSize
         , DataUsedSize  = CAST(NULL AS BIGINT)
         , d.LogSize
         , LogUsedSize   = CAST(NULL AS BIGINT)
         , RecoveryModel = t.recovery_model_desc
         , LogReuseWait  = t.log_reuse_wait_desc
    FROM sys.databases t WITH(NOLOCK)
    LEFT JOIN (
        SELECT [database_id]
             , DataSize = SUM(CASE WHEN [type] = 0 THEN CAST(size AS BIGINT) END)
             , LogSize  = SUM(CASE WHEN [type] = 1 THEN CAST(size AS BIGINT) END)
        FROM sys.master_files WITH(NOLOCK)
        GROUP BY [database_id]
    ) d ON d.[database_id] = t.[database_id]
    WHERE t.[state] = 0
        AND t.[database_id] != 2
        AND ISNULL(HAS_DBACCESS(t.[name]), 1) = 1
    

Ma hope o ka hoʻokō ʻana i nā palapala i luna aʻe, e ʻike ʻia kahi puka makani e loaʻa ana ka ʻike pōkole e pili ana i nā ʻikepili o ka mea i koho ʻia o MS SQL Server:

Nānā i ka mea hana manuahi SQLIndexManager

Pono e hoʻomaopopo ʻia e hōʻike ʻia ka ʻike ākea ma muli o nā kuleana. Ina ma laila sysadmin, a laila hiki iā ʻoe ke koho i ka ʻikepili mai ka ʻike sys.master_files. Inā ʻaʻohe kuleana e like me ia, a laila hoʻihoʻi wale ʻia ka ʻikepili i ʻole e hoʻolōʻihi i ka noi.

Maanei pono ʻoe e koho i nā ʻikepili o ka hoihoi a kaomi i ke pihi "OK".

Ma hope aʻe, e hoʻokō ʻia kēia ʻatikala no kēlā me kēia waihona i koho ʻia e nānā i ke kūlana o nā kuhikuhi:

Ka helu helu kūlana

declare @Fragmentation float=15;
declare @MinIndexSize bigint=768;
declare @MaxIndexSize bigint=1048576;
declare @PreDescribeSize bigint=32768;
SET NOCOUNT ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
IF OBJECT_ID('tempdb.dbo.#AllocationUnits') IS NOT NULL
DROP TABLE #AllocationUnits
CREATE TABLE #AllocationUnits (
ContainerID   BIGINT PRIMARY KEY
, ReservedPages BIGINT NOT NULL
, UsedPages     BIGINT NOT NULL
)
INSERT INTO #AllocationUnits (ContainerID, ReservedPages, UsedPages)
SELECT [container_id]
, SUM([total_pages])
, SUM([used_pages])
FROM sys.allocation_units WITH(NOLOCK)
GROUP BY [container_id]
HAVING SUM([total_pages]) BETWEEN @MinIndexSize AND @MaxIndexSize
IF OBJECT_ID('tempdb.dbo.#ExcludeList') IS NOT NULL
DROP TABLE #ExcludeList
CREATE TABLE #ExcludeList (ID INT PRIMARY KEY)
INSERT INTO #ExcludeList
SELECT [object_id]
FROM sys.objects WITH(NOLOCK)
WHERE [type] IN ('V', 'U')
AND ( [is_ms_shipped] = 1 )
IF OBJECT_ID('tempdb.dbo.#Partitions') IS NOT NULL
DROP TABLE #Partitions
SELECT [object_id]
, [index_id]
, [partition_id]
, [partition_number]
, [rows]
, [data_compression]
INTO #Partitions
FROM sys.partitions WITH(NOLOCK)
WHERE [object_id] > 255
AND [rows] > 0
AND [object_id] NOT IN (SELECT * FROM #ExcludeList)
IF OBJECT_ID('tempdb.dbo.#Indexes') IS NOT NULL
DROP TABLE #Indexes
CREATE TABLE #Indexes (
ObjectID         INT NOT NULL
, IndexID          INT NOT NULL
, IndexName        SYSNAME NULL
, PagesCount       BIGINT NOT NULL
, UnusedPagesCount BIGINT NOT NULL
, PartitionNumber  INT NOT NULL
, RowsCount        BIGINT NOT NULL
, IndexType        TINYINT NOT NULL
, IsAllowPageLocks BIT NOT NULL
, DataSpaceID      INT NOT NULL
, DataCompression  TINYINT NOT NULL
, IsUnique         BIT NOT NULL
, IsPK             BIT NOT NULL
, FillFactorValue  INT NOT NULL
, IsFiltered       BIT NOT NULL
, PRIMARY KEY (ObjectID, IndexID, PartitionNumber)
)
INSERT INTO #Indexes
SELECT ObjectID         = i.[object_id]
, IndexID          = i.index_id
, IndexName        = i.[name]
, PagesCount       = a.ReservedPages
, UnusedPagesCount = CASE WHEN ABS(a.ReservedPages - a.UsedPages) > 32 THEN a.ReservedPages - a.UsedPages ELSE 0 END
, PartitionNumber  = p.[partition_number]
, RowsCount        = ISNULL(p.[rows], 0)
, IndexType        = i.[type]
, IsAllowPageLocks = i.[allow_page_locks]
, DataSpaceID      = i.[data_space_id]
, DataCompression  = p.[data_compression]
, IsUnique         = i.[is_unique]
, IsPK             = i.[is_primary_key]
, FillFactorValue  = i.[fill_factor]
, IsFiltered       = i.[has_filter]
FROM #AllocationUnits a
JOIN #Partitions p ON a.ContainerID = p.[partition_id]
JOIN sys.indexes i WITH(NOLOCK) ON i.[object_id] = p.[object_id] AND p.[index_id] = i.[index_id] 
WHERE i.[type] IN (0, 1, 2, 5, 6)
AND i.[object_id] > 255
DECLARE @files TABLE (ID INT PRIMARY KEY)
INSERT INTO @files
SELECT DISTINCT [data_space_id]
FROM sys.database_files WITH(NOLOCK)
WHERE [state] != 0
AND [type] = 0
IF @@ROWCOUNT > 0 BEGIN
DELETE FROM i
FROM #Indexes i
LEFT JOIN sys.destination_data_spaces dds WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] AND i.PartitionNumber = dds.[destination_id]
WHERE ISNULL(dds.[data_space_id], i.DataSpaceID) IN (SELECT * FROM @files)
END
DECLARE @DBID   INT
, @DBNAME SYSNAME
SET @DBNAME = DB_NAME()
SELECT @DBID = [database_id]
FROM sys.databases WITH(NOLOCK)
WHERE [name] = @DBNAME
IF OBJECT_ID('tempdb.dbo.#Fragmentation') IS NOT NULL
DROP TABLE #Fragmentation
CREATE TABLE #Fragmentation (
ObjectID         INT NOT NULL
, IndexID          INT NOT NULL
, PartitionNumber  INT NOT NULL
, Fragmentation    FLOAT NOT NULL
, PRIMARY KEY (ObjectID, IndexID, PartitionNumber)
)
INSERT INTO #Fragmentation (ObjectID, IndexID, PartitionNumber, Fragmentation)
SELECT i.ObjectID
, i.IndexID
, i.PartitionNumber
, r.[avg_fragmentation_in_percent]
FROM #Indexes i
CROSS APPLY sys.dm_db_index_physical_stats(@DBID, i.ObjectID, i.IndexID, i.PartitionNumber, 'LIMITED') r
WHERE i.PagesCount <= @PreDescribeSize
AND r.[index_level] = 0
AND r.[alloc_unit_type_desc] = 'IN_ROW_DATA'
AND i.IndexType IN (0, 1, 2)
IF OBJECT_ID('tempdb.dbo.#Columns') IS NOT NULL
DROP TABLE #Columns
CREATE TABLE #Columns (
ObjectID     INT NOT NULL
, ColumnID     INT NOT NULL
, ColumnName   SYSNAME NULL
, SystemTypeID TINYINT NULL
, IsSparse     BIT
, IsColumnSet  BIT
, MaxLen       INT
, PRIMARY KEY (ObjectID, ColumnID)
)
INSERT INTO #Columns
SELECT ObjectID     = [object_id]
, ColumnID     = [column_id]
, ColumnName   = [name]
, SystemTypeID = [system_type_id]
, IsSparse     = [is_sparse]
, IsColumnSet  = [is_column_set]
, MaxLen       = [max_length]
FROM sys.columns WITH(NOLOCK)
WHERE [object_id] IN (SELECT DISTINCT i.ObjectID FROM #Indexes i)
IF OBJECT_ID('tempdb.dbo.#IndexColumns') IS NOT NULL
DROP TABLE #IndexColumns
CREATE TABLE #IndexColumns (
ObjectID   INT NOT NULL
, IndexID    INT NOT NULL
, OrderID    INT NOT NULL
, ColumnID   INT NOT NULL
, IsIncluded BIT NOT NULL
, PRIMARY KEY (ObjectID, IndexID, ColumnID)
)
INSERT INTO #IndexColumns
SELECT ObjectID   = [object_id]
, IndexID    = [index_id]
, OrderID    = CASE WHEN [is_included_column] = 0 THEN [key_ordinal] ELSE [index_column_id] END
, ColumnID   = [column_id]
, IsIncluded = ISNULL([is_included_column], 0)
FROM sys.index_columns ic WITH(NOLOCK)
WHERE EXISTS(
SELECT *
FROM #Indexes i
WHERE i.ObjectID = ic.[object_id]
AND i.IndexID = ic.[index_id]
AND i.IndexType IN (1, 2)
)
IF OBJECT_ID('tempdb.dbo.#Lob') IS NOT NULL
DROP TABLE #Lob
CREATE TABLE #Lob (
ObjectID    INT NOT NULL
, IndexID     INT NOT NULL
, IsLobLegacy BIT
, IsLob       BIT
, PRIMARY KEY (ObjectID, IndexID)
)
INSERT INTO #Lob (ObjectID, IndexID, IsLobLegacy, IsLob)
SELECT c.ObjectID
, IndexID     = ISNULL(i.IndexID, 1)
, IsLobLegacy = MAX(CASE WHEN c.SystemTypeID IN (34, 35, 99) THEN 1 END)
, IsLob       = 0
FROM #Columns c
LEFT JOIN #IndexColumns i ON c.ObjectID = i.ObjectID AND c.ColumnID = i.ColumnID
WHERE c.SystemTypeID IN (34, 35, 99)
GROUP BY c.ObjectID
, i.IndexID
IF OBJECT_ID('tempdb.dbo.#Sparse') IS NOT NULL
DROP TABLE #Sparse
CREATE TABLE #Sparse (ObjectID INT PRIMARY KEY)
INSERT INTO #Sparse
SELECT DISTINCT ObjectID
FROM #Columns
WHERE IsSparse = 1
OR IsColumnSet = 1
IF OBJECT_ID('tempdb.dbo.#AggColumns') IS NOT NULL
DROP TABLE #AggColumns
CREATE TABLE #AggColumns (
ObjectID        INT NOT NULL
, IndexID         INT NOT NULL
, IndexColumns    NVARCHAR(MAX)
, IncludedColumns NVARCHAR(MAX)
, PRIMARY KEY (ObjectID, IndexID)
)
INSERT INTO #AggColumns
SELECT t.ObjectID
, t.IndexID
, IndexColumns = STUFF((
SELECT ', [' + c.ColumnName + ']'
FROM #IndexColumns i
JOIN #Columns c ON i.ObjectID = c.ObjectID AND i.ColumnID = c.ColumnID
WHERE i.ObjectID = t.ObjectID
AND i.IndexID = t.IndexID
AND i.IsIncluded = 0
ORDER BY i.OrderID
FOR XML PATH(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 2, '')
, IncludedColumns = STUFF((
SELECT ', [' + c.ColumnName + ']'
FROM #IndexColumns i
JOIN #Columns c ON i.ObjectID = c.ObjectID AND i.ColumnID = c.ColumnID
WHERE i.ObjectID = t.ObjectID
AND i.IndexID = t.IndexID
AND i.IsIncluded = 1
ORDER BY i.OrderID
FOR XML PATH(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 2, '')
FROM (
SELECT DISTINCT ObjectID, IndexID
FROM #Indexes
WHERE IndexType IN (1, 2)
) t
SELECT i.ObjectID
, i.IndexID
, i.IndexName
, ObjectName       = o.[name]
, SchemaName       = s.[name]
, i.PagesCount
, i.UnusedPagesCount
, i.PartitionNumber
, i.RowsCount
, i.IndexType
, i.IsAllowPageLocks
, u.TotalWrites
, u.TotalReads
, u.TotalSeeks
, u.TotalScans
, u.TotalLookups
, u.LastUsage
, i.DataCompression
, f.Fragmentation
, IndexStats       = STATS_DATE(i.ObjectID, i.IndexID)
, IsLobLegacy      = ISNULL(lob.IsLobLegacy, 0)
, IsLob            = ISNULL(lob.IsLob, 0)
, IsSparse         = CAST(CASE WHEN p.ObjectID IS NULL THEN 0 ELSE 1 END AS BIT)
, IsPartitioned    = CAST(CASE WHEN dds.[data_space_id] IS NOT NULL THEN 1 ELSE 0 END AS BIT)
, FileGroupName    = fg.[name]
, i.IsUnique
, i.IsPK
, i.FillFactorValue
, i.IsFiltered
, a.IndexColumns
, a.IncludedColumns
FROM #Indexes i
JOIN sys.objects o WITH(NOLOCK) ON o.[object_id] = i.ObjectID
JOIN sys.schemas s WITH(NOLOCK) ON s.[schema_id] = o.[schema_id]
LEFT JOIN #AggColumns a ON a.ObjectID = i.ObjectID AND a.IndexID = i.IndexID
LEFT JOIN #Sparse p ON p.ObjectID = i.ObjectID
LEFT JOIN #Fragmentation f ON f.ObjectID = i.ObjectID AND f.IndexID = i.IndexID AND f.PartitionNumber = i.PartitionNumber
LEFT JOIN (
SELECT ObjectID      = [object_id]
, IndexID       = [index_id]
, TotalWrites   = NULLIF([user_updates], 0)
, TotalReads    = NULLIF([user_seeks] + [user_scans] + [user_lookups], 0)
, TotalSeeks    = NULLIF([user_seeks], 0)
, TotalScans    = NULLIF([user_scans], 0)
, TotalLookups  = NULLIF([user_lookups], 0)
, LastUsage     = (
SELECT MAX(dt)
FROM (
VALUES ([last_user_seek])
, ([last_user_scan])
, ([last_user_lookup])
, ([last_user_update])
) t(dt)
)
FROM sys.dm_db_index_usage_stats WITH(NOLOCK)
WHERE [database_id] = @DBID
) u ON i.ObjectID = u.ObjectID AND i.IndexID = u.IndexID
LEFT JOIN #Lob lob ON lob.ObjectID = i.ObjectID AND lob.IndexID = i.IndexID
LEFT JOIN sys.destination_data_spaces dds WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] AND i.PartitionNumber = dds.[destination_id]
JOIN sys.filegroups fg WITH(NOLOCK) ON ISNULL(dds.[data_space_id], i.DataSpaceID) = fg.[data_space_id] 
WHERE o.[type] IN ('V', 'U')
AND (
f.Fragmentation >= @Fragmentation
OR
i.PagesCount > @PreDescribeSize
OR
i.IndexType IN (5, 6)
)

E like me ka mea i ʻike ʻia mai nā nīnau iā lākou iho, hoʻohana pinepine ʻia nā papa manawa pōkole. Hana ʻia kēia i ʻole e hoʻohui hou ʻia, a i ke ʻano o kahi hoʻolālā nui, hiki ke hana like ʻia ka hoʻolālā i ka wā e hoʻokomo ai i ka ʻikepili, no ka mea, hiki ke hoʻokomo i nā mea hoʻololi papaʻaina i hoʻokahi wale nō.

Ma hope o ka hoʻokō ʻana i ka palapala i luna, e ʻike ʻia kahi puka aniani me kahi papa kuhikuhi:

Nānā i ka mea hana manuahi SQLIndexManager

Hiki iā ʻoe ke hōʻike i nā ʻike kikoʻī ʻē aʻe ma aneʻi, e like me:

  1. waihona waihona
  2. helu o na pauku
  3. lā a me ka manawa o ke kelepona hope
  4. hoʻokuʻu
  5. waihona waihona

a pēlā aku nō.
Hiki ke hoʻopilikino ʻia nā mea ʻōlelo iā lākou iho:

Nānā i ka mea hana manuahi SQLIndexManager

Ma nā pūnaewele o ke kolamu Fix, hiki iā ʻoe ke koho i ka hana e hana ʻia i ka wā o ka loiloi. Eia kekahi, i ka pau ʻana o ka scan, koho ʻia kahi hana paʻamau e pili ana i nā hoʻonohonoho i koho ʻia:

Nānā i ka mea hana manuahi SQLIndexManager

Pono ʻoe e koho i nā index i makemake ʻia no ka hana ʻana.

Ke hoʻohana nei i ka papa kuhikuhi nui, hiki iā ʻoe ke mālama i ka palapala (ʻo ka pihi hoʻokahi e hoʻomaka i ke kaʻina hana optimization pono'ī):

Nānā i ka mea hana manuahi SQLIndexManager

a mālama i ka papaʻaina i nā ʻano like ʻole (ʻo ka pihi like e hiki ai iā ʻoe ke wehe i nā kikoʻī kikoʻī no ka nānā ʻana a me ka hoʻonui ʻana i nā kuhikuhi):

Nānā i ka mea hana manuahi SQLIndexManager

Hiki iā ʻoe ke hōʻano hou i ka ʻike ma ke kaomi ʻana i ke pihi ʻekolu ma ka hema ma ka papa kuhikuhi nui ma ka ʻaoʻao o ke aniani hoʻonui.

ʻO ke pihi me kahi aniani hoʻonui e hiki iā ʻoe ke koho i nā ʻikepili i makemake ʻia no ka noʻonoʻo ʻana.

ʻAʻohe ʻōnaehana kōkua piha i kēia manawa. No laila, e kaomi i ke pihi "?" e hōʻike wale ʻia kahi puka aniani me ka ʻike kumu e pili ana i ka huahana polokalamu:

Nānā i ka mea hana manuahi SQLIndexManager

Ma waho aʻe o nā mea a pau i hōʻike ʻia ma luna nei, loaʻa i ka papa kuhikuhi kahi pahu hulina:

Nānā i ka mea hana manuahi SQLIndexManager

I ka hoʻomaka ʻana i ke kaʻina hana index optimization:

Nānā i ka mea hana manuahi SQLIndexManager

Hiki iā ʻoe ke nānā i kahi log o nā hana i hana ʻia ma lalo o ka pukaaniani:

Nānā i ka mea hana manuahi SQLIndexManager

Ma ka puka aniani hoʻonohonoho kikoʻī no ka nānā ʻana a me ka loiloi ʻana, hiki iā ʻoe ke hoʻonohonoho i nā koho maʻalahi:

Nānā i ka mea hana manuahi SQLIndexManager

Nā noi no ka noi:

  1. hiki iā ʻoe ke hoʻololi i nā helu helu ʻaʻole wale no nā kuhikuhi a ma nā ʻano like ʻole (hoʻopau piha a ʻāpana paha)
  2. ʻAʻole hiki ke koho wale i kahi waihona, akā i nā kikowaena like ʻole (maʻalahi kēia inā nui nā manawa o MS SQL Server)
  3. No ka maʻalahi o ka hoʻohana ʻana, manaʻo ʻia e kāʻei i nā kauoha i loko o nā hale waihona puke a hoʻopuka iā lākou i nā kauoha PowerShell, e like me ka hana ʻana, no ka laʻana, ma aneʻi:
  4. dbatools.io/commands
  5. hiki ke mālama a hoʻololi i nā hoʻonohonoho pilikino no ka noi holoʻokoʻa a, inā pono, no kēlā me kēia manawa o MS SQL Server a me kēlā me kēia waihona.
  6. Mai nā helu 2 a me 4, makemake ʻoe e hana i nā pūʻulu e nā ʻikepili a me nā pūʻulu e nā manawa MS SQL Server, no laila ua like nā hoʻonohonoho.
  7. e ʻimi i nā papa kuhikuhi papalua (paʻa a piha ʻole, ʻokoʻa iki a ʻokoʻa paha i nā kolamu i hoʻokomo ʻia)
  8. No ka mea, hoʻohana wale ʻia ʻo SQLIndexManager no MS SQL Server DBMS, pono e noʻonoʻo i kēia ma ka inoa, no ka laʻana, penei: SQLIndexManager no MS SQL Server
  9. E hoʻoneʻe i nā ʻāpana GUI ʻole o ka palapala noi i loko o nā modula kaʻawale a kākau hou iā lākou ma .NET Core 2.1

I ka manawa e kākau ai, ua hoʻomohala ikaika ʻia ka mea 6 o nā makemake a aia ke kākoʻo ma ke ʻano o ka ʻimi ʻana i nā kope piha a like.

Nānā i ka mea hana manuahi SQLIndexManager

Pūnaewele

Source: www.habr.com

Pākuʻi i ka manaʻo hoʻopuka