On Fri, Jan 23, 2009 at 2:38 AM, Shane Hathaway <sh...@hathawaymix.org> wrote:
> Santi,
>
> I hope you don't mind me discussing your database in public.  I'm not going
> to talk about anything that looks like it could be private.  Other
> RelStorage users might benefit from the analysis.
>

Hi Shane

That's right, of course.  Furthermore, thanks a lot for this analysis.

> Looking at your database, I see that something bad happened just before
> transaction 250499913441768123.  That number is an encoded time stamp:
>
>  >>> from ZODB.TimeStamp import TimeStamp
>  >>> from ZODB.utils import p64
>  >>> str(TimeStamp(p64(250499913441768123)))
>  '2008-11-17 19:36:04.913130'
>
> The transaction log entry says "initial database creation", which means that
> the database had no root object (OID 0), so ZODB created one and started a
> brand new database.  Strange!  This happened about an hour after a
> transaction labeled:
>
>  /asp_ekartek/kmkey_iso/portal_setup/manage_doUpgrades
>
> I'm guessing that an upgrade script did something horribly wrong that day.
>

I've been revising what happens this day, and I think we are near to
get the guilty.    This day the database was migrated from
DirectoryStorage to Relstorage.    AFAIK, the upgrade should be done
in DirectoryStorage, before the conversion to Relstorage.   I don't
think the upgrade can corrupt the database, all operations ara high
level ones, and we never change ZODB objects by hand.   So, the
problem should be in the conversion process.

I attach the script we use to do the conversion.   Be free to include
in Relstorage if you think it's useful and it is well done (as I said,
I really don't know much about ZODB, I just mix zodbconvert.py with
some DirectoryStorage code)

> Furthermore, the entry for OID 0 in the current_object table points to an
> old transaction rather than the most recent transaction that modified OID 0.
>  That's not supposed to happen, even when you undo.  I hope RelStorage
> didn't do that!
>
> Did you or someone on your team change current_object by hand?  I can
> understand why you would, since a simple modification to current_object
> would be a nice quick fix for the broken upgrade.  The fix would not be
> complete, though, because now the object_state table and the current_object
> table disagree on the current state of OID 0.
>
> According to object_state, even now, the current state of OID 0 still points
> to the small object graph that was accidentally created on November 17.  The
> pack code relies more on object_state than on current_object, so the pack
> code sees only a handful of objects that are reachable.  Packing with
> garbage collection removes everything that is not reachable.
>
> The current_object table is really just a cache of object_state.  If the
> schema were fully normalized, there would not be a current_object table.  In
> theory, the current_object table makes it possible to load ZODB objects
> quickly.  But if the current_object table results in problems like this, I
> need to consider alternatives.
>

We've used the attached script to convert a lot of others databases,
that are packing successfully, so something special should occurs in
this case.    The only explanation I can found is that the conversion
would be done without unmounting the DirectoryStorage database from
its Zope mount point, and that caused the problem.   The right way we
use to convert databases from DS to RS is, first of all, detach them
from Zope, then convert, and then mount the resultant RS, but It's
possible that were a human mistake that day.

> In any case, I believe you can get out of this mess pretty easily.  You need
> to delete the extra object states for OID 0 created on November 17.  I tried
> this in my copy of your database:
>
> delete from object_state where zoid = 0 and tid in (
>  250499913441768123, 250499913748614178);
>
> After that, "select count(1) from object_state where zoid = 0;" should tell
> you there is only one state in the database for OID 0.  Packing should work
> fine then.  It seemed to do the right thing on my copy, but I don't have
> your application code to check it.
>

Thank you very much for that information.  I really could not be able
to found it myself.    We will try it as soon as possible and let you
know the results

Regards
-- 
Santi Camps (Earcon S.L.)
http://www.earcon.com
http://www.kmkey.com
#!/usr/bin/env python
##############################################################################
#
# Copyright (c) 2008 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZODB storage conversion utility.

See http://wiki.zope.org/ZODB/ZODBConvert for details.
"""

import logging
import optparse
from persistent.TimeStamp import TimeStamp
from StringIO import StringIO
import sys
import ZConfig
from ZODB.utils import oid_repr
from DirectoryStorage.FullSimpleIterator import FullSimpleIterator
from DirectoryStorage.snapshot import snapshot

schema_xml = """
<schema>
  <import package="ZODB"/>
  <import package="relstorage"/>
  <import package="DirectoryStorage"/>
  <section type="ZODB.storage" name="destination" attribute="destination"
    required="yes" />
</schema>
"""


def storage_has_data(storage):
    return True


def main():
    logging.basicConfig()

    parser = optparse.OptionParser(description=__doc__,
        usage="%prog [options] config_file dirstorage_path")
    parser.add_option(
        "--dry-run", dest="dry_run", action="store_true",
        help="Attempt to open the storages, then explain what would be done")
    parser.add_option(
        "--clear", dest="clear", action="store_true",
        help="Clear the contents of the destination storage before copying")
    parser.add_option(
        "-v", "--verbose", dest="verbose", action="store_true",
        help="Show verbose information while copying")
    parser.set_defaults(dry_run=False, clear=False, verbose=False)
    options, args = parser.parse_args()

    if len(args) != 2:
        parser.error("The name of one configuration file is required, and the path of dirstorage too.")

    schema = ZConfig.loadSchemaFile(StringIO(schema_xml))
    config, handler = ZConfig.loadConfig(schema, args[0])
    destination = config.destination.open()

    source =  FullSimpleIterator(args[1], options.verbose)

    print "Storages opened successfully."

    if options.dry_run:
        print "Dry run mode: not changing the destination."
        if storage_has_data(destination):
            print "Warning: the destination storage has data"
        count = 0
        for txn in source.iterator():
            print '%s user=%s description=%s' % (
                TimeStamp(txn.tid), txn.user, txn.description)
            for rec in txn:
                print '  oid=%s length=%d' % (oid_repr(rec.oid), len(rec.data))
            count += 1
        print "Would copy %d transactions." % count
        sys.exit(0)

    if options.clear:
        if hasattr(destination, 'zap_all'):
            destination.zap_all()
        else:
            msg = ("Error: no API is known for clearing this type of storage."
                " Use another method.")
            sys.exit(msg)

    destination.copyTransactionsFrom(source, verbose=options.verbose)

    source.close()
    destination.close()

    print 'All transactions copied successfully.'


if __name__ == '__main__':
    main()

_______________________________________________
For more information about ZODB, see the ZODB Wiki:
http://www.zope.org/Wikis/ZODB/

ZODB-Dev mailing list  -  ZODB-Dev@zope.org
http://mail.zope.org/mailman/listinfo/zodb-dev

Reply via email to