|
15 | 15 | "cell_type": "markdown", |
16 | 16 | "metadata": {}, |
17 | 17 | "source": [ |
18 | | - "## Backup Validatation of a database using Live Mount\n", |
19 | | - "\n", |
| 18 | + "## Backup Validatation of a database using Live Mount" |
| 19 | + ] |
| 20 | + }, |
| 21 | + { |
| 22 | + "cell_type": "markdown", |
| 23 | + "metadata": {}, |
| 24 | + "source": [ |
20 | 25 | "Live mount allows for near instant recovery of a database. If a database restore/export normally takes hours, then live mounting a database will take a few minutes. Live Mount does a full recovery of a database to either the same SQL Server Instance with a different database name or another SQL Server Instance with the same or different database name. The recovery of the database is much faster, because Rubrik does not need to copy the contents of the backup from the Rubrik Cluster back to the SQL Server. All of the recovery work is done on the Rubrik cluster itself. Then the database files are presented to the SQL Server Instance via a secure SMB3 share that is only accessible by the machine the share is mounted to.\n", |
21 | 26 | "\n", |
22 | 27 | "Live Mounting a database is great for a lot of different use cases:\n", |
|
29 | 34 | "\n", |
30 | 35 | "A key parameter is RecoveryDateTime. All dates in Rubrik are stored in UTC format. This parameter is expecting a fully qualified date and time in UTC format. example value is 2018-08-01T02:00:00.000Z. In the example below, we are pulling the latest recovery point that Rubrik knows about.\n", |
31 | 36 | "\n", |
32 | | - "**This article serves as a way to demonstrate how to use Live Mount for Backup Validation.**" |
| 37 | + "**This article serves as a way to demonstrate how to use Live Mount for Backup Validation.**\n", |
| 38 | + "\n", |
| 39 | + "***The code examples below make use of the Rubrik, SQL Server and dbatools Powershell Modules. This is meant to be an example and not the explicit way to achieve backup validation and database integrity checks. Please review this content and use as a way to write your own validation process.***" |
33 | 40 | ] |
34 | 41 | }, |
35 | 42 | { |
|
39 | 46 | "### Set up environment for all next steps. " |
40 | 47 | ] |
41 | 48 | }, |
42 | | - { |
43 | | - "cell_type": "markdown", |
44 | | - "metadata": {}, |
45 | | - "source": [ |
46 | | - "##### Install the SQLServer package" |
47 | | - ] |
48 | | - }, |
49 | 49 | { |
50 | 50 | "cell_type": "code", |
51 | 51 | "execution_count": null, |
|
69 | 69 | "cell_type": "markdown", |
70 | 70 | "metadata": {}, |
71 | 71 | "source": [ |
72 | | - "#### Connect to the Rubrik Cluster" |
| 72 | + "### Connect to the Rubrik Cluster" |
73 | 73 | ] |
74 | 74 | }, |
75 | 75 | { |
|
101 | 101 | "header {User-Agent, Authorization}\n", |
102 | 102 | "id \n", |
103 | 103 | "authType Token\n", |
104 | | - "time 01/13/2022 18:34:50\n", |
| 104 | + "time 01/14/2022 16:04:36\n", |
105 | 105 | "userId 5e8de809-821c-4569-b53b-8bc3aa5b2f2a\n", |
106 | 106 | "\n" |
107 | 107 | ] |
|
116 | 116 | "cell_type": "markdown", |
117 | 117 | "metadata": {}, |
118 | 118 | "source": [ |
119 | | - "#### Get details about the database from the Rubrik Cluster" |
| 119 | + "### Get details about the database from the Rubrik Cluster" |
120 | 120 | ] |
121 | 121 | }, |
122 | 122 | { |
|
134 | 134 | }, |
135 | 135 | "metadata": {}, |
136 | 136 | "output_type": "display_data" |
137 | | - }, |
138 | | - { |
139 | | - "name": "stdout", |
140 | | - "output_type": "stream", |
141 | | - "text": [ |
142 | | - "\n", |
143 | | - "\u001b[32;1mid : \u001b[0mMssqlDatabase:::42527305-5e85-4777-8689-955237d3d951\n", |
144 | | - "\u001b[32;1mname : \u001b[0mAdventureWorks2019\n", |
145 | | - "\u001b[32;1mconfiguredSlaDomainId : \u001b[0mINHERIT\n", |
146 | | - "\u001b[32;1mconfiguredSlaDomainName : \u001b[0mInherit\n", |
147 | | - "\u001b[32;1mconfiguredSlaDomainType : \u001b[0mProtectionSla\n", |
148 | | - "\u001b[32;1mprimaryClusterId : \u001b[0m39b92c18-d897-4b55-a7f9-17ff178616d0\n", |
149 | | - "\u001b[32;1misConfiguredSlaDomainRetentionLocked : \u001b[0mFalse\n", |
150 | | - "\u001b[32;1mslaLastUpdateTime : \u001b[0m08/06/2021 14:05:30\n", |
151 | | - "\u001b[32;1meffectiveSlaDomainId : \u001b[0m4be3bc1f-6936-432e-86ef-220554df0c35\n", |
152 | | - "\u001b[32;1meffectiveSlaDomainName : \u001b[0mSQL Server\n", |
153 | | - "\u001b[32;1misEffectiveSlaDomainRetentionLocked : \u001b[0mFalse\n", |
154 | | - "\u001b[32;1meffectiveSlaSourceObjectId : \u001b[0mMssqlInstance:::8c66675e-8c84-45b6-a136-73dce79d2372\n", |
155 | | - "\u001b[32;1meffectiveSlaSourceObjectName : \u001b[0mMSSQLSERVER\n", |
156 | | - "\u001b[32;1mslaAssignment : \u001b[0mDerived\n", |
157 | | - "\u001b[32;1mretentionSlaDomainId : \u001b[0m4be3bc1f-6936-432e-86ef-220554df0c35\n", |
158 | | - "\u001b[32;1mrootProperties : \u001b[0m@{rootType=Host; rootId=Host:::93c5aed0-2140-4c3a-a464-d57b8\n", |
159 | | - " \u001b[32;1m\u001b[0ma8dcfbb; rootName=rp-sql19s-001.perf.rubrik.com}\n", |
160 | | - "\u001b[32;1minstanceId : \u001b[0mMssqlInstance:::8c66675e-8c84-45b6-a136-73dce79d2372\n", |
161 | | - "\u001b[32;1minstanceName : \u001b[0mMSSQLSERVER\n", |
162 | | - "\u001b[32;1misRelic : \u001b[0mFalse\n", |
163 | | - "\u001b[32;1mcopyOnly : \u001b[0mFalse\n", |
164 | | - "\u001b[32;1mlogBackupFrequencyInSeconds : \u001b[0m3600\n", |
165 | | - "\u001b[32;1mlogBackupRetentionHours : \u001b[0m168\n", |
166 | | - "\u001b[32;1misLiveMount : \u001b[0mFalse\n", |
167 | | - "\u001b[32;1misLogShippingSecondary : \u001b[0mFalse\n", |
168 | | - "\u001b[32;1mrecoveryModel : \u001b[0mFULL\n", |
169 | | - "\u001b[32;1mstate : \u001b[0mONLINE\n", |
170 | | - "\u001b[32;1mhasPermissions : \u001b[0mTrue\n", |
171 | | - "\u001b[32;1misInAvailabilityGroup : \u001b[0mFalse\n", |
172 | | - "\u001b[32;1mreplicas : \u001b[0m{@{instanceId=MssqlInstance:::8c66675e-8c84-45b6-a136-73dce7\n", |
173 | | - " \u001b[32;1m\u001b[0m9d2372; instanceName=MSSQLSERVER; recoveryModel=FULL; state=\n", |
174 | | - " \u001b[32;1m\u001b[0mONLINE; hasPermissions=True; isStandby=False; recoveryForkGu\n", |
175 | | - " \u001b[32;1m\u001b[0mid=307AC875-147C-4D77-AA65-8DD58C631ED5; isArchived=False; i\n", |
176 | | - " \u001b[32;1m\u001b[0msDeleted=False; rootProperties=}}\n", |
177 | | - "\u001b[32;1munprotectableReasons : \u001b[0m{}\n", |
178 | | - "\u001b[32;1mnumMissedSnapshot : \u001b[0m0\n", |
179 | | - "\u001b[32;1mlastSnapshotTime : \u001b[0m01/13/2022 08:00:08\n", |
180 | | - "\u001b[32;1mincludeBackupTaskInfo : \u001b[0mFalse\n", |
181 | | - "\u001b[32;1misOnline : \u001b[0mTrue\n", |
182 | | - "\n", |
183 | | - "\n" |
184 | | - ] |
185 | 137 | } |
186 | 138 | ], |
187 | 139 | "source": [ |
188 | 140 | "# Get database information from Rubrik\n", |
189 | 141 | "$RubrikDatabase = Get-RubrikDatabase -Name $SourceDatabaseName -ServerInstance $SourceSQLServerInstance\n", |
190 | | - "$RubrikDatabase | Format-List *" |
| 142 | + "# $RubrikDatabase | Format-List *" |
| 143 | + ] |
| 144 | + }, |
| 145 | + { |
| 146 | + "cell_type": "markdown", |
| 147 | + "metadata": {}, |
| 148 | + "source": [ |
| 149 | + "### Mount the database to a SQL Server" |
191 | 150 | ] |
192 | 151 | }, |
193 | 152 | { |
194 | 153 | "cell_type": "markdown", |
195 | 154 | "metadata": {}, |
196 | 155 | "source": [ |
197 | | - "#### Mount the database to a SQL Server" |
| 156 | + "The below example will live mount a database to the latest recovery point that Rubrik knows about. Depending on the recovery model of the database and the backups that have been run against the database, this could include the snapshot and the transaction log backups. " |
198 | 157 | ] |
199 | 158 | }, |
200 | 159 | { |
|
218 | 177 | "output_type": "stream", |
219 | 178 | "text": [ |
220 | 179 | "\n", |
221 | | - "\u001b[32;1mid : \u001b[0mMSSQL_DB_MOUNT_23a5e731-04ef-48ef-9c77-4e8a89db4e9b_4f453e22-eeb9-489a-acaf-d665403fc08\n", |
222 | | - " \u001b[32;1m\u001b[0md:::0\n", |
| 180 | + "\u001b[32;1mid : \u001b[0mMSSQL_DB_MOUNT_4be91b80-e043-416e-bc8a-1400414b0e84_e821f854-90fe-442a-bb8d-d60fb4a7cc8\n", |
| 181 | + " \u001b[32;1m\u001b[0m2:::0\n", |
223 | 182 | "\u001b[32;1mstatus : \u001b[0mSUCCEEDED\n", |
224 | | - "\u001b[32;1mstartTime : \u001b[0m01/13/2022 23:34:59\n", |
225 | | - "\u001b[32;1mendTime : \u001b[0m01/13/2022 23:35:43\n", |
226 | | - "\u001b[32;1mnodeId : \u001b[0mcluster:::RVMHM204S006832\n", |
227 | | - "\u001b[32;1mlinks : \u001b[0m{@{href=https://10.8.49.101/api/v1/mssql/db/mount/5642b730-80d1-4e42-8ae6-8fc326eb2dc5;\n", |
228 | | - " \u001b[32;1m\u001b[0m rel=result}, @{href=https://10.8.49.101/api/v1/mssql/request/MSSQL_DB_MOUNT_23a5e731-0\n", |
229 | | - " \u001b[32;1m\u001b[0m4ef-48ef-9c77-4e8a89db4e9b_4f453e22-eeb9-489a-acaf-d665403fc08d:::0; rel=self}}\n", |
| 183 | + "\u001b[32;1mstartTime : \u001b[0m01/14/2022 21:04:59\n", |
| 184 | + "\u001b[32;1mendTime : \u001b[0m01/14/2022 21:05:57\n", |
| 185 | + "\u001b[32;1mnodeId : \u001b[0mcluster:::RVMHM204S005225\n", |
| 186 | + "\u001b[32;1mlinks : \u001b[0m{@{href=https://10.8.49.101/api/v1/mssql/db/mount/efced71f-81aa-4d89-91ff-6e20f34c87ea;\n", |
| 187 | + " \u001b[32;1m\u001b[0m rel=result}, @{href=https://10.8.49.101/api/v1/mssql/request/MSSQL_DB_MOUNT_4be91b80-e\n", |
| 188 | + " \u001b[32;1m\u001b[0m043-416e-bc8a-1400414b0e84_e821f854-90fe-442a-bb8d-d60fb4a7cc82:::0; rel=self}}\n", |
230 | 189 | "\n", |
231 | 190 | "\n" |
232 | 191 | ] |
|
247 | 206 | "cell_type": "markdown", |
248 | 207 | "metadata": {}, |
249 | 208 | "source": [ |
| 209 | + "### Confirm that database is live mounted\n", |
250 | 210 | "A Live mount of a database is the equivalent to doing a T-SQL Restore with your native backups. SQL Server has recovered the snapshot via the SQL Server VSS Writer, and if applicable, rolled the database forward to a point in time chosen by the user. This means we have applied all transactions from the time the snapshot has happened until the point in time chosen. Once a database has been Live Mounted to a SQL Server, the database is ready for any read/write query you would like to run. " |
251 | 211 | ] |
252 | 212 | }, |
|
283 | 243 | "Invoke-Sqlcmd -ServerInstance $TargetSQLServerInstance -Query $Query | Format-Table" |
284 | 244 | ] |
285 | 245 | }, |
| 246 | + { |
| 247 | + "cell_type": "markdown", |
| 248 | + "metadata": {}, |
| 249 | + "source": [ |
| 250 | + "## DBCC CHECKDB on Live Mounted Database" |
| 251 | + ] |
| 252 | + }, |
| 253 | + { |
| 254 | + "cell_type": "markdown", |
| 255 | + "metadata": {}, |
| 256 | + "source": [ |
| 257 | + "#### Look Where Live Mount Database Files Reside\n", |
| 258 | + "A Live Mounted database is a database that resides on the Rubrik Storage. It is then presented back to the SQL Server via an secure SMB3 share. When you look at the database files, you will see they reside on a UNC path. " |
| 259 | + ] |
| 260 | + }, |
| 261 | + { |
| 262 | + "cell_type": "code", |
| 263 | + "execution_count": null, |
| 264 | + "metadata": { |
| 265 | + "dotnet_interactive": { |
| 266 | + "language": "pwsh" |
| 267 | + } |
| 268 | + }, |
| 269 | + "outputs": [ |
| 270 | + { |
| 271 | + "name": "stdout", |
| 272 | + "output_type": "stream", |
| 273 | + "text": [ |
| 274 | + "\n", |
| 275 | + "\u001b[32;1mDB_Name type_desc logical_name physical_name\u001b[0m\n", |
| 276 | + "\u001b[32;1m------- --------- ------------ -------------\u001b[0m\n", |
| 277 | + "AdventureWorks2019_LiveMount ROWS AdventureWorks2017 \\\\10.8.49.107\\jhcaiqc0e8c3ajbxynia70…\n", |
| 278 | + "AdventureWorks2019_LiveMount LOG AdventureWorks2017_log \\\\10.8.49.107\\jhcaiqc0e8c3ajbxynia70…\n", |
| 279 | + "\n" |
| 280 | + ] |
| 281 | + } |
| 282 | + ], |
| 283 | + "source": [ |
| 284 | + "$Query = \"SELECT DB_NAME() as DB_Name\n", |
| 285 | + ", type_desc\n", |
| 286 | + ", name as logical_name\n", |
| 287 | + ", physical_name\n", |
| 288 | + "FROM sys.database_files\"\n", |
| 289 | + "Invoke-Sqlcmd -ServerInstance $TargetSQLServerInstance -Query $Query -Database $MountedDatabaseName" |
| 290 | + ] |
| 291 | + }, |
| 292 | + { |
| 293 | + "cell_type": "markdown", |
| 294 | + "metadata": {}, |
| 295 | + "source": [ |
| 296 | + "Because this database is sitting on a UNC path, network latency can slow down access to the files. Additionally, the files are not sitting on your production storage array, so performance will not be the same. When you do a DBCC CHECKDB, an hidden database snapshot is created on the same location as the database files. DBCC CHECKDB, then runs its checks against the hidden snapshot. In this case, they will be created on the UNC path where the live mount is residing on. \n", |
| 297 | + "\n", |
| 298 | + "To make things peform a bit better, you should create your database snapshot of the live mounted database on the storage that is attached to the SQL Server. This will consume next to no storage on your SQL Server, but can help increase the performance of the DBCC CHECKDB operation. " |
| 299 | + ] |
| 300 | + }, |
| 301 | + { |
| 302 | + "cell_type": "markdown", |
| 303 | + "metadata": {}, |
| 304 | + "source": [ |
| 305 | + "### Create the database snapshot based off of the live mount" |
| 306 | + ] |
| 307 | + }, |
| 308 | + { |
| 309 | + "cell_type": "code", |
| 310 | + "execution_count": null, |
| 311 | + "metadata": { |
| 312 | + "dotnet_interactive": { |
| 313 | + "language": "pwsh" |
| 314 | + } |
| 315 | + }, |
| 316 | + "outputs": [ |
| 317 | + { |
| 318 | + "name": "stdout", |
| 319 | + "output_type": "stream", |
| 320 | + "text": [ |
| 321 | + "\n", |
| 322 | + "\u001b[32;1mName Status Size Space Avai Recovery Compat. Owner Co\u001b[0m\n", |
| 323 | + "\u001b[32;1m lable Model Level ll\u001b[0m\n", |
| 324 | + "\u001b[32;1m at\u001b[0m\n", |
| 325 | + "\u001b[32;1m io\u001b[0m\n", |
| 326 | + "\u001b[32;1m n\u001b[0m\n", |
| 327 | + "\u001b[32;1m---- ------ ---- ---------- -------- ------- ----- --\u001b[0m\n", |
| 328 | + "AdventureWorks2019_L Normal 336.00 MB 58.22 MB Simple 140 NT AUTHORITY\\SYSTEM SQ\n", |
| 329 | + "iveMount_DBCC L_\n", |
| 330 | + " La\n", |
| 331 | + " ti\n", |
| 332 | + " n1\n", |
| 333 | + " _G\n", |
| 334 | + " en\n", |
| 335 | + " er\n", |
| 336 | + " al\n", |
| 337 | + " _C\n", |
| 338 | + " P1\n", |
| 339 | + " _C\n", |
| 340 | + " I_\n", |
| 341 | + " AS\n", |
| 342 | + "\n" |
| 343 | + ] |
| 344 | + } |
| 345 | + ], |
| 346 | + "source": [ |
| 347 | + "$SnapshotName = \"$($MountedDatabaseName)_DBCC\"\n", |
| 348 | + "$DefaultSQLPaths = Get-DbaDefaultPath -SqlInstance $TargetSQLServerInstance\n", |
| 349 | + "New-DbaDbSnapshot -SQLInstance $TargetSQLServerInstance -Database $MountedDatabaseName -Path $DefaultSQLPaths.Data -Name $SnapshotName" |
| 350 | + ] |
| 351 | + }, |
| 352 | + { |
| 353 | + "cell_type": "markdown", |
| 354 | + "metadata": {}, |
| 355 | + "source": [ |
| 356 | + "### Run DBCC CHECKDB" |
| 357 | + ] |
| 358 | + }, |
| 359 | + { |
| 360 | + "cell_type": "code", |
| 361 | + "execution_count": null, |
| 362 | + "metadata": { |
| 363 | + "dotnet_interactive": { |
| 364 | + "language": "pwsh" |
| 365 | + } |
| 366 | + }, |
| 367 | + "outputs": [ |
| 368 | + { |
| 369 | + "name": "stdout", |
| 370 | + "output_type": "stream", |
| 371 | + "text": [ |
| 372 | + "\n", |
| 373 | + "\u001b[32;1mDate : \u001b[0m01/14/2022 21:09:59\n", |
| 374 | + "\u001b[32;1mSource : \u001b[0mspid65\n", |
| 375 | + "\u001b[32;1mText : \u001b[0mDBCC CHECKDB (AdventureWorks2019_LiveMount_DBCC) executed by perf\\chris.lumnah fou\n", |
| 376 | + " \u001b[32;1m\u001b[0mnd 0 errors and repaired 0 errors. Elapsed time: 0 hours 0 minutes 3 seconds. \n", |
| 377 | + "\u001b[32;1m \u001b[0m\n", |
| 378 | + "\u001b[32;1mArchiveNo : \u001b[0m0\n", |
| 379 | + "\u001b[32;1mServerInstance : \u001b[0mrp-sql19s-001.perf.rubrik.com\n", |
| 380 | + "\n", |
| 381 | + "\n" |
| 382 | + ] |
| 383 | + } |
| 384 | + ], |
| 385 | + "source": [ |
| 386 | + " $results = Invoke-Sqlcmd -Query \"dbcc checkdb(); select @@spid as SessionID;\" -ServerInstance $TargetSQLServerInstance -Database $SnapshotName \n", |
| 387 | + " $spid = \"spid\" + $results.sessionID\n", |
| 388 | + " Get-SqlErrorLog -ServerInstance $($TargetSQLServerInstance) | where-object { $_.Source -eq $spid } | Sort-Object -Property Date -Descending | Select -First 1" |
| 389 | + ] |
| 390 | + }, |
| 391 | + { |
| 392 | + "cell_type": "markdown", |
| 393 | + "metadata": {}, |
| 394 | + "source": [ |
| 395 | + "### Remove database snapshot" |
| 396 | + ] |
| 397 | + }, |
| 398 | + { |
| 399 | + "cell_type": "code", |
| 400 | + "execution_count": null, |
| 401 | + "metadata": { |
| 402 | + "dotnet_interactive": { |
| 403 | + "language": "pwsh" |
| 404 | + } |
| 405 | + }, |
| 406 | + "outputs": [ |
| 407 | + { |
| 408 | + "name": "stdout", |
| 409 | + "output_type": "stream", |
| 410 | + "text": [ |
| 411 | + "\n", |
| 412 | + "\u001b[32;1mComputerName : \u001b[0mrp-sql19s-001\n", |
| 413 | + "\u001b[32;1mInstanceName : \u001b[0mMSSQLSERVER\n", |
| 414 | + "\u001b[32;1mSqlInstance : \u001b[0mrp-sql19s-001\n", |
| 415 | + "\u001b[32;1mName : \u001b[0mAdventureWorks2019_LiveMount_DBCC\n", |
| 416 | + "\u001b[32;1mStatus : \u001b[0mDropped\n", |
| 417 | + "\n", |
| 418 | + "\n" |
| 419 | + ] |
| 420 | + } |
| 421 | + ], |
| 422 | + "source": [ |
| 423 | + "Remove-DbaDbSnapshot -SqlInstance $TargetSQLServerInstance -Snapshot $SnapshotName -Confirm:$false" |
| 424 | + ] |
| 425 | + }, |
| 426 | + { |
| 427 | + "cell_type": "markdown", |
| 428 | + "metadata": {}, |
| 429 | + "source": [ |
| 430 | + "## Unmount the Database Live Mount" |
| 431 | + ] |
| 432 | + }, |
286 | 433 | { |
287 | 434 | "cell_type": "code", |
288 | 435 | "execution_count": null, |
|
0 commit comments