Showing posts with label storage. Show all posts
Showing posts with label storage. Show all posts

2024-03-31

Disassembling a hard disk

Hard disks with spinning platters are going away very fast. During easter cleanup in my workshop I found a old 750 GB hard disk, and decided not only to take it to the local recycling station but to disassemple it to see how it it constructed. During the disassembly I took some photos with my phone - they are not near professional quality but they are still usefull with the comments.

The drive is a Seagate Barracuda ES.2 (data sheet) with a SATA 3Gb/s interface. It runs with 7200 RPM which made it rater usefull when it was released. I stopped using the drive when I really swithched to SSD in my workstation. There is nothing wrong with the drive - it just went obsolete to me.

To remove the cover there are at first six T9 screws. But hidden behind the sticker there are three more screws, also T9.
With the cover removed there is direct access to the inner components. Also this is the point of no return to use the drive again as the disk is now filled with dirt from the normal atmosphere.
On the backside of the hard disk the circut board is placed. It is fastned with T6 screws but none hidden. The connection from the board to the inner drive is through some contacts. The picture below shows two sets of contacts with one set of three contacts for the electrical engine spinning the platters and another set of four by five contacts for the head.
A closer look at the board shows chips from Seagate and Samsung. The external connections are direct from the board to the physical interfaces.
Under the board and a metallic sticker there is another hidden T9 screw. This holds the head.
The connection for the head is on a small board. But is quite easy pulled out and disconnected from the head.
The head unit movements are controlled with a coil on the back of the arms and two powerfull static magnets. The top magnet is only held in place by the magnetic force and can be removed with a small screwdriver.
The arm with heads and coil is one unit together with a small board. This head unit is removed without any tools.
A closer look at the head unit from another angle show a small board and the arms with the physical heads to transport electro-magnetic signals to and from the platters.
Going from buttom to top on the picture below it looks like the buttom arm only hold one head to communicate with the lower side of the lowest platter.
The next two arms holds two heads each to communicate with the upper side of the platter below the arm and the lower side of another platter placed above the arm.
The fourth arm hold only one head to communicate with the upper side of the top platter.
There is a fifth arm but this hold no heads and is not connected on the board.
I guess that the head unit was designed and partly constructed for up to four platters where this hard-drive only has three platters as shown later.
The bottom magnet that was under the head unit is also quite easy removed without any tools. I guess that the small plastic spring to the right on the picture below is some sort of anti-vibration or shock-absorbtion.
The spindle holding the platters on the axle is removed with six T9 screws.
The first platter is removed without tools. Between the platters there is a open ring of metal fastned to the chassis. I guess this is to stabilize the platters when they are spinning. Also there is a inne ring to give space for the heads.
After all three platters are removed the chassis looks quite empty. I have tried to lay out the components to show the stack from buttom to top. Between the platters there is a inner ring and a open outer ring.
The outher part of the electric motor spinning the platters is removed with brute force as indicated by the tools on the picture below. The outer part is the static magnetics where the inner part is the coils. How the outher part is fastned I can't see. There are no obvious traces of glue.
The three-pin connector for the motor on the back of the chassis is glued on but easy removed with a small screwdriver.
The inner part of the motor with the coils is removed with brute force and another set of tools.
All in all I count about 55 major components in the hard drive. In that there is about 31 screws.
Looking at modern storage there has certainly been some major changes. Compared to a NVMe storage unit with only one major components in a single board and no mechanics the technological span is amazing. But also compared to an elder SAS SSD drive typically with one board, around three screws and two metal half-shells the technological evolution is significant.

In general and looking back the flash storage was a paradigm shift where all our notes on spindles, RPMs and configuration tricks like short-strike suddenly were to no use. With NVMe many rules of thumb were obsolete. As PCIe (PCI-SIG) evolve we will see new fantastic features also on NVMe and CXL. But the next paradigm shift does not come from evole the current mainstream technology but when a completely new technology matures and push to the side the mainstream tech.

2016-09-04

IOmeter execution

Installation

Download IOmeter from iometer.org. I also download the documentation to be sure that I have the updated documentation.

Manual Setup

The drive must be defined on each worker before any other configuration in the IOmeter GUI.
You can only define one drive ("target") per worker.
Set up for 20 workers if the server has that many cores. If you have more cores than that you still don't need to define more that 20 workers in IOmeter.

IOmeter need a storage size to test on. It should three times larger than the complete amount of cache storage in the stack to the drive. On a SAN-drive this gives that the test file might be rather large. If you don't know the cache size and are working on local disks you could start with 40GB.
The maximum disk size is a little tricky as it is measured in number of sectors in IOmeter.
To get from storage amount like in GB to number of sectors you need to know the sector size. On a lot of disks it is 512B but some large disks with rotating platters mights have a sector size on 4KB. The number you can get in PowerShell with the CIM class Win32_DiskDrive where the attribute BytesPerSector gives the value.
When you have the sector size and know the size you want for maximum size the the calculation can be like this on a 40GB maximum:
(40GB * 1024MB * 1024KB * 1024B) / 512B = 83.886.080 sectors

In the tab "Access Specifications" I chose Default and added it as assigned.
The definition then can be altered by editing the default specification.
The "Transfer Request Size" is changed to the sized wanted for the test, e.g. 64 Kilobytes.
As SQL Server Database Engine mainly works sequential setting "Percent Random/Sequential Distribution" should be set to 100% Sequential. This setting is for some tests set to 100% Random.
A lot of SQL Server storage activity on data i reading, and then the setting "Percent Read/Write Distribution" should be set to 100% Read. This setting is for some test set to 100% Write.

On the tab "Results Display" the setting "Update Frequency (seconds)" should be set for 2 seconds to display the testing in a usefull way.

Start with 12 outstanding I/Os. Justify up and down to get highest bandwidth ("Total MBs per Second (Decimal)") before latency ("Average I/O Response Time (ms)") grows.

Test

A test series could be like this

  • Read 8 KB random I/O
  • Read 32 KB random I/O
  • Read 64 KB random I/O
  • Read 256 KB random I/O
  • Read 8 KB sequential I/O
  • Read 64 KB sequential I/O
  • Read 256 KB sequential I/O
  • Read 1024 KB sequential I/O. IOmeter changes this entry to 1 MB.
  • Write 8 KB random I/O
  • Write 32 KB random I/O
  • Write 64 KB random I/O
  • Write 256 KB random I/O
  • Write 8 KB sequential I/O
  • Write 32 KB sequential I/O
  • Write 64 KB sequential I/O
  • Write 256 KB sequential I/O

Measures

Setup a local Performance Monitor to local disk in blg-files.
Measure Logical Disk, not Physical Disk.

Manual Execution

The IOmeter status shows "Preparing Drives" while creating the file. And it will take quite some time. You should plan on a few hours.
After the file is created it is reused for each run om the same disk. Even if the testing parameters are altered.

Let each test run for about ten minutes.

Cleanup

The test file "iobw.tst" is in the root of the drive. When IOmeter is stopped this file can be deleted. The deletion requires administrator rights.
Be nice and clean up after yourself ;-)

Discussion

The IOmeter execution could be automated like I have done before with SQLIO. But as a manual evaluation is needed for the adjustments on outstanding I/Os it is practically not of much use to build an automated execution.

To be fair I have not discovered the detailed values in handling IOmeter myself. This has been delivered by Michael Frandsen (LinkedIn: michaelfrandsendk) in other context than this.

2013-08-22

Backup failed - nonrecoverable I/O

On a SQL 2008 SP2 (10.0.4000) I take database backup with a Maintenance Plan. This morning I had a failed database backup:
Executing the query "BACKUP DATABASE [thedatabase] TO  DISK = N'T:\\Backup\\th..." failed with the following error: "A nonrecoverable I/O error occurred on file "T:\\Backup\\thedatabase_backup_2011_01_18_221400_5137140.bak:" 112(failed to retrieve text for this error. Reason: 15105).
No nice :-(

When I looked in the log file generated by the Maintenance Plan, the error message was:
Task start: 2011-01-18T22:14:00.
Task end: 2011-01-18T22:14:01.
Failed:(-1073548784) Executing the query "BACKUP DATABASE [thedatabase] TO  DISK = N'T:\\Backup\\th..." failed with the following error: "A nonrecoverable I/O error occurred on file "T:\\Backup\\thedatabase_backup_2011_01_18_221400_5137140.bak:" 112(failed to retrieve text for this error. Reason: 15105).
BACKUP DATABASE is terminating abnormally.". Possible failure reasons: Problems with the query, "ResultSet" property not set correctly, parameters not set correctly, or connection not established correctly.


When I looked in the Windows System Log, there was no relevant entries.
But looking at the drive and the free space I saw that there was not space enough for the next backup file.
After a cleanup and manual execution of the Maintenance Plan everything was green.

The amount of free space can be looked up with PowerShell like this:
Get-WmiObject -Query "SELECT FreeSpace, Size FROM Win32_LogicalDisk WHERE DeviceID = 'T:'" -ComputerName SANDY.sqladmin.lan |
Format-Table @{Name='Freespace (GB)';Expression={"{0:N1}" -f($_.FreeSpace/1gb)}}, @{Name='Size (GB)';Expression={"{0:N1}" -f($_.Size/1gb)}} -AutoSize

The value of the parameter ComputerName should be changed to the actual databaseserver, also value in the WQL WHERE clause should be changed to the drive indicated in the error message.

The follow up is to order some additional storage.

I find the error message somewhat misleading. It looks like the ResultSet part is from the connectivity and not from the root error.

History

2011-01-19 : This is postede for the first time.
2013-08-22 : The PowerShell script is added.

2012-03-29

CPU count from T-SQL

I would like to know the number of processors (cores) on a given computer. No matter what the CPU affinity is or how many database instances is running on the computer.

The answer is given by the DMV "sys.dm_os_schedulers"
SELECT MAX([cpu_id]) + 1 AS [cpu_count]
FROM [master].[sys].[dm_os_schedulers];


I could use WMI and the Win32_Processor class (MSDN Library), but I don't always have the necessary rights to read the value remote.

2011-03-09

"BACKUP DATABASE is terminating abnormally."

This morning I had a error on a SQL 2000/Windows Server 2000 installation in a backup by a Maintenance Plan.
In the SQL Agent job the error was shown by
Executed as user: *****. sqlmaint.exe failed. [SQLSTATE 42000] (Error 22029).  The step failed.

The Maintenance plan log said:
[11] Database ServicedeskDB: Database Backup...
    Destination: [********201103082203.BAK]
[Microsoft SQL-DMO (ODBC SQLState: 42000)] Error 3202: [Microsoft][ODBC SQL Server Driver][SQL Server]Write on '********201103082203.BAK' failed, status = 112. See the SQL Server error log for more details.
[Microsoft][ODBC SQL Server Driver][SQL Server]BACKUP DATABASE is terminating abnormally.


In the SQL Error Log There were these entries.
2011-03-08 22:04:11.52 spid66    BackupMedium::ReportIoError: write failure on backup device '********201103082203.BAK'. Operating system error 112(error not found).
2011-03-08 22:04:11.53 spid66    Internal I/O request 0x6B514990: Op: Write, pBuffer: 0x06350000, Size: 983040, Position: 4470543872, UMS: Internal: 0x103, InternalHigh: 0x0, Offset: 0xA771600, OffsetHigh: 0x1, m_buf: 0x06350000, m_len: 983040, m_actualBytes: 0, m_errcode: 112, BackupFile: ********201103082203.BAK
2011-03-08 22:04:11.53 backup    BACKUP failed to complete the command BACKUP DATABASE [*****] TO  DISK = N'********201103082203.BAK' WITH  INIT ,  NOUNLOAD ,  NOSKIP ,  STATS = 10,  NOFORMAT


By the SQL Error Log it could look like a storage failure :-(
Not a nice way to start the day.

When I looked at the Windows System Log there were no entries on storage failure.
But looking at the storage I saw that there were 4.15 GB available, and that the last backup took 4.16 GB.

After a quick cleanup and a manual execution of the job - with success - the conclusion is that the error indicates a lack of available storage.

2010-12-22

Disk Info

I have a daily interest to know how the disks are used, and this script give some useful data from the Windows point of view.

function Get-Disk {
[CmdletBinding()]
param(
  [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
  [String]$ComputerName
)
Begin {
  "Get-Disk( '$ComputerName' )" | Write-Verbose
  $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
}

Process {
  $CimSessionOption = New-CimSessionOption -Protocol Dcom
  $CimSession = New-CimSession -SessionOption $CimSessionOption -Verbose:$false -ComputerName $ComputerName
  $SqlDisks = @()
  'Get Local Disks...' | Write-Verbose
  $Disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' -CimSession $CimSession -Verbose:$false
  foreach ($Disk in $Disks) {
    "Disk.DeviceID = '$($Disk.DeviceID)'." | Write-Verbose
    $SqlDisk = New-Object -TypeName PSObject
    $SqlDisk.PSObject.TypeNames.Insert(0, 'SqlAdmin.Disk')
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveLetter -Value $Disk.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DiskFreeSpaceBytes -Value $Disk.FreeSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DiskSizeBytes -Value $Disk.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name FileSystem -Value $Disk.FileSystem
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeDirty -Value $Disk.VolumeDirty
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeName -Value $Disk.VolumeName
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeSerialNumber -Value $Disk.VolumeSerialNumber

    $Partition = Get-CimAssociatedInstance -CimInstance $Disk -ResultClassName Win32_DiskPartition -Verbose:$false
    "Partition.DeviceID = '$($Partition.DeviceID)'." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionDeviceID -Value $Partition.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name BlockSizeBytes -Value $Partition.BlockSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name BlockCount -Value $Partition.NumberOfBlocks
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionDiskIndex -Value $Partition.DiskIndex
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionSizeBytes -Value $Partition.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name StartingOffset -Value $Partition.StartingOffset
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionType -Value $Partition.Type

    $Drive = Get-CimAssociatedInstance -CimInstance $Partition -ResultClassName Win32_DiskDrive -Verbose:$false
    "Drive.DeviceID = '$($Drive.DeviceID)'." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DrivePartitionCount -Value $Drive.Partitions
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveBytesPerSector -Value $Drive.BytesPerSector
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveDeviceID -Value $Drive.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name InterfaceType -Value $Drive.InterfaceType
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveFirmwareRevision -Value $Drive.FirmwareRevision
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveModel -Value $Drive.Model
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveSizeBytes -Value $Drive.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveStatus -Value $Drive.Status
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveSerialNumber -Value $Drive.SerialNumber

    $Volume = $Volumes = Get-CimInstance -ClassName Win32_Volume -Filter "DriveLetter='$($Disk.Name)'" -CimSession $CimSession -Verbose:$false
    "Volume.Label = '$($Volume.Label)'." | Write-Verbose
    'Running Defrag Analysis...' | Write-Verbose
    $DefragStopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $Report = Invoke-CimMethod -CimSession $CimSession -InputObject $Volume -MethodName DefragAnalysis -Verbose:$false
    $DefragStopwatch.Stop()
    "Defrag Analysis completed. Duration = $($DefragStopwatch.Elapsed.ToString()) [hh:mm:ss.ddd]." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFileSize -Value $Report.DefragAnalysis.AverageFileSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFragmentsPerFile -Value $Report.DefragAnalysis.AverageFragmentsPerFile
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFreeSpacePerExtent -Value     $Report.DefragAnalysis.AverageFreeSpacePerExtent
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name ClusterSize -Value $Report.DefragAnalysis.ClusterSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeFreeSpace -Value $Report.DefragAnalysis.FreeSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name FreeSpacePercentFragmentation -Value     $Report.DefragAnalysis.FreeSpacePercentFragmentation
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name LargestFreeSpaceExtent -Value $Report.DefragAnalysis.LargestFreeSpaceExtent
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name MFTPercentInUse -Value $Report.DefragAnalysis.MFTPercentInUse
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name MFTRecordCount -Value $Report.DefragAnalysis.MFTRecordCount
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalExcessFragments -Value $Report.DefragAnalysis.TotalExcessFragments
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFiles -Value $Report.DefragAnalysis.TotalFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFolders -Value $Report.DefragAnalysis.TotalFolders
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFragmentedFiles -Value $Report.DefragAnalysis.TotalFragmentedFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFreeSpaceExtents -Value $Report.DefragAnalysis.TotalFreeSpaceExtents
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalMFTFragments -Value $Report.DefragAnalysis.TotalMFTFragments
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalMFTSize -Value $Report.DefragAnalysis.TotalMFTSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalUnmovableFiles -Value $Report.DefragAnalysis.TotalUnmovableFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name UsedSpace -Value $Report.DefragAnalysis.UsedSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeSize -Value $Report.DefragAnalysis.VolumeSize

    $SqlDisks += $SqlDisk
  }
}

End {
  $Stopwatch.Stop()
  "$($SqlDisks.Count) disks found on the computer. Duration = $($Stopwatch.Elapsed.ToString()) [hh:mm:ss.ddd]." | Write-Verbose
  $SqlDisks
}

} # Get-Disk()

### INVOKE ###
$MyDisks = Get-Disk -ComputerName '.' -Verbose
$MyDisks | Out-GridView


I use the DCOM protocol due to some restrictions in some network segments.

The usage of the CmdLet Get-CimAssociatedInstance makes associating the classes mush easier compared to associating the classes in WMI.
The execution of the method DefragAnalysis is different used from CIM than WMI. The use of the CmdLet Invoke-CimMethod took some time to get, but I find it much cleaner to use.

When the script is executed, the output could look like this:

DriveLetter                   : C:
DiskFreeSpaceBytes            : 488987631616
DiskSizeBytes                 : 750153363456
FileSystem                    : NTFS
VolumeDirty                   : False
VolumeName                    : ********
VolumeSerialNumber            : ********
PartitionDeviceID             : Disk #0, Partition #0
BlockSizeBytes                : 512
BlockCount                    : 1465143296
PartitionDiskIndex            : 0
PartitionSizeBytes            : 750153367552
StartingOffset                : 1048576
PartitionType                 : Installable File System
DrivePartitionCount           : 1
DriveBytesPerSector           : 512
DriveDeviceID                 : \\.\PHYSICALDRIVE0
InterfaceType                 : IDE
DriveFirmwareRevision         : EXT0
DriveModel                    : Samsung SSD 840 EVO 750G
DriveSizeBytes                : 750153761280
DriveStatus                   : OK
DriveSerialNumber             : ********    
AverageFileSize               : 142
AverageFragmentsPerFile       : 1,15
AverageFreeSpacePerExtent     : 6483968
ClusterSize                   : 4096
VolumeFreeSpace               : 489014767616
FreeSpacePercentFragmentation : 31
LargestFreeSpaceExtent        : 334636171264
MFTPercentInUse               : 100
MFTRecordCount                : 473855
TotalExcessFragments          : 54011
TotalFiles                    : 339026
TotalFolders                  : 76795
TotalFragmentedFiles          : 12689
TotalFreeSpaceExtents         : 75369
TotalMFTFragments             : 2
TotalMFTSize                  : 485228544
TotalUnmovableFiles           : 147
UsedSpace                     : 261138595840
VolumeSize                    : 750153363456
The verbose output shows some durations on the execution:
VERBOSE: Get-Disk( '.' )
VERBOSE: Get Local Disks...
...
VERBOSE: Disk.DeviceID = 'C:'.
VERBOSE: Partition.DeviceID = 'Disk #0, Partition #0'.
VERBOSE: Drive.DeviceID = '\\.\PHYSICALDRIVE0'.
VERBOSE: Volume.Label = '********'.
VERBOSE: Running Defrag Analysis...
VERBOSE: Defrag Analysis completed. Duration = 00:00:56.9287395 [hh:mm:ss.ddd].
VERBOSE: 2 disks found on the computer. Duration = 00:00:59.5802508 [hh:mm:ss.ddd].


The script is not optimized for performance and do have a response time on some seconds. The response time increased especially when I added the defragmentation data.

Please notice that the classes Win32_Volume and Win32_DefragAnalysis are not available on Windows XP or earlier.

Reference

MSDN Library: Win32_DiskDrive, Win32_DiskPartition, Win32_LogicalDisk, Win32_Volume and „WMI Tasks: Disks and File Systems“.
Richard Saddaway: "Defrag Analysis Part 2".


History

2010-12-22 First release of the script.
2015-06-25 Second release of the script, now using CIM CmsLets.