在并行Python中使用回调更新数据库

时间:2012-07-15 00:01:22

标签: python sqlalchemy parallel-python

我正在尝试对我正在使用SQLAlchemy访问的SQlite数据库中的大约200,000个条目进行一些文本处理。我想并行化它(我正在看并行Python),但我不确定如何做到这一点。

我希望每次处理一个条目时都提交会话,这样如果我需要停止脚本,我就不会丢失它已经完成的工作。但是,当我尝试将session.commit()命令传递给回调函数时,它似乎不起作用。

from assignDB import *
from sqlalchemy.orm import sessionmaker
import pp, sys, fuzzy_substring

def matchIng(rawIng, ingreds):
maxScore = 0
choice = ""
for (ingred, parentIng) in ingreds.iteritems():
    score = len(ingred)/(fuzzy_substring(ingred,rawIng)+1)
    if score > maxScore:
        maxScore = score
        choice = ingred
        refIng = parentIng  
return (refIng, choice, maxScore)

def callbackFunc(match, session, inputTuple):
    print inputTuple
    match.refIng_id = inputTuple[0]
    match.refIng_name = inputTuple[1]
    match.matchScore = inputTuple[2]
    session.commit()

# tuple of all parallel python servers to connect with
ppservers = ()
#ppservers = ("10.0.0.1",)

if len(sys.argv) > 1:
    ncpus = int(sys.argv[1])
    # Creates jobserver with ncpus workers
    job_server = pp.Server(ncpus, ppservers=ppservers)
else:
    # Creates jobserver with automatically detected number of workers
    job_server = pp.Server(ppservers=ppservers)

print "Starting pp with", job_server.get_ncpus(), "workers"

ingreds = {}
for synonym, parentIng in session.query(IngSyn.synonym, IngSyn.parentIng): 
    ingreds[synonym] = parentIng

jobs = []
for match in session.query(Ingredient).filter(Ingredient.refIng_id == None):
    rawIng = match.ingredient
    jobs.append((match, job_server.submit(matchIng,(rawIng,ingreds),    (fuzzy_substring,),callback=callbackFunc,callbackargs=(match,session))))

会话从assignDB导入。我没有收到任何错误,只是没有更新数据库。

感谢您的帮助。

更新 这是fuzzy_substring

的代码
def fuzzy_substring(needle, haystack):
    """Calculates the fuzzy match of needle in haystack,
    using a modified version of the Levenshtein distance
    algorithm.
    The function is modified from the levenshtein function
    in the bktree module by Adam Hupp"""
    m, n = len(needle), len(haystack)

    # base cases
    if m == 1:
        return not needle in haystack
    if not n:
        return m

    row1 = [0] * (n+1)
    for i in range(0,m):
        row2 = [i+1]
        for j in range(0,n):
            cost = ( needle[i] != haystack[j] )

            row2.append( min(row1[j+1]+1, # deletion
                               row2[j]+1, #insertion
                               row1[j]+cost) #substitution
                           )
        row1 = row2
    return min(row1)
我从这里得到的是Fuzzy Substring。就我而言,“针”是~8000种可能的选择之一,而haystack是我想要匹配的原始字符串。我循环遍历所有可能的“针”并选择得分最高的那针。

2 个答案:

答案 0 :(得分:3)

不看你的具体代码,可以说:

  1. 使用无服务器的SQLite和
  2. 通过并行寻求提高写入性能
  3. 是互不相容的欲望。请SQLite FAQ

      

    ...但是,客户端/服务器数据库引擎(如PostgreSQL,MySQL,   或Oracle)通常支持更高级别的并发和允许   多个进程要同时写入同一个数据库   时间。这在客户端/服务器数据库中是可能的,因为存在   始终是一个可以协调的良好控制的服务器进程   访问。如果您的应用程序需要大量并发,那么   您应该考虑使用客户端/服务器数据库。但经验   表明大多数应用程序需要的并发性要比它们少得多   设计师想象。 ...

    即使没有SQLAlchemy使用的任何门控和排序,这也是如此。根本不清楚 - 如果有的话 - 并行Python工作正在完成。

    我的建议:让它正常工作首先,然后寻找优化。特别是pp秘密酱可能根本不会给你带来太大的收益,即使它工作得很好。

    在回复评论时添加

    如果fuzzy_substring匹配是瓶颈,它似乎与数据库访问完全分离,您应该记住这一点。在没有看到fuzzy_substring正在做什么的情况下,一个好的开始假设是你可以进行算法改进,这可以使单线程编程在计算上可行。 Approximate string matching是一个研究得很好的问题,选择正确的算法通常远比“投入更多处理器”好得多。

    在这个意义上更好的是你有更清晰的代码,不浪费分割和重新组合问题的开销,最后有一个更可扩展和可调试的程序。

答案 1 :(得分:0)

@msw提供了an excellent overview of the problem,给出了考虑并行化的一般方法。

尽管有这些评论,但我最终还是要完成这项工作:

from assignDB import *
from sqlalchemy.orm import sessionmaker
import pp, sys, fuzzy_substring  

def matchIng(rawIng, ingreds):
    maxScore = 0
    choice = ""
    for (ingred, parentIng) in ingreds.iteritems():
        score = len(ingred)/(fuzzy_substring(ingred,rawIng)+1)
        if score > maxScore:
            maxScore = score
            choice = ingred
            refIng = parentIng  
    return (refIng, choice, maxScore)

# tuple of all parallel python servers to connect with
ppservers = ()
#ppservers = ("10.0.0.1",)

if len(sys.argv) > 1:
    ncpus = int(sys.argv[1])
    # Creates jobserver with ncpus workers
    job_server = pp.Server(ncpus, ppservers=ppservers)
else:
    # Creates jobserver with automatically detected number of workers
    job_server = pp.Server(ppservers=ppservers)

print "Starting pp with", job_server.get_ncpus(), "workers"

ingreds = {}
for synonym, parentIng in session.query(IngSyn.synonym, IngSyn.parentIng): 
    ingreds[synonym] = parentIng

rawIngredients = session.query(Ingredient).filter(Ingredient.refIng_id == None)
numIngredients = session.query(Ingredient).filter(Ingredient.refIng_id == None).count()
stepSize = 30

for i in range(0, numIngredients, stepSize):
    print i
    print numIngredients

    if i + stepSize > numIngredients:
        stop = numIngredients
    else:
        stop = i + stepSize

    jobs = []
    for match in rawIngredients[i:stop]:
        rawIng = match.ingredient
        jobs.append((match, job_server.submit(matchIng,(rawIng,ingreds),    (fuzzy_substring,))))

    job_server.wait()

    for match, job in jobs:
        inputTuple = job()
        print match.ingredient
        print inputTuple
        match.refIng_id = inputTuple[0]
        match.refIng_name = inputTuple[1]
        match.matchScore = inputTuple[2]
    session.commit()

基本上,我把问题分成了几块。在并行匹配30个子字符串后,将返回结果并将其提交到数据库。我有点随意选择30,所以在优化这个数字时可能会有所收获。它似乎加快了一点,因为我现在使用处理器中的所有3(!)内核。