This error occurs when using relative move methods (such as MovePrevious, MoveNext, and so on) with a dynamic cursor resultset.
It can also occur when you use a dynamic cursor after another user has deleted rows just above or below your current rowset window (that is, the rows you have cached on your client). When you move toward the deleted rows, some of the rows in your current window return since you are asking for the previous or next rowset size of rows. When you're near the end or beginning of a resultset, you can get data overlap since there are not enough rows to get a unique new set.
Setting a lower rowset size can help solve this problem. It degrades performance, but if performance is a concern, you're better off not using a dynamic cursor in the first place, since they're not the speediest performers when moving data compared to other cursors.
Because of this, and because of the problem with error messages when data has been deleted, it is recommended that you avoid using dynamic cursors unless you need them and know what you're doing. A keyset or static cursor is much easier to deal with than a dynamic cursor, and a client-side batch static is probably the best overall choice for most client/server development.