存储大量简单数据的建议

Jer*_*nin 1 database-design sql-server

我希望抓取大量网页 (500,000,000,000) 记录并能够存储链接结构以备日后使用。我计划布置数据库的方式如下:

2张桌子

表:页面

ID            URL - Max Length = 2048 chars
----------    -------------------------------
1             http://www.site1.com/page.php
2             http://www.site2.com/page-abc.php
3             http://www.site3.com/page-1.php
4             http://www.site4.com/page-cd.php
5             http://www.site5.com/page-nice.php
6             http://www.site6.com/page-some.php
7             http://www.site7.com/page-hrmm.php
8             http://www.site8.com/page-stack.php
9             http://www.site9.com/page-ex.php
10            http://www.site10.com/page-dba.php
Run Code Online (Sandbox Code Playgroud)

表:链接

Page          Links
----------    -------------------------------
2             1
3             1
4             1
5             1
6             1
7             1
8             1
8             9
9             1
10            1
Run Code Online (Sandbox Code Playgroud)

基本上,我将能够看到哪些网页链接到每个网站的重复/几级深度。我想绘制一个大型网站网络及其链接模式。

所以我需要知道是否有更好的方法来这样做,也许还有一些关于如何设计数据库结构/系统的建议。我计划从 PostgreSQL 开始,因为我已经使用过它,但是有了这么多的数据,我对任何事情都持开放态度。

Aar*_*and 6

为了减少空间需求,您可以考虑以下几点:

  1. 不费心去存储http://或引导www.——这只是浪费空间(尽管在少数情况下这www.是必需的,因为人们不知道如何正确配置他们的网站)。
  2. 确保使用数据压缩。大多数系统仍然受 I/O 限制,而不是受 CPU 限制。
  3. 任何域名只存储一次,页面URL单独存储。两者实际上都可能重复并且多次存储它们是浪费的。因此,例如而不是:

    CREATE TABLE dbo.Pages
    (
      ID BIGINT IDENTITY(1,1) PRIMARY KEY, -- need BIGINT for 500 billion rows
      URL VARCHAR(2048) -- far too large to apply UNIQUE
    );
    
    CREATE TABLE dbo.PageLinks
    (
      PageID BIGINT NOT NULL FOREIGN KEY REFERENCES dbo.Pages(ID),
      LinkID BIGINT NOT NULL FOREIGN KEY REFERENCES dbo.Pages(ID),
      PRIMARY KEY (PageID, LinkID)
    );
    
    Run Code Online (Sandbox Code Playgroud)

    你可以这样做:

    CREATE TABLE dbo.Domains
    (
      DomainID INT IDENTITY(1,1) PRIMARY KEY, -- probably no more than 2BN domains
      DomainName VARCHAR(255) NOT NULL
    ) WITH (DATA_COMPRESSION = PAGE);
    
    CREATE UNIQUE INDEX dn ON dbo.Domains(DomainName)
      WITH (DATA_COMPRESSION = PAGE);
    
    CREATE TABLE dbo.URLs
    (
      URLID INT IDENTITY(1,1) PRIMARY KEY, -- maybe you need BIGINT here
      URL VARCHAR(2048) NOT NULL -- still can't apply UNIQUE here
      -- but you can have the same URL (e.g. /page.php) from two different 
      -- domains only listed once.
    );
    
    CREATE TABLE dbo.DomainURLs
    (
      DomainURLID INT IDENTITY(1,1) PRIMARY KEY, -- may also need BIGINT here
      DomainID INT NOT NULL FOREIGN KEY REFERENCES dbo.Domains(DomainID),
      URLID INT NOT NULL FOREIGN KEY REFERENCES dbo.URLs(URLID)
    ) WITH (DATA_COMPRESSION = PAGE);
    
    CREATE UNIQUE INDEX du ON dbo.DomainURLs(DomainID, URLID)
      WITH (DATA_COMPRESSION = PAGE);
    
    Run Code Online (Sandbox Code Playgroud)

    是的,表设计和查询语义会复杂得多,但是一旦您索引同一站点上的许多页面(或跨不同站点具有相同 URL 的许多页面),它的扩展性会更好。


只是为了展示这里节省空间的潜力。只考虑 URL 的存储并忽略它们之间的链接,让我们只看页面、域和 URL 表(公平地说,我什至会用压缩测试您的页面表)。

请注意,我们利用了这样一个事实,即您可能会按字母顺序依次索引每个站点,而不是在每次迭代时散列和索引随机 URL。这允许压缩在这种情况下尽可能好地工作。

对于一些示例数据,在我的系统上这会生成大约 121,000 行,但这取决于很多因素,例如系统上有多少数据库、数据库中有多少对象、任何非系统对象的设计(例如列数),甚至@@VERSION:

;WITH x AS 
(
  SELECT 
    d = t.name + CONVERT(VARCHAR(5), d.database_id) + '.com', 
    p = '/' + c.name + '.php'
   FROM sys.all_objects AS t
   CROSS JOIN sys.databases AS d
   INNER JOIN sys.all_columns AS c
   ON t.[object_id] = c.[object_id]
 )
 SELECT d, p 
 FROM x
 ORDER BY d, p;
Run Code Online (Sandbox Code Playgroud)

示例结果:

all_columns1.com    /collation_name.php
all_columns1.com    /column_id.php
all_columns1.com    /default_object_id.php
all_columns1.com    /is_ansi_padded.php
all_columns1.com    /is_column_set.php
...
Run Code Online (Sandbox Code Playgroud)

现在让我们用它来填充我们的四个表:

;WITH x AS 
(
  SELECT 
    d = t.name + CONVERT(VARCHAR(5), d.database_id) + '.com', 
    p = '/' + c.name + '.php'
   FROM sys.all_objects AS t
   CROSS JOIN sys.databases AS d
   INNER JOIN sys.all_columns AS c
   ON t.[object_id] = c.[object_id]
 )
 SELECT d, p 
 INTO #blat
 FROM x
 ORDER BY d, p;

 CREATE CLUSTERED INDEX x ON #blat(d, p);

 INSERT dbo.Pages(URL) SELECT 'http://www.' + d + p FROM #blat ORDER BY d, p;

 INSERT dbo.Pages_compressed(URL) SELECT 'http://www.' + d + p FROM #blat ORDER BY d, p;

 INSERT dbo.Domains(DomainName) SELECT DISTINCT d FROM #blat ORDER BY d;

 INSERT dbo.URLs(URL) SELECT DISTINCT p FROM #blat;
Run Code Online (Sandbox Code Playgroud)

当然,现在您必须通过交叉引用域和 URL 来构建连接表。但这仍然会显示通过不存储相同的域名或页面名称两次可以节省多少空间,即使将这些东西放在一起意味着更复杂的逻辑:

EXEC sp_spaceused 'dbo.Pages';            -- 8,904 KB
EXEC sp_spaceused 'dbo.Pages_compressed'; -- 4,552 KB
EXEC sp_spaceused 'dbo.Domains';          --   656 KB
EXEC sp_spaceused 'dbo.URLs';             --   136 KB
Run Code Online (Sandbox Code Playgroud)

那就是 121,000 个 URL。5000亿?让我们推断一下。每个完整的 URL,未经压缩,您将存储大约 73 个字节。压缩后,每个 URL 38 字节。我的方法:每个 URL 6.5 个字节。(暂时放弃,我的示例数据是多么不切实际。假设我已经接近平均 URL 长度,并且 (a) 您将在每个域上索引许多 URL 并且 (b) 您将获得跨多个域的页面路径重复。)

因此,忽略这些现实,简单的数学计算表明,您的未压缩方法和我的方法之间的存储空间减少了 90% 以上:

Your method, uncompressed = 33.2 TB
Your method, compressed   = 17.1 TB
My method                 =  2.9 TB
Run Code Online (Sandbox Code Playgroud)

同样,它更复杂,因此需要更多的前期工作,但通常这是值得的。您只需设计架构并围绕它编写一次代码;您将维护它,嗯,您希望该服务存在多久?