Tuesday, November 22, 2011

Concepts in Disk Sizing

Here are the basics of what you need to know about disk sizing for the Exchange server. This article may not be comprehensive, but it should be enough to:
  1. Determine if you have sufficient disks 
  2. Detect if you have a disk bottleneck 
  3. Calculate the number of disk I/Os per second per user (also known as IOPS/user) 
  4. Estimate how many disks you need for a new server, based on past user behavior. 
"The amount of  IO" is the number of reads and writes to a drive. The actual bytes that are read or written are less interesting than the number of times the disk head has to move to a location.There is often confusion around the size of a disk (the number of bytes that can be stored on it) and the throughput (the number of IOs per second that can be read and written). Throughput is usually measured in IOPS (IOs per second or io/sec). It's important to know the maximum throughput (the maximum number of IOs your disks can sustain), because if you exceed that maximum, Hello Outlook Popup! RPC latencies will quickly go through the roof when maximum disk throughput is exceeded. When someone is referring to a disk bottleneck, they are referring to a throughput bottleneck, not a limitation of disk space.

Also, for this discussion, when I say IO, I'm usually refering to the physical disk\disk transfers per second to the database drives, but the basic principles can apply to sizing the rest of the drives as well. The reason I focus on the database drives is that Exchange server makes heavy use of the disks that house the database drives. For comparison, the store writes 1/10 the number of IOs to the log drive compared to the database drives. Even though I focus on database drives, be aware that SMTP queue drives and Exchange temp drives, depending on your company's email users, can also be heavy consumers of IO, and you will want to make sure you aren't exceeding the disk maximum throughput of those drives either.

Determining if you have enough disks

How do you know if your drives are healthy? The simplest way to check is to measure how long it takes for a read and write (referred to as the read and write latency). Take a look at the physical disk\sec per read and sec per write counters for your database drives. The server reports this in seconds, but we generally talk about it in ms. Are the latencies under 20 ms? If so, excellent. Your users are probably happy (or at least, complaining about something other than email responsiveness). If the latencies are larger than 20 ms, it's time to take a look at your disk usage. Do the physical disk\disk transfers/sec counters exceed the maximum throughput of your drives? Aah, now you ask, how do I determine the maximum throughput of my drives?

The best way to determine maximum throughput is to measure it. The jetstress tool is an excellent way to measure the maximum throughput of your disks. The documentation explains how to do this, so I'll skip that here. However, to use jetstress, you have to test your disks in a lab (not in a production environment). So what do you do if you have already have a server in production, and suspect you have exceeded the maximum throughput? The best thing you can do is make an estimate. Here's how I make estimates (there are many tricks, but these are fairly simple):
  1. Most disks can do between 130 to 180 IOPS. 
  2. Exchange typically has a Read-to-Write (R:W) ratio of 3:1 or 2:1. 
  3. We recommend that you plan to use less than 80% disk utilization at peak load. 
Raid 0 (striping) has the same cost as no raid. Reads and writes happen once.

Raid 0+1 requires two disk IOs for every write (the mirrored data is written twice)

Raid 5 requires four disk IOs for every write (two reads, two writes to calculate and write parity)

 I'm skipping the math unless someone asks for it. Essentially, this translates into the values in the tables below. These are the values you should when you estimate how much disk throughput is available for users during peak load.

Tables to lookup recommended maximum disk throughput per disk:

Table 1. Estimated maximum disk throughput for No Raid or Raid 0

R:W ratio \ Disk speed 130 IOs per second 180 IOs per second
3:1 104 IOPS 144 IOPS
2:1 104 IOPS 144 IOPS


Table 2. Estimated maximum disk throughput for Raid 0+1 (or Raid 10)

R:W ratio \ Disk speed 130 IOs per second 180 IOs per second
3:1 83 IOPS 115 IOPS
2:1 78 IOPS 108 IOPS


Table 3: Estimated maximum disk throughput for Raid 5

R:W ratio \ Disk speed 130 IOs per second 180 IOs per second
3:1 59 IOPS 82 IOPS
2:1 52 IOPS 72 IOPS


I'm too lazy to even use tables, so I take the conservative approach and assume I can safely get a throughput of 80 IOs per second for most disks, in a raid 0+1 configuration (Raid 0+1 is generally recommended for most database drives).

If you have multiple drives (or "spindles") connected in a raid configuration, multiply the throughput by the number of drives. Thus, 10 disks in raid 0+1 will safely support a load of 800 IOPS. I spoke with one customer who recently changed disks. The company had previously had 6 small disks, and recently replaced them with 3 large disks. Since then, users had been seeing a lot of Outlook popups while waiting for messages to open, change folders etc. When the disks were replaced with fewer larger disks, the 3 disks were unable to deliver the io throughput that 6 disks were able to deliver. The disks were bottlenecked; io latency went up, and so did RPC latency as a consequence. Solution? Put more disks in there! I want to stress that this is not an uncommon scenario - it seems perfectly reasonable to move to fewer larger disks….but you can see here how it can get your server into trouble.

What if your disks throughput is below the maximum, but the latencies are still high? Sometimes the problem is a configuration problem (eg, max queue depth) or is occuring because you are sharing SAN drives with another application, which is consuming a lot of io bandwidth. When Exchange is competing with another application for io, user experience suffers. If you are having a poor latency and think the throughputs are well below the disk maximum throughput, you will have to go back to your disk guru and start troubleshooting. In general, we don't recommend sharing database SAN spindles with other applications. And never never share log drives with any other application (this significantly reduces the throughput).

Detecting disk bottlenecks

It's pretty simple to tell if you have a disk bottleneck. If the latencies to your disk drives are greater than 20 ms (0.02 as measured from physical disk\disk seconds per read and disk seconds per write), then disks are starting to be an issue. You can survive on disks with 50 ms latencies, but the user experience improves significantly if they are reduced. On our internal exchange servers, we keep the latency to 10 ms for read IOs, and around 1 ms for write io (write latencies can be very low if you have a battery-back write-back cache).

You should be able to confirm the cause of your bottleneck (exceeding maximum disk throughput) by measuring the physical disk\disk transfers per second and comparing with your estimated maximum throughput.

Calculate your IOPS/user

If you've been reading some of the whitepapers or attending talks on Exchange server, you've probably seen references to IOPS per user. Generally, this refers to the number of IO read and write requests to the database drive, divided by the number of users.

Measure the physical disk\disk transfers per second for all databases for between 20 minutes to 2 hours during your most active time (for example, this is from 9-11 AM on a Monday here at Microsoft). During this time, also measure the number of active users (MSExchangeIS\Active User Count). Take an average of these counters. Sum the disk transfers/sec for each database, divide the first number by the second and… Voila! You have just calculated the number of IOPS per user.

Keep in mind that the number of IOPS/user is determined by how active your users are. You may find that this differs from server to server (and database to database). Don't sweat it. These numbers are used as guidelines, but accurate numbers aren't always necessary…as long as you build in a little overhead when planning & populating your servers. However, you can use these numbers to help decide when you want to move users from a busy server to another server.

(Note that, as a general practice, it's a good idea to always measure the server when it's at peak load. When you are sizing your servers, you always need to plan for maximum usage… and then leave a little buffer overhead for those extra special days…like when all the users return from Christmas break).

Estimate how many disks you need for a new server, based on past user behavior.

Now that you know how to measure (via jetstress) or estimate (from above) maximum disk throughput, and you know the IOPS/user, it's a simple task to plan for how many disks you'll need for a new server.

Assuming the new users have a similar email usage profile (are using the same clients, have the same percentage of plugins, send about the same mail), then here's how you go about it:

Calculate the throughput you will need. (multiply the number of users on the new server by the number of IOPS/user)

Divide the throughput by the maximum throughput of the disks you are using (use the numbers from the table above, or the result from jetstress * 0.8. The numbers in the table above already include the 80% max usage to build in some overhead). Round up. This will give you the minimum number of disks that you will need for the server. Next, divide by the number of databases, and round up. This will give you the number of disks you need per database (or repeat with storage groups if you databases share the same physical drive).

That's it! Oh, I suppose it's always a good idea to do an example:

Ok, suppose I am hiring 5000 people (growth is good!), and I want to figure out how to size my server. My current users require 0.4 IOPS per user, and I expect the new guys to be just as hard working as my current employees. I will need a total of 2000 IOPS.

I'm going to buy fast disks capable of 180 IOPS, which I'm going configure in Raid 0+1. From the table, I can expect to get around 108 IOs per second. 2000 IOPS/108 IOPS per disk = 18.5. This will imply that I'm going to need 19 disks, if all IOs were all going to the same place. But they aren't of course (backup times would be unwieldy!!!) - I'm planning to have 20 db spread across 4 storage groups. The databases on the same storage group will share the same disk. So each storage group disk will need to support 2000/4 = 500 IOPS. That means each storage group disk will need to have 500/108 = 4.6 disks. Rounding up shows that I will need 5 disks for each storage group. So the total number of disks I will need is 5*4 = 20 disks.

Suppose after buying my disks, I test them in the lab and my jet stress tests of these disks only show 120 IOs per second total, which gives me 96 IOPS to play with after I've multiplied by 0.8 to give me a 20% safety buffer. I redo my calculations and find it doesn't affect my results (because I'd already rounded up earlier). So, I'm ready to build out my server and add the new users.

(Note I haven't calculated the amount of disk space capacity I'll need…I'll leave this for the readers…unless I get specific requests. In many cases, disk capacity is less of an issue because disk space on disks has risen significantly. For most Exchange customers, the real issue is around disk throughput).

Thanks to you (the reader) for taking the time to read this - I hope you have found the content interesting :)

What Is RAID?

RAID (Redundant Array of Independent Disks) is a technology for managing how data is stored on the physical disks that reside in your system or are attached to it. A key aspect of RAID is the ability to span physical disks so that the combined storage capacity of multiple physical disks can be treated as a single, extended chunk of disk space. Another key aspect of RAID is the ability to maintain redundant data which can be used to restore data in the event of a disk failure.

RAID uses different techniques, such as striping, mirroring, and parity, to store and reconstruct data. There are different RAID levels that use different methods for storing and reconstructing data. The RAID levels have different characteristics in terms of read/write performance, data protection, and storage capacity. Not all RAID levels maintain redundant data, which means for some RAID levels lost data cannot be restored. Which RAID level you choose depends on whether your priority is performance, protection, or storage capacity.

NOTE: The RAID Advisory Board (RAB) defines the specifications used to implement RAID. Although the RAID Advisory Board (RAB) defines the RAID levels, commercial implementation of RAID levels by different vendors may vary from the actual RAID specifications. An implementation used by a particular vendor may affect the read and write performance and the degree of data redundancy.

Hardware and Software RAID

RAID can be implemented with either hardware or software. A system using hardware RAID has a RAID controller that implements the RAID levels and processes data reads and writes to the physical disks. When using software RAID, the operating system must implement the RAID levels. For this reason, using software RAID by itself can slow system performance. You can, however, use software RAID on top of hardware RAID volumes to provide greater performance and variety in the configuration of RAID volumes. For example, you can mirror a pair of hardware RAID 5 volumes across two RAID controllers to provide RAID controller redundancy.


RAID Concepts

RAID uses particular techniques for writing data to disks. These techniques enable RAID to provide data redundancy or better performance. These techniques include:
  • Mirroring — Duplicating data from one physical disk to another physical disk. Mirroring provides data redundancy by maintaining two copies of the same data on different physical disks. If one of the disks in the mirror fails, the system can continue to operate using the unaffected disk. Both sides of the mirror contain the same data at all times. Either side of the mirror can act as the operational side. A mirrored RAID disk group is comparable in performance to a RAID 5 disk group in read operations but faster in write operations.
  • Striping — Disk striping writes data across all physical disks in a virtual disk. Each stripe consists of consecutive virtual disk data addresses that are mapped in fixed-size units to each physical disk in the virtual disk using a sequential pattern. For example, if the virtual disk includes five physical disks, the stripe writes data to physical disks one through five without repeating any of the physical disks. The amount of space consumed by a stripe is the same on each physical disk. The portion of a stripe that resides on a physical disk is a stripe element. Striping by itself does not provide data redundancy. Striping in combination with parity does provide data redundancy.
  • Stripe size — The total disk space consumed by a stripe not including a parity disk. For example, consider a stripe that contains 64KB of disk space and has 16KB of data residing on each disk in the stripe. In this case, the stripe size is 64KB and the stripe element size is 16KB.
  • Stripe element — A stripe element is the portion of a stripe that resides on a single physical disk.
  • Stripe element size — The amount of disk space consumed by a stripe element. For example, consider a stripe that contains 64KB of disk space and has 16KB of data residing on each disk in the stripe. In this case, the stripe element size is 16KB and the stripe size is 64KB.
  • Parity — Parity refers to redundant data that is maintained using an algorithm in combination with striping. When one of the striped disks fails, the data can be reconstructed from the parity information using the algorithm.
  • Span — A span is a RAID technique used to combine storage space from groups of physical disks into a RAID 10 or 50 virtual disk.

RAID Levels

Each RAID level uses some combination of mirroring, striping, and parity to provide data redundancy or improved read and write performance. For specific information on each RAID level, see "Choosing RAID Levels and Concatenation."

ORGANIZING DATA STORAGE FOR AVAILABILITY AND PERFORMANCE

RAID provides different methods or RAID levels for organizing the disk storage. Some RAID levels maintain redundant data so that you can restore data after a disk failure. Different RAID levels may also entail an increase or decrease in the system's I/O (read and write) performance.
Maintaining redundant data requires the use of additional physical disks. As more disks become involved, the likelihood of a disk failure increases. Because of the differences in I/O performance and redundancy, one RAID level may be more appropriate than another based on the applications in the operating environment and the nature of the data being stored.

When choosing concatenation or a RAID level, the following performance and cost considerations apply:
  • Availability or fault-tolerance. Availability or fault-tolerance refers to a system's ability to maintain operations and provide access to data even when one of its components has failed. In RAID volumes, availability or fault-tolerance is achieved by maintaining redundant data. Redundant data includes mirrors (duplicate data) and parity information (reconstructing data using an algorithm).
  • Performance. Read and write performance can be increased or decreased depending on the RAID level you choose. Some RAID levels may be more appropriate for particular applications.
  • Cost efficiency. Maintaining the redundant data or parity information associated with RAID volumes requires additional disk space. In situations where the data is temporary, easily reproduced, or non-essential, the increased cost of data redundancy may not be justified.
  • Mean Time Between Failure (MBTF). Using additional disks to maintain data redundancy also increases the chance of disk failure at any given moment. Although this cannot be avoided in situations where redundant data is a requirement, it does have implications for the workload of your organization's system support staff.

CHOOSING RAID LEVELS AND CONCATENATION

You can use RAID or concatenation to control data storage on multiple disks. Each RAID level or concatenation has different performance and data protection characteristics.
The following sections provide specific information on how each RAID level or concatenation store data as well as their performance and protection characteristics.
  • "Concatenation"
  • "RAID Level 0 (Striping)"
  • "RAID Level 1 (Mirroring)"
  • "RAID Levels 5 (Striping with distributed parity)"
  • "RAID Level 50 (Striping over RAID 5 sets)"
  • "RAID Level 10 (Striping over mirror sets)"
  • "RAID Level 1-Concatenated (Concatenated mirror)"
  • "Comparing RAID Level and Concatenation Performance"


Concatenation

In Storage Management, concatenation refers to storing data on either one physical disk or on disk space that spans multiple physical disks. When spanning more than one disk, concatenation enables the operating system to view multiple physical disks as a single disk.
Data stored on a single disk can be considered a simple volume. This disk could also be defined as a virtual disk that comprises only a single physical disk. Data that spans more than one physical disk can be considered a spanned volume. Multiple concatenated disks can also be defined as a virtual disk that comprises more than one physical disk.
A dynamic volume that spans to separate areas of the same disk is also considered concatenated.
When a physical disk in a concatenated or spanned volume fails, the entire volume becomes unavailable. Because the data is not redundant, it cannot be restored by rebuilding from a mirrored disk or parity information. Restoring from a backup is the only option.
Because concatenated volumes do not use disk space to maintain redundant data, they are more cost-efficient than volumes that use mirrors or parity information. A concatenated volume may be a good choice for data that is temporary, easily reproduced, or that does not justify the cost of data redundancy. In addition, a concatenated volume can easily be expanded by adding an additional physical disk.

Figure 3-1. Concatenating Disks
  • Concatenates n disks as one large virtual disk with a capacity of n disks. Data fills up the first disk before it is written to the second disk.
  • No redundancy data is kept. When a disk fails, the large virtual disk fails.
  • No performance gain.
  • No redundancy








RAID Level 0 (Striping)

RAID 0 uses data striping, which is writing data in equal-sized segments across the physical disks. RAID 0 does not provide data redundancy.

Figure 3-2. Striping Disks

RAID 0 Characteristics:
  • Groups n disks as one large virtual disk with a capacity of (smallest disk size)*n disks.
  • Data is stored to the disks alternately.
  • No redundancy data is kept. When a disk fails, the large virtual disk fails with no means of rebuilding the data.
  • Better read and write performance.








RAID Level 1 (Mirroring)

RAID 1 is the simplest form of maintaining redundant data. In RAID 1, data is mirrored or duplicated on one or more physical disks. If a physical disk on one side of the mirror fails, then the data can be rebuilt using the physical disk on the other side of the mirror.
Figure 3-3. Mirroring Disks


RAID 1 Characteristics:
  • Groups n + n disks as one virtual disk with the capacity of n disks. The controllers currently supported by Storage Management allow the selection of two disks when creating a RAID 1. Because these disks are mirrored, the total storage capacity is equal to one disk.
  • Data is replicated on the two disks.
  • When a disk fails, the virtual disk still works. The data will be read from the failed disk's mirror.
  • Better read performance, but slightly slower write performance.
  • Redundancy for protection of data.
  • RAID 1 is more expensive in terms of disk space since twice the number of disks are used than required to store the data without redundancy.

Related Information:
See the following:

  • "Organizing Data Storage for Availability and Performance"
  • "Comparing RAID Level and Concatenation Performance"
  • "Controller-supported RAID Levels"
  • "Number of Physical Disks per Virtual Disk"
  • "Maximum Number of Virtual Disks per Controller"


RAID Levels 5 (Striping with distributed parity)

RAID 5 provides data redundancy by using data striping in combination with parity information. Rather than dedicating a physical disk to parity, however, the parity information is striped across all physical disks in the disk group.

Figure 3-4. Striping Disks with Distributed Parity

RAID 5 Characteristics:
  • Groups n disks as one large virtual disk with a capacity of (n-1) disks. 
  • Redundant information (parity) is alternately stored on all disks. 
  • When a disk fails, the virtual disk still works, but it is operating in a degraded state. The data is reconstructed from the surviving disks. 
  • Better read performance, but slower write performance. 
  • Redundancy for protection of data.






RAID Level 50 (Striping over RAID 5 sets)

RAID 50 is striping over more than one span of physical disks. For example, a RAID 5 disk group that is implemented with three physical disks and then continues on with a disk group of three more physical disks would be a RAID 50.

It is possible to implement RAID 50 even when the hardware does not directly support it. In this case, you can implement more than one RAID 5 virtual disks and then convert the RAID 5 disks to dynamic disks. You can then create a dynamic volume that is spanned across all RAID 5 virtual disks.

Figure 3-5. RAID 50

RAID 50 Characteristics:
  • Groups n*s disks as one large virtual disk with a capacity of s*(n-1) disks, where s is the number of spans and n is the number of disks within each span. 
  • Redundant information (parity) is alternately stored on all disks of each RAID 5 span.
  • Better read performance, but slower write performance.
  • Requires proportionally as much parity information as standard RAID 5.
  • Data is striped across all spans. RAID 50 is more expensive in terms of disk space.



RAID Level 10 (Striping over mirror sets)

The RAID Advisory Board considers RAID Level 10 to be an implementation of RAID level 1. RAID 10 combines mirrored physical disks (RAID 1) with data striping (RAID 0). With RAID 10, data is striped across multiple physical disks. The striped disk group is then mirrored onto another set of physical disks. RAID 10 can be considered a mirror of stripes.


Figure 3-6. Striping Over Mirrored Disk Groups

RAID 10 Characteristics:
  • Groups n disks as one large virtual disk with a capacity of (n/2) disks.
  • Mirror images of the data are striped across sets of physical disks. This level provides redundancy through mirroring.
  • When a disk fails, the virtual disk is still functional. The data will be read from the surviving mirrored disk.
  • Improved read performance and write performance. 
  • Redundancy for protection of data.


RAID Level 1-Concatenated (Concatenated mirror)

RAID 1-concatenated is a RAID 1 disk group that spans across more than a single pair of physical disks. This combines the advantages of concatenation with the redundancy of RAID 1. No striping is involved in this RAID type.

Also, RAID 1 Concatenated can be implemented on hardware that supports only RAID 1 by creating multiple RAID 1 virtual disks, upgrading the virtual disks to dynamic disks, and then using spanning to concatenate all of the RAID 1 virtual disks into one large dynamic volume.

Figure 3-7. RAID 1-Concatenated

















Considerations for RAID 10 and 50 on PERC 3/SC, 3/DCL, 3/DC, 3/QC, 4/SC, 4/DC, 4e/DC, 4/Di, 4e/Si, 4e/Di, and CERC ATA100/4ch Controllers

On the PERC 3/SC, 3/DCL, 3/DC, 3/QC, 4/SC, 4/DC, 4e/DC, 4/Di, 4e/Si, 4e/Di, and CERC ATA100/4ch controllers, there are special considerations when implementing RAID 10 or RAID 50 on a disk group that has disks of different sizes. When implementing RAID 10 or RAID 50, disk space is spanned to create the stripes and mirrors. The span size can vary to accommodate the different disk sizes. There is, however, the possibility that a portion of the largest disk in the disk group will be unusable, resulting in wasted disk space. For example, consider an disk group that has the following disks:

Disk A = 40 GB
Disk B = 40 GB
Disk C = 60 GB
Disk D = 80 GB

In this example, data will be spanned across all four disks until Disk A and Disk B and 40 GB on each of Disk C and D are completely full. Data will then be spanned across Disks C and D until Disk C is full. This leaves 20 GB of disk space remaining on Disk D. Data cannot be written to this disk space, as there is no corresponding disk space available in the disk group to create redundant data.

COMPARING RAID LEVEL AND CONCATENATION PERFORMANCE

The following table compares the performance characteristics associated with the more common RAID levels. This table provides general guidelines for choosing a RAID level. Keep in mind the needs of your particular environment when choosing a RAID level.



RAID Level and Concatenation Performance Comparison
RAID
Level
Data
Availability
Read Performance
Write Performance
Rebuild Performance
Minimum Disks Required
Suggested Uses
Concatenation
No gain.
No gain.
No gain.
N/A
1 or 2 depending on the controller.
More cost efficient than redundant RAID levels. Use for noncritical data.
RAID 0
None
Very Good
Very Good
N/A
N
Noncritical data
RAID 1
Excellent
Very Good
Good
Good
2N
(N = 1)
Small databases, database logs, critical information
RAID 5
Good
Sequential reads: good. Transactional reads: Very good
Fair, unless using write-back cache
Poor
N + 1
(N = at least two disks)
Databases and other read-intensive transactional uses
RAID 10
Excellent
Very Good
Fair
Good
2N x X
Data-intensive environments (large records)
RAID 50
Excellent
Very Good
Fair
Fair
N + 2
(N = at least 4)
Medium-sized transactional or data-intensive uses
N = Number of physical disks
X = Number of RAID sets



Thursday, November 17, 2011

GDX Glaucoma Test


What is glaucoma?

Over two million Americans have glaucoma making it one of the biggest causes of legal blindness in the United States. Glaucoma can rob people of their vision even though they don’t have any visual symptoms or pain. In fact, half of those with glaucoma don’t even know it. The disease is not easily diagnosed. For example, the standard test, which measures eye pressure, fails to uncover glaucoma in one third of patients with the disease. No wonder glaucoma is called the “sneak thief of sight.”

Don’t let glaucoma sneak up on you.

Now there is a revolutionary new technology that can help doctors find glaucoma earlier, while there’s still time: the Zeiss GDx glaucoma exam, from a trusted leader in innovative diagnostic instruments for eyecare.

GDx Nerve Fiber Analyzer is a scanning laser system that detects early glaucoma by measuring thw thickness of the retinal nerve fiber layer.

Also has a database containing bilateral retinal-verve fiber measurements on hundreds of normal subjects.
The database automatically compares patients' scans with normals matched for age, race and gender, then
generates a color printout of the results.

The GDx is capable of identifying damage in patients in whom we have no other way of detecting it.
We can identify patients before they ever have visual field loss.

The laser acquires data through an undilated pupil in 0.7 seconds. Then the Windows-based software
compares 65,000 measurement points to the normative database, based on the patient's age, race and gender.
Three images of each eye are taken, and the software averages the results. New images can be acquired while the computer processes the information in the background. The patient is aware of a flickering red-light field in the examined eye, but there is no discomfort.
A better glaucoma detection technology

During our vision examination, we routinely perform the preliminary ZEISSGDx™ Screening Test. If this test shows borderline or abnormal results, then we will perform the full GDx™ Exam. Most health insurance companies will allow this extended test for early detection of glaucoma.

This significant diagnostic breakthrough has been found to provide precise, accurate information to help diagnose glaucoma earlier, and possibly start treatment before vision loss occurs.

The ZEISS GDx exam is safe and comfortable and takes 3 minutes or less. The ZEISS GDx scans the retinal nerve fiber layer in the back of the eye using polarized light and measures its thickness at 36,000 points.

These measurements are then mapped by computer and compared to those of healthy, glaucoma-free patients. An abnormally thin nerve fiber layer indicates that glaucoma may be present.
What makes the Zeiss GDx exam so revolutionary?



Unlike the puff test, the Zeiss GDx exam actually lets your doctor see the pattern and thickness of the nerve fibers in the back of your eyes, then compares the results to normal values. If your nerve fibers are thinner than normal, this could indicate glaucoma long before any vision has been lost. As a result, your doctor will have more time to treat the disease.
How does the Zeiss GDx exam work?

The test is a quick and comfortable part of a complete eye exam. Plus, you don’t have to have your pupils dilated. You simply look into the Zeiss GDx system while it safely scans the back of your eye. Total exam time usually takes less than a minute, and the system creates easy-to-read images that your doctor can quickly analyze.
Enhanced patient services

Including the ZEISS GDx screening and exam in our practice is another example of our commitment to provide the most advanced and comprehensive eyecare services. If you have any questions about your vision or the health of your eyes, please contact our office. We are more than happy to help with any of your eye care needs.
What makes me at risk?





  • Immediate Family History of Glaucoma?




















































Do you have:
  • A Family History of Glaucoma?
  • African-American or Latino Ancestry?
  • High Eye Pressure?

If you answered no to all four of the above questions, a complete eye exam is still strongly recommended if:
  • You are 20–29 years old and have never had a complete eye exam
  • You are 30–39 years old and it has been over 5 years since your last complete eye exam
  • You are 40–64 years old and it has been over 2 years since your last complete eye exam
  • You are 65 or older and it has been over a year since your last complete eye exam


GDX for Normal (No Glaucoma) and Glaucoma



Abnormal, Glaucoma
Normal, No Glaucoma
















Nerve Fiber Analysis




Wednesday, November 16, 2011

Word2Number

'Indonesian version'
Public Function KonversiHuruf(AngkaDiKonversi As Currency) ' Maximal Rp 922,337,203,685,477
    Dim Hasil, HasilTmp, IsiString, Str3Dgt, Angka, Satuan As String
    Dim Keberapa, Pecah123, PanjangString, ValGroup, ValDgt1, ValDgt2, ValDgt3 As Integer
    Dim AsliBerapa As Currency
    AsliBerapa = AngkaDiKonversi
    If AsliBerapa < 0 Then
        AngkaDiKonversi = AsliBerapa * -1
    End If
    Angka = "       Satu    Dua     Tiga    Empat   Lima    Enam    Tujuh   Delapan Sembilan"
    If AngkaDiKonversi = 0 Or AngkaDiKonversi > 922337203685477# Then ' Cek = 0 & Maximal
        KonversiHuruf = "0 Rupiah"
        Exit Function
    End If
    ' Cari Panjang String dari Angka yang akan di Konversi Misal 123456 = 6 Character
    PanjangString = Len(LTrim(Format(AngkaDiKonversi, "###############")))
    ' Masukkan Angka ke String Temporary Contoh : isian = 123456 -> 000000000123456
    IsiString = "000000000000000"
    Mid(IsiString, Len(IsiString) + 1 - PanjangString, PanjangString) = AngkaDiKonversi
    Keberapa = 1
    For Keberapa = 1 To 5 ' Ambil 3 Digit Group 5 kali -> (111),(222),(333),(444),(555)
        Satuan = IIf(Keberapa = 1, "Trilyun", IIf(Keberapa = 2, "Milyar", _
            IIf(Keberapa = 3, "Juta", IIf(Keberapa = 4, "Ribu", " "))))
        HasilTmp = " "
        Str3Dgt = IIf(Keberapa = 1, Left(IsiString, 3), IIf(Keberapa = 2, _
            Mid(IsiString, 4, 3), IIf(Keberapa = 3, Mid(IsiString, 7, 3), _
            IIf(Keberapa = 4, Mid(IsiString, 10, 3), Mid(IsiString, 13, 3)))))
        ValGroup = Val(Str3Dgt) ' Value dari String 3 Character Misal 123
        ValDgt1 = Val(Left(Str3Dgt, 1)) ' Value dari Character ke 1 Misal 123 -> 1
        ValDgt2 = Val(Left(Right(Str3Dgt, 2), 1)) ' Value dari Character ke 2 Misal 123 -> 2
        ValDgt3 = Val(Right(Str3Dgt, 1)) ' Value dari Character ke 3 Misal 123 -> 3
        If ValGroup <> 0 Then
            If ValGroup = 1 Then ' Khusus untuk 1 dalam Ribuan = "Seribu"
                HasilTmp = IIf(Keberapa = 4, " Seribu ", " Satu " + Satuan + " ")
            Else
                If ValGroup <> 0 Then
                    ' Digit Paling Kanan dari 3 Digit Group : Misal 123 -> 3
                    If ValDgt3 <> 0 Then
                        HasilTmp = Trim(Mid(Angka, ValDgt3 * 8, 8))
                    End If
                    ' Digit Kedua / Tengah dari 3 Digit Group : Misal 123 -> 2
                    If ValDgt2 > 0 Then '>19
                        If ValDgt2 <> 1 Then
                            HasilTmp = Trim(Mid(Angka, ValDgt2 * 8, 8)) + _
                                " Puluh " + Trim(HasilTmp)
                        Else '< 20
                            If ValDgt3 = 0 Then
                                HasilTmp = "Sepuluh"
                            Else
                                If ValDgt3 = 1 Then
                                    HasilTmp = "Sebelas"
                                Else ' Selain 10 dan 11
                                    HasilTmp = Trim(Mid(Angka, ValDgt3 * 8, 8)) + " Belas"
                                End If
                            End If
                        End If
                    End If
                    ' Digit Paling Kiri dari 3 Digit Group : Misal 123 -> 1
                    If ValDgt1 = 1 Then
                        HasilTmp = "Seratus " + Trim(HasilTmp)
                    Else
                        If ValDgt1 > 1 Then
                            HasilTmp = Trim(Mid(Angka, ValDgt1 * 8, 8)) + " Ratus " + _
                                Trim(HasilTmp)
                        End If
                    End If
                End If
                HasilTmp = " " + Trim(HasilTmp) + " " + Satuan
            End If
        End If
        Hasil = RTrim(Hasil) + RTrim(HasilTmp)
    Next Keberapa ' Proses Group 3 Digit berikutnya
    ' Return value to caller Program
    If AsliBerapa < 0 Then
        KonversiHuruf = "Minus : " & LTrim(Hasil) & " Rupiah"
    Else
        KonversiHuruf = LTrim(Hasil) & " Rupiah"
    End If
End Function

'English version'

Option Explicit
'Main Function
Function SpellNumber(ByVal MyNumber)
    Dim Dollars, Cents, Temp
    Dim DecimalPlace, Count
    ReDim Place(9) As String
    Place(2) = " Thousand "
    Place(3) = " Million "
    Place(4) = " Billion "
    Place(5) = " Trillion "
    ' String representation of amount.
    MyNumber = Trim(Str(MyNumber))
    ' Position of decimal place 0 if none.
    DecimalPlace = InStr(MyNumber, ".")
    ' Convert cents and set MyNumber to dollar amount.
    If DecimalPlace > 0 Then
        Cents = GetTens(Left(Mid(MyNumber, DecimalPlace + 1) & _
                  "00", 2))
        MyNumber = Trim(Left(MyNumber, DecimalPlace - 1))
    End If
    Count = 1
    Do While MyNumber <> ""
        Temp = GetHundreds(Right(MyNumber, 3))
        If Temp <> "" Then Dollars = Temp & Place(Count) & Dollars
        If Len(MyNumber) > 3 Then
            MyNumber = Left(MyNumber, Len(MyNumber) - 3)
        Else
            MyNumber = ""
        End If
        Count = Count + 1
    Loop
    Select Case Dollars
        Case ""
            Dollars = "No Dollars"
        Case "One"
            Dollars = "One Dollar"
         Case Else
            Dollars = Dollars & " Dollars"
    End Select
    Select Case Cents
        Case ""
            Cents = " and No Cents"
        Case "One"
            Cents = " and One Cent"
              Case Else
            Cents = " and " & Cents & " Cents"
    End Select
    SpellNumber = Dollars & Cents
End Function
      
' Converts a number from 100-999 into text
Function GetHundreds(ByVal MyNumber)
    Dim Result As String
    If Val(MyNumber) = 0 Then Exit Function
    MyNumber = Right("000" & MyNumber, 3)
    ' Convert the hundreds place.
    If Mid(MyNumber, 1, 1) <> "0" Then
        Result = GetDigit(Mid(MyNumber, 1, 1)) & " Hundred "
    End If
    ' Convert the tens and ones place.
    If Mid(MyNumber, 2, 1) <> "0" Then
        Result = Result & GetTens(Mid(MyNumber, 2))
    Else
        Result = Result & GetDigit(Mid(MyNumber, 3))
    End If
    GetHundreds = Result
End Function
      
' Converts a number from 10 to 99 into text.
Function GetTens(TensText)
    Dim Result As String
    Result = ""           ' Null out the temporary function value.
    If Val(Left(TensText, 1)) = 1 Then   ' If value between 10-19...
        Select Case Val(TensText)
            Case 10: Result = "Ten"
            Case 11: Result = "Eleven"
            Case 12: Result = "Twelve"
            Case 13: Result = "Thirteen"
            Case 14: Result = "Fourteen"
            Case 15: Result = "Fifteen"
            Case 16: Result = "Sixteen"
            Case 17: Result = "Seventeen"
            Case 18: Result = "Eighteen"
            Case 19: Result = "Nineteen"
            Case Else
        End Select
    Else                                 ' If value between 20-99...
        Select Case Val(Left(TensText, 1))
            Case 2: Result = "Twenty "
            Case 3: Result = "Thirty "
            Case 4: Result = "Forty "
            Case 5: Result = "Fifty "
            Case 6: Result = "Sixty "
            Case 7: Result = "Seventy "
            Case 8: Result = "Eighty "
            Case 9: Result = "Ninety "
            Case Else
        End Select
        Result = Result & GetDigit _
            (Right(TensText, 1))  ' Retrieve ones place.
    End If
    GetTens = Result
End Function
     
' Converts a number from 1 to 9 into text.
Function GetDigit(Digit)
    Select Case Val(Digit)
        Case 1: GetDigit = "One"
        Case 2: GetDigit = "Two"
        Case 3: GetDigit = "Three"
        Case 4: GetDigit = "Four"
        Case 5: GetDigit = "Five"
        Case 6: GetDigit = "Six"
        Case 7: GetDigit = "Seven"
        Case 8: GetDigit = "Eight"
        Case 9: GetDigit = "Nine"
        Case Else: GetDigit = ""
    End Select
End Function