Sobes.tech
Senior

/ * There are two PostgreSQL servers: * PROD - OLTP server, * STATS - server for long analytical queries. Currently, the production database has a large (10Tb) table: CREATE TABLE profiles( id SERIAL, data JSONB ) The table may have "holes", i.e., some `id`s may be skipped. You need to write a program to copy the profiles table from PROD to STATS. The following interfaces are assumed for database interaction: type Row []interface{} type Database interface { // the implementation of the Database interface can re-establish connections // the call to SaveRows is idempotent io.Closer GetMaxID(ctx context.Context) (uint64, error) LoadRows(ctx context.Context, minID, maxID uint64) ([]Row, error) // [minID, maxID] SaveRows(ctx context.Context, rows []Row) error } func Connect(ctx context.Context, dbname string) (Database, error) // CopyTable // If full=false, then continue data transfer from the last error point // If full=true, then transfer all data func CopyTable(fromName string, toName string, full bool) error { // ... your code } If the `full=false` option is passed, the program should continue transferring data from the last error point. If `full=true`, it should transfer all data. **Basic level**: - sequential data transfer in 1 thread - recovery after failures (option `full=false`) Additional information: - if necessary, you can extend the interface by adding your methods - if necessary, you can use the **database/sql** package directly

sobes.tech AI

Answer from AI

To solve the task of copying a large profiles table from the PROD server to STATS with the ability to resume after failures and an option for full copying, the following approach can be implemented:

  1. Determine the last successfully copied ID on the STATS server. For this, you can add a method to the Database interface, for example, GetMaxCopiedID(ctx context.Context) (uint64, error), which will return the maximum id already copied into the table on the STATS server.

  2. When starting the CopyTable function:

    • If full=true, start copying from id=1 (or the minimum id in the PROD table).
    • If full=false, request the last copied id on STATS and start copying from id = lastCopiedID + 1.
  3. For copying data, use batch ranges of ids, for example, 1000 records at a time. In a loop:

    • Get the maximum id in the PROD table via GetMaxID.
    • Load rows from PROD with ids in the range [startID, startID+batchSize-1] via LoadRows.
    • Save them to STATS via SaveRows.
    • Update startID = startID + batchSize.
  4. If a failure occurs during copying, the next run with full=false will resume from the last successfully saved id.

Example of extending the interface and implementing CopyTable:

// Extending the Database interface to get the maximum copied id
func (db Database) GetMaxCopiedID(ctx context.Context) (uint64, error) {
    // Implementation depends on the structure of the table on STATS
    // For example, SELECT MAX(id) FROM profiles
}

func CopyTable(fromName string, toName string, full bool) error {
    ctx := context.Background()
    fromDB, err := Connect(ctx, fromName)
    if err != nil {
        return err
    }
    defer fromDB.Close()

    toDB, err := Connect(ctx, toName)
    if err != nil {
        return err
    }
    defer toDB.Close()

    var startID uint64 = 1
    if !full {
        startID, err = toDB.GetMaxCopiedID(ctx)
        if err != nil {
            return err
        }
        startID++
    }

    maxID, err := fromDB.GetMaxID(ctx)
    if err != nil {
        return err
    }

    batchSize := uint64(1000)

    for currentID := startID; currentID <= maxID; currentID += batchSize {
        endID := currentID + batchSize - 1
        if endID > maxID {
            endID = maxID
        }

        rows, err := fromDB.LoadRows(ctx, currentID, endID)
        if err != nil {
            return err
        }

        if len(rows) == 0 {
            continue
        }

        err = toDB.SaveRows(ctx, rows)
        if err != nil {
            return err
        }
    }

    return nil
}

Thus, the program sequentially transfers data, recovers after failures, and supports the full copy option.