零、释义

  • milvus:向量数据库
  • langchain:python提醒工程框架

一、背景

  • 本篇文章基于一个BUG的排查和解决过程,试图还原在某些场景下多过程编程的【陷阱】,达到前事不忘;后事之师的成果。
  • 程序基于python,但论断和情理实用于所有语言

二、BUG问题体现

  • 最近的一段提醒工程相干的python代码,在不同操作系统的状况下,体现不一样

    • 在macos零碎与linux零碎的单过程、macos零碎的多过程状况下均能够失常运行:
    • 在linux的多过程状况下会卡在与milvus交互的中央,如下图

三、假如

  1. milvus服务端导致(磁盘满了、内存满了、服务忙碌等)
  2. 网络异样导致
  3. 操作系统导致
  4. 连贯milvus所用的底层调用包导致

四、假如验证和BUG排查思路

  1. ❌【milvus服务端导致】

    1. 独自测试了milvus的读写,服务自身没有问题,milvus所在服务器也是衰弱状态,不存在资源不足的状况。排除1
  2. ❌【网络异样导致】

    1. telnet端口通,ping通且稳固,网络是OK的。排除2
  3. ✅【操作系统导致】✅【连贯milvus所用的底层调用包导致】

    1. 初步判断为多过程导致的问题,那么为何macos中的多过程失常,linux零碎的多过程就有问题呢?
    2. 排查思维链(Chain-of-Thought)

      1. 于是翻阅python对于多过程模块的官网文档,直到看到了这样一段话

      2. python多过程在不同操作系统,默认启动子过程的形式是不一样的,在windows和macos上,默认应用【spawn】,而在linux上,默认是用【fork】,那么问题很有可能出在这两种不同的启动形式上。
      3. 本着控制变量法的debug形式,我在linux上将子过程的启动形式指定为了【spawn】,✅问题解决,程序胜利运行
      4. 至此,尽管外表上问题解决了, 但我对解决此BUG的播种只有:【spawn】大法好,对其余稍深层次的细节无所不知,遗留有一些关键问题:

        1. spawn是什么
        2. fork是什么
        3. 为什么针对此BUG,spawn能够,fork不行
        4. 如果咱们偏要用fork来做,行不行,怎么做?
      5. 于是,又回过头认真看了官网文档介绍以及 python官网issue讨论区,(如下图)

      6. spawn与fork概念如下

        1. spawn:从头构建一个子过程,父过程的数据等拷贝到子过程空间内,领有本人的Python解释器,所以须要从新加载一遍父过程的包,因而启动较慢,因为数据都是本人的,安全性较高
        2. fork:除了必要的启动资源外,其余变量,包,数据等都继承自父过程,并且是copy-on-write的,也就是共享了父过程的一些内存页,因而启动较快,然而因为大部分都用的父过程数据,所以是不平安的过程
      7. fork有可能导致不平安的过程,是因为fork用到copy-on-write技术,会继承父过程的数据和堆栈,由此导致一些不平安的问题。
      8. 那么针对此BUG,具体是哪个中央导致了不平安呢?

        1. 既然是milvus连贯出了错,那先从连贯下手,排查发现,
        2. 首先,主过程所在文件在import模块的时候,其中一个模块(文件)发动了一次milvus的连贯,如下图

        3. 而后,主过程开始启动子过程(fork),子过程调用langchain的milvus模块,langchain中milvus连贯初始化的代码是这样写的

        4. 子过程在上图中的步骤2的时候卡住,经排查是因为子过程基本没有连上milvus,然而步骤1明明曾经判断过,如果没有连贯,则创立。
        5. 再进一步看看connections.has_connection("default")这个函数,如下图

        6. 函数会判断self._connected_alias变量中是否有记录,进一步看看这个变量怎么来的

        7. 在连贯milvus时,程序保护一个self._connected_alias变量来记录是否存在连贯,connections.has_connection("default")函数只是去self._connected_alias中查看是否有连贯记录,
        8. 至此发现问题关键所在,父过程在第一次连贯milvus的时候,程序在self._connected_alias变量中记录了连贯信息,当fork子过程的时候,self._connected_alias变量被一并继承给了子过程,而当子过程应用connections.has_connection("default")函数判断与milvus的连贯状态的时候,发现了从父过程继承过去的self._connected_alias变量的已连贯信息,于是判断为已有连贯,导致子过程在理论没有连贯milvus的状况下间接加载milvus的数据,引发谬误。

五、解决方案

解决方案1

计划

  • 采纳spawn形式启动子过程

长处

  • 简略粗犷,子过程和父过程独立,数据隔离,过程平安
  • 拓展和保护绝对不便,不必放心相似的BUG

有余

  • spawn形式,会老老实实地copy父过程的数据(即便不须要),比拟占内存空间,启动会慢一些

解决方案2

计划

  • 采纳fork形式启动子过程,须要对代码做如下批改

    • 如果能够删除主过程中连贯milvus的代码

      • 将milvus连贯工作都放到子过程中做
    • 如果不能删除主过程中连贯milvus的代码

      • 在子过程判断与milvus是否已连贯的时候,不采纳connections.has_connection("default")函数,而是查看本过程本身的套接字连贯,防止来自父过程继承脏数据的净化,须要新增have_socket函数,做法如下
    def have_socket():  have_socket = False  process_netstat = psutil.Process(os.getpid())  for _socket in process_netstat.connections():      if _socket.raddr.port == MILVUS_PORT:          have_socket = True  return have_socketif not have_socket():  connections.connect(**connection_args)

长处

  • 采纳fork,子过程启动快,通过优化代码逻辑,防止过程不平安的状况

有余

  • 后续的代码拓展和保护都要留神代码逻辑,防止相似BUG

六、总结

  • 写多线程/多过程代码的时候,须要留神具体代码逻辑,防止继承的脏数据导致线程/过程不平安
  • 对于资源束缚不大,性能要求不高的场景,多过程一律用spawn

七、号外

  • 【python开发组音讯】将spawn在所有平台上设置为默认选项曾经提上日程 ,打算3.14版本正式上线

    • https://discuss.python.org/t/switching-default-multiprocessin...
  • 【fork的长处和利用场景】fork也不是一无是处,对于只读数据须要共享的状况,还是十分省内存资源,

    • 比方编写模型预测的并发服务,fork只加载1份模型到内存,而spawn会加载N份,gunicorn的-preload参数就是基于fork的copy-on-write技术,达到模型只加载一次的目标
In general, fork is bad, but it's also convenient and people rely on it to prepare data in a main process and then "duplicate" the process to inherit cooked data. -Victor Stinner

版本信息

  • python3.11.4
  • langchain==0.0.146

References

  1. Python crashes on macOS after fork with no exec
  2. multiprocessing's default posix start method of 'fork' is broken: change to 'spawn’
  3. Multiprocessing causes Python to crash and gives an error may have been in progress in another thread when fork() was called
  4. 机器学习模型API多过程内存共享
  5. 写时复制
  6. https://docs.python.org/3/library/multiprocessing.html
  7. https://discuss.python.org/t/switching-default-multiprocessin...