2024-10-12 12:01:44
在这篇博文中,笔者(山月)想记录自己如何解决BookWyrm部署过程中遇到的数据库问题,特别是在远程用户搜索时遇到的 “function similarity(character varying, unknown) does not exist” 错误。
背景是笔者(山月)从YunoHost搬迁到现有环境,使用Docker Compose来运行BookWyrm,但在搜索用户时总是出现数据库报错,提示找不到相似度函数。经过查找,了解到这是由于PostgreSQL中缺少pg_trgm
扩展导致的。
为了解决这个问题,笔者(山月)进入PostgreSQL数据库,并运行以下命令来安装所需的扩展:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
执行上述命令后,数据库中添加了相应的相似度函数,错误终于得到了修复,用户搜索功能也正常运行了。
看来,有时候问题并不是代码本身,而是数据库的配置。
希望这篇文章能帮助到遇到类似问题的人,如果你也在使用BookWyrm或其他基于PostgreSQL的应用,记得检查是否已安装所有必要的扩展,尤其是像pg_trgm
这样的函数库,它能为用户搜索和文本相似度匹配提供很大的帮助。
2024-10-12 11:28:53
最近笔者(山月)将BookWyrm应用从YunoHost迁移到了使用Docker Compose的独立服务器环境,然而在新的环境中遇到了严重的问题:数据库中的表无法正常新增内容,例如无法添加图书、作者,或更新阅读状态等。在调试过程中,每次进行数据迁移操作时,都会遇到数据库主键重复的报错。这篇博文记录了笔者解决这一问题的全过程,希望能够帮助遇到相似困境的朋友们。
在将BookWyrm从YunoHost迁移到Docker Compose环境后,笔者(山月)发现新增数据的操作频繁失败,无论是添加图书、作者,还是更新阅读状态,都会遇到数据库报错的问题。具体表现为,当执行Django的migrate
命令时,报错显示主键重复(例如 django.db.utils.IntegrityError: duplicate key value violates unique constraint
)。
笔者最初的解决方案是手动调整有问题的数据库表的主键自增计数器。但由于涉及的表非常多,一个个手动调整非常低效。这个过程也让笔者意识到,数据库迁移中某些自增序列可能没有同步,导致插入新记录时主键重复。
在多次尝试手动解决主键问题无果后,笔者(山月)决定寻找一种更加自动化的方法,来确保所有数据库表的自增序列都能够同步。笔者最终找到了一个有效的方法:编写一个函数自动校准数据库中所有表的自增序列。
这个函数通过遍历数据库中所有具有自增序列的表,查询每个表的最大主键值,并将自增序列设置为该最大值加一。这样,所有表的自增序列都能够与表中现有数据保持一致,避免了重复主键错误。
下面是这个自动校准函数的实现:
-- 避免重复创建函数
DROP FUNCTION IF EXISTS reset_sequences_for_tables();
-- 重新创建函数
CREATE OR REPLACE FUNCTION reset_sequences_for_tables() RETURNS VOID AS $$
DECLARE
table_name_text TEXT;
seq_name TEXT;
max_id INT;
default_value TEXT;
pk_column_name TEXT;
BEGIN
FOR table_name_text IN
SELECT t.table_name
FROM information_schema.tables AS t
WHERE t.table_schema = 'public'
AND EXISTS (
SELECT 1
FROM information_schema.columns AS c
WHERE c.table_name = t.table_name
AND c.column_default ILIKE 'nextval%'
)
LOOP
SELECT column_name INTO pk_column_name
FROM information_schema.columns AS c
WHERE c.table_name = table_name_text
AND c.column_default ILIKE 'nextval%';
SELECT column_default INTO default_value
FROM information_schema.columns AS c
WHERE c.table_name = table_name_text
AND c.column_name = pk_column_name;
seq_name := substring(default_value from E'\'(\\w+)\'::regclass');
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = seq_name) THEN
EXECUTE 'SELECT MAX(' || pk_column_name || ') FROM ' || table_name_text INTO max_id;
IF max_id IS NULL THEN
EXECUTE 'SELECT setval(' || quote_literal(seq_name) || ', 1, false)';
ELSE
EXECUTE 'SELECT setval(' || COALESCE(quote_literal(seq_name), 'null') || ', ' || max_id || ')';
END IF;
ELSE
RAISE NOTICE 'Sequence not found for table: %', table_name_text;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
创建函数后,执行它来重置所有自增序列的值:
SELECT reset_sequences_for_tables();
这一步将自动校准所有有自增序列的表的主键序列,让它们与表中的最大ID对应,避免重复主键的情况。
执行该函数后,所有表的自增序列都得到了自动校准,数据库主键重复的问题也顺利解决了。执行migrate
命令不再报错,BookWyrm的功能恢复正常,现在可以正常添加图书、作者和更新阅读状态。
在迁移BookWyrm应用时,数据库中的自增序列可能因为不同的环境和数据迁移方式而失去同步,导致主键重复的问题。这篇博客记录了如何通过编写一个函数,自动校准PostgreSQL数据库中所有表的自增序列,确保数据一致性并解决问题。
如果你也遇到类似的问题,希望这篇文章能够对你有所帮助。
2024-05-04 22:47:30
好吧,我标题党了,非常地微信,非常地小红书。主要是刚刚处理完问题,心情难以平抑。
简单来说,如果您是QNAP NAS使用者,在用QNAP官方提供的MariaDB(如,MariaDB 10)时,千万不要将MariaDB的配置文件(.conf
)中,default-storage-engine
这一行的默认配置改为InnoDB
。
经常使用QTS的朋友都知道,QTS虽然是基于Linux设计的,但QNAP有对Linux进行魔改,乃至我们常用的一些软件,如Apache、MySQL等,也都做了自己的魔改。网上的很多文档里提供的/data
路径、.conf
路径等,在QTS下也是讨不着的。想在QTS找到这些路径,有以下两种思路:
/share/CACHEDEV1_DATA/.qpkg/
下面对应的程序目录里翻翻看——例如传说中的MySQL配置文件my.cnf
,在QTS下是/.../.qpkg/MariaDB10/etc/mariadb.conf
;/etc
、/var
之类的目录找找——不过在QTS的场合,多半是一些管道软连接,例如/etc/my.cnf
实际指向/mnt/HDA_ROOT/.config/my.cnf
;而迷惑人的是,该文件可能是别的MySQL实例的配置文件,而不是我们平常容易用到的QTS应用市场释出的MariaDB的配置文件。言归正传,MariaDB的配置文件(.conf
),即/.../.qpkg/MariaDB10/etc/mariadb.conf
,其中的default-storage-engine
这一行,默认配置为MyISAM
。据说,虽然MyISAM可以作为临时解决方案,但考虑到数据库的完整性和性能,长期来看最好能使InnoDB可用。从个人的业务需求出发,当时是想要改为InnoDB
了。
顺带一提,为了确认所有可用的存储引擎及其状态,可以使用这则sql命令:
SHOW ENGINES;
例如,我的场合,就收到如此输出:
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+
(从略)
| MyISAM | DEFAULT | Non-transactional engine with good performance and small data footprint | NO | NO | NO |
(从略)
| InnoDB | NO | Supports transactions, row-level locking, foreign keys and encryption for tables | NULL | NULL | NULL |
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+
8 rows in set (0.000 sec)
再次言归正传。所以要我就查找 default-storage-engine
这一行,将其更改为:
default-storage-engine = InnoDB
并确保没有任何行包含 skip-innodb
。如果有,需要将其注释掉(在行前加 #
)或删除。
然后增加InnoDB相关配置(可选),为了优化 InnoDB 的性能。在 [mysqld]
部分添加以下配置:
innodb_buffer_pool_size = 1G # 根据你服务器的内存大小调整
innodb_log_file_size = 256M
innodb_log_buffer_size = 32M
innodb_flush_log_at_trx_commit = 1
innodb_file_per_table = 1
innodb_thread_concurrency = 8
修改配置文件后,需要重启数据库服务以使更改生效。一般能想到以下命令:
sudo systemctl restart mariadb.service
或者
sudo systemctl restart mysql.service
然而哪条在我的QTS都用不了。QTS图形界面下的AppCenter则不提供重启选项——这应该是为了数据稳定性考虑。于是我访问了QTS的shell界面,在App Management那边手动restart
了MariaDB。
注意,一旦你尝试start
或者restart
一款程序,就不要再发出或start
或restart
或stop
的指令了——直到你在图形界面确确实实看到系统消息栏显示该程序“已启动”(如果你的指令是restart
,那么你会先在系统消息栏看到“已停止”,过很久才会看到“已启动”,这时候才安全了),然后再去做你想要做的下一步指令。不要好像没有看到什么反应,就想要重复发出指令——在QTS下,这样会把数据搞坏。
按道理,如果数据库服务器重启成功,则需要验证InnoDB是否启用。重新连接到数据库,然后执行:
SHOW ENGINES;
检查 InnoDB 的状态是否为DEFAULT
或YES
。
我顺利重启了MariaDB,然而噩梦这才开始——从shell环境尝试访问mysql
,账号密码、什么的照常要求输入,但是输进去后就访问不进mysql命令行环境了。QTS的图形界面那边也打不开MariaDB 10——虽然软件状态显示为“已启动”。
我甚至重启了NAS,并再次尝试从shell、图形界面访问,仍然不行。一度以为是QTS restart一款程序会比较暴力,导致因进程被强行中断而数据坏掉。但又觉得官方提供的实践方式应该不至于不靠谱乃尔。出于试看看的心态找到MariaDB的配置文件(.conf
),将其中的default-storage-engine
这一行,改回默认配置,即MyISAM
。然后按前述方法从shell restart MariaDB 10。
一切恢复正常了。
作为结尾,所以,如果您是QNAP NAS使用者,在用QNAP官方提供的MariaDB(如,MariaDB 10)的时候,千万不要将MariaDB的配置文件(.conf
)中,default-storage-engine
这一行的默认配置改为InnoDB
。
2024-03-20 21:30:15
Wikibase是一组MediaWiki的扩展功能,用于在中央存储库中处理版本化数据。其主要组件包括:
Wikibase的核心功能是提供一个协作空间,供个人和团体以结构化的方式贡献、编辑和管理信息。它可以轻松地被计算机消费,翻译成多种语言,并作为链接开放数据网络的一部分与世界共享。
MediaWiki具备以.xml
格式导出内容的功能,结构化的Wikibase内容(属性、项目、词位)自然不例外。
但是,如果直接从[[Special:Import]]
页面上传.xml
,会被告知无法导入Wikibase内容。因为系统默认阻止这样的批量上传,避免Wikibase属性(P
后面的数字)、项目(Q
后面的数字)、词位(L
后面的数字)的唯一标识符冲突。
通过在MediaWiki服务器根目录的LocalSettings.php
中,加入一行这样的代码:
# 启用Wikibase项目的导入
$wgWBRepoSettings['allowEntityImport'] = true;
来取消限制。
但是这样导入之后,再想通过[[Special:NewItem]]
新建项目,却会被告知:
不可以建立一个新页面。 它已经存在。
这是因为想要新建的页面的唯一标识符与现存页面已有的唯一标识符冲突了。
——等等,可是我们在新建项目的时候,又没办法强行指定唯一标识符。难道是系统在不知道唯一标识符已经被占用的情况下,还想要指定该唯一标识符?
是的。
在MediaWiki所使用的数据库里,我们能看到这一数据表:
****_wb_id_counters
****
是实际设置的表头。每个人设的表头并不一样。在LocalSettings.php
中可以找到表头设定。例如笔者是这么设置的:
# MySQL具体设置
$wgDBprefix = "sanguok_";
那么我就会看到这样的数据表名:
sanguok_wb_id_counters
好的。那么在这一数据表中,有以下内容(以笔者情况为例):
id_type | id_value |
---|---|
77696B69626173652D6974656D | 6 |
在Wikibase的wb_id_counters
表中,id_type
和id_value
列用来追踪不同类型实体的ID计数。这里的77696B69626173652D6974656D
实际上是一个十六进制编码的字符串。当将这个十六进制字符串解码为ASCII文本时,它代表了某种类型的实体标识符。
让我们将77696B69626173652D6974656D
这串十六进制代码解码成ASCII文本来看看它表示什么:
hex_string = "77696B69626173652D6974656D"
ascii_string = bytes.fromhex(hex_string).decode('utf-8')
ascii_string
解码后,77696B69626173652D6974656D
代表的ASCII文本是wikibase-item
。这意味着在wb_id_counters
表中,这个条目是用来追踪Wikibase中“项目”(即item,例如Q1
、Q2
等)的最新ID计数的。在我的情况中,id_value
为6
,这表明追踪到的最新项目ID是Q6
。
然而,在我的实际情况里,经过批量导入后,Wikibase中最新的条目是item:Q10000
,这显然与wb_id_counters
表中的记录不匹配。
为了解决这个问题,需要根据实际的最新项目ID(在我的情况下是Q10000
),手动更新wb_id_counters
表中相应wikibase-item
类型的id_value
——例如在我的情况下,改为10000
。
之后,再试看看能否透过[[Special:NewItem]]
新建条目。
应该能够成功的。
2024-02-26 13:39:18
神保町這邊,素以舊書店之多聞。上次認認真真逛神保町的舊書店,已經是大疫以前的事情了。前兩年重返日京,也去了兩回神保町左近,但都是赴飯局。東京舊書街的話,本鄉正門前一帶、白山京華路逛過幾回,早稻田、高馬沿途則是過門未入——畢竟現在未在彼處讀書,沒法以前那樣通學途中順路翻兩頁。
今日由九段下車站落車,六號出口出站。於是一路向北。
先到了玉川堂。這是一家書道相關的店鋪。門口的玻璃櫥窗內,擺了一尊《九成宮醴泉銘》碑的縮小件。店內售有日文書道帖、寺社朱印帳、筆、墨等。前日謁櫻神宮時,已經「重金」求得了朱印帳,否則可能還真考慮在這裏買一冊。——不過據說,最好分別以兩本不同的朱印帳,去收集神道神社和佛教寺院的御朱印,因部分神職人員不會寫混有兩種御朱印的朱印帳。這樣的話,似乎再買一冊,亦是一擇。
山本書店,內有大量中國文、史關聯書籍。至其帙冊之語言,有日語,也有中文。這家中文書店,倒是我以前印象較淺的,算是今日收穫了。淺淺翻閱了幾本書:
接著去了波多野書店。店內則與前一家大不同,賣的是日本相關書籍了——歷史方面等等。在這裏買了:
凡兩本。皆屬岩波文庫。都是高中時候就聽聞,想著有日能一睹的書了。因爲實在是對這個曾經同樣通行漢文的國家,如何面對近代化浪潮的,留下了哪些時代的肉筆,而感到興趣。
長島書店。店內文學的書佔不少,大概可分爲日本文學,以及華語文學(的日譯,及其在日本、以日語寫就的研究著作)。
文華堂。滿櫥子都是戰爭相關的書籍。近代以來、二戰爲止的書又佔了絕大部分——例如滿蒙問題、舊日軍等。在此之前的,例如剛步入近代時的削藩,也有見到相關書籍。
中川書店。這本書基本可以說是迎合日本大部分中年男人趣味的書店了——大人的教養,曾經的日本有多好,日本該往何處去之類。
南海堂書店。以日本史的書籍爲主,其中大量近代史內容。
之後進到了神田古書センター(神田古書中心)。是一棟大樓,一到五層都是舊書店。一層是書肆高山本店。店中書類,跨度很大,趣味性質很強。一半是日本傳統戲曲,剩下一半是廚藝、武術(日本武道、中國武術——少林拳法、氣功等)、書道字帖之類。
二層爲漫畫店——夢野書房。大部分都是上了年頭的漫畫單行本、雜誌,紙面都有些泛黃。然而有一本《鬼滅之刃》,堂而皇之立在中間,竟毫無違和感。
三層爲鳥海書房,主打自然主題,動植物、本草、釣魚相關。四層爲梓書房,教育關聯。
五層爲みわ書房,都是舊童書。有過道通往薰風花乃堂,爲古物、礦物關聯,牆上也掛了一些植物畫。
古書中心旁邊,是神保町ブックセンター(神保町圖書中心)。一層爲書店+咖啡店,可以在店內消費飲食,一邊喝咖啡一邊從壁櫥上取書看;也可以只買書;也可以二者兼有。
二層是秦川堂書店。店內裝潢頗爲考究。店內的古書,有百年前的教科書、老文獻等。此外有老地圖、經年但無字跡的美術明信片、舊觀光手冊、舊溫泉攻略、舊書籤等古物,即便不是專門的收藏家,拾其一二,以爲裝點,也頗雅緻。
神保町一帶不止舊書店、藝術品店,一路逛下來,還有看到家古著店。
街角拐彎後,到了大雲堂書店。售書以日本史爲主,也有書道的內容。
明倫館書店,屬於自然科學系(雖然門口裝有文學賞的應徵廣告),內爲國內外的自然科學、建築、風景園林讀物。想到以前朋友來日本看望我,其中有學習風景園林者,我也有帶他們來這裏逛。
一誠堂書店。賣日本文學的書,也有賣日本近代以來出版事情的書。對於出版宅、書物宅(有這類宅的吧?)來說,應會感到興奮。看到書店的棕色櫃檯,我就想起一段往事。那時候我第一次來神保町,到這邊興奮地挑書,結帳的時候發現帶的錢不夠,還差一兩百日元。店頭的老爺爺問我是不是學生,從哪裏來,在哪裏唸書,教我務必勤勉於學習,然後居然把這不足的錢給免了。這實在是讓我很意外,我都做好割愛的準備了。想來實在暖心。
澤口書店的書,則與以下界隈相關:鐵道、電影、思想。當然也有一些日本文學作品(例如宮澤賢治)的初版。
不遠處,小宮山書店令我十分震撼。這家不純賣書。一層就是舊電影海報、畫集、寫真集、單幅畫作、單幅寫真等,陳列於櫥櫃,掛於壁上待沽。但如是則已矣。登到二層,簡直是三島樂園。牆上貼著三島由紀夫的照片(其中有與川端康成同框的照片),賣有三島文學研究的著作、三島由紀夫的真跡。其中一幅,是縱書「三島由紀夫」五字。一幅寫著:
鍊武
又一幅寫著:
處卋若大夢
三層則是一些前衛藝術。四層未去,震撼而離店。
最後從神保町車站上車,結束了一日的覯書之旅。
當然,神保町一帶的書店,遠不止這些。有些人統計爲四百家,有些統計則爲130家。今天隨便走走逛逛,已感到有些累。要全部逛個遍,相信需要時間。
今天所逛過的書店,也並沒有盡記在上,只是挑了些特點殘留在腦海中的寫了。其實神保町有兩家在愛書人士當中相當有名的中文書店——東方書店、內山書店。
一直幻想在這樣的舊書店中打工的樣子。整理這些老書,與不同時代、地域的思想們對話,與懂行的顧客暢聊,與乘興而來的路人介紹自己的所愛。翻書,對客,個中趣事還可與網上書友分享。多事一件美事。以前關注過一位在這樣的書店裏打工的博主,後來他回中國任教了。往後沒在網上再看到相關的分享了。
2023-12-22 18:38:00
众所周知,WordPress,简直电老虎来的。
从笔者实际运营经验来看,查看服务器状态,就会经常看到php-fpm: pool (站点域名)
这一进程,对鄙服务器CPU资源的占用,其最低线稳定于⅔,动辄则99%,甚至于满格。
为此可能需要调整pm.max_children
、pm.start_servers
、pm.min_spare_servers
和pm.max_spare_servers
这些参数。
PHP-FPM站点池的配置文件通常位于/etc/php/(版本号)/fpm/pool.d/www.conf
。当然,有时根据实际情况,修改的对象会是/etc/php/(版本号)/fpm/pool.d/(具体域名).conf
。
在一些环境下,上述文件可能不支持直接修改,或者修改后也不会永久性保存。这时则根据实际情况来找寻配置位置,例如网站根目录下的.user.ini
(记得配置好正确的文件权限)。
在许多PHP-FPM默认配置文件里,pm
模式会被设置为ondemand
,而pm.max_children
的值被设置为4。在ondemand
模式下,PHP-FPM只会在请求到达时启动新的子进程,并在子进程空闲一段时间后关闭它们。这种模式对于低流量的网站是非常有效的,但对于高流量的网站可能会导致额外的CPU消耗,因为需要频繁地创建和销毁子进程。以下是一些可能的优化建议:
修改进程管理模式:
pm
模式从ondemand
更改为dynamic
。在dynamic
模式下,PHP-FPM将保持一定数量的空闲进程,以便能够更快地响应新的请求。pm = dynamic
增加最大子进程数:
pm.max_children
的值决定了同时能处理多少请求。如果服务器资源允许,你可能想要增加这个值以处理更多的并发请求。pm.max_children = 16 ; 或更高的值,视服务器资源而定
设置服务器进程数:
dynamic
模式下,你还可以设置pm.start_servers
,pm.min_spare_servers
和pm.max_spare_servers
参数来控制空闲进程的数量。例如:pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
优化最大请求数:
pm.max_requests
参数决定了每个子进程在重新生成之前可以处理多少请求。这可以防止可能的内存泄漏。你可以根据贵站的具体情况调整这个值。pm.max_requests = 5000 ; 或其他适合你的网站的值,4000可能够用了
调整空闲进程超时:
pm.process_idle_timeout
参数决定了一个子进程在被关闭之前可以保持空闲多长时间。你可以根据需要调整这个值。pm.process_idle_timeout = 20s ; 或其他适合你的网站的值
以上建议可以帮助你优化PHP-FPM的配置,以更好地处理高流量并减少CPU的消耗。每次修改配置后,确保重启PHP-FPM服务以使更改生效。同时,建议在一个测试环境中尝试这些更改,以确保它们不会影响贵站的正常运行。
PHP版本:
慢日志:
WordPress网站报错和服务器资源过度使用,也可能是由多种因素造成。
本文仅聚焦于PHP配置,只不过若果按照本文方式修改,仍有问题,也可以考虑下如下方向:
插件问题:
主题问题:
代码优化:
数据库优化:
缓存:
CDN服务:
服务器配置:
服务器资源:
定期监控: